Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved lookahead for arrow functions. #203

Merged
merged 5 commits into from Jul 25, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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: "() =>", "(): ", and "() {".
// 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens in the following case?

var a = true ? () : false;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but that's not a valid expression anyway. Cyrus and I discussed this offline, and it doesn't seem like you'd get such a big win there.

Still, if you'd like to file a bug for it, I'd be more than happy to take it on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok

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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did tihs comment move outside of the block that it is relevant to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Felt like it would be more consistent since all the other comment blocks reside outside the checks. I'm feeling against it now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, with different phrasing, I think it might be fine.

// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is odd, and inconsistent. Why would we not have smilar logic here to recover and tell you => expected?

~
!!! Cannot find name 'x'.

var d = (x: number, y: string) { };
~
!!! ')' expected.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is bad. it's clearly a lambd, but we're giving an error about a missing paren. (a: should always trigger arrow function parsing. The old compiler supported this properly.

~
!!! ',' 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not good handling for this case. i think you need to explicitly look for id and treat that as an arrow function.

~
!!! ',' 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

celle_ci

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline, resolved.

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 => { };
}