diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 48eb40461cb11..573f298a6838c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -299,6 +299,12 @@ module ts { Count // Number of parsing contexts } + enum Tristate { + False, + True, + Unknown + } + function parsingContextErrors(context: ParsingContext): DiagnosticMessage { switch (context) { case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected; @@ -1262,6 +1268,11 @@ module ts { } } + function isExpressionStatement(): boolean { + // As per the grammar, neither '{' nor 'function' can start an expression statement. + return token !== SyntaxKind.OpenBraceToken && token !== SyntaxKind.FunctionKeyword && isExpression(); + } + function parseExpression(noIn?: boolean): Expression { var expr = parseAssignmentExpression(noIn); while (parseOptional(SyntaxKind.CommaToken)) { @@ -1405,33 +1416,43 @@ module ts { function tryParseParenthesizedArrowFunctionExpression(): Expression { var pos = getNodePos(); - // Note isParenthesizedArrowFunctionExpression returns true, false or undefined. var triState = isParenthesizedArrowFunctionExpression(); - if (triState !== false) { - // If we *definitely* had an arrow function expression, then just parse one out. - // Otherwise we *maybe* had an arrow function and we need to *try* to parse it out - // (which will ensure we rollback if we fail). - var sig = triState === true - ? parseSignatureAndArrow() - : tryParse(parseSignatureAndArrow); - - // If we got a signature, we're good to go. consume the rest of this arrow function. - if (sig) { + + // It is not a parenthesized arrow function. + if (triState === Tristate.False) { + return undefined; + } + + // If we're certain that we have an arrow function expression, then just parse one out. + if (triState === Tristate.True) { + var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken); + + // If we have an arrow, then try to parse the body. + if (parseExpected(SyntaxKind.EqualsGreaterThanToken)) { return parseArrowExpressionTail(pos, sig, /*noIn:*/ false); } + // If not, we're probably better off bailing out and returning a bogus function expression. + else { + return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, /* name */ undefined, sig, createMissingNode()); + } + } + + // Otherwise, *maybe* we had an arrow function and we need to *try* to parse it out + // (which will ensure we rollback if we fail). + var sig = tryParse(parseSignatureAndArrow); + if (sig === undefined) { + return undefined; + } + else { + return parseArrowExpressionTail(pos, sig, /*noIn:*/ false); } - - // Was not a parenthesized arrow function. - return undefined; } - // Note: this function returns a tristate: - // - // true -> there is definitely a parenthesized arrow function here. - // false -> there is definitely *not* a parenthesized arrow function here. - // undefined -> there *might* be a parenthesized arrow function here. Speculatively - // look ahead to be sure. - function isParenthesizedArrowFunctionExpression(): boolean { + // True -> There is definitely a parenthesized arrow function here. + // False -> There is definitely *not* a parenthesized arrow function here. + // Unknown -> There *might* be a parenthesized arrow function here. + // Speculatively look ahead to be sure. + function isParenthesizedArrowFunctionExpression(): Tristate { if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { return lookAhead(() => { var first = token; @@ -1442,7 +1463,7 @@ module ts { // that this must be an arrow function. Note, this may be too aggressive // for the "()" case. It's not uncommon for this to appear while editing // code. We should look to see if there's actually a => before proceeding. - return true; + return Tristate.True; } if (!isIdentifier()) { @@ -1450,31 +1471,30 @@ module ts { // look like a lambda. Note: we could be a little more lenient and allow // (public or (private. These would not ever actually be allowed, // but we could provide a good error message instead of bailing out. - return false; + return Tristate.False; } - // This *could* be a parenthesized arrow function. Return 'undefined' to let - // the caller know. - return undefined; + // This *could* be a parenthesized arrow function. + // Return Unknown to let the caller know. + return Tristate.Unknown; } else { Debug.assert(first === SyntaxKind.LessThanToken); - // If we have "<" not followed by an identifier, then this definitely is not - // an arrow function. + // If we have "<" not followed by an identifier, + // then this definitely is not an arrow function. if (!isIdentifier()) { - return false; + return Tristate.False; } - // This *could* be a parenthesized arrow function. Return 'undefined' to let - // the caller know. - return undefined; + // This *could* be a parenthesized arrow function. + return Tristate.Unknown; } }); } // Definitely not a parenthesized arrow function. - return false; + return Tristate.False; } function parseSignatureAndArrow(): ParsedSignature { @@ -1484,8 +1504,33 @@ module ts { } function parseArrowExpressionTail(pos: number, sig: ParsedSignature, noIn: boolean): FunctionExpression { - var body = token === SyntaxKind.OpenBraceToken ? parseBody() : parseAssignmentExpression(noIn); - return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, undefined, sig, body); + var body: Node; + + if (token === SyntaxKind.OpenBraceToken) { + body = parseBody(/* ignoreMissingOpenBrace */ false) + } + else if (isStatement(/* inErrorRecovery */ true) && !isExpressionStatement() && token !== SyntaxKind.FunctionKeyword) { + // Check if we got a plain statement (i.e. no expression-statements, no functions expressions/declarations) + // + // Here we try to recover from a potential error situation in the case where the + // user meant to supply a block. For example, if the user wrote: + // + // a => + // var v = 0; + // } + // + // they may be missing an open brace. Check to see if that's the case so we can + // try to recover better. If we don't do this, then the next close curly we see may end + // up preemptively closing the containing construct. + // + // Note: even when 'ignoreMissingOpenBrace' is passed as true, parseBody will still error. + body = parseBody(/* ignoreMissingOpenBrace */ true); + } + else { + body = parseAssignmentExpression(noIn); + } + + return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, /* name */ undefined, sig, body); } function isAssignmentOperator(): boolean { @@ -1761,7 +1806,7 @@ module ts { node.name = parsePropertyName(); if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken); - var body = parseBody(); + var body = parseBody(/* ignoreMissingOpenBrace */ false); node.initializer = makeFunctionExpression(SyntaxKind.FunctionExpression, node.pos, node.name, sig, body); } else { @@ -1795,7 +1840,7 @@ module ts { parseExpected(SyntaxKind.FunctionKeyword); var name = isIdentifier() ? parseIdentifier() : undefined; var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken); - var body = parseBody(); + var body = parseBody(/* ignoreMissingOpenBrace */ false); return makeFunctionExpression(SyntaxKind.FunctionExpression, pos, name, sig, body); } @@ -1822,9 +1867,9 @@ module ts { // STATEMENTS - function parseBlock(): Block { + function parseBlock(ignoreMissingOpenBrace: boolean): Block { var node = createNode(SyntaxKind.Block); - if (parseExpected(SyntaxKind.OpenBraceToken)) { + if (parseExpected(SyntaxKind.OpenBraceToken) || ignoreMissingOpenBrace) { node.statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } @@ -1834,8 +1879,8 @@ module ts { return finishNode(node); } - function parseBody(): Block { - var block = parseBlock(); + function parseBody(ignoreMissingOpenBrace: boolean): Block { + var block = parseBlock(ignoreMissingOpenBrace); block.kind = SyntaxKind.FunctionBlock; return block; } @@ -2022,7 +2067,7 @@ module ts { function parseTokenAndBlock(token: SyntaxKind, kind: SyntaxKind): Block { var pos = getNodePos(); parseExpected(token); - var result = parseBlock(); + var result = parseBlock(/* ignoreMissingOpenBrace */ false); result.kind = kind; result.pos = pos; return result; @@ -2037,7 +2082,7 @@ module ts { var typeAnnotationColonLength = scanner.getTextPos() - typeAnnotationColonStart; var typeAnnotation = parseTypeAnnotation(); parseExpected(SyntaxKind.CloseParenToken); - var result = parseBlock(); + var result = parseBlock(/* ignoreMissingOpenBrace */ false); result.kind = SyntaxKind.CatchBlock; result.pos = pos; result.variable = variable; @@ -2114,14 +2159,10 @@ module ts { } } - function isStatementOrFunction(inErrorRecovery: boolean): boolean { - return token === SyntaxKind.FunctionKeyword || isStatement(inErrorRecovery); - } - function parseStatement(): Statement { switch (token) { case SyntaxKind.OpenBraceToken: - return parseBlock(); + return parseBlock(/* ignoreMissingOpenBrace */ false); case SyntaxKind.VarKeyword: return parseVariableStatement(); case SyntaxKind.FunctionKeyword: @@ -2168,7 +2209,7 @@ module ts { var initialPosition = scanner.getTokenPos(); var errorCountBeforeBody = file.syntacticErrors.length; if (token === SyntaxKind.OpenBraceToken) { - var body = parseBody(); + var body = parseBody(/* ignoreMissingOpenBrace */ false); if (body && inAmbientContext && file.syntacticErrors.length === errorCountBeforeBody) { var diagnostic = isConstructor ? Diagnostics.A_constructor_implementation_cannot_be_declared_in_an_ambient_context : Diagnostics.A_function_implementation_cannot_be_declared_in_an_ambient_context; grammarErrorAtPos(initialPosition, 1, diagnostic); @@ -2340,7 +2381,7 @@ module ts { node.typeParameters = sig.typeParameters; node.parameters = sig.parameters; node.type = sig.type; - node.body = parseBody(); + node.body = parseBody(/* ignoreMissingOpenBrace */ false); return finishNode(node); } @@ -2364,7 +2405,7 @@ module ts { if (token === SyntaxKind.OpenBracketToken) { return true; } - + // If we were able to get any potential identifier... if (idToken !== undefined) { // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. diff --git a/tests/baselines/reference/arrowFunctionMissingCurlyWithSemicolon.errors.txt b/tests/baselines/reference/arrowFunctionMissingCurlyWithSemicolon.errors.txt new file mode 100644 index 0000000000000..a0cf2c00518f0 --- /dev/null +++ b/tests/baselines/reference/arrowFunctionMissingCurlyWithSemicolon.errors.txt @@ -0,0 +1,7 @@ +==== tests/cases/compiler/arrowFunctionMissingCurlyWithSemicolon.ts (1 errors) ==== + // Should error at semicolon. + var f = () => ; + ~ +!!! Expression expected. + var b = 1 * 2 * 3 * 4; + var square = (x: number) => x * x; \ No newline at end of file diff --git a/tests/baselines/reference/emptyMemberAccess.errors.txt b/tests/baselines/reference/emptyMemberAccess.errors.txt index 4c3b417338f89..122c3957554c6 100644 --- a/tests/baselines/reference/emptyMemberAccess.errors.txt +++ b/tests/baselines/reference/emptyMemberAccess.errors.txt @@ -1,9 +1,11 @@ -==== tests/cases/compiler/emptyMemberAccess.ts (1 errors) ==== +==== tests/cases/compiler/emptyMemberAccess.ts (2 errors) ==== function getObj() { ().toString(); ~ !!! '=>' expected. + ~~~~~~~~ +!!! Cannot find name 'toString'. } \ No newline at end of file diff --git a/tests/baselines/reference/parserEmptyParenthesizedExpression1.errors.txt b/tests/baselines/reference/parserEmptyParenthesizedExpression1.errors.txt index 722c7323606ad..d84b0a9755a3b 100644 --- a/tests/baselines/reference/parserEmptyParenthesizedExpression1.errors.txt +++ b/tests/baselines/reference/parserEmptyParenthesizedExpression1.errors.txt @@ -1,6 +1,8 @@ -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (1 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (2 errors) ==== function getObj() { ().toString(); ~ !!! '=>' expected. + ~~~~~~~~ +!!! Cannot find name 'toString'. } \ No newline at end of file diff --git a/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt b/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt index 472e7e237a39d..eca3954b80d8b 100644 --- a/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt +++ b/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserMissingLambdaOpenBrace1.ts (9 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserMissingLambdaOpenBrace1.ts (5 errors) ==== class C { where(filter: Iterator): Query { ~~~~~~~~~~~~~~~~~~~~ @@ -10,18 +10,10 @@ !!! Cannot find name 'fromDoWhile'. var index = 0; ~~~ -!!! Expression expected. +!!! '{' expected. return this.doWhile((item, i) => filter(item, i) ? test(item, index++) : true); ~~~~~~~ !!! Property 'doWhile' does not exist on type 'C'. - ~~~~ -!!! Cannot find name 'test'. }); - ~ -!!! Unexpected token. A constructor, method, accessor, or property was expected. - ~ -!!! Unexpected token. A constructor, method, accessor, or property was expected. } - } - ~ -!!! Declaration or statement expected. \ No newline at end of file + } \ No newline at end of file diff --git a/tests/baselines/reference/uncaughtCompilerError2.errors.txt b/tests/baselines/reference/uncaughtCompilerError2.errors.txt index 85d841ed9f4df..70395e04bdd20 100644 --- a/tests/baselines/reference/uncaughtCompilerError2.errors.txt +++ b/tests/baselines/reference/uncaughtCompilerError2.errors.txt @@ -1,7 +1,9 @@ -==== tests/cases/compiler/uncaughtCompilerError2.ts (1 errors) ==== +==== tests/cases/compiler/uncaughtCompilerError2.ts (2 errors) ==== function getObj() { ().toString(); ~ !!! '=>' expected. + ~~~~~~~~ +!!! Cannot find name 'toString'. } \ No newline at end of file diff --git a/tests/cases/compiler/arrowFunctionMissingCurlyWithSemicolon.ts b/tests/cases/compiler/arrowFunctionMissingCurlyWithSemicolon.ts new file mode 100644 index 0000000000000..67e2e6110f900 --- /dev/null +++ b/tests/cases/compiler/arrowFunctionMissingCurlyWithSemicolon.ts @@ -0,0 +1,4 @@ +// Should error at semicolon. +var f = () => ; +var b = 1 * 2 * 3 * 4; +var square = (x: number) => x * x; \ No newline at end of file