8000 Merge pull request #203 from Microsoft/arrowLookAhead · wheercool/TypeScript@c625cd9 · GitHub
[go: up one dir, main page]

Skip to content

Commit c625cd9

Browse files
Merge pull request microsoft#203 from Microsoft/arrowLookAhead
Improved lookahead for arrow functions.
2 parents cb98c5a + b0c59e7 commit c625cd9

14 files changed

+276
-106
lines changed

src/compiler/parser.ts

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,64 +1637,87 @@ module ts {
16371637
function tryParseParenthesizedArrowFunctionExpression(): Expression {
16381638
var pos = getNodePos();
16391639

1640+
// Indicates whether we are certain that we should parse an arrow expression.
16401641
var triState = isParenthesizedArrowFunctionExpression();
16411642

1642-
// It is not a parenthesized arrow function.
16431643
if (triState === Tristate.False) {
16441644
return undefined;
16451645
}
16461646

1647-
// If we're certain that we have an arrow function expression, 8000 then just parse one out.
16481647
if (triState === Tristate.True) {
16491648
var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken);
16501649

16511650
// If we have an arrow, then try to parse the body.
1652-
if (parseExpected(SyntaxKind.EqualsGreaterThanToken)) {
1653-
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false);
1651+
// Even if not, try to parse if we have an opening brace, just in case we're in an error state.
1652+
if (parseExpected(SyntaxKind.EqualsGreaterThanToken) || token === SyntaxKind.OpenBraceToken) {
1653+
return parseArrowExpressionTail(pos, sig, /* noIn: */ false);
16541654
}
1655-
// If not, we're probably better off bailing out and returning a bogus function expression.
16561655
else {
1656+
// If not, we're probably better off bailing out and returning a bogus function expression.
16571657
return makeFunctionExpression(SyntaxKind.ArrowFunction, pos, /* name */ undefined, sig, createMissingNode());
16581658
}
16591659
}
16601660

1661-
// Otherwise, *maybe* we had an arrow function and we need to *try* to parse it out
1662-
// (which will ensure we rollback if we fail).
1663-
var sig = tryParse(parseSignatureAndArrow);
1664-
if (sig === undefined) {
1665-
return undefined;
1661+
// *Maybe* we had an arrow function and we need to try to parse it out,
1662+
// rolling back and trying other parses if we fail.
1663+
var sig = tryParseSignatureIfArrowOrBraceFollows();
1664+
if (sig) {
1665+
parseExpected(SyntaxKind.EqualsGreaterThanToken);
1666+
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false);
16661667
}
16671668
else {
1668-
return parseArrowExpressionTail(pos, sig, /*noIn:*/ false);
1669+
return undefined;
16691670
}
16701671
}
16711672

