diff --git a/JSTests/stress/yield-await-class-field-initializer-expr.js b/JSTests/stress/yield-await-class-field-initializer-expr.js new file mode 100644 index 0000000000000..b143452310381 --- /dev/null +++ b/JSTests/stress/yield-await-class-field-initializer-expr.js @@ -0,0 +1,117 @@ +// Tests for 'yield' and 'await' inside class field initializers, where the class is declared inside +// async and generator functions. +const verbose = false; + +const wrappers = ['none', 'generator', 'async']; +const fieldModifiers = ['', 'static']; +const fieldInitExprs = ['yield', 'yield 42', 'function() { yield 21; }', 'await', 'await 3', '() => await 7', 'function() { await 9; }']; + +function genTestCases() { + let cases = []; + for (const wrapper of wrappers) { + for (const fieldModifier of fieldModifiers) { + for (const fieldInitExpr of fieldInitExprs) { + cases.push({ wrapper, fieldModifier, fieldInitExpr }); + } + } + } + return cases; +} + +function genTestScript(c) { + let tabs = 0; + let script = ""; + + function append(line) { + for (t = 0; t < tabs; t++) + script += ' '; + script += line + '\n'; + } + + switch (c.wrapper) { + case 'generator': + append('function * g() {'); + break; + case 'async': + append('async function f() {'); + break; + case 'none': + break; + } + tabs++; + append('class C {'); + tabs++; + append(`${c.fieldModifier} f = ${c.fieldInitExpr};`); + tabs--; + append('}'); + tabs--; + if (c.wrapper !== 'none') append('}'); + return script; +} + +function expected(c, result, error) { + if (c.fieldInitExpr === 'await') { + // 'await' will parse as an identifier. + if (c.wrapper === 'none' && c.fieldModifier === 'static') { + // In this case, 'await' as identifier produces a ReferenceError. + return result === null && error instanceof ReferenceError; + } + // In these cases, 'await' as identifier has value 'undefined'). + return result === undefined && error === null; + } + // All other cases should result in a SyntaxError. + return result === null && error instanceof SyntaxError; +} + +// Verify that 'await' and 'yield' do not parse as keywords (directly) inside class field initializers. +function testNegativeCases() { + cases = genTestCases(); + + for (const c of cases) { + let script = genTestScript(c); + let result = null; + let error = null; + try { + result = eval(script); + } catch (e) { + error = e; + } + + if (verbose || !expected(c, result, error)) { + print(`Case: ${c.wrapper}:${c.fieldModifier}:${c.fieldInitExpr}`); + print(`Script:\n${script}`); + print(`Result: ${result}`); + print(`Error: ${error}\n`); + } + } +} + +// Verify that 'await' and 'yield' work inside anonymous async / generator functions +// used as class field initializers. +function testPositiveCases() { + function assertEq(got, expected) { + if (got !== expected) { + throw Error(`Got: ${got}, Expected: ${expected}`); + } + } + + class C { + asyncFn0 = async y => await y; + asyncFn1 = async () => { return await 5 }; + asyncFn2 = async function(x) { return await x; }; + + yieldFn0 = function* () { yield 9; }; + yieldFn1 = async function* () { yield await 11; }; + }; + let c = new C(); + + c.asyncFn0(3).then(x => assertEq(x, 3)); + c.asyncFn1().then(x => assertEq(x, 5)); + c.asyncFn2(7).then(x => assertEq(x, 7)); + + assertEq(c.yieldFn0().next().value, 9); + c.yieldFn1().next().then(x => assertEq(x.value, 11)); +} + +testNegativeCases(); +testPositiveCases(); \ No newline at end of file diff --git a/Source/JavaScriptCore/parser/Parser.cpp b/Source/JavaScriptCore/parser/Parser.cpp index ee8255d2a40a3..f189dfac989d2 100644 --- a/Source/JavaScriptCore/parser/Parser.cpp +++ b/Source/JavaScriptCore/parser/Parser.cpp @@ -2239,6 +2239,7 @@ template TreeFunctionBody Parser::parseFunctionBo ConstructorKind constructorKind, SuperBinding superBinding, FunctionBodyType bodyType, unsigned parameterCount) { SetForScope overrideParsingClassFieldInitializer(m_parserState.isParsingClassFieldInitializer, bodyType == StandardFunctionBodyBlock ? false : m_parserState.isParsingClassFieldInitializer); + SetForScope maybeUnmaskAsync(m_parserState.classFieldInitMasksAsync, isAsyncFunctionParseMode(m_parseMode) ? false : m_parserState.classFieldInitMasksAsync); bool isArrowFunctionBodyExpression = bodyType == ArrowFunctionBodyExpression; if (!isArrowFunctionBodyExpression) { next(); @@ -3224,6 +3225,7 @@ template TreeClassExpression Parser::parseClass(T size_t usedVariablesSize = currentScope()->currentUsedVariablesSize(); currentScope()->pushUsedVariableSet(); SetForScope overrideParsingClassFieldInitializer(m_parserState.isParsingClassFieldInitializer, true); + SetForScope maskAsync(m_parserState.classFieldInitMasksAsync, true); classScope->setExpectedSuperBinding(SuperBinding::Needed); initializer = parseAssignmentExpression(context); classScope->setExpectedSuperBinding(SuperBinding::NotNeeded); @@ -4331,6 +4333,9 @@ template TreeExpression Parser::parseYieldExpress // http://ecma-international.org/ecma-262/6.0/#sec-generator-function-definitions-static-semantics-early-errors failIfTrue(m_parserState.functionParsePhase == FunctionParsePhase::Parameters, "Cannot use yield expression within parameters"); + // https://github.com/tc39/ecma262/issues/3333 + failIfTrue(m_parserState.isParsingClassFieldInitializer, "Cannot use yield expression inside class field initializer expression"); + JSTokenLocation location(tokenLocation()); JSTextPosition divotStart = tokenStartPosition(); ASSERT(match(YIELD)); @@ -4357,6 +4362,7 @@ template TreeExpression Parser::parseAwaitExpress ASSERT(currentScope()->isAsyncFunction() || isModuleParseMode(sourceParseMode())); ASSERT(isAsyncFunctionParseMode(sourceParseMode()) || isModuleParseMode(sourceParseMode())); ASSERT(m_parserState.functionParsePhase != FunctionParsePhase::Parameters); + ASSERT(!m_parserState.classFieldInitMasksAsync); JSTokenLocation location(tokenLocation()); JSTextPosition divotStart = tokenStartPosition(); next(); @@ -5091,7 +5097,7 @@ template TreeExpression Parser::parsePrimaryExpre semanticFailIfTrue(currentScope()->isStaticBlock(), "The 'await' keyword is disallowed in the IdentifierReference position within static block"); if (m_parserState.functionParsePhase == FunctionParsePhase::Parameters) semanticFailIfFalse(m_parserState.allowAwait, "Cannot use 'await' within a parameter default expression"); - else if (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())) + else if (!m_parserState.classFieldInitMasksAsync && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode()))) return parseAwaitExpression(context); goto identifierExpression; @@ -5588,7 +5594,7 @@ template TreeExpression Parser::parseUnaryExpress bool hasPrefixUpdateOp = false; unsigned lastOperator = 0; - if (UNLIKELY(match(AWAIT) && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) { + if (UNLIKELY(match(AWAIT) && !m_parserState.classFieldInitMasksAsync && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) { semanticFailIfTrue(currentScope()->isStaticBlock(), "Cannot use 'await' within static block"); return parseAwaitExpression(context); } diff --git a/Source/JavaScriptCore/parser/Parser.h b/Source/JavaScriptCore/parser/Parser.h index d0cb48082a1e5..b93bcf8df65b5 100644 --- a/Source/JavaScriptCore/parser/Parser.h +++ b/Source/JavaScriptCore/parser/Parser.h @@ -2018,6 +2018,7 @@ class Parser { const Identifier* lastPrivateName { nullptr }; bool allowAwait { true }; bool isParsingClassFieldInitializer { false }; + bool classFieldInitMasksAsync { false }; }; // If you're using this directly, you probably should be using