Skip to content

Commit

Permalink
Improved lookahead for arrow functions.
Browse files Browse the repository at this point in the history
Fixes issue #34.

Specifically:
    * We no longer automatically assume "()" is a function expression. It must be followed by an arrow, colon, or curly brace.
    * If an arrow is missing following a signature, but a curly brace is present, we assume the user forgot the arrow and try to parse the body anyway.
  • Loading branch information
DanielRosenwasser committed Jul 22, 2014
1 parent c8fc26a commit 426e8e5
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 37 deletions.
49 changes: 33 additions & 16 deletions src/compiler/parser.ts
Expand Up @@ -1445,6 +1445,7 @@ module ts {
function tryParseParenthesizedArrowFunctionExpression(): Expression {
var pos = getNodePos();

// Whether we are certain that we should parse an arrow expression.
var triState = isParenthesizedArrowFunctionExpression();

// It is not a parenthesized arrow function.
Expand All @@ -1457,11 +1458,12 @@ module ts {
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);
// Even if not, try to parse if we have an opening brace, just in case we're in an error state.
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 {
// If not, we're probably better off bailing out and returning a bogus function expression.
return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, /* name */ undefined, sig, createMissingNode());
}
}
Expand All @@ -1477,29 +1479,44 @@ module ts {
}
}

// True -> There is definitely a parenthesized arrow function here.
// False -> There is definitely *not* a parenthesized arrow function here.
// True -> We definitely expect a parenthesized arrow function here.
// False -> There *cannot* 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 {
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
return lookAhead(() => {
var first = token;
nextToken();
var second = nextToken();

if (first === SyntaxKind.OpenParenToken) {
if (token === SyntaxKind.CloseParenToken || token === SyntaxKind.DotDotDotToken) {
// Simple cases. if we see () or (... then presume that presume
// 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.
if (second === SyntaxKind.CloseParenToken) {
// Simple cases: "() =>", "(): " "() {".
// This is an arrow function with no parameters.
// The last one is not actually an arrow function,
// 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;
}

// 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.
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;
}

Expand Down
145 changes: 145 additions & 0 deletions tests/baselines/reference/arrowFunctionsMissingTokens.errors.txt
@@ -0,0 +1,145 @@
==== tests/cases/compiler/arrowFunctionsMissingTokens.ts (39 errors) ====

module missingArrowsWithCurly {
var a = () { };
~
!!! '=>' expected.

var b = (): void { }
~
!!! '=>' expected.

var c = (x) { };
~
!!! ',' expected.
~
!!! Cannot find name 'x'.

var d = (x: number, y: string) { };
~
!!! ')' expected.
~
!!! ',' expected.
~
!!! Variable declaration expected.
~
!!! Cannot find name 'x'.

var e = (x: number, y: string): void { };
~
!!! ')' expected.
~
!!! ',' expected.
~
!!! Variable declaration expected.
~~~~
!!! Variable declaration expected.
~
!!! Cannot find name 'x'.
}

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 ceci_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.
~
!!! ',' expected.
~
!!! Cannot find name 'x'.

var e = (x: number, y: string): void;
~
!!! ')' expected.
~
!!! ',' expected.
~
!!! Variable declaration expected.
~~~~
!!! Variable declaration expected.
~
!!! Expression expected.
~
!!! Cannot find name 'x'.
}

module okay {
var a = () => { };

var b = (): void => { }

var c = (x) => { };

var d = (x: number, y: string) => { };

var e = (x: number, y: string): void => { };
}
8 changes: 3 additions & 5 deletions tests/baselines/reference/emptyMemberAccess.errors.txt
@@ -1,11 +1,9 @@
==== tests/cases/compiler/emptyMemberAccess.ts (2 errors) ====
==== tests/cases/compiler/emptyMemberAccess.ts (1 errors) ====
function getObj() {

().toString();
~
!!! '=>' expected.
~~~~~~~~
!!! Cannot find name 'toString'.
~
!!! Expression expected.

}

4 changes: 2 additions & 2 deletions tests/baselines/reference/es6ClassTest9.errors.txt
Expand Up @@ -2,8 +2,8 @@
declare class foo();
~
!!! '{' expected.
~
!!! '=>' expected.
~
!!! Expression expected.
function foo() {}
~~~
!!! Duplicate identifier 'foo'.
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/parser566700.errors.txt
@@ -1,4 +1,4 @@
==== tests/cases/conformance/parser/ecmascript5/RegressionTests/parser566700.ts (1 errors) ====
var v = ()({});
~
!!! '=>' expected.
~
!!! Expression expected.
@@ -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() {
().toString();
~
!!! '=>' expected.
~~~~~~~~
!!! Cannot find name 'toString'.
~
!!! Expression expected.
}
@@ -1,4 +1,4 @@
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/Expressions/parserErrorRecovery_Expression1.ts (1 errors) ====
var v = ()({});
~
!!! '=>' expected.
~
!!! Expression expected.
8 changes: 3 additions & 5 deletions tests/baselines/reference/uncaughtCompilerError2.errors.txt
@@ -1,9 +1,7 @@
==== tests/cases/compiler/uncaughtCompilerError2.ts (2 errors) ====
==== tests/cases/compiler/uncaughtCompilerError2.ts (1 errors) ====
function getObj() {
().toString();
~
!!! '=>' expected.
~~~~~~~~
!!! Cannot find name 'toString'.
~
!!! Expression expected.
}

66 changes: 66 additions & 0 deletions tests/cases/compiler/arrowFunctionsMissingTokens.ts
@@ -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 ceci_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 => { };
}

0 comments on commit 426e8e5

Please sign in to comment.