1672-
// True -> There is definitely a parenthesized arrow function here.
1673-
// False -> There is definitely *not* a parenthesized arrow function here.
1673+
// True -> We definitely expect a parenthesized arrow function here.
1674+
// False -> There *cannot* be a parenthesized arrow function here.
16741675
// Unknown -> There *might* be a parenthesized arrow function 9E88 here.
1675-
// Speculatively look ahead to be sure.
1676+
// Speculatively look ahead to be sure, and rollback if not.
16761677
function isParenthesizedArrowFunctionExpression(): Tristate {
16771678
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
16781679
return lookAhead(() => {
16791680
var first = token;
1680-
nextToken();
1681+
var second = nextToken();
1682+
16811683
if (first === SyntaxKind.OpenParenToken) {
1682-
if (token === SyntaxKind.CloseParenToken || token === SyntaxKind.DotDotDotToken) {
1683-
// Simple cases. if we see () or (... then presume that presume
1684-
// that this must be an arrow function. Note, this may be too aggressive
1685-
// for the "()" case. It's not uncommon for this to appear while editing
1686-
// code. We should look to see if there's actually a => before proceeding.
1684+
if (second === SyntaxKind.CloseParenToken) {
1685+
// Simple cases: "() =>", "(): ", and "() {".
1686+
// This is an arrow function with no parameters.
1687+
// The last one is not actually an arrow function,
1688+
// but this is probably what the user intended.
1689+
var third = nextToken();
1690+
switch (third) {
1691+
case SyntaxKind.EqualsGreaterThanToken:
1692+
case SyntaxKind.ColonToken:
1693+
case SyntaxKind.OpenBraceToken:
1694+
return Tristate.True;
1695+
default:
1696+
return Tristate.False;
1697+
}
1698+
}
1699+
1700+
// Simple case: "(..."
1701+
// This is an arrow function with a rest parameter.
1702+
if (second === SyntaxKind.DotDotDotToken) {
16871703
return Tristate.True;
16881704
}
16891705

1706+
// If we had "(" followed by something that's not an identifier,
1707+
// then this definitely doesn't look like a lambda.
1708+
// Note: we could be a little more lenient and allow
1709+
// "(public" or "(private". These would not ever actually be allowed,
1710+
// but we could provide a good error message instead of bailing out.
16901711
if (!isIdentifier()) {
1691-
// We had "(" not followed by an identifier. This definitely doesn't
1692-
// look like a lambda. Note: we could be a little more lenient and allow
1693-
// (public or (private. These would not ever actually be allowed,
1694-
// but we could provide a good error message instead of bailing out.
16951712
return Tristate.False;
16961713
}
16971714

1715+
// If we have something like "(a:", then we must have a
1716+
// type-annotated parameter in an arrow function expression.
1717+
if (nextToken() === SyntaxKind.ColonToken) {
1718+
return Tristate.True;
1719+
}
1720+
16981721
// This *could* be a parenthesized arrow function.
16991722
// Return Unknown to let the caller know.
17001723
return Tristate.Unknown;
@@ -1718,10 +1741,24 @@ module ts {
17181741
return Tristate.False;
17191742
}
17201743

1721-
function parseSignatureAndArrow(): ParsedSignature {
1722-
var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken);
1723-
parseExpected(SyntaxKind.EqualsGreaterThanToken);
1724-
return sig;
1744+
function tryParseSignatureIfArrowOrBraceFollows(): ParsedSignature {
1745+
return tryParse(() => {
1746+
var sig = parseSignature(SyntaxKind.CallSignature, SyntaxKind.ColonToken);
1747+
1748+
// Parsing a signature isn't enough.
1749+
// Parenthesized arrow signatures often look like other valid expressions.
1750+
// For instance:
1751+
// - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value.
1752+
// - "(x,y)" is a comma expression parsed as a signature with two parameters.
1753+
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
1754+
//
1755+
// So we need just a bit of lookahead to ensure that it can only be a signature.
1756+
if (token === SyntaxKind.EqualsGreaterThanToken || token === SyntaxKind.OpenBraceToken) {
1757+
return sig;
1758+
}
1759+
1760+
return undefined;
1761+
});
17251762
}
17261763

17271764
function parseArrowExpressionTail(pos: number, sig: ParsedSignature, noIn: boolean): FunctionExpression {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
==== tests/cases/compiler/arrowFunctionsMissingTokens.ts (24 errors) ====
2+
3+
module missingArrowsWithCurly {
4+
var a = () { };
5+
~
6+
!!! '=>' expected.
7+
8+
var b = (): void { }
9+
~
10+
!!! '=>' expected.
11+
12+
var c = (x) { };
13+
~
14+
!!! '=>' expected.
15+
16+
var d = (x: number, y: string) { };
17+
~
18+
!!! '=>' expected.
19+
20+
var e = (x: number, y: string): void { };
21+
~
22+
!!! '=>' expected.
23+
}
24+
25+
module missingCurliesWithArrow {
26+
module withStatement {
27+
var a = () => var k = 10;};
28+
~~~
29+
!!! '{' expected.
30+
31+
var b = (): void => var k = 10;}
32+
~~~
33+
!!! '{' expected.
34+
35+
var c = (x) => var k = 10;};
36+
~~~
37+
!!! '{' expected.
38+
39+
var d = (x: number, y: string) => var k = 10;};
40+
~~~
41+
!!! '{' expected.
42+
43+
var e = (x: number, y: string): void => var k = 10;};
44+
~~~
45+
!!! '{' expected.
46+
47+
var f = () => var k = 10;}
48+
~~~
49+
!!! '{' expected.
50+
}
51+
52+
module withoutStatement {
53+
var a = () => };
54+
~
55+
!!! Expression expected.
56+
57+
var b = (): void => }
58+
~
59+
!!! Expression expected.
60+
61+
var c = (x) => };
62+
~
63+
!!! Expression expected.
64+
65+
var d = (x: number, y: string) => };
66+
~
67+
!!! Expression expected.
68+
69+
var e = (x: number, y: string): void => };
70+
~
71+
!!! Expression expected.
72+
73+
var f = () => }
74+
~
75+
!!! Expression expected.
76+
}
77+
~
78+
!!! Declaration or statement expected.
79+
}
80+
~
81+
!!! Declaration or statement expected.
82+
83+
module ce_nEst_pas_une_arrow_function {
84+
var a = ();
85+
~
86+
!!! Expression expected.
87+
88+
var b = (): void;
89+
~
90+
!!! '=>' expected.
91+
92+
var c = (x);
93+
~
94+
!!! Cannot find name 'x'.
95+
96+
var d = (x: number, y: string);
97+
~
98+
!!! '=>' expected.
99+
100+
var e = (x: number, y: string): void;
101+
~
102+
!!! '=>' expected.
103+
}
104+
105+
module okay {
106+
var a = () => { };
107+
108+
var b = (): void => { }
109+
110+
var c = (x) => { };
111+
112+
var d = (x: number, y: string) => { };
113+
114+
var e = (x: number, y: string): void => { };
115+
}
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
==== tests/cases/compiler/emptyMemberAccess.ts (2 errors) ====
1+
==== tests/cases/compiler/emptyMemberAccess.ts (1 errors) ====
22
function getObj() {
33

44
().toString();
5-
~
6-
!!! '=>' expected.
7-
~~~~~~~~
8-
!!! Cannot find name 'toString'.
5+
~
6+
!!! Expression expected.
97

108
}
119

tests/baselines/reference/es6ClassTest9.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
declare class foo();
33
~
44
!!! '{' expected.
5-
~
6-
!!! '=>' expected.
5+
~
6+
!!! Expression expected.
77
function foo() {}
88
~~~
99
!!! Duplicate identifier 'foo'.

tests/baselines/reference/fatarrowfunctionsErrors.errors.txt

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
==== tests/cases/compiler/fatarrowfunctionsErrors.ts (25 errors) ====
1+
==== tests/cases/compiler/fatarrowfunctionsErrors.ts (18 errors) ====
22
foo((...Far:any[])=>{return 0;})
33
~~~
44
!!! Cannot find name 'foo'.
@@ -39,25 +39,11 @@
3939
~
4040
!!! '=>' expected.
4141
var x2 = (a:number) :void {};
42-
~
43-
!!! ')' expected.
44-
~
45-
!!! ',' expected.
46-
~
47-
!!! Variable declaration expected.
48-
~~~~
49-
!!! Variable declaration expected.
50-
~
51-
!!! Cannot find name 'a'.
42+
~
43+
!!! '=>' expected.
5244
var x3 = (a:number) {};
53-
~
54-
!!! ')' expected.
55-
~
56-
!!! ',' expected.
5745
~
58-
!!! Variable declaration expected.
59-
~
60-
!!! Cannot find name 'a'.
46+
!!! '=>' expected.
6147
var x4= (...a: any[]) { };
6248
~
6349
!!! '=>' expected.

tests/baselines/reference/invalidTryStatements2.errors.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (5 errors) ====
1+
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (4 errors) ====
22
function fn() {
33
try {
44
} catch { // syntax error, missing '(x)'
@@ -10,9 +10,7 @@
1010
~~~~~
1111
!!! Statement expected.
1212
~
13-
!!! ';' expected.
14-
~
15-
!!! Cannot find name 'x'.
13+
!!! '=>' expected.
1614

1715
finally{ } // error missing try
1816
~~~~~~~
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
==== tests/cases/conformance/parser/ecmascript5/RegressionTests/parser566700.ts (1 errors) ====
22
var v = ()({});
3-
~
4-
!!! '=>' expected.
3+
~
4+
!!! Expression expected.
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (2 errors) ====
1+
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEmptyParenthesizedExpression1.ts (1 errors) ====
22
function getObj() {
33
().toString();
4-
~
5-
!!! '=>' expected.
6-
~~~~~~~~
7-
!!! Cannot find name 'toString'.
4+
~
5+
!!! Expression expected.
86
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/Expressions/parserErrorRecovery_Expression1.ts (1 errors) ====
22
var v = ()({});
3-
~
4-
!!! '=>' expected.
3+
~
4+
!!! Expression expected.
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList5.ts (4 errors) ====
1+
==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList5.ts (2 errors) ====
22
(a:number => { }
3-
~
4-
!!! ')' expected.
53
~~
6-
!!! ';' expected.
7-
~
8-
!!! Cannot find name 'a'.
9-
~~~~~~
10-
!!! Cannot find name 'number'.
4+
!!! ',' expected.
5+
~
6+
!!! ')' expected.

0 commit comments

Comments
 (0)
0