diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 79b5eb30c49..64cd8219a01 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -531,6 +531,18 @@ namespace ts { let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + interface Fail extends Node { kind: SyntaxKind.Unknown; } + interface FailList extends NodeArray { pos: -1; } + let Fail: Fail; + let FailList: FailList; + function isFail(x: Node | undefined): x is Fail { + Debug.assert(Fail !== undefined); + return x === Fail; + } + function isFailList(x: NodeArray | undefined): x is FailList { + Debug.assert(Fail !== undefined); + return x === FailList; + } // tslint:enable variable-name let sourceFile: SourceFile; @@ -681,6 +693,9 @@ namespace ts { IdentifierConstructor = objectAllocator.getIdentifierConstructor(); SourceFileConstructor = objectAllocator.getSourceFileConstructor(); + Fail = createNode(SyntaxKind.Unknown) as Fail; + FailList = createNodeArray([], -1) as FailList; + sourceText = _sourceText; syntaxCursor = _syntaxCursor; @@ -736,7 +751,7 @@ namespace ts { processReferenceComments(sourceFile); sourceFile.statements = parseList(ParsingContext.SourceElements, parseStatement); - Debug.assert(token() === SyntaxKind.EndOfFileToken); + Debug.assertEqual(token(), SyntaxKind.EndOfFileToken); sourceFile.endOfFileToken = addJSDocComment(parseTokenNode() as EndOfFileToken); setExternalModuleIndicator(sourceFile); @@ -1003,7 +1018,7 @@ namespace ts { return currentToken = scanner.scanJsxAttributeValue(); } - function speculationHelper(callback: () => T, isLookAhead: boolean): T { + function speculationHelper(callback: () => T, isLookAhead: boolean): T | undefined { // Keep track of the state we'll need to rollback to if lookahead fails (or if the // caller asked us to always reset our state). const saveToken = currentToken; @@ -1015,6 +1030,7 @@ namespace ts { // descent nature of our parser. However, we still store this here just so we can // assert that invariant holds. const saveContextFlags = contextFlags; + const saveParsingContext = parsingContext; // If we're only looking ahead, then tell the scanner to only lookahead as well. // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the @@ -1023,7 +1039,8 @@ namespace ts { ? scanner.lookAhead(callback) : scanner.tryScan(callback); - Debug.assert(saveContextFlags === contextFlags); + Debug.assertEqual(saveContextFlags, contextFlags); + Debug.assertEqual(saveParsingContext, parsingContext); // If our callback returned something 'falsy' or we're just looking ahead, // then unconditionally restore us to where we were. @@ -1577,7 +1594,7 @@ namespace ts { return createNodeArray(list, listPos); } - function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { const node = currentNode(parsingContext); if (node) { return consumeNode(node); @@ -1901,17 +1918,24 @@ namespace ts { } // Parses a comma-delimited list of elements - function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray { + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray; + function parseDelimitedList(kind: ParsingContext, parseElement: () => T | Fail, considerSemicolonAsDelimiter?: boolean): NodeArray | FailList; + function parseDelimitedList(kind: ParsingContext, parseElement: () => T | Fail, considerSemicolonAsDelimiter?: boolean): NodeArray | FailList { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; - const list = []; + const list: T[] = []; const listPos = getNodePos(); let commaStart = -1; // Meaning the previous token was not a comma while (true) { if (isListElement(kind, /*inErrorRecovery*/ false)) { const startPos = scanner.getStartPos(); - list.push(parseListElement(kind, parseElement)); + const elem = parseListElement(kind, parseElement); + if (isFail(elem)) { + parsingContext = saveParsingContext; + return FailList; + } + list.push(elem); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { @@ -2271,7 +2295,13 @@ namespace ts { isStartOfType(/*inStartOfParameter*/ true); } - function parseParameter(requireEqualsToken?: boolean): ParameterDeclaration { + function tryParseParameter(): ParameterDeclaration | Fail { + return parseParameterWorker(/*inSpeculation*/ true); + } + function parseParameter(): ParameterDeclaration { + return parseParameterWorker(/*inSpeculation*/ false) as ParameterDeclaration; + } + function parseParameterWorker(inSpeculation: boolean): ParameterDeclaration | Fail { const node = createNode(SyntaxKind.Parameter); if (token() === SyntaxKind.ThisKeyword) { node.name = createIdentifier(/*isIdentifier*/ true); @@ -2285,7 +2315,11 @@ namespace ts { // FormalParameter [Yield,Await]: // BindingElement[?Yield,?Await] - node.name = parseIdentifierOrPattern(); + const name = parseIdentifierOrPattern(inSpeculation); + if (isFail(name)) { + return Fail; + } + node.name = name; if (getFullWidth(node.name) === 0 && !hasModifiers(node) && isModifierKind(token())) { // in cases like // 'use strict' @@ -2300,20 +2334,27 @@ namespace ts { node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken); node.type = parseParameterType(); - node.initializer = parseInitializer(/*inParameter*/ true, requireEqualsToken); + const initializer = parseInitializer(/*inParameter*/ true, inSpeculation); + if (isFail(initializer)) { + return Fail; + } + node.initializer = initializer; return addJSDocComment(finishNode(node)); } - function fillSignature( - returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, - flags: SignatureFlags, - signature: SignatureDeclaration): void { + /** @return 'true' on success. */ + function fillSignature(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, flags: SignatureFlags, signature: SignatureDeclaration, inSpeculation?: boolean): boolean { if (!(flags & SignatureFlags.JSDoc)) { signature.typeParameters = parseTypeParameters(); } - signature.parameters = parseParameterList(flags); + const parameters = parseParameterList(flags, inSpeculation); + if (isFailList(parameters)) { + return false; + } + signature.parameters = parameters; signature.type = parseReturnType(returnToken, !!(flags & SignatureFlags.Type)); + return true; } function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined { @@ -2336,7 +2377,7 @@ namespace ts { return false; } - function parseParameterList(flags: SignatureFlags) { + function parseParameterList(flags: SignatureFlags, inSpeculation: boolean): NodeArray | FailList { // FormalParameters [Yield,Await]: (modified) // [empty] // FormalParameterList[?Yield,Await] @@ -2357,9 +2398,9 @@ namespace ts { setYieldContext(!!(flags & SignatureFlags.Yield)); setAwaitContext(!!(flags & SignatureFlags.Await)); - const result = parseDelimitedList(ParsingContext.Parameters, - flags & SignatureFlags.JSDoc ? parseJSDocParameter : () => parseParameter(!!(flags & SignatureFlags.RequireCompleteParameterList))); - + const result = parseDelimitedList( + ParsingContext.Parameters, + flags & SignatureFlags.JSDoc ? parseJSDocParameter : inSpeculation ? tryParseParameter : parseParameter); setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); @@ -3032,14 +3073,16 @@ namespace ts { while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher()); } - if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); } + return expr; } - function parseInitializer(inParameter: boolean, requireEqualsToken?: boolean): Expression { + function parseInitializer(inParameter: boolean): Expression | undefined; + function parseInitializer(inParameter: boolean, inSpeculation?: boolean): Expression | Fail | undefined; + function parseInitializer(inParameter: boolean, inSpeculation?: boolean): Expression | Fail | undefined { if (token() !== SyntaxKind.EqualsToken) { // It's not uncommon during typing for the user to miss writing the '=' token. Check if // there is no newline after the last token and if we're on an expression. If so, parse @@ -3054,12 +3097,8 @@ namespace ts { // do not try to parse initializer return undefined; } - if (inParameter && requireEqualsToken) { - // = is required when speculatively parsing arrow function parameters, - // so return a fake initializer as a signal that the equals token was missing - const result = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics._0_expected, "=") as Identifier; - result.escapedText = "= not found" as __String; - return result; + if (inSpeculation) { + return Fail; } } @@ -3225,7 +3264,7 @@ namespace ts { // it out, but don't allow any ambiguity, and return 'undefined' if this could be an // expression instead. const arrowFunction = triState === Tristate.True - ? parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ true) + ? parseParenthesizedArrowFunctionExpressionHead(/*inSpeculation*/ false) : tryParse(parsePossibleParenthesizedArrowFunctionExpressionHead); if (!arrowFunction) { @@ -3373,7 +3412,7 @@ namespace ts { } function parsePossibleParenthesizedArrowFunctionExpressionHead(): ArrowFunction { - return parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ false); + return parseParenthesizedArrowFunctionExpressionHead(/*inSpeculation*/ true); } function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined { @@ -3409,7 +3448,7 @@ namespace ts { return Tristate.False; } - function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction { + function parseParenthesizedArrowFunctionExpressionHead(inSpeculation: boolean): ArrowFunction | undefined { const node = createNode(SyntaxKind.ArrowFunction); node.modifiers = parseModifiersForArrowFunction(); const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None; @@ -3420,7 +3459,10 @@ namespace ts { // a => (b => c) // And think that "(b =>" was actually a parenthesized arrow function with a missing // close paren. - fillSignature(SyntaxKind.ColonToken, isAsync | (allowAmbiguity ? SignatureFlags.None : SignatureFlags.RequireCompleteParameterList), node); + + if (!fillSignature(SyntaxKind.ColonToken, isAsync | (inSpeculation ? SignatureFlags.RequireCompleteParameterList : SignatureFlags.None), node, inSpeculation)) { + return undefined; + } // If we couldn't get parameters, we definitely could not parse out an arrow function. if (!node.parameters) { @@ -3435,8 +3477,7 @@ namespace ts { // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. // // So we need just a bit of lookahead to ensure that it can only be a signature. - if (!allowAmbiguity && ((token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) || - find(node.parameters, p => p.initializer && ts.isIdentifier(p.initializer) && p.initializer.escapedText === "= not found"))) { + if (inSpeculation && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) { // Returning undefined here will cause our caller to rewind to where we started from. return undefined; } @@ -4574,7 +4615,6 @@ namespace ts { if (saveDecoratorContext) { setDecoratorContext(/*val*/ false); } - const node = createNode(SyntaxKind.FunctionExpression); node.modifiers = parseModifiers(); parseExpected(SyntaxKind.FunctionKeyword); @@ -4590,7 +4630,6 @@ namespace ts { fillSignature(SyntaxKind.ColonToken, isGenerator | isAsync, node); node.body = parseFunctionBlock(isGenerator | isAsync); - if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); } @@ -4653,7 +4692,6 @@ namespace ts { } const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); - if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); } @@ -5227,18 +5265,38 @@ namespace ts { // DECLARATIONS + function tryParseArrayBindingElement(): ArrayBindingElement | Fail { + return parseArrayBindingElementWorker(/*inSpeculation*/ true); + } function parseArrayBindingElement(): ArrayBindingElement { + return parseArrayBindingElementWorker(/*inSpeculation*/ false) as ArrayBindingElement; + } + function parseArrayBindingElementWorker(inSpeculation: boolean): ArrayBindingElement | Fail { if (token() === SyntaxKind.CommaToken) { return createNode(SyntaxKind.OmittedExpression); } const node = createNode(SyntaxKind.BindingElement); node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - node.name = parseIdentifierOrPattern(); - node.initializer = parseInitializer(/*inParameter*/ false); + const name = parseIdentifierOrPattern(inSpeculation); + if (isFail(name)) { + return Fail; + } + node.name = name; + const init = parseInitializer(/*inParameter*/ false, inSpeculation); + if (isFail(init)) { + return Fail; + } + node.initializer = init; return finishNode(node); } + function tryParseObjectBindingElement(): BindingElement | Fail { + return parseObjectBindingElementWorker(/*inSpeculation*/ true); + } function parseObjectBindingElement(): BindingElement { + return parseObjectBindingElementWorker(/*inSpeculation*/ false) as BindingElement; + } + function parseObjectBindingElementWorker(inSpeculation: boolean): BindingElement | Fail { const node = createNode(SyntaxKind.BindingElement); node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); const tokenIsIdentifier = isIdentifier(); @@ -5249,24 +5307,46 @@ namespace ts { else { parseExpected(SyntaxKind.ColonToken); node.propertyName = propertyName; - node.name = parseIdentifierOrPattern(); + const name = parseIdentifierOrPattern(inSpeculation); + if (isFail(name)) { + return Fail; + } + node.name = name; } - node.initializer = parseInitializer(/*inParameter*/ false); + const init = parseInitializer(/*inParameter*/ false, inSpeculation); + if (isFail(init)) { + return Fail; + } + node.initializer = init; return finishNode(node); } - function parseObjectBindingPattern(): ObjectBindingPattern { + function parseObjectBindingPattern(inSpeculation: boolean): ObjectBindingPattern | Fail { const node = createNode(SyntaxKind.ObjectBindingPattern); parseExpected(SyntaxKind.OpenBraceToken); - node.elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); + const elements = parseDelimitedList( + ParsingContext.ObjectBindingElements, + inSpeculation ? tryParseObjectBindingElement : parseObjectBindingElement, + /*considerSemicolonAsDelimiter*/ undefined); + if (isFailList(elements)) { + return Fail; + } + node.elements = elements; parseExpected(SyntaxKind.CloseBraceToken); return finishNode(node); } - function parseArrayBindingPattern(): ArrayBindingPattern { + function parseArrayBindingPattern(inSpeculation: boolean): ArrayBindingPattern | Fail { const node = createNode(SyntaxKind.ArrayBindingPattern); parseExpected(SyntaxKind.OpenBracketToken); - node.elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); + const elements = parseDelimitedList( + ParsingContext.ArrayBindingElements, + inSpeculation ? tryParseArrayBindingElement : parseArrayBindingElement, + /*considerSemicolonAsDelimiter*/ undefined); + if (isFailList(elements)) { + return Fail; + } + node.elements = elements; parseExpected(SyntaxKind.CloseBracketToken); return finishNode(node); } @@ -5275,12 +5355,14 @@ namespace ts { return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken || isIdentifier(); } - function parseIdentifierOrPattern(): Identifier | BindingPattern { + function parseIdentifierOrPattern(): Identifier | BindingPattern; + function parseIdentifierOrPattern(inSpeculation: boolean): Identifier | BindingPattern | Fail; + function parseIdentifierOrPattern(inSpeculation?: boolean): Identifier | BindingPattern | Fail { if (token() === SyntaxKind.OpenBracketToken) { - return parseArrayBindingPattern(); + return parseArrayBindingPattern(inSpeculation); } if (token() === SyntaxKind.OpenBraceToken) { - return parseObjectBindingPattern(); + return parseObjectBindingPattern(inSpeculation); } return parseIdentifier(); } @@ -5328,9 +5410,7 @@ namespace ts { else { const savedDisallowIn = inDisallowInContext(); setDisallowInContext(inForStatementInitializer); - node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration); - setDisallowInContext(savedDisallowIn); } @@ -5428,7 +5508,7 @@ namespace ts { } } - function parseNonParameterInitializer() { + function parseNonParameterInitializer(): Expression | undefined { return parseInitializer(/*inParameter*/ false); } diff --git a/tests/baselines/reference/parserArrowFunctionExpression7.js b/tests/baselines/reference/parserArrowFunctionExpression7.js new file mode 100644 index 00000000000..0646a1bf438 --- /dev/null +++ b/tests/baselines/reference/parserArrowFunctionExpression7.js @@ -0,0 +1,16 @@ +//// [parserArrowFunctionExpression7.ts] +({ + async m() { + for (;;) { + } + } +}); + + +//// [parserArrowFunctionExpression7.js] +({ + async m() { + for (;;) { + } + } +}); diff --git a/tests/baselines/reference/parserArrowFunctionExpression7.symbols b/tests/baselines/reference/parserArrowFunctionExpression7.symbols new file mode 100644 index 00000000000..0dbc2cf05a1 --- /dev/null +++ b/tests/baselines/reference/parserArrowFunctionExpression7.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression7.ts === +({ + async m() { +>m : Symbol(m, Decl(parserArrowFunctionExpression7.ts, 0, 2)) + + for (;;) { + } + } +}); + diff --git a/tests/baselines/reference/parserArrowFunctionExpression7.types b/tests/baselines/reference/parserArrowFunctionExpression7.types new file mode 100644 index 00000000000..072a1548bd2 --- /dev/null +++ b/tests/baselines/reference/parserArrowFunctionExpression7.types @@ -0,0 +1,13 @@ +=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression7.ts === +({ +>({ async m() { for (;;) { } }}) : { m(): Promise; } +>{ async m() { for (;;) { } }} : { m(): Promise; } + + async m() { +>m : () => Promise + + for (;;) { + } + } +}); + diff --git a/tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression7.ts b/tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression7.ts new file mode 100644 index 00000000000..65911cf0fc6 --- /dev/null +++ b/tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression7.ts @@ -0,0 +1,7 @@ +// @target: esnext +({ + async m() { + for (;;) { + } + } +});