diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e990c0f675c32..19855c5ac6305 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4685,12 +4685,20 @@ namespace ts { && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); } + function hasOptionalChain(node: Node) { + while (true) { + if (node.flags & NodeFlags.OptionalChain) return true; + if (!isNonNullExpression(node)) return false; + node = node.expression; + } + } + function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); propertyAccess.expression = expression; propertyAccess.questionDotToken = questionDotToken; propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || hasOptionalChain(expression)) { propertyAccess.flags |= NodeFlags.OptionalChain; if (isPrivateIdentifier(propertyAccess.name)) { parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); @@ -4716,7 +4724,7 @@ namespace ts { } parseExpected(SyntaxKind.CloseBracketToken); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || hasOptionalChain(expression)) { indexedAccess.flags |= NodeFlags.OptionalChain; } return finishNode(indexedAccess); @@ -4803,7 +4811,7 @@ namespace ts { callExpr.questionDotToken = questionDotToken; callExpr.typeArguments = typeArguments; callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || hasOptionalChain(expression)) { callExpr.flags |= NodeFlags.OptionalChain; } expression = finishNode(callExpr); @@ -4815,7 +4823,7 @@ namespace ts { callExpr.expression = expression; callExpr.questionDotToken = questionDotToken; callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || hasOptionalChain(expression)) { callExpr.flags |= NodeFlags.OptionalChain; } expression = finishNode(callExpr); diff --git a/src/compiler/transformers/es2020.ts b/src/compiler/transformers/es2020.ts index bc5ea6771e6cb..e70ac12aa1b5d 100644 --- a/src/compiler/transformers/es2020.ts +++ b/src/compiler/transformers/es2020.ts @@ -44,7 +44,7 @@ namespace ts { function flattenChain(chain: OptionalChain) { const links: OptionalChain[] = [chain]; while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) { - chain = cast(chain.expression, isOptionalChain); + chain = cast(skipPartiallyEmittedExpressions(chain.expression), isOptionalChain); links.unshift(chain); } return { expression: chain.expression, chain: links }; diff --git a/tests/baselines/reference/callChain.js b/tests/baselines/reference/callChain.js index f67166dcde372..0b26ed119d07e 100644 --- a/tests/baselines/reference/callChain.js +++ b/tests/baselines/reference/callChain.js @@ -35,7 +35,10 @@ const v: number | undefined = o4?.(incr); // GH#33744 declare const o5: () => undefined | (() => void); -o5()?.(); +o5()?.(); + +// GH#36031 +o2?.b()!.toString //// [callChain.js] "use strict"; @@ -73,3 +76,5 @@ o2 === null || o2 === void 0 ? void 0 : o2["b"].apply(o2, __spreadArrays([1], [2 (_m = o3["b"]) === null || _m === void 0 ? void 0 : _m.call.apply(_m, __spreadArrays([o3, 1], [2, 3], [4])).c; var v = o4 === null || o4 === void 0 ? void 0 : o4(incr); (_o = o5()) === null || _o === void 0 ? void 0 : _o(); +// GH#36031 +o2 === null || o2 === void 0 ? void 0 : o2.b().toString; diff --git a/tests/baselines/reference/callChain.symbols b/tests/baselines/reference/callChain.symbols index d0a266c41ea42..841c34679e37d 100644 --- a/tests/baselines/reference/callChain.symbols +++ b/tests/baselines/reference/callChain.symbols @@ -156,3 +156,11 @@ declare const o5: () => undefined | (() => void); o5()?.(); >o5 : Symbol(o5, Decl(callChain.ts, 35, 13)) +// GH#36031 +o2?.b()!.toString +>o2?.b()!.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>o2?.b : Symbol(b, Decl(callChain.ts, 6, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 31)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types index 6ba17855cb3e1..d511b77869f06 100644 --- a/tests/baselines/reference/callChain.types +++ b/tests/baselines/reference/callChain.types @@ -264,3 +264,13 @@ o5()?.(); >o5() : (() => void) | undefined >o5 : () => (() => void) | undefined +// GH#36031 +o2?.b()!.toString +>o2?.b()!.toString : (radix?: number | undefined) => string +>o2?.b()! : number +>o2?.b() : number | undefined +>o2?.b : ((...args: any[]) => number) | undefined +>o2 : { b: (...args: any[]) => number; } | undefined +>b : ((...args: any[]) => number) | undefined +>toString : (radix?: number | undefined) => string + diff --git a/tests/baselines/reference/elementAccessChain.js b/tests/baselines/reference/elementAccessChain.js index 5dd4e79b918f3..c700d392795ef 100644 --- a/tests/baselines/reference/elementAccessChain.js +++ b/tests/baselines/reference/elementAccessChain.js @@ -22,7 +22,11 @@ o5["b"]?.()["c"].d?.["e"]; // GH#33744 declare const o6: () => undefined | ({ x: number }); -o6()?.["x"]; +o6()?.["x"]; + +// GH#36031 +o2?.["b"]!.c +o2?.["b"]!["c"] //// [elementAccessChain.js] "use strict"; @@ -39,3 +43,6 @@ o2 === null || o2 === void 0 ? void 0 : o2.b["c"]; (_m = (_l = o5["b"]) === null || _l === void 0 ? void 0 : _l.call(o5)["c"].d) === null || _m === void 0 ? void 0 : _m.e; (_p = (_o = o5["b"]) === null || _o === void 0 ? void 0 : _o.call(o5)["c"].d) === null || _p === void 0 ? void 0 : _p["e"]; (_q = o6()) === null || _q === void 0 ? void 0 : _q["x"]; +// GH#36031 +o2 === null || o2 === void 0 ? void 0 : o2["b"].c; +o2 === null || o2 === void 0 ? void 0 : o2["b"]["c"]; diff --git a/tests/baselines/reference/elementAccessChain.symbols b/tests/baselines/reference/elementAccessChain.symbols index 5e892012173fc..9224498f5b427 100644 --- a/tests/baselines/reference/elementAccessChain.symbols +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -106,3 +106,13 @@ declare const o6: () => undefined | ({ x: number }); o6()?.["x"]; >o6 : Symbol(o6, Decl(elementAccessChain.ts, 22, 13)) +// GH#36031 +o2?.["b"]!.c +>o2?.["b"]!.c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + +o2?.["b"]!["c"] +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>"c" : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + diff --git a/tests/baselines/reference/elementAccessChain.types b/tests/baselines/reference/elementAccessChain.types index a186074e86bed..db9833253ec37 100644 --- a/tests/baselines/reference/elementAccessChain.types +++ b/tests/baselines/reference/elementAccessChain.types @@ -141,3 +141,20 @@ o6()?.["x"]; >o6 : () => { x: number; } | undefined >"x" : "x" +// GH#36031 +o2?.["b"]!.c +>o2?.["b"]!.c : string +>o2?.["b"]! : { c: string; } +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>c : string + +o2?.["b"]!["c"] +>o2?.["b"]!["c"] : string +>o2?.["b"]! : { c: string; } +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>"c" : "c" + diff --git a/tests/baselines/reference/propertyAccessChain.js b/tests/baselines/reference/propertyAccessChain.js index a2ece64f98690..4f90fd4d75f24 100644 --- a/tests/baselines/reference/propertyAccessChain.js +++ b/tests/baselines/reference/propertyAccessChain.js @@ -19,7 +19,10 @@ declare const o6: () => undefined | ({ x: number }); o6()?.x; // GH#34109 -o1?.b ? 1 : 0; +o1?.b ? 1 : 0; + +// GH#36031 +o2?.b!.c //// [propertyAccessChain.js] "use strict"; @@ -32,3 +35,5 @@ o2 === null || o2 === void 0 ? void 0 : o2.b.c; (_f = o6()) === null || _f === void 0 ? void 0 : _f.x; // GH#34109 (o1 === null || o1 === void 0 ? void 0 : o1.b) ? 1 : 0; +// GH#36031 +o2 === null || o2 === void 0 ? void 0 : o2.b.c; diff --git a/tests/baselines/reference/propertyAccessChain.symbols b/tests/baselines/reference/propertyAccessChain.symbols index 21f694a57f903..e97c1700a5f9d 100644 --- a/tests/baselines/reference/propertyAccessChain.symbols +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -85,3 +85,11 @@ o1?.b ? 1 : 0; >o1 : Symbol(o1, Decl(propertyAccessChain.ts, 0, 13)) >b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) +// GH#36031 +o2?.b!.c +>o2?.b!.c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) +>o2?.b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(propertyAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) + diff --git a/tests/baselines/reference/propertyAccessChain.types b/tests/baselines/reference/propertyAccessChain.types index 60428ac5c1eb7..e4b193eb7ef33 100644 --- a/tests/baselines/reference/propertyAccessChain.types +++ b/tests/baselines/reference/propertyAccessChain.types @@ -89,3 +89,12 @@ o1?.b ? 1 : 0; >1 : 1 >0 : 0 +// GH#36031 +o2?.b!.c +>o2?.b!.c : string +>o2?.b! : { c: string; } +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>c : string + diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts index bbfc3aa42f929..f0c5f5179bcc3 100644 --- a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -36,4 +36,7 @@ const v: number | undefined = o4?.(incr); // GH#33744 declare const o5: () => undefined | (() => void); -o5()?.(); \ No newline at end of file +o5()?.(); + +// GH#36031 +o2?.b()!.toString \ No newline at end of file diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts index 20dfaab0c86d7..f525e8cb3111e 100644 --- a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -23,4 +23,8 @@ o5["b"]?.()["c"].d?.["e"]; // GH#33744 declare const o6: () => undefined | ({ x: number }); -o6()?.["x"]; \ No newline at end of file +o6()?.["x"]; + +// GH#36031 +o2?.["b"]!.c +o2?.["b"]!["c"] \ No newline at end of file diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts index 1f9e0bc0550c8..96b89bb30b9cd 100644 --- a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -20,4 +20,7 @@ declare const o6: () => undefined | ({ x: number }); o6()?.x; // GH#34109 -o1?.b ? 1 : 0; \ No newline at end of file +o1?.b ? 1 : 0; + +// GH#36031 +o2?.b!.c \ No newline at end of file