Skip to content

Commit b934c8b

Browse files
authored
Merge pull request #18417 from Microsoft/fail-spec-lambda-parsing-on-parameter-initialiser-missing-=
Fail speculative parsing of arrow functions when their parameter initialisers are missing a =
2 parents 0de1b23 + 5d51a42 commit b934c8b

14 files changed

+871
-20
lines changed

src/compiler/parser.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,7 +2240,7 @@ namespace ts {
22402240
isStartOfType(/*inStartOfParameter*/ true);
22412241
}
22422242

2243-
function parseParameter(): ParameterDeclaration {
2243+
function parseParameter(requireEqualsToken?: boolean): ParameterDeclaration {
22442244
const node = <ParameterDeclaration>createNode(SyntaxKind.Parameter);
22452245
if (token() === SyntaxKind.ThisKeyword) {
22462246
node.name = createIdentifier(/*isIdentifier*/ true);
@@ -2269,19 +2269,11 @@ namespace ts {
22692269

22702270
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
22712271
node.type = parseParameterType();
2272-
node.initializer = parseBindingElementInitializer(/*inParameter*/ true);
2272+
node.initializer = parseInitializer(/*inParameter*/ true, requireEqualsToken);
22732273

22742274
return addJSDocComment(finishNode(node));
22752275
}
22762276

2277-
function parseBindingElementInitializer(inParameter: boolean) {
2278-
return inParameter ? parseParameterInitializer() : parseNonParameterInitializer();
2279-
}
2280-
2281-
function parseParameterInitializer() {
2282-
return parseInitializer(/*inParameter*/ true);
2283-
}
2284-
22852277
function fillSignature(
22862278
returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken,
22872279
flags: SignatureFlags,
@@ -2334,7 +2326,8 @@ namespace ts {
23342326
setYieldContext(!!(flags & SignatureFlags.Yield));
23352327
setAwaitContext(!!(flags & SignatureFlags.Await));
23362328

2337-
const result = parseDelimitedList(ParsingContext.Parameters, flags & SignatureFlags.JSDoc ? parseJSDocParameter : parseParameter);
2329+
const result = parseDelimitedList(ParsingContext.Parameters,
2330+
flags & SignatureFlags.JSDoc ? parseJSDocParameter : () => parseParameter(!!(flags & SignatureFlags.RequireCompleteParameterList)));
23382331

23392332
setYieldContext(savedYieldContext);
23402333
setAwaitContext(savedAwaitContext);
@@ -3011,7 +3004,7 @@ namespace ts {
30113004
return expr;
30123005
}
30133006

3014-
function parseInitializer(inParameter: boolean): Expression {
3007+
function parseInitializer(inParameter: boolean, requireEqualsToken?: boolean): Expression {
30153008
if (token() !== SyntaxKind.EqualsToken) {
30163009
// It's not uncommon during typing for the user to miss writing the '=' token. Check if
30173010
// there is no newline after the last token and if we're on an expression. If so, parse
@@ -3026,11 +3019,17 @@ namespace ts {
30263019
// do not try to parse initializer
30273020
return undefined;
30283021
}
3022+
if (inParameter && requireEqualsToken) {
3023+
// = is required when speculatively parsing arrow function parameters,
3024+
// so return a fake initializer as a signal that the equals token was missing
3025+
const result = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics._0_expected, "=") as Identifier;
3026+
result.escapedText = "= not found" as __String;
3027+
return result;
3028+
}
30293029
}
30303030

30313031
// Initializer[In, Yield] :
30323032
// = AssignmentExpression[?In, ?Yield]
3033-
30343033
parseExpected(SyntaxKind.EqualsToken);
30353034
return parseAssignmentExpressionOrHigher();
30363035
}
@@ -3345,8 +3344,7 @@ namespace ts {
33453344
function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined {
33463345
// We do a check here so that we won't be doing unnecessarily call to "lookAhead"
33473346
if (token() === SyntaxKind.AsyncKeyword) {
3348-
const isUnParenthesizedAsyncArrowFunction = lookAhead(isUnParenthesizedAsyncArrowFunctionWorker);
3349-
if (isUnParenthesizedAsyncArrowFunction === Tristate.True) {
3347+
if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) {
33503348
const asyncModifier = parseModifiersForArrowFunction();
33513349
const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0);
33523350
return parseSimpleArrowFunctionExpression(<Identifier>expr, asyncModifier);
@@ -3380,7 +3378,6 @@ namespace ts {
33803378
const node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
33813379
node.modifiers = parseModifiersForArrowFunction();
33823380
const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
3383-
33843381
// Arrow functions are never generators.
33853382
//
33863383
// If we're speculatively parsing a signature for a parenthesized arrow function, then
@@ -3403,7 +3400,8 @@ namespace ts {
34033400
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
34043401
//
34053402
// So we need just a bit of lookahead to ensure that it can only be a signature.
3406-
if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) {
3403+
if (!allowAmbiguity && ((token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) ||
3404+
find(node.parameters, p => p.initializer && ts.isIdentifier(p.initializer) && p.initializer.escapedText === "= not found"))) {
34073405
// Returning undefined here will cause our caller to rewind to where we started from.
34083406
return undefined;
34093407
}
@@ -5152,7 +5150,7 @@ namespace ts {
51525150
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
51535151
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
51545152
node.name = parseIdentifierOrPattern();
5155-
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
5153+
node.initializer = parseInitializer(/*inParameter*/ false);
51565154
return finishNode(node);
51575155
}
51585156

@@ -5169,7 +5167,7 @@ namespace ts {
51695167
node.propertyName = propertyName;
51705168
node.name = parseIdentifierOrPattern();
51715169
}
5172-
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
5170+
node.initializer = parseInitializer(/*inParameter*/ false);
51735171
return finishNode(node);
51745172
}
51755173

@@ -5208,7 +5206,7 @@ namespace ts {
52085206
node.name = parseIdentifierOrPattern();
52095207
node.type = parseTypeAnnotation();
52105208
if (!isInOrOfKeyword(token())) {
5211-
node.initializer = parseInitializer(/*inParameter*/ false);
5209+
node.initializer = parseNonParameterInitializer();
52125210
}
52135211
return finishNode(node);
52145212
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : Symbol(foo, Decl(parserArrowFunctionExpression5.ts, 0, 0))
4+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
5+
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
9+
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
10+
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
11+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
12+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
13+
14+
};
15+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : (q: string, b: number) => boolean | (() => void)
4+
>q : string
5+
>b : number
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
9+
>true : true
10+
>(q ? true : false) : boolean
11+
>q ? true : false : boolean
12+
>q : string
13+
>true : true
14+
>false : false
15+
>(b = q.length, function() { }) : () => void
16+
>b = q.length, function() { } : () => void
17+
>b = q.length : number
18+
>b : number
19+
>q.length : number
20+
>q : string
21+
>length : number
22+
>function() { } : () => void
23+
24+
};
25+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [parserArrowFunctionExpression6.ts]
2+
function foo(q: string, b: number) {
3+
return true ? (q ? true : false) : (b = q.length, function() { });
4+
};
5+
6+
7+
//// [parserArrowFunctionExpression6.js]
8+
function foo(q, b) {
9+
return true ? (q ? true : false) : (b = q.length, function () { });
10+
}
11+
;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression6.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : Symbol(foo, Decl(parserArrowFunctionExpression6.ts, 0, 0))
4+
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
5+
>b : Symbol(b, Decl(parserArrowFunctionExpression6.ts, 0, 23))
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
9+
>b : Symbol(b, Decl(parserArrowFunctionExpression6.ts, 0, 23))
10+
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
11+
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
12+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
13+
14+
};
15+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression6.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : (q: string, b: number) => boolean | (() => void)
4+
>q : string
5+
>b : number
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
9+
>true : true
10+
>(q ? true : false) : boolean
11+
>q ? true : false : boolean
12+
>q : string
13+
>true : true
14+
>false : false
15+
>(b = q.length, function() { }) : () => void
16+
>b = q.length, function() { } : () => void
17+
>b = q.length : number
18+
>b : number
19+
>q.length : number
20+
>q : string
21+
>length : number
22+
>function() { } : () => void
23+
24+
};
25+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [parserRegularExpressionDivideAmbiguity6.ts]
2+
function c255lsqr8h(a7, a6, a5, a4, a3, a2, a1, a0) {
3+
let r = [];
4+
let v;
5+
r[0] = (v = a0*a0) & 0xFFFF;
6+
r[1] = (v = ((v / 0x10000) | 0) + 2*a0*a1) & 0xFFFF;
7+
r[2] = (v = ((v / 0x10000) | 0) + 2*a0*a2 + a1*a1) & 0xFFFF;
8+
r[3] = (v = ((v / 0x10000) | 0) + 2*a0*a3 + 2*a1*a2) & 0xFFFF;
9+
r[4] = (v = ((v / 0x10000) | 0) + 2*a0*a4 + 2*a1*a3 + a2*a2) & 0xFFFF;
10+
r[5] = (v = ((v / 0x10000) | 0) + 2*a0*a5 + 2*a1*a4 + 2*a2*a3) & 0xFFFF;
11+
r[6] = (v = ((v / 0x10000) | 0) + 2*a0*a6 + 2*a1*a5 + 2*a2*a4 + a3*a3) & 0xFFFF;
12+
r[7] = (v = ((v / 0x10000) | 0) + 2*a0*a7 + 2*a1*a6 + 2*a2*a5 + 2*a3*a4) & 0xFFFF;
13+
r[8] = (v = ((v / 0x10000) | 0) + 2*a1*a7 + 2*a2*a6 + 2*a3*a5 + a4*a4) & 0xFFFF;
14+
r[9] = (v = ((v / 0x10000) | 0) + 2*a2*a7 + 2*a3*a6 + 2*a4*a5) & 0xFFFF;
15+
r[10] = (v = ((v / 0x10000) | 0) + 2*a3*a7 + 2*a4*a6 + a5*a5) & 0xFFFF;
16+
r[11] = (v = ((v / 0x10000) | 0) + 2*a4*a7 + 2*a5*a6) & 0xFFFF;
17+
r[12] = (v = ((v / 0x10000) | 0) + 2*a5*a7 + a6*a6) & 0xFFFF;
18+
r[13] = (v = ((v / 0x10000) | 0) + 2*a6*a7) & 0xFFFF;
19+
r[14] = (v = ((v / 0x10000) | 0) + a7*a7) & 0xFFFF;
20+
r[15] = ((v / 0x10000) | 0);
21+
return r;
22+
}
23+
24+
25+
//// [parserRegularExpressionDivideAmbiguity6.js]
26+
function c255lsqr8h(a7, a6, a5, a4, a3, a2, a1, a0) {
27+
var r = [];
28+
var v;
29+
r[0] = (v = a0 * a0) & 0xFFFF;
30+
r[1] = (v = ((v / 0x10000) | 0) + 2 * a0 * a1) & 0xFFFF;
31+
r[2] = (v = ((v / 0x10000) | 0) + 2 * a0 * a2 + a1 * a1) & 0xFFFF;
32+
r[3] = (v = ((v / 0x10000) | 0) + 2 * a0 * a3 + 2 * a1 * a2) & 0xFFFF;
33+
r[4] = (v = ((v / 0x10000) | 0) + 2 * a0 * a4 + 2 * a1 * a3 + a2 * a2) & 0xFFFF;
34+
r[5] = (v = ((v / 0x10000) | 0) + 2 * a0 * a5 + 2 * a1 * a4 + 2 * a2 * a3) & 0xFFFF;
35+
r[6] = (v = ((v / 0x10000) | 0) + 2 * a0 * a6 + 2 * a1 * a5 + 2 * a2 * a4 + a3 * a3) & 0xFFFF;
36+
r[7] = (v = ((v / 0x10000) | 0) + 2 * a0 * a7 + 2 * a1 * a6 + 2 * a2 * a5 + 2 * a3 * a4) & 0xFFFF;
37+
r[8] = (v = ((v / 0x10000) | 0) + 2 * a1 * a7 + 2 * a2 * a6 + 2 * a3 * a5 + a4 * a4) & 0xFFFF;
38+
r[9] = (v = ((v / 0x10000) | 0) + 2 * a2 * a7 + 2 * a3 * a6 + 2 * a4 * a5) & 0xFFFF;
39+
r[10] = (v = ((v / 0x10000) | 0) + 2 * a3 * a7 + 2 * a4 * a6 + a5 * a5) & 0xFFFF;
40+
r[11] = (v = ((v / 0x10000) | 0) + 2 * a4 * a7 + 2 * a5 * a6) & 0xFFFF;
41+
r[12] = (v = ((v / 0x10000) | 0) + 2 * a5 * a7 + a6 * a6) & 0xFFFF;
42+
r[13] = (v = ((v / 0x10000) | 0) + 2 * a6 * a7) & 0xFFFF;
43+
r[14] = (v = ((v / 0x10000) | 0) + a7 * a7) & 0xFFFF;
44+
r[15] = ((v / 0x10000) | 0);
45+
return r;
46+
}

0 commit comments

Comments
 (0)