Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scopes: Flexible scope "kind" on original scope prevents translation of function names in stack traces #133

Open
szuend opened this issue Oct 2, 2024 · 6 comments

Comments

@szuend
Copy link
Collaborator

szuend commented Oct 2, 2024

Given the following authored code:

function foo() {  | (A) kind "function", name: "foo"
  if (...) {      |    | (B) kind "block", name: null
    x();          |    |
  }               |    |
}                 |

And the generated code:

function f() { | (R1)
  if (...) {   |    | (R2)
    x();       |    |
  }            |    |
}              |

The scopes proposal would put (A) as the definition of (R1) and (B) as the definition of (R2).

The call-site of x() would produce a stack frame with f 2:5. With that position we find the generated range (R2) and the corresponding original scope of (B). (B) doesn't have a name so we can't say what the original function name was.

The right thing to do, is walk the original scope chain outwards until we find an original scope that's "callable" (in this case (R1) and get the name from there, but "kind" is an arbitrary string, so we don't know how far to walk.

There are a couple of solutions how we can fix this:

  1. Revert the change that made kind into an arbitrary text and keep a pre-defined list of global, function and block. We can still add a kindLabel so generators can label original scopes more appropriately based on the source language.

  2. Leave "kind" as-is, but add a "isCallable"/"isFunction"/"showsUpInStackTrace" flag on original scopes.

  3. Require that anything callable has "kind" of 'function'. This seems very brittle to me and I'd prefer either a flag or a fixed original scope type.

Note that we can't just simply walk the original scope chain outwards until we find something with a name. Generators could emit scopes for classes, modules or namespaces with names that don't make sense to show in a stack trace.

@jridgewell
Copy link
Member

The generic kinds were meant to be defined by the language instead of by the spec. Eg, we left a Note: JavaScript implementations should use 'global', 'class', 'function', and 'block'., but C++ might choose different names.

@szuend
Copy link
Collaborator Author

szuend commented Oct 7, 2024

That seems very brittle. If a generator chooses to emit 'fun' or 'method', it'll break one of the most important use cases for source maps (translating stack traces). IMO if we want to go this route, then we need to make the wording around 'function' stronger. That is generators "must" use 'function' kind for anything callable.

@jridgewell
Copy link
Member

That would be fine with me. As long as we leave the specification open for other languages, I think we should be able to mandate stronger designs for JS specifically.

@szuend
Copy link
Collaborator Author

szuend commented Oct 7, 2024

Other languages would also have to use 'function' as the kind for anything callable. Otherwise they'll have the same problem when translating stack traces (regardless of whether they compiled to JS or WASM)

@jridgewell
Copy link
Member

I think we're talking about separate use cases? I'm treating source map specification as a general purpose tool appropriate for any language and any environment (eg, rust running native code in a terminal). For generators targeting JS/WASM, the expectation is that it'll have to work with browser's dev tools and we should specify that explicitly.

@szuend
Copy link
Collaborator Author

szuend commented Oct 7, 2024

I'm not sure I fully follow. If we consider the use-case of translating stack traces, then regardless of runtime environment, source language and target language you always have to translate from a stack frame in the target language, to a stack frame on the authored side.

To identify an authored scope based on stack frame generated position you have to do the following:

  1. Find the inner-most generated range that contains the stack frame's position
  2. Abort, if that range doesn't have a "definition".
  3. Starting at the "definition" authored scope, walk the scope chain upwards until you find an authored scope that shows up in a stack trace in the source language.

This algorithm is universal, regardless of runtime environment (browser, node, native), source language (JS, TS, Rust) and target language (JS, WASM, native). The only difference is that generated ranges either correspond to line/columns (for JS), code offsets (WASM) or memory addresses (native).

The question is, how do you identify that an authored scope is something "callable" in the authored language, aka we can use it's name in a stack trace. Using the "kind" as a signal is weak, since it does not apply to all authored languages. This puts the burden on tooling to maintain a list of all the different "kind"s that are considered functions in various authored languages.

If we don't want to add a separate signal for this concept (like a flag or a type), then we need to prescribe that EVERY generator for EVERY source language uses 'function' as the "kind" for any authored scope that can show up in a stack trace. Otherwise tooling has no chance to translate stack traces with correct source language semantics, especially once inlining/outlining comes into play.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants