Skip to content

Fix super() and this in async arrows inside class constructors - #2127

Open
tmikov wants to merge 1 commit into
facebook:static_hfrom
tmikov:issue-2126
Open

Fix super() and this in async arrows inside class constructors#2127
tmikov wants to merge 1 commit into
facebook:static_hfrom
tmikov:issue-2126

Conversation

@tmikov

@tmikov tmikov commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

An async arrow becomes three nested IR functions: an outer async
function, an outer generator function, and the inner generator arrow
holding the user code. genAsyncFunction() and genGeneratorFunction()
pass capturedState and typedClassContext down to the nested
FunctionContexts, but not legacyClassContext.

By the time the arrow's parameters and body are emitted,
hasLegacyClassContext() is false, so IRGen takes the typed-class path
for super() and this. This crashed:

  class A {}
  new class extends A {
    constructor() {
      (async(a = super(), b) => {})();
    }
  }

on the "SemanticResolver must check super() is in a class with a
superclass" assertion, because superClassNode_ is only set for typed
classes. A super() in the arrow body instead of a parameter
initializer crashed the same way.

this was worse, because it didn't crash. genThisExpression() skipped
the derived-constructor TDZ guard and returned the raw ?CHECKED_this
slot. The IR verifier rejects that. A release build has no verifier and
leaks the empty sentinel into JS instead of throwing ReferenceError.

Pass legacyClassContext down in both places. Non-arrow async and
generator methods are unaffected: only emitFunctionEpilogue() and
initCaptureStateInES5FunctionHelper() consult the class context in
these outer functions, and both also require a constructor
constructorKind or DefinitionKind. The outer ES5Function wrappers
never have one.

Fixes #2126
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

An async arrow becomes three nested IR functions: an outer async
function, an outer generator function, and the inner generator arrow
holding the user code. `genAsyncFunction()` and `genGeneratorFunction()`
pass `capturedState` and `typedClassContext` down to the nested
`FunctionContext`s, but not `legacyClassContext`.

By the time the arrow's parameters and body are emitted,
`hasLegacyClassContext()` is false, so IRGen takes the typed-class path
for `super()` and `this`. This crashed:

  class A {}
  new class extends A {
    constructor() {
      (async(a = super(), b) => {})();
    }
  }

on the "SemanticResolver must check super() is in a class with a
superclass" assertion, because `superClassNode_` is only set for typed
classes. A `super()` in the arrow body instead of a parameter
initializer crashed the same way.

`this` was worse, because it didn't crash. `genThisExpression()` skipped
the derived-constructor TDZ guard and returned the raw `?CHECKED_this`
slot. The IR verifier rejects that. A release build has no verifier and
leaks the empty sentinel into JS instead of throwing ReferenceError.

Pass `legacyClassContext` down in both places. Non-arrow async and
generator methods are unaffected: only `emitFunctionEpilogue()` and
`initCaptureStateInES5FunctionHelper()` consult the class context in
these outer functions, and both also require a constructor
`constructorKind` or `DefinitionKind`. The outer `ES5Function` wrappers
never have one.

Fixes facebook#2126
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Aug 9, 2026
FunctionContext outerFnContext{this, outerFn, functionNode->getSemInfo()};
// Propagate the enclosing class context so that the inner function, which
// holds the actual user code, can use super() and 'this'.
outerFnContext.legacyClassContext = legacyClassContext;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should always be doing this copy. In practice I think it shouldn't matter since we correctly validate super() calls earlier in SemanticResolver, but I think we should only be doing this copy in if (isAsyncArrow) { branch?

@meta-codesync

meta-codesync Bot commented Aug 10, 2026

Copy link
Copy Markdown

@tmikov has imported this pull request. If you are a Meta employee, you can view this in D115505066.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assertion failure in IRGen/ESTreeIRGen-expr.cpp: "SemanticResolver must check super() is in a class with a superclass"

2 participants