Skip to content

Fail speculative parsing of arrow functions when their parameter initialisers are missing a = #18417

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2240,7 +2240,7 @@ namespace ts {
isStartOfType(/*inStartOfParameter*/ true);
}

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

node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
node.type = parseParameterType();
node.initializer = parseBindingElementInitializer(/*inParameter*/ true);
node.initializer = parseInitializer(/*inParameter*/ true, requireEqualsToken);

return addJSDocComment(finishNode(node));
}

function parseBindingElementInitializer(inParameter: boolean) {
return inParameter ? parseParameterInitializer() : parseNonParameterInitializer();
}

function parseParameterInitializer() {
return parseInitializer(/*inParameter*/ true);
}

function fillSignature(
returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken,
flags: SignatureFlags,
Expand Down Expand Up @@ -2334,7 +2326,8 @@ namespace ts {
setYieldContext(!!(flags & SignatureFlags.Yield));
setAwaitContext(!!(flags & SignatureFlags.Await));

const result = parseDelimitedList(ParsingContext.Parameters, flags & SignatureFlags.JSDoc ? parseJSDocParameter : parseParameter);
const result = parseDelimitedList(ParsingContext.Parameters,
flags & SignatureFlags.JSDoc ? parseJSDocParameter : () => parseParameter(!!(flags & SignatureFlags.RequireCompleteParameterList)));

setYieldContext(savedYieldContext);
setAwaitContext(savedAwaitContext);
Expand Down Expand Up @@ -3017,7 +3010,7 @@ namespace ts {
return expr;
}

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

// Initializer[In, Yield] :
// = AssignmentExpression[?In, ?Yield]

parseExpected(SyntaxKind.EqualsToken);
return parseAssignmentExpressionOrHigher();
}
Expand Down Expand Up @@ -3351,8 +3350,7 @@ namespace ts {
function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined {
// We do a check here so that we won't be doing unnecessarily call to "lookAhead"
if (token() === SyntaxKind.AsyncKeyword) {
const isUnParenthesizedAsyncArrowFunction = lookAhead(isUnParenthesizedAsyncArrowFunctionWorker);
if (isUnParenthesizedAsyncArrowFunction === Tristate.True) {
if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) {
const asyncModifier = parseModifiersForArrowFunction();
const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0);
return parseSimpleArrowFunctionExpression(<Identifier>expr, asyncModifier);
Expand Down Expand Up @@ -3386,7 +3384,6 @@ namespace ts {
const node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
node.modifiers = parseModifiersForArrowFunction();
const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;

// Arrow functions are never generators.
//
// If we're speculatively parsing a signature for a parenthesized arrow function, then
Expand All @@ -3409,7 +3406,8 @@ namespace ts {
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
//
// So we need just a bit of lookahead to ensure that it can only be a signature.
if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) {
if (!allowAmbiguity && ((token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) ||
find(node.parameters, p => p.initializer && ts.isIdentifier(p.initializer) && p.initializer.escapedText === "= not found"))) {
// Returning undefined here will cause our caller to rewind to where we started from.
return undefined;
}
Expand Down Expand Up @@ -5158,7 +5156,7 @@ namespace ts {
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
node.name = parseIdentifierOrPattern();
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
node.initializer = parseInitializer(/*inParameter*/ false);
return finishNode(node);
}

Expand All @@ -5175,7 +5173,7 @@ namespace ts {
node.propertyName = propertyName;
node.name = parseIdentifierOrPattern();
}
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
node.initializer = parseInitializer(/*inParameter*/ false);
return finishNode(node);
}

Expand Down Expand Up @@ -5214,7 +5212,7 @@ namespace ts {
node.name = parseIdentifierOrPattern();
node.type = parseTypeAnnotation();
if (!isInOrOfKeyword(token())) {
node.initializer = parseInitializer(/*inParameter*/ false);
node.initializer = parseNonParameterInitializer();
}
return finishNode(node);
}
Expand Down
15 changes: 15 additions & 0 deletions tests/baselines/reference/parserArrowFunctionExpression5.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
function foo(q: string, b: number) {
>foo : Symbol(foo, Decl(parserArrowFunctionExpression5.ts, 0, 0))
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))

return true ? (q ? true : false) : (b = q.length, function() { });
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

};

25 changes: 25 additions & 0 deletions tests/baselines/reference/parserArrowFunctionExpression5.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
function foo(q: string, b: number) {
>foo : (q: string, b: number) => boolean | (() => void)
>q : string
>b : number

return true ? (q ? true : false) : (b = q.length, function() { });
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
>true : true
>(q ? true : false) : boolean
>q ? true : false : boolean
>q : string
>true : true
>false : false
>(b = q.length, function() { }) : () => void
>b = q.length, function() { } : () => void
>b = q.length : number
>b : number
>q.length : number
>q : string
>length : number
>function() { } : () => void

};

11 changes: 11 additions & 0 deletions tests/baselines/reference/parserArrowFunctionExpression6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//// [parserArrowFunctionExpression6.ts]
function foo(q: string, b: number) {
return true ? (q ? true : false) : (b = q.length, function() { });
};


//// [parserArrowFunctionExpression6.js]
function foo(q, b) {
return true ? (q ? true : false) : (b = q.length, function () { });
}
;
15 changes: 15 additions & 0 deletions tests/baselines/reference/parserArrowFunctionExpression6.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression6.ts ===
function foo(q: string, b: number) {
>foo : Symbol(foo, Decl(parserArrowFunctionExpression6.ts, 0, 0))
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
>b : Symbol(b, Decl(parserArrowFunctionExpression6.ts, 0, 23))

return true ? (q ? true : false) : (b = q.length, function() { });
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
>b : Symbol(b, Decl(parserArrowFunctionExpression6.ts, 0, 23))
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>q : Symbol(q, Decl(parserArrowFunctionExpression6.ts, 0, 13))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))

};

25 changes: 25 additions & 0 deletions tests/baselines/reference/parserArrowFunctionExpression6.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression6.ts ===
function foo(q: string, b: number) {
>foo : (q: string, b: number) => boolean | (() => void)
>q : string
>b : number

return true ? (q ? true : false) : (b = q.length, function() { });
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
>true : true
>(q ? true : false) : boolean
>q ? true : false : boolean
>q : string
>true : true
>false : false
>(b = q.length, function() { }) : () => void
>b = q.length, function() { } : () => void
>b = q.length : number
>b : number
>q.length : number
>q : string
>length : number
>function() { } : () => void

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//// [parserRegularExpressionDivideAmbiguity6.ts]
function c255lsqr8h(a7, a6, a5, a4, a3, a2, a1, a0) {
let r = [];
let v;
r[0] = (v = a0*a0) & 0xFFFF;
r[1] = (v = ((v / 0x10000) | 0) + 2*a0*a1) & 0xFFFF;
r[2] = (v = ((v / 0x10000) | 0) + 2*a0*a2 + a1*a1) & 0xFFFF;
r[3] = (v = ((v / 0x10000) | 0) + 2*a0*a3 + 2*a1*a2) & 0xFFFF;
r[4] = (v = ((v / 0x10000) | 0) + 2*a0*a4 + 2*a1*a3 + a2*a2) & 0xFFFF;
r[5] = (v = ((v / 0x10000) | 0) + 2*a0*a5 + 2*a1*a4 + 2*a2*a3) & 0xFFFF;
r[6] = (v = ((v / 0x10000) | 0) + 2*a0*a6 + 2*a1*a5 + 2*a2*a4 + a3*a3) & 0xFFFF;
r[7] = (v = ((v / 0x10000) | 0) + 2*a0*a7 + 2*a1*a6 + 2*a2*a5 + 2*a3*a4) & 0xFFFF;
r[8] = (v = ((v / 0x10000) | 0) + 2*a1*a7 + 2*a2*a6 + 2*a3*a5 + a4*a4) & 0xFFFF;
r[9] = (v = ((v / 0x10000) | 0) + 2*a2*a7 + 2*a3*a6 + 2*a4*a5) & 0xFFFF;
r[10] = (v = ((v / 0x10000) | 0) + 2*a3*a7 + 2*a4*a6 + a5*a5) & 0xFFFF;
r[11] = (v = ((v / 0x10000) | 0) + 2*a4*a7 + 2*a5*a6) & 0xFFFF;
r[12] = (v = ((v / 0x10000) | 0) + 2*a5*a7 + a6*a6) & 0xFFFF;
r[13] = (v = ((v / 0x10000) | 0) + 2*a6*a7) & 0xFFFF;
r[14] = (v = ((v / 0x10000) | 0) + a7*a7) & 0xFFFF;
r[15] = ((v / 0x10000) | 0);
return r;
}


//// [parserRegularExpressionDivideAmbiguity6.js]
function c255lsqr8h(a7, a6, a5, a4, a3, a2, a1, a0) {
var r = [];
var v;
r[0] = (v = a0 * a0) & 0xFFFF;
r[1] = (v = ((v / 0x10000) | 0) + 2 * a0 * a1) & 0xFFFF;
r[2] = (v = ((v / 0x10000) | 0) + 2 * a0 * a2 + a1 * a1) & 0xFFFF;
r[3] = (v = ((v / 0x10000) | 0) + 2 * a0 * a3 + 2 * a1 * a2) & 0xFFFF;
r[4] = (v = ((v / 0x10000) | 0) + 2 * a0 * a4 + 2 * a1 * a3 + a2 * a2) & 0xFFFF;
r[5] = (v = ((v / 0x10000) | 0) + 2 * a0 * a5 + 2 * a1 * a4 + 2 * a2 * a3) & 0xFFFF;
r[6] = (v = ((v / 0x10000) | 0) + 2 * a0 * a6 + 2 * a1 * a5 + 2 * a2 * a4 + a3 * a3) & 0xFFFF;
r[7] = (v = ((v / 0x10000) | 0) + 2 * a0 * a7 + 2 * a1 * a6 + 2 * a2 * a5 + 2 * a3 * a4) & 0xFFFF;
r[8] = (v = ((v / 0x10000) | 0) + 2 * a1 * a7 + 2 * a2 * a6 + 2 * a3 * a5 + a4 * a4) & 0xFFFF;
r[9] = (v = ((v / 0x10000) | 0) + 2 * a2 * a7 + 2 * a3 * a6 + 2 * a4 * a5) & 0xFFFF;
r[10] = (v = ((v / 0x10000) | 0) + 2 * a3 * a7 + 2 * a4 * a6 + a5 * a5) & 0xFFFF;
r[11] = (v = ((v / 0x10000) | 0) + 2 * a4 * a7 + 2 * a5 * a6) & 0xFFFF;
r[12] = (v = ((v / 0x10000) | 0) + 2 * a5 * a7 + a6 * a6) & 0xFFFF;
r[13] = (v = ((v / 0x10000) | 0) + 2 * a6 * a7) & 0xFFFF;
r[14] = (v = ((v / 0x10000) | 0) + a7 * a7) & 0xFFFF;
r[15] = ((v / 0x10000) | 0);
return r;
}
Loading