Skip to content
This repository has been archived by the owner on Sep 8, 2021. It is now read-only.

Disallow await as an IdentifierReference or BindingIdentifier inside of a static block #27

Closed
rbuckton opened this issue Nov 10, 2020 · 13 comments · Fixed by #32
Closed
Assignees
Labels
bug Something isn't working
Milestone

Comments

@rbuckton
Copy link
Collaborator

rbuckton commented Nov 10, 2020

The proposal currently does not allow a static {} block to inherit the [Await] flag of its surrounding context. We intend to leave open the possibility of supporting this in the future, though not at this time. However, by passing [~Await] to the statement list, we allow the use of await as an identifier inside of the static {} block, which would make adopting the outer [Await] context a breaking change.

To address this concern, we should disallow await as an IdentifierReference or BindingIdentifier directly inside of a static {} block (while still allowing it inside of a function nested inside of the block).

Note that this is less of an issue with yield, as yield is treated as a reserved word in strict mode, and class bodies are always strict.

Related: #7, #24

@rbuckton rbuckton added the bug Something isn't working label Nov 10, 2020
@rbuckton rbuckton added this to the Stage 3 milestone Nov 10, 2020
@rbuckton rbuckton self-assigned this Nov 10, 2020
@bakkot
Copy link

bakkot commented Nov 10, 2020

I think the cleanest solution would be to pass [+Await] (when in an async context) and then explicitly have an early error for AwaitExpression and for await.

@rbuckton
Copy link
Collaborator Author

I'm concerned if we do that that if we introduced any new features under [+Await] we'd just have to keep excluding them.

@bakkot
Copy link

bakkot commented Jan 14, 2021

Isn't that what we'd want? It seems like static blocks should either inherit the outer context's async-ness wholesale or not at all, not piecemeal. (Though I still don't understand why we'd go with "not at all".)

@rbuckton
Copy link
Collaborator Author

We're reserving it because there are still questions about what to do here, as we have been discussing in #7. Reserving await ensures we don't allow someone to write this:

class C {
  static {
    let await = 1;
  }
}

And we can later decide how we want to treat await. We have to do this for await and not yield because yield is already a reserved word in strict mode code, but await is not.

@Kingwl
Copy link
Member

Kingwl commented Mar 8, 2021

Hi folks. I'm very confused about the concept.

IdentifierReference : Identifier
It is a Syntax Error if the code matched by this production is nested, directly or indirectly (but not crossing function or static initialization block boundaries), within a ClassStaticBlock and the StringValue of Identifier is "arguments" or "await".

I have searched on specification. But there's not section about what exactly is the function boundaries.

For example:

var await = 1

class C {
  static {
    class D {
      field = await
      static staticFields = await
      [await] = await
    }
  }
}

Does the field crossing thefunction boundaries? Does the staticFields crossing the function boundaries? Does the [await] crossing the function boundaries?

There's only two references of function boundaries before: breakStatement and continueStatement.
That means if you wanna crossing the function boundaries in class declaration, You have to wrapper the statement inside a function. that could be covered by the function boundaries concept.

But for now. IdentifierReference could crossing the function boundaries too. And we don't need a wrapper function anymore. So we might to clarify What is the boundary or Does class declaration is a function boundary.

@rbuckton
Copy link
Collaborator Author

rbuckton commented Mar 18, 2021

That's a good question. await isn't a reserved word like yield. For yield, all of the above would be errors outside of a generator function, and only the computed property name would be valid inside of a generator function (in that it would evaluate a YieldExpression). Since await isn't reserved, it can be used as an identifier in more places.

A "function boundary" is essentially anywhere code can execute when the function is called, such as the parameter list (initializers), and the body. A class body is not a function boundary, since the heritage clause and computed property names are evaluated in the lexical scope containing the class, not in the class itself. Per the class fields specification, field initializers have an implicit function boundary (each initializer is wrapped in a function and evaluated with an appropriate this binding).

Consider this:

var await = 1;
async function f() {
  function g() {
    console.log(await);
  }
  g();
}
f(); // prints 1

Applying that to your example:

var await = 1

class C {
  static {
    class D {
      field = await /*1*/
      static staticFields = await /*2*/
      [await /*3*/] = await /*4*/
    }
  }
}
  1. Instance field initializers in D are evaluated as if they were in a function, but that function cannot be async. Since its a function boundary, the above IdentifierReference: `await` check for whether its nested in a static {} block stops because it would cross a function boundary. As a result, this is not an error and looking up await will result in finding the value 1.
  2. Static field initializers have the same semantics as instance field initializers, so this would not be an error.
  3. Computed Property names are evaluated in the outer scope. Since the class body is not a function boundary, the computed property name is nested in a static{} block, therefore the identifier await is an error.
  4. Since this is an instance field, the same logic as (1) applies.

So, the only error that should be reported for the above example is the [await] = ... (3), since walking up from await hits a static {} block before it hits a function boundary. For the cases (1), (2), and (4) you hit an implicit function boundary due to how class field initialization is specified.

@Kingwl
Copy link
Member

Kingwl commented Mar 19, 2021

Thanks for the explanations. Could we wrote these up into the spec?

@JLHwung
Copy link
Contributor

JLHwung commented Apr 1, 2021

A "function boundary" is essentially anywhere code can execute when the function is called, such as the parameter list (initializers), and the body

  • Does parameter binding belong to "function boundary"?
// Currently both Babel and V8 throw
class C { static { (await) => {} } }
  • Does computed property used in parameter initializers belong to "function boundary"? It is evaluated in the outer scope though.
// Currently both Babel and V8 throw
class C { static { ({ [await] : x }) => {} } }

@syg
Copy link
Contributor

syg commented Apr 1, 2021

Does parameter binding belong to "function boundary"?

From the perspective of Contains, arrow function parameters do not cross a function boundary, but non-arrow function parameters do. So the answer for your example is "no".

Does computed property used in parameter initializers belong to "function boundary"? It is evaluated in the outer scope though.

Same thing: no for arrows, yes for non-arrows.

@rbuckton
Copy link
Collaborator Author

rbuckton commented Apr 1, 2021

Its also tricky for paren-less arrow functions. For example, the following is illegal today:

async function f() {
  (await => {}); error
  ((await) => {}); error
  (function(await) {}); // ok
}

In an async function, you parse the cover grammar for the arrow function parameters in the +Await context, so they can't be legal.

Another way I could handle this is parse the body of static {} in +Await and then error if the block Contains AwaitExpression.

@bakkot
Copy link

bakkot commented Apr 1, 2021

Another way I could handle this is parse the body of static {} in +Await and then error if the block Contains AwaitExpression.

I still think that's the cleanest solution.

@syg
Copy link
Contributor

syg commented May 15, 2021

I still think that's the cleanest solution.

Big, explicit +1 for this approach after reasoning through the implementation for when to allow await as identifier.

@rbuckton
Copy link
Collaborator Author

I agree. I'll spend some time on updates to the spec text next week.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants