-
Notifications
You must be signed in to change notification settings - Fork 10
Different parsing errors between Babel and V8: await as identifier #43
Comments
This perhaps should be a v8 bug? |
I posted here because I am not sure if But since If we throw If we decide both should throw, we should update the definition of function boundaries to rule out arrow functions. |
an arrow function does not bind |
Ah there is an SE on 5.1:
I think current behaviour is good. On #27 (comment), can you add an editor note that function boundary does not include the binding identifier of function declaration? That will clearly mark |
Thanks for the report!
I think this is a V8 bug, I'll double check.
This is a Babel bug.
This is a spec bug in the spec draft of the class static blocks proposal. It should align with what the class fields is doing, which is using this ContainsArguments abstract operations that can look through arrow functions. Instead, the class static blocks proposal is using the "doesn't cross function boundaries" prose language, which isn't precise enough. |
@syg Yeah I agree that
I agree that I have reported another two cases in #27 (comment) where both Babel and V8 throw but it seems to be allowed by the spec. |
C = class { static { function await() {} } }; Actually I take this back. From a the perspective of the Contains Static Semantics, the function binding identifier does cross the function boundary, so this actually should be allowed. @rbuckton what was your intention here? |
Given that |
Since |
The current V8 implementation seems to follow Contains Static Semantics, although the |
(sorry, misclick) |
@ljharb Oh I was meant to say From a parser viewpoint, throwing for |
That's not true. |
If The goal is to reserve The goal is for let await;
class C {
static {
let await; // illegal, cannot declare a new binding for await
let { await } = {}; // illegal, cannot declare a new binding for await
let { await: other } = {}; // legal
await; // illegal
await(1); // illegal
function await() {}; // illegal
class await {}; // illegal
({ await }); // illegal short-hand property reference
({ [await]: 1 }); // illegal
class D {
await = 1; // legal
x = await; // legal (initializers have an implicit function boundary)
[await] = 1; // illegal (computed property names are evaluated outside of a class body
};
(function await() {}); // legal, 'await' in function expression name not bound inside of static block
(class await {}); // legal, 'await' in class expression name not bound inside of static block
(function () { return await; }); // legal, 'await' is inside of a new function boundary
(() => await); // legal, 'await' is inside of a new function boundary
await: // illegal, 'await' cannot be used as a label
break await; // illegal, 'await' cannot be used as a label
}
} |
I mentioned in #27 that another way we could specify this is to parse the body of |
@rbuckton Thanks for a detailed example. In the cases listed above, both Babel and V8 throws (async function() { (class await {}); }) The other cases match current implementations if Unlike FunctionExpression, the ClassExpression inherits the |
Interesting. Its likely because of the oddities of how the class name's binding works: C = { x: 1 }
(class C { [C.x]() {} }); // error: Cannot access 'C' before initialization Even though computed property names essentially evaluate in the outer scope, we introduce an environment record in between that scope and class element evaluation in which |
Oh my goodness. 🤦 |
We really should simplify the things --- just make the |
It is reserved in modules, actually! Alas, not all code is in modules. (I'm not sure why it wasn't reserved in classes; probably to make it easier to refactor from functions?)
Well, it helps anyone who shipped code that used |
Only at the top level, IIRC. You can still do |
No, it's reserved everywhere: there is an Early Error rule for identifiers named "await" when the goal symbol of the syntactic grammar is |Module|. (This doesn't run into the tricky edge cases you've been facing here because the reservation is comprehensive, meaning you don't have to worry about function boundaries and arrow parameters so on.) |
I was just looking at that section and completely missed that rule. 😓 |
Yeah, there's a lot of special cases in the grammar. Not sure I would've been able to find it if I hadn't already known where to look. |
One of the examples above surprised me a bit: class C {
static {
class D {
x = await; // legal (initializers have an implicit function boundary)
}
}
} I don't object to that specific design decision, but the phrasing of the relevant early error doesn't really convey it:
The term "function boundary" is not formally defined, so some readers may not recognize that the boundary of the "implicit function" qualifies. |
Although @rbuckton's earlier comment makes the intention clear, there are some more ambiguities in the term "function boundary" which may confuse readers of the current proposed text. Are function "names" inside the function boundary? The intent is for this to depend on how the bindings interact with the relevant environment records. However, the early error isn't defined in terms of environment records. The interpretation that function "names" are inside the function boundary isn't as clear cut. There is at least one aspect of the specification where the notional boundary does not match the environment records--the interpretation of "strict mode code." Readers may interpret "function boundary" to follow the behavior of strict mode instead of the behavior of environment records. Are class "names" inside a function boundary? The intent is for this to match the answer for function "names." Readers may not agree that a class definition constitutes a function boundary, though. For one, the Also, it's notoriously complicated to say on which side of the boundary default parameter initializers fall. It seems to me that there's enough ambiguity here to warrant a formal definition of the term "function boundary." Would you folks agree? |
I'm looking into the switch to using
But its a bit harder to check for |
Would I add it as a special case to https://tc39.es/ecma262/#sec-static-semantics-contains? Something like this:
We do something similar in |
The cleanest solution would be to split This involves touching a few dozen places in the spec, but they're all straightforward. If you'd like I can do this as a PR to ecma262 and you can add an editor's note to the section in this proposal containing the reference to |
I ended up going with a slightly different approach and added a |
Related versions: Babel 7.13.9 V8 9.1.216
await as BindingIdentifier of FunctionDeclaration
Babel throws, V8 passes.
According to #27 (comment), a function boundary includes parameters and body. So the binding identifier
await
should throw here.await as IdentifierReference in ConciseBody of ArrowFunctionExpression
Babel throws, V8 passes.
Babel throws, V8 throws.
In the spec we have same treatment for IdentifierReference when its SV is
arguments
orawait
, so they should either both throw or both pass. I am not clear whether function boundary includes an arrow function. But since this already throwI am good to throw for
C = class { static { () => arguments } }
.I don't have strong opinion for
C = class { static { () => await } };
, I tend to forbid it even though we allowC = class { static p = () => await }
./cc @syg since you implemented static block features.
The text was updated successfully, but these errors were encountered: