Skip to content

Commit 2cff7e0

Browse files
committed
Improve arrow function parsing
1 parent c27b6e8 commit 2cff7e0

File tree

4 files changed

+107
-77
lines changed

4 files changed

+107
-77
lines changed

src/ast.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ export function nodeIsCallable(kind: NodeKind): bool {
117117
case NodeKind.IDENTIFIER:
118118
case NodeKind.CALL:
119119
case NodeKind.ELEMENTACCESS:
120-
case NodeKind.PROPERTYACCESS: return true;
120+
case NodeKind.PROPERTYACCESS:
121+
case NodeKind.PARENTHESIZED: return true;
121122
}
122123
return false;
123124
}

src/parser.ts

Lines changed: 85 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,7 @@ export class Parser extends DiagnosticEmitter {
13951395

13961396
var body: Statement | null;
13971397
if (isArrow) {
1398-
body = this.parseStatement(tn, false);
1398+
body = this.parseArrowFunctionBody(tn);
13991399
} else {
14001400
if (!tn.skip(Token.OPENBRACE)) {
14011401
this.error(
@@ -1420,6 +1420,11 @@ export class Parser extends DiagnosticEmitter {
14201420
return Node.createFunctionExpression(declaration);
14211421
}
14221422

1423+
private parseArrowFunctionBody(tn: Tokenizer): Statement | null {
1424+
// TODO: should parse an expression unless the next token is `{`.
1425+
return this.parseStatement(tn, false);
1426+
}
1427+
14231428
parseClassOrInterface(
14241429
tn: Tokenizer,
14251430
flags: CommonFlags,
@@ -2965,80 +2970,7 @@ export class Parser extends DiagnosticEmitter {
29652970

29662971
// ParenthesizedExpression
29672972
// FunctionExpression
2968-
case Token.OPENPAREN: {
2969-
2970-
// determine whether this is a function expression
2971-
if (tn.skip(Token.CLOSEPAREN)) { // must be a function expression (fast route)
2972-
return this.parseFunctionExpressionCommon(
2973-
tn,
2974-
Node.createEmptyIdentifierExpression(tn.range(startPos)),
2975-
[],
2976-
true
2977-
);
2978-
}
2979-
let state = tn.mark();
2980-
let again = true;
2981-
do {
2982-
switch (tn.next(IdentifierHandling.PREFER)) {
2983-
2984-
// function expression
2985-
case Token.DOT_DOT_DOT: {
2986-
tn.reset(state);
2987-
return this.parseFunctionExpression(tn);
2988-
}
2989-
// can be both
2990-
case Token.IDENTIFIER: {
2991-
tn.readIdentifier();
2992-
switch (tn.next()) {
2993-
2994-
// if we got here, check for arrow
2995-
case Token.CLOSEPAREN: {
2996-
if (!tn.skip(Token.EQUALS_GREATERTHAN)) {
2997-
again = false;
2998-
break;
2999-
}
3000-
// fall-through
3001-
}
3002-
// function expression
3003-
case Token.COLON: { // type annotation
3004-
tn.reset(state);
3005-
return this.parseFunctionExpression(tn);
3006-
}
3007-
// can be both
3008-
case Token.QUESTION: // optional parameter or ternary
3009-
case Token.COMMA: {
3010-
break; // continue
3011-
}
3012-
// parenthesized expression
3013-
// case Token.EQUALS: // missing type annotation for simplicity
3014-
default: {
3015-
again = false;
3016-
break;
3017-
}
3018-
}
3019-
break;
3020-
}
3021-
// parenthesized expression
3022-
default: {
3023-
again = false;
3024-
break;
3025-
}
3026-
}
3027-
} while (again);
3028-
tn.reset(state);
3029-
3030-
// parse parenthesized
3031-
expr = this.parseExpression(tn);
3032-
if (!expr) return null;
3033-
if (!tn.skip(Token.CLOSEPAREN)) {
3034-
this.error(
3035-
DiagnosticCode._0_expected,
3036-
tn.range(), ")"
3037-
);
3038-
return null;
3039-
}
3040-
return Node.createParenthesizedExpression(expr, tn.range(startPos, tn.pos));
3041-
}
2973+
case Token.OPENPAREN: return this.parseArrowFunctionOrParenthesizedExpression(tn, startPos);
30422974
// ArrayLiteralExpression
30432975
case Token.OPENBRACKET: {
30442976
let elementExpressions = new Array<Expression | null>();
@@ -3133,7 +3065,24 @@ export class Parser extends DiagnosticEmitter {
31333065
);
31343066
}
31353067
case Token.IDENTIFIER: {
3136-
return Node.createIdentifierExpression(tn.readIdentifier(), tn.range(startPos, tn.pos));
3068+
const name = tn.readIdentifier();
3069+
const range = tn.range(startPos, tn.pos);
3070+
const id = Node.createIdentifierExpression(name, range);
3071+
if (!tn.skip(Token.EQUALS_GREATERTHAN)) return id;
3072+
3073+
let param = new ParameterNode();
3074+
param.parameterKind = ParameterKind.DEFAULT;
3075+
param.name = id;
3076+
param.type = Node.createOmittedType(range);
3077+
return Node.createFunctionExpression(Node.createFunctionDeclaration(
3078+
Node.createEmptyIdentifierExpression(range),
3079+
null,
3080+
Node.createSignature([param], Node.createOmittedType(range), null, false, range),
3081+
this.parseArrowFunctionBody(tn),
3082+
null,
3083+
CommonFlags.ARROW,
3084+
range
3085+
));
31373086
}
31383087
case Token.THIS: {
31393088
return Node.createThisExpression(tn.range(startPos, tn.pos));
@@ -3193,6 +3142,66 @@ export class Parser extends DiagnosticEmitter {
31933142
}
31943143
}
31953144

3145+
private parseArrowFunctionOrParenthesizedExpression(tn: Tokenizer, startPos: number): Expression | null {
3146+
if (tn.skip(Token.CLOSEPAREN)) { // must be a function expression (fast route)
3147+
return this.parseFunctionExpressionCommon(
3148+
tn,
3149+
Node.createEmptyIdentifierExpression(tn.range(startPos)),
3150+
[],
3151+
true
3152+
);
3153+
}
3154+
3155+
var state = tn.mark();
3156+
var isArrow = this.lookAheadIsArrowFunction(tn);
3157+
tn.reset(state);
3158+
if (isArrow) {
3159+
return this.parseFunctionExpression(tn);
3160+
} else {
3161+
// parse parenthesized
3162+
const expr = this.parseExpression(tn);
3163+
if (!expr) return null;
3164+
if (!tn.skip(Token.CLOSEPAREN)) {
3165+
this.error(
3166+
DiagnosticCode._0_expected,
3167+
tn.range(), ")"
3168+
);
3169+
return null;
3170+
}
3171+
return Node.createParenthesizedExpression(expr, tn.range(startPos, tn.pos));
3172+
}
3173+
}
3174+
3175+
private lookAheadIsArrowFunction(tn: Tokenizer): boolean {
3176+
while (true) {
3177+
switch (tn.next(IdentifierHandling.PREFER)) {
3178+
case Token.DOT_DOT_DOT: return true; // rest argument
3179+
case Token.IDENTIFIER: {
3180+
tn.readIdentifier();
3181+
switch (tn.next()) {
3182+
// if we got here, check for arrow
3183+
case Token.CLOSEPAREN: {
3184+
return tn.skip(Token.EQUALS_GREATERTHAN) || tn.skip(Token.COLON);
3185+
}
3186+
case Token.COLON: return true; // type annotation
3187+
case Token.QUESTION: { // optional parameter or ternary
3188+
// Arrow function may be `(x?: i32) => 0`, `(x?, y) => 0`, or `(x?) => 0`.
3189+
// Anything else is a conditional expression.
3190+
return (tn.skip(Token.COLON) || tn.skip(Token.COMMA) || tn.skip(Token.CLOSEPAREN));
3191+
}
3192+
case Token.COMMA: {
3193+
break; // continue
3194+
}
3195+
// case Token.EQUALS: // missing type annotation for simplicity
3196+
default: return false;
3197+
}
3198+
break;
3199+
}
3200+
default: return false;
3201+
}
3202+
}
3203+
}
3204+
31963205
tryParseTypeArgumentsBeforeArguments(
31973206
tn: Tokenizer
31983207
): CommonTypeNode[] | null {

tests/parser/arrow-functions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
x => x;
2+
(x): i32 => x;
3+
(x: i32) => x;
4+
(x?) => x;
5+
(x?, y?) => x;
6+
(x?: i32) => x;
7+
(b ? x : y);
8+
(b ? f : g)();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(x: ): => x;
2+
(x: ): i32 => x;
3+
(x: i32): => x;
4+
(x?: ): => x;
5+
(x?: , y?: ): => x;
6+
(x?: i32): => x;
7+
(b ? x : y);
8+
(b ? f : g)();
9+
// ERROR 1110: "Type expected." in arrow-functions.ts:3:8
10+
// ERROR 1110: "Type expected." in arrow-functions.ts:4:4
11+
// ERROR 1110: "Type expected." in arrow-functions.ts:5:8
12+
// ERROR 1110: "Type expected." in arrow-functions.ts:6:9

0 commit comments

Comments
 (0)