tc39 / ecma262

Status, process, and documents for ECMA-262
https://tc39.es/ecma262/
Other
14.85k stars 1.27k forks source link

Classes are haunted: `yield` and `await` probably shouldn't be allowed in field initializers #3333

Open syg opened 1 month ago

syg commented 1 month ago

FieldDefinition passes along [?Yield, ?Await], which means the RHS of field initializers inside async functions and generators allow yield and await expressions to be parsed.

Suspending in the middle of construction of a class instance is super cursed!

It is arguably sensible for static initializers, but even that seems gross.

Behavior diverges among browser implementations. JSC parses both yield and await expressions in initializers, and SpiderMonkey and V8 reject yield and await expressions.

$ cat ./test-class-await.js
async function f() {
  class C {
    field = await 42;
  }
}
$ eshost -s ./test-class-await.js 
#### JavaScriptCore

#### SpiderMonkey

SyntaxError: unexpected token: numeric literal:

#### V8

SyntaxError: Unexpected number
$ cat ./test-class-yield.js
function* f() {
  class C {
    field = yield 42;
  }
}
$ eshost -s ./test-class-yield.js 
#### JavaScriptCore

#### SpiderMonkey

SyntaxError: yield is a reserved identifier:

#### V8

SyntaxError: Unexpected strict mode reserved word
$ cat ./test-class-static-await.js
async function f() {
  class C {
    static field = await 42;
  }
}
$ eshost -s ./test-class-static-await.js
#### JavaScriptCore

#### SpiderMonkey

SyntaxError: unexpected token: numeric literal:

#### V8

SyntaxError: Unexpected reserved word
$ cat ./test-class-static-yield.js
function* f() {
  class C {
    static field = yield 42;
  }
}
$ eshost -s ./test-class-static-yield.js
#### JavaScriptCore

#### SpiderMonkey

SyntaxError: yield is a reserved identifier:

#### V8

SyntaxError: Unexpected strict mode reserved word
bakkot commented 1 month ago

The maximally consistent interpretation is to say that initializer RHSes are basically new function bodies and therefore should unset the yield/await flags, but we could also just unconditionally ban them. We do that for await in static blocks (by unconditionally setting the flags and then having an early error for "contains await").

bakkot commented 1 month ago

https://github.com/tc39/ecma262/issues/2437

syg commented 1 month ago

The maximally consistent interpretation is to say that initializer RHSes are basically new function bodies and therefore should unset the yield/await flags, but we could also just unconditionally ban them. We do that for await in static blocks (by unconditionally setting the flags and then having an early error for "contains await").

Agreed. I think these should all be [~Yield, +Await, ~Return] with Early Error ban for await, like static blocks.

kmiller68 commented 1 month ago

While we're at it... can we also ban syntactically

class C {
   #secrets;
   [this.#secrets] = 42;
   static [this.#secrets] = 42;
};

or

class C {
   static #secrets;
   [this.#secrets] = 42;
   static [this.#secrets] = 42;
};

Maybe this should be in a different issue though. Right now I think they're all a runtime error (Although, until recently it was a crash in JSC).

nicolo-ribaudo commented 1 month ago

@kmiller68 Babel relies on it being syntactically valid to compile decorators, and banning it would break it:

https://babeljs.io/repl#?builtIns=false&code_lz=AIEwpgxgBBA2CGBnRUCCUDeAoKVSQCYpEAXeEgS2gGIAPAbiwF8g&modules=false&sourceType=module&presets=&version=7.24.5&externalPlugins=%40babel%2Fplugin-proposal-decorators%407.24.1