Closed Bug 1799525 Opened 3 years ago Closed 2 years ago

Support private identifiers in decorators

Categories

(Core :: JavaScript Engine, enhancement, P3)

enhancement

Tracking

()

RESOLVED FIXED
120 Branch
Tracking Status
firefox120 --- fixed

People

(Reporter: dminor, Assigned: dminor)

References

(Blocks 1 open bug)

Details

Attachments

(4 files)

Decorators can be private identifiers, so we'll need to add support for parsing and using them.

Severity: -- → N/A
Priority: -- → P3
Assignee: nobody → dminor
Status: NEW → ASSIGNED

I think this upstream test may be mistaken: https://github.com/tc39/test262/blob/main/test/language/statements/class/decorator/syntax/class-valid/decorator-member-expr-private-identifier.js

A simplified version of this test is:

  class G {
    static #dec1() {}
    static {
      @G.#dec1 class G {}
    }
  }

In my current implementation, this results in a ReferenceError: can't access lexical declaration 'G' before initialization.

I think this might be the correct behaviour, because this code:

class C {
    static dec() {}
    static {
        this.x = C.dec();
        class C {}
    }
}

also results in a can't access lexical declaration 'C' before initialization in Firefox, and a ReferenceError: Cannot access 'C' before initialization in Chrome.

I'm having trouble tracking down how this behaviour is defined in the specification. :arai, do you know if the test262 test above is correct or not?

Flags: needinfo?(arai.unmht)

Yes, the testcase looks wrong.
The inner class should use different name.

The behavior is defined in the following:

1. Creating class static block's function and calling it

  1. The class static block is converted into a function using:
    • the class environments as F.[[Environment]] and F.[[PrivateEnvironment]]
    • ClassStaticBlockBody as F.[[ECMAScriptCode]]
  2. The enclosing lexical environment's binding is initialized to the class constructor
  3. The class static block's function is called

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-class-definitions-runtime-semantics-evaluation

15.8.21 Runtime Semantics: Evaluation

ClassDeclaration :  class BindingIdentifier ClassTail

  1. If DecoratorList opt is present, then
    a. Let decorators be ? DecoratorListEvaluation of DecoratorList.
  2. Else, let decorators be a new empty List.
  3. Perform ? BindingClassDeclarationEvaluation of this ClassDeclaration with
     argument decorators.
  4. Return empty.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-bindingclassdeclarationevaluation

15.7.15 Runtime Semantics: BindingClassDeclarationEvaluation

ClassDeclaration :   DecoratorList opt class BindingIdentifier ClassTail

  1. Let className be StringValue of BindingIdentifier .
  2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments
     className, className, and decorators
  ...

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation

15.7.14 Runtime Semantics: ClassDefinitionEvaluation

ClassTail :  ClassHeritage opt { ClassBody opt }

  1. Let env be the LexicalEnvironment of the running execution context.
  2. Let classEnv be NewDeclarativeEnvironment(env).
  ...
  4. Let outerPrivateEnvironment be the running execution context's
     PrivateEnvironment.
  5. Let classPrivateEnvironment be
     NewPrivateEnvironment(outerPrivateEnvironment).
  ...
  12. Set the running execution context's LexicalEnvironment to classEnv.
  13. Set the running execution context's PrivateEnvironment to
      classPrivateEnvironment.
  ...
  19. If ClassBody opt is not present, let elements be a new empty List.
  20. Else, let elements be NonConstructorElements of ClassBody .
  ...
  24. Let staticElements be a new empty List.
  25. For each ClassElement e of elements, do
    a. If IsStatic of e is false, then
      ...
    b. Else,
      i. Let element be Completion(ClassElementEvaluation of e with
         argument F).
    ...
    d. Set element to element.[[Value]].
    e. If element is a PrivateElement, then
      ...
    f. Else if element is a ClassFieldDefinition Record, then
      ...
    g. Else if element is a ClassStaticBlockDefinition Record, then
      i. Append element to staticElements.
  26. Set the running execution context's LexicalEnvironment to env.
  27. If classBinding is not undefined, then
    a. Perform ! classEnv.InitializeBinding(classBinding, F).
  ...
  31. For each element elementRecord of staticElements, do
    a. If elementRecord is a ClassFieldDefinition Record, then
      ...
    b. Else,
      i. Assert: elementRecord is a ClassStaticBlockDefinition Record.
      ii. Let result be Completion(Call(elementRecord.[[BodyFunction]],
          F)).
    ...
  ...

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-static-semantics-isstatic

15.8.4 Static Semantics: IsStatic

ClassElement :  ClassStaticBlock

  1. Return true.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-static-semantics-classelementevaluation

15.8.18 Runtime Semantics: ClassElementEvaluation

ClassElement :  ClassStaticBlock

  1. Return ClassStaticBlockDefinitionEvaluation of ClassStaticBlock with
     argument object.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classstaticblockdefinitionevaluation&secAll=true

15.7.11 Runtime Semantics: ClassStaticBlockDefinitionEvaluation

ClassStaticBlock :  static { ClassStaticBlockBody }

  1. Let lex be the running execution context's LexicalEnvironment.
  2. Let privateEnv be the running execution context's PrivateEnvironment.
  3. Let sourceText be the empty sequence of Unicode code points.
  4. Let formalParameters be an instance of the production FormalParameters
     :  [empty] .
  5. Let bodyFunction be OrdinaryFunctionCreate(%Function.prototype%,
     sourceText, formalParameters, ClassStaticBlockBody , non-lexical-this,
     lex, privateEnv).
  6. Perform MakeMethod(bodyFunction, homeObject).
  7. Return the ClassStaticBlockDefinition Record { [[BodyFunction]]:
     bodyFunction }.

2. Bindings in class static block's function body

When the class static block's function is called at ClassDefinitionEvaluation Step 31.b.ii:

  1. func.[[ECMAScriptCode]] is ClassStaticBlockBody,
  2. The function's lexical environemnt's bindings are created with uninitialized for the inner class

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-evaluatebody&secAll=true

10.2.1.3 Runtime Semantics: EvaluateBody

ClassStaticBlockBody :  ClassStaticBlockStatementList

  1. Assert: argumentsList is empty.
  2. Return ? EvaluateClassStaticBlockBody of ClassStaticBlockBody with
     argument functionObject.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-evaluateclassstaticblockbody&secAll=true

15.7.12 Runtime Semantics: EvaluateClassStaticBlockBody

ClassStaticBlockBody :  ClassStaticBlockStatementList

  1. Perform ? FunctionDeclarationInstantiation(functionObject, « »).
  2. Return ? Evaluation of ClassStaticBlockStatementList .

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-functiondeclarationinstantiation&secAll=true

10.2.11 FunctionDeclarationInstantiation ( func, argumentsList )

  2. Let code be func.[[ECMAScriptCode]].
  ...
  33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
  34. For each element d of lexDeclarations, do
    ...
    b. For each element dn of the BoundNames of d, do
      i. If IsConstantDeclaration of d is true, then
        1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
      ii. Else,
        1. Perform ! lexEnv.CreateMutableBinding(dn, false).
  ...

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-static-semantics-lexicallyscopeddeclarations&secAll=true

8.2.5 Static Semantics: LexicallyScopedDeclarations

ClassStaticBlockStatementList :  StatementList

  1. Return the TopLevelLexicallyScopedDeclarations of StatementList .

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-static-semantics-toplevellexicallyscopeddeclarations&secAll=true

8.2.9 Static Semantics: TopLevelLexicallyScopedDeclarations

StatementList :  StatementList StatementListItem

  1. Let declarations1 be TopLevelLexicallyScopedDeclarations of
     StatementList .
  2. Let declarations2 be TopLevelLexicallyScopedDeclarations of
     StatementListItem .
  3. Return the list-concatenation of declarations1 and declarations2.

StatementListItem :  Declaration

  1. If Declaration is Declaration :  HoistableDeclaration , then
    a. Return a new empty List.
  2. Return « Declaration ».

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-static-semantics-boundnames

8.2.1 Static Semantics: BoundNames

ClassDeclaration : DecoratorList opt class BindingIdentifier ClassTail

  1. Return the BoundNames of BindingIdentifier.

3. Evaluating class static block's function body

When inner ClassDeclaration inside the function body is evaluated:

  1. DecoratorList is evaluated
  2. The the inner class's binding for the function's lexical environemnt is initialized

So, when evaluating the DecoratorList, the function's lexical environemnt already has a mutable binding for the inner class, but the inner class's binding is initialized after DecoratorList, and the name C inside the decorator should see the uninitialized binding for the class static block's function, instead of the enclosing environment's outer class.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-evaluatebody&secAll=true

10.2.1.3 Runtime Semantics: EvaluateBody

ClassStaticBlockBody :  ClassStaticBlockStatementList

  1. Assert: argumentsList is empty.
  2. Return ? EvaluateClassStaticBlockBody of ClassStaticBlockBody with
     argument functionObject.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-evaluateclassstaticblockbody&secAll=true

15.7.12 Runtime Semantics: EvaluateClassStaticBlockBody

ClassStaticBlockBody :  ClassStaticBlockStatementList

  1. Perform ? FunctionDeclarationInstantiation(functionObject, « »).
  2. Return ? Evaluation of ClassStaticBlockStatementList .

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-functions-and-classes

A.4 Functions and Classes

ClassStaticBlockStatementList :
   StatementList[~Yield, +Await, ~Return] opt

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-block-runtime-semantics-evaluation&secAll=true

14.2.2 Runtime Semantics: Evaluation

StatementList :  StatementList StatementListItem

  1. Let sl be ? Evaluation of StatementList .
  2. Let s be Completion(Evaluation of StatementListItem ).
  3. Return ? UpdateEmpty(s, sl).

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-statements&secAll=true

A.3 Statements

Declaration[Yield, Await] :
   HoistableDeclaration[?Yield, ?Await, ~Default]
   ClassDeclaration[?Yield, ?Await, ~Default]
   LexicalDeclaration[+In, ?Yield, ?Await]

StatementListItem[Yield, Await, Return] :
   Statement[?Yield, ?Await, ?Return]
   Declaration[?Yield, ?Await]

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-class-definitions-runtime-semantics-evaluation

15.8.21 Runtime Semantics: Evaluation

ClassDeclaration :  DecoratorList opt class BindingIdentifier ClassTail

  1. If DecoratorList opt is present, then
    a. Let decorators be ? DecoratorListEvaluation of DecoratorList.
  2. Else, let decorators be a new empty List.
  3. Perform ? BindingClassDeclarationEvaluation of this ClassDeclaration with
     argument decorators
  4. Return empty.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-decoratorlistevaluation

15.7.2 Runtime Semantics: DecoratorListEvaluation

DecoratorList : DecoratorList opt Decorator

  1. If DecoratorList is present, then
    a. Let leftValue be ? DecoratorListEvaluation of DecoratorList.
  2. Else,
    a. Let leftValue be a new empty List.
  3. Let rightValue be ? DecoratorEvaluation of Decorator.
  4. Prepend rightValue to leftValue.
  5. Return leftValue.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-decoratorevaluation

15.7.1 Runtime Semantics: DecoratorEvaluation

Decorator : @ DecoratorMemberExpression

  1. Let expr be the MemberExpression that is covered by DecoratorMemberExpression.
  2. Let ref be ? Evaluation of expr.
  3. Let value be ? GetValue(ref).
  4. Return DecoratorDefinition Record { [[Decorator]]: value, [[Receiver]]: ref }.

https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-bindingclassdeclarationevaluation

15.8.20 Runtime Semantics: BindingClassDeclarationEvaluation

ClassDeclaration :  DecoratorList opt class BindingIdentifier ClassTail

  1. Let className be StringValue of BindingIdentifier .
  2. Let value be ? ClassDefinitionEvaluation of ClassTail with arguments
     className, className, and decorators.
  3. Set value.[[SourceText]] to the source text matched by
     ClassDeclaration .
  4. Let env be the running execution context's LexicalEnvironment.
  5. Perform ? InitializeBoundName(className, value, env).
  6. Return value.
Flags: needinfo?(arai.unmht)

Thank you, I've opened https://github.com/tc39/test262/pull/3907 to discuss this upstream.

To help with debugging.

Depends on D187126

Attachment #9350894 - Attachment description: Bug 1799525 - Update comment in jstests.list; r=arai! → WIP: Bug 1799525 - Update comment in jstests.list
Attachment #9350894 - Attachment description: WIP: Bug 1799525 - Update comment in jstests.list → Bug 1799525 - Update comment in jstests.list; r=arai!
Pushed by dminor@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/68a6354b17f3 Support dumping decorators in classes; r=arai https://hg.mozilla.org/integration/autoland/rev/be2f634bbd35 Add support for private identifiers in decorators; r=arai https://hg.mozilla.org/integration/autoland/rev/a5113ad24829 Don't evaluate decorators in scope of decorated class; r=arai https://hg.mozilla.org/integration/autoland/rev/2faa632c12e2 Update comment in jstests.list; r=arai
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: