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

RFC: Scope (fn) name inference #44

Open
Swatinem opened this issue May 25, 2022 · 0 comments
Open

RFC: Scope (fn) name inference #44

Swatinem opened this issue May 25, 2022 · 0 comments

Comments

@Swatinem
Copy link
Member

Over at https://github.com/Swatinem/js-source-scopes I have been experimenting with extracting scopes, and their names from minified source; and re-mapping the individual name components using a sourcemaps names, with surprisingly great success.

We should make up our minds how we expect this inference to work.

This inference works differently in the rendered stack traces of various browsers, which is the primary use-case we are after.

Discussion

Function instance name

The Function instance name is defined like this:

The value of the "name" property is a String that is descriptive of the function. The name has no semantic significance but is typically a variable or property name that is used to refer to the function at its point of definition in ECMAScript code.

For named functions, this is trivial, but for anonymous functions, the spec defines a whole section on how the name should be inferred:

In particular the runtime semantics for NamedEvaluation is being "called" with the name as defined using various ambient syntax.

For example const a = () => {}; is clearly defined by the spec as the following:

LexicalBinding : BindingIdentifier Initializer

    1. Let bindingId be StringValue of BindingIdentifier.
    2. ...
    3. If IsAnonymousFunctionDefinition(Initializer) is true, then
        a. Let value be ? NamedEvaluation of Initializer with argument bindingId.

The final .name property of functions is evaluated at runtime and does support dynamic constructs like so:

$ a = "abc", b = {[a]: () => {}}, b.abc.name
> "abc"

Names for property Assignments

The spec only infers name from relatively simple constructs. Neither Firefox nor Chrome infer a name for this snippet:

$ a = {}, a.abc = () => {}, a.abc.name
> ""

Interesting though that at least the firefox devtools internally do infer the name, and the stacktrace even has the complete object path to the function:

Bildschirmfoto 2022-05-25 um 15 24 55

My prototype infers the name from the complete expression the function was assigned to.

Class/Prototype/Constructors

Another interesting case is classes, constructors, and methods either on the class directly or on the prototype.

class X {
  static y() {}
  z() {}
}
X.prototype.w = () => {};

As before, the .name property for all of these is their name, except w for which it is empty.
However, when throwing an Error, the stack trace will say X.w in chrome/node, and X.prototype.w in Firefox for the method defined on the prototype, but just z in Firefox for the class method.
In stack traces, Chrome/Node will prefix this with the class name of that specific instance, not the name of the class defining the method if you use inheritance!

My personal intuition says that I would like to have the names X.y for the static function, and X.prototype.z for the instance function; as that is how you would refer to them via code.

Anonymous Callbacks

The most complicated case would be "completely" anonymous functions used as callbacks.

const myCallback = useCallback(() => {}, []);

Per spec, this function has no name itself. In stack traces, Chrome/Node do not infer a name at all. Firefox however lists all of the named scopes in which the callback is defined.

Options that we have in this case are:

  • Anonymous callback to useCallback.
  • Anonymous callback which results in myCallback.

The second option is very much specific to the use-case in React, as the framework returns a wrapper that is then being called. We can’t infer from pure syntax if that is the case, or if the callback is immediately invoked, such as with Array.prototype.map, in which case the first option would make more sense.

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

1 participant