Merge pull request #203 from Microsoft/arrowLookAhead

Improved lookahead for arrow functions.
This commit is contained in:
Daniel Rosenwasser 2014-07-24 18:41:34 -07:00
commit c625cd9259
14 changed files with 276 additions and 106 deletions

View file

@ -1637,64 +1637,87 @@ module ts {
function tryParseParenthesizedArrowFunctionExpression(): Expression { function tryParseParenthesizedArrowFunctionExpression(): Expression {
var pos = getNodePos(); var pos = getNodePos();
// Indicates whether we are certain that we should parse an arrow expression.
var triState = isParenthesizedArrowFunctionExpression(); var triState = isParenthesizedArrowFunctionExpression();
// It is not a parenthesized arrow function.
if (triState === Tristate.False) { if (triState === Tristate.False) {
return undefined; return undefined;
} }
// If we're certain that we have an arrow function expression, then just parse one out.
if (triState === Tristate.True) { if (triState === Tristate.True) {
var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken); var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken);
// If we have an arrow, then try to parse the body. // If we have an arrow, then try to parse the body.
if (parseExpected(SyntaxKind.EqualsGreaterThanToken)) { // Even if not, try to parse if we have an opening brace, just in case we're in an error state.
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false); if (parseExpected(SyntaxKind.EqualsGreaterThanToken) || token === SyntaxKind.OpenBraceToken) {
return parseArrowExpressionTail(pos, sig, /* noIn: */ false);
} }
// If not, we're probably better off bailing out and returning a bogus function expression.
else { else {
// If not, we're probably better off bailing out and returning a bogus function expression.
return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, /* name */ undefined, sig, createMissingNode()); 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 // *Maybe* we had an arrow function and we need to try to parse it out,
// (which will ensure we rollback if we fail). // rolling back and trying other parses if we fail.
var sig = tryParse(parseSignatureAndArrow); var sig = tryParseSignatureIfArrowOrBraceFollows();
if (sig === undefined) { if (sig) {
return undefined; parseExpected(SyntaxKind.EqualsGreaterThanToken);
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false);
} }
else { else {
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false); return undefined;
} }
} }
// True -> There is definitely a parenthesized arrow function here. // True -> We definitely expect a parenthesized arrow function here.
// False -> There is definitely *not* a parenthesized arrow function here. // False -> There *cannot* be a parenthesized arrow function here.
// Unknown -> There *might* be a parenthesized arrow function here. // Unknown -> There *might* be a parenthesized arrow function here.
// Speculatively look ahead to be sure. // Speculatively look ahead to be sure, and rollback if not.
function isParenthesizedArrowFunctionExpression(): Tristate { function isParenthesizedArrowFunctionExpression(): Tristate {
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) { if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
return lookAhead(() => { return lookAhead(() => {
var first = token; var first = token;
nextToken(); var second = nextToken();
if (first === SyntaxKind.OpenParenToken) { if (first === SyntaxKind.OpenParenToken) {
if (token === SyntaxKind.CloseParenToken || token === SyntaxKind.DotDotDotToken) { if (second === SyntaxKind.CloseParenToken) {
// Simple cases. if we see () or (... then presume that presume // Simple cases: "() =>", "(): ", and "() {".
// that this must be an arrow function. Note, this may be too aggressive // This is an arrow function with no parameters.
// for the "()" case. It's not uncommon for this to appear while editing // The last one is not actually an arrow function,
// code. We should look to see if there's actually a => before proceeding. // but this is probably what the user intended.
var third = nextToken();
switch (third) {
case SyntaxKind.EqualsGreaterThanToken:
case SyntaxKind.ColonToken:
case SyntaxKind.OpenBraceToken:
return Tristate.True;
default:
return Tristate.False;
}
}
// Simple case: "(..."
// This is an arrow function with a rest parameter.
if (second === SyntaxKind.DotDotDotToken) {
return Tristate.True; return Tristate.True;
} }
// If we had "(" followed by something that's not an identifier,
// then this definitely doesn't 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.
if (!isIdentifier()) { if (!isIdentifier()) {
// We had "(" not followed by an identifier. This definitely doesn't
// 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 Tristate.False; return Tristate.False;
} }
// If we have something like "(a:", then we must have a
// type-annotated parameter in an arrow function expression.
if (nextToken() === SyntaxKind.ColonToken) {
return Tristate.True;
}
// This *could* be a parenthesized arrow function. // This *could* be a parenthesized arrow function.
// Return Unknown to let the caller know. // Return Unknown to let the caller know.
return Tristate.Unknown; return Tristate.Unknown;
@ -1718,10 +1741,24 @@ module ts {
return Tristate.False; return Tristate.False;
} }
function parseSignatureAndArrow(): ParsedSignature { function tryParseSignatureIfArrowOrBraceFollows(): ParsedSignature {
var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken); return tryParse(() => {
parseExpected(SyntaxKind.EqualsGreaterThanToken); var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken);
return sig;
// Parsing a signature isn't enough.
// Parenthesized arrow signatures often look like other valid expressions.
// For instance:
// - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value.
// - "(x,y)" is a comma expression parsed as a signature with two parameters.
// - "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 (token === SyntaxKind.EqualsGreaterThanToken || token === SyntaxKind.OpenBraceToken) {
return sig;
}
return undefined;
});
} }
function parseArrowExpressionTail(pos: number, sig: ParsedSignature, noIn: boolean): FunctionExpression { function parseArrowExpressionTail(pos: number, sig: ParsedSignature, noIn: boolean): FunctionExpression {

View file

@ -0,0 +1,115 @@
==== tests/cases/compiler/arrowFunctionsMissingTokens.ts (24 errors) ====
module missingArrowsWithCurly {
var a = () { };
~
!!! '=>' expected.
var b = (): void { }
~
!!! '=>' expected.
var c = (x) { };
~
!!! '=>' expected.
var d = (x: number, y: string) { };
~
!!! '=>' expected.
var e = (x: number, y: string): void { };
~
!!! '=>' expected.
}
module missingCurliesWithArrow {
module withStatement {
var a = () => var k = 10;};
~~~
!!! '{' expected.
var b = (): void => var k = 10;}
~~~
!!! '{' expected.
var c = (x) => var k = 10;};
~~~
!!! '{' expected.
var d = (x: number, y: string) => var k = 10;};
~~~
!!! '{' expected.
var e = (x: number, y: string): void => var k = 10;};
~~~
!!! '{' expected.
var f = () => var k = 10;}
~~~
!!! '{' expected.
}
module withoutStatement {
var a = () => };
~
!!! Expression expected.
var b = (): void => }
~
!!! Expression expected.
var c = (x) => };
~
!!! Expression expected.
var d = (x: number, y: string) => };
~
!!! Expression expected.
var e = (x: number, y: string): void => };
~
!!! Expression expected.
var f = () => }
~
!!! Expression expected.
}
~
!!! Declaration or statement expected.
}
~
!!! Declaration or statement expected.
module ce_nEst_pas_une_arrow_function {
var a = ();
~
!!! Expression expected.
var b = (): void;
~
!!! '=>' expected.
var c = (x);
~
!!! Cannot find name 'x'.
var d = (x: number, y: string);
~
!!! '=>' expected.
var e = (x: number, y: string): void;
~
!!! '=>' expected.
}
module okay {
var a = () => { };
var b = (): void => { }
var c = (x) => { };
var d = (x: number, y: string) => { };
var e = (x: number, y: string): void => { };
}

View file

@ -1,11 +1,9 @@
==== tests/cases/compiler/emptyMemberAccess.ts (2 errors) ==== ==== tests/cases/compiler/emptyMemberAccess.ts (1 errors) ====
function getObj() { function getObj() {
().toString(); ().toString();
~ ~
!!! '=>' expected. !!! Expression expected.
~~~~~~~~
!!! Cannot find name 'toString'.
} }

View file

@ -2,8 +2,8 @@
declare class foo(); declare class foo();
~ ~
!!! '{' expected. !!! '{' expected.
~ ~
!!! '=>' expected. !!! Expression expected.
function foo() {} function foo() {}
~~~ ~~~
!!! Duplicate identifier 'foo'. !!! Duplicate identifier 'foo'.

View file

@ -1,4 +1,4 @@
==== tests/cases/compiler/fatarrowfunctionsErrors.ts (25 errors) ==== ==== tests/cases/compiler/fatarrowfunctionsErrors.ts (18 errors) ====
foo((...Far:any[])=>{return 0;}) foo((...Far:any[])=>{return 0;})
~~~ ~~~
!!! Cannot find name 'foo'. !!! Cannot find name 'foo'.
@ -39,25 +39,11 @@
~ ~
!!! '=>' expected. !!! '=>' expected.
var x2 = (a:number) :void {}; var x2 = (a:number) :void {};
~ ~
!!! ')' expected. !!! '=>' expected.
~
!!! ',' expected.
~
!!! Variable declaration expected.
~~~~
!!! Variable declaration expected.
~
!!! Cannot find name 'a'.
var x3 = (a:number) {}; var x3 = (a:number) {};
~
!!! ')' expected.
~
!!! ',' expected.
~ ~
!!! Variable declaration expected. !!! '=>' expected.
~
!!! Cannot find name 'a'.
var x4= (...a: any[]) { }; var x4= (...a: any[]) { };
~ ~
!!! '=>' expected. !!! '=>' expected.

View file

@ -1,4 +1,4 @@
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (5 errors) ==== ==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (4 errors) ====
function fn() { function fn() {
try { try {
} catch { // syntax error, missing '(x)' } catch { // syntax error, missing '(x)'
@ -10,9 +10,7 @@
~~~~~ ~~~~~
!!! Statement expected. !!! Statement expected.
~ ~
!!! ';' expected. !!! '=>' expected.
~
!!! Cannot find name 'x'.
finally{ } // error missing try finally{ } // error missing try
~~~~~~~ ~~~~~~~

View file

@ -1,4 +1,4 @@
==== tests/cases/conformance/parser/ecmascript5/RegressionTests/parser566700.ts (1 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/RegressionTests/parser566700.ts (1 errors) ====
var v = ()({}); var v = ()({});
~ ~
!!! '=>' expected. !!! Expression expected.

View file

@ -1,8 +1,6 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (2 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (1 errors) ====
function getObj() { function getObj() {
().toString(); ().toString();
~ ~
!!! '=>' expected. !!! Expression expected.
~~~~~~~~
!!! Cannot find name 'toString'.
} }

View file

@ -1,4 +1,4 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/Expressions/parserErrorRecovery_Expression1.ts (1 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/Expressions/parserErrorRecovery_Expression1.ts (1 errors) ====
var v = ()({}); var v = ()({});
~ ~
!!! '=>' expected. !!! Expression expected.

View file

@ -1,10 +1,6 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList5.ts (4 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList5.ts (2 errors) ====
(a:number => { } (a:number => { }
~
!!! ')' expected.
~~ ~~
!!! ';' expected. !!! ',' expected.
~ ~
!!! Cannot find name 'a'. !!! ')' expected.
~~~~~~
!!! Cannot find name 'number'.

View file

@ -1,4 +1,4 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts (23 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserUnterminatedGeneric2.ts (15 errors) ====
declare module ng { declare module ng {
interfaceICompiledExpression { interfaceICompiledExpression {
~ ~
@ -6,24 +6,8 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! Cannot find name 'interfaceICompiledExpression'. !!! Cannot find name 'interfaceICompiledExpression'.
(context: any, locals?: any): any; (context: any, locals?: any): any;
~ ~
!!! ')' expected. !!! '=>' expected.
~
!!! Expression expected.
~
!!! ';' expected.
~
!!! Statement expected.
~~~~~~~
!!! Cannot find name 'context'.
~~~
!!! Cannot find name 'any'.
~~~~~~
!!! Cannot find name 'locals'.
~~~
!!! Cannot find name 'any'.
~~~
!!! Cannot find name 'any'.
assign(context: any, value: any): any; assign(context: any, value: any): any;
~ ~
!!! ',' expected. !!! ',' expected.

View file

@ -1,12 +1,6 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ArrowFunctions/parserX_ArrowFunction1.ts (4 errors) ==== ==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ArrowFunctions/parserX_ArrowFunction1.ts (1 errors) ====
var v = (a: ) => { var v = (a: ) => {
~
!!! ')' expected.
~ ~
!!! Variable declaration expected. !!! Type expected.
~~
!!! ';' expected.
~
!!! Cannot find name 'a'.
}; };

View file

@ -1,9 +1,7 @@
==== tests/cases/compiler/uncaughtCompilerError2.ts (2 errors) ==== ==== tests/cases/compiler/uncaughtCompilerError2.ts (1 errors) ====
function getObj() { function getObj() {
().toString(); ().toString();
~ ~
!!! '=>' expected. !!! Expression expected.
~~~~~~~~
!!! Cannot find name 'toString'.
} }

View file

@ -0,0 +1,66 @@
module missingArrowsWithCurly {
var a = () { };
var b = (): void { }
var c = (x) { };
var d = (x: number, y: string) { };
var e = (x: number, y: string): void { };
}
module missingCurliesWithArrow {
module withStatement {
var a = () => var k = 10;};
var b = (): void => var k = 10;}
var c = (x) => var k = 10;};
var d = (x: number, y: string) => var k = 10;};
var e = (x: number, y: string): void => var k = 10;};
var f = () => var k = 10;}
}
module withoutStatement {
var a = () => };
var b = (): void => }
var c = (x) => };
var d = (x: number, y: string) => };
var e = (x: number, y: string): void => };
var f = () => }
}
}
module ce_nEst_pas_une_arrow_function {
var a = ();
var b = (): void;
var c = (x);
var d = (x: number, y: string);
var e = (x: number, y: string): void;
}
module okay {
var a = () => { };
var b = (): void => { }
var c = (x) => { };
var d = (x: number, y: string) => { };
var e = (x: number, y: string): void => { };
}