From 0f8c1f799df8f18a1d33a5680955626f62cf13c3 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 30 Jan 2020 23:54:20 -0800 Subject: [PATCH 1/3] Fix emit for optional chain with non-null assertion --- src/compiler/parser.ts | 16 ++++++++++++---- src/compiler/transformers/es2020.ts | 2 +- tests/baselines/reference/callChain.js | 7 ++++++- tests/baselines/reference/callChain.symbols | 8 ++++++++ tests/baselines/reference/callChain.types | 10 ++++++++++ tests/baselines/reference/elementAccessChain.js | 9 ++++++++- .../reference/elementAccessChain.symbols | 10 ++++++++++ .../reference/elementAccessChain.types | 17 +++++++++++++++++ .../baselines/reference/propertyAccessChain.js | 7 ++++++- .../reference/propertyAccessChain.symbols | 8 ++++++++ .../reference/propertyAccessChain.types | 9 +++++++++ .../optionalChaining/callChain/callChain.ts | 5 ++++- .../elementAccessChain/elementAccessChain.ts | 6 +++++- .../propertyAccessChain/propertyAccessChain.ts | 5 ++++- 14 files changed, 108 insertions(+), 11 deletions(-) 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 From d187ca71ccd500725d1e8394369df79a2e3e6bf8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 14 Feb 2020 15:31:42 -0800 Subject: [PATCH 2/3] Treat '!' differently inside chain vs end of chain --- src/compiler/binder.ts | 24 +++++++++++--- src/compiler/checker.ts | 9 ++++- src/compiler/debug.ts | 18 ++++++---- src/compiler/factory.ts | 33 +++++-------------- src/compiler/factoryPublic.ts | 23 ++++++++++--- src/compiler/parser.ts | 32 +++++++++++++----- src/compiler/transformers/es2020.ts | 2 ++ src/compiler/types.ts | 5 +++ src/compiler/utilities.ts | 6 +--- src/compiler/utilitiesPublic.ts | 30 +++++++++-------- src/services/utilities.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 10 ++++-- tests/baselines/reference/api/typescript.d.ts | 10 ++++-- tests/baselines/reference/callChain.js | 4 ++- tests/baselines/reference/callChain.symbols | 9 ++++- tests/baselines/reference/callChain.types | 18 +++++++--- .../baselines/reference/elementAccessChain.js | 8 +++-- .../reference/elementAccessChain.symbols | 13 ++++++-- .../reference/elementAccessChain.types | 32 ++++++++++++++---- .../reference/propertyAccessChain.js | 4 ++- .../reference/propertyAccessChain.symbols | 9 ++++- .../reference/propertyAccessChain.types | 17 +++++++--- .../optionalChaining/callChain/callChain.ts | 3 +- .../elementAccessChain/elementAccessChain.ts | 6 ++-- .../propertyAccessChain.ts | 3 +- 25 files changed, 229 insertions(+), 101 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1cd8e16a0495f..5e800801a8994 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -833,6 +833,9 @@ namespace ts { case SyntaxKind.CallExpression: bindCallExpressionFlow(node); break; + case SyntaxKind.NonNullExpression: + bindNonNullExpressionFlow(node); + break; case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocEnumTag: @@ -1665,18 +1668,22 @@ namespace ts { } function bindOptionalChainRest(node: OptionalChain) { - bind(node.questionDotToken); switch (node.kind) { case SyntaxKind.PropertyAccessExpression: + bind(node.questionDotToken); bind(node.name); break; case SyntaxKind.ElementAccessExpression: + bind(node.questionDotToken); bind(node.argumentExpression); break; case SyntaxKind.CallExpression: + bind(node.questionDotToken); bindEach(node.typeArguments); bindEach(node.arguments); break; + case SyntaxKind.NonNullExpression: + break; } } @@ -1692,7 +1699,7 @@ namespace ts { // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost // chain node. We then treat the entire node as the right side of the expression. - const preChainLabel = node.questionDotToken ? createBranchLabel() : undefined; + const preChainLabel = isOptionalChainRoot(node) ? createBranchLabel() : undefined; bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); if (preChainLabel) { currentFlow = finishFlowLabel(preChainLabel); @@ -1715,7 +1722,16 @@ namespace ts { } } - function bindAccessExpressionFlow(node: AccessExpression) { + function bindNonNullExpressionFlow(node: NonNullExpression | NonNullChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); + } + else { + bindEachChild(node); + } + } + + function bindAccessExpressionFlow(node: AccessExpression | PropertyAccessChain | ElementAccessChain) { if (isOptionalChain(node)) { bindOptionalChainFlow(node); } @@ -1724,7 +1740,7 @@ namespace ts { } } - function bindCallExpressionFlow(node: CallExpression) { + function bindCallExpressionFlow(node: CallExpression | CallChain) { if (isOptionalChain(node)) { bindOptionalChainFlow(node); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 542c6f6e19022..7c90968132cb5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25900,8 +25900,15 @@ namespace ts { return targetType; } + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } + function checkNonNullAssertion(node: NonNullExpression) { - return getNonNullableType(checkExpression(node.expression)); + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); } function checkMetaProperty(node: MetaProperty): Type { diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 9adc40950548c..a01b128ad25fc 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -189,13 +189,17 @@ namespace ts { assertNode) : noop; - export const assertNotNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( - test === undefined || !test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, - assertNode) - : noop; + export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string): asserts node is Exclude; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string): void; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string): void { + if (shouldAssert(AssertionLevel.Normal)) { + assert( + test === undefined || node === undefined || !test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, + assertNotNode); + } + } export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 64613525939cd..c5bdcd278b929 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1335,9 +1335,11 @@ namespace ts { export const enum OuterExpressionKinds { Parentheses = 1 << 0, - Assertions = 1 << 1, - PartiallyEmittedExpressions = 1 << 2, + TypeAssertions = 1 << 1, + NonNullAssertions = 1 << 2, + PartiallyEmittedExpressions = 1 << 3, + Assertions = TypeAssertions | NonNullAssertions, All = Parentheses | Assertions | PartiallyEmittedExpressions } @@ -1349,8 +1351,9 @@ namespace ts { return (kinds & OuterExpressionKinds.Parentheses) !== 0; case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: + return (kinds & OuterExpressionKinds.TypeAssertions) !== 0; case SyntaxKind.NonNullExpression: - return (kinds & OuterExpressionKinds.Assertions) !== 0; + return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0; case SyntaxKind.PartiallyEmittedExpression: return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; } @@ -1360,34 +1363,16 @@ namespace ts { export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { - let previousNode: Node; - do { - previousNode = node; - if (kinds & OuterExpressionKinds.Parentheses) { - node = skipParentheses(node); - } - - if (kinds & OuterExpressionKinds.Assertions) { - node = skipAssertions(node); - } - - if (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) { - node = skipPartiallyEmittedExpressions(node); - } + while (isOuterExpression(node, kinds)) { + node = node.expression; } - while (previousNode !== node); - return node; } export function skipAssertions(node: Expression): Expression; export function skipAssertions(node: Node): Node; export function skipAssertions(node: Node): Node { - while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) { - node = (node).expression; - } - - return node; + return skipOuterExpressions(node, OuterExpressionKinds.Assertions); } function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { diff --git a/src/compiler/factoryPublic.ts b/src/compiler/factoryPublic.ts index 2c59abbb494dc..c0b64e45330d0 100644 --- a/src/compiler/factoryPublic.ts +++ b/src/compiler/factoryPublic.ts @@ -1076,10 +1076,8 @@ namespace ts { } export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateIdentifier) { - if (isOptionalChain(node) && isIdentifier(node.name) && isIdentifier(name)) { - // Not sure why this cast was necessary: the previous line should already establish that node.name is an identifier - const theNode = node as (typeof node & { name: Identifier }); - return updatePropertyAccessChain(theNode, expression, node.questionDotToken, name); + if (isPropertyAccessChain(node)) { + return updatePropertyAccessChain(node, expression, node.questionDotToken, cast(name, isIdentifier)); } // Because we are updating existed propertyAccess we want to inherit its emitFlags // instead of using the default from createPropertyAccess @@ -1653,11 +1651,28 @@ namespace ts { } export function updateNonNullExpression(node: NonNullExpression, expression: Expression) { + if (isNonNullChain(node)) { + return updateNonNullChain(node, expression); + } return node.expression !== expression ? updateNode(createNonNullExpression(expression), node) : node; } + export function createNonNullChain(expression: Expression) { + const node = createSynthesizedNode(SyntaxKind.NonNullExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + return node; + } + + export function updateNonNullChain(node: NonNullChain, expression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a NonNullExpression using updateNonNullChain. Use updateNonNullExpression instead."); + return node.expression !== expression + ? updateNode(createNonNullChain(expression), node) + : node; + } + export function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { const node = createSynthesizedNode(SyntaxKind.MetaProperty); node.keywordToken = keywordToken; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 19855c5ac6305..b993136e8286c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4685,12 +4685,26 @@ 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 tryReparseOptionalChain(node: Expression) { + if (node.flags & NodeFlags.OptionalChain) { + return true; } + // check for an optional chain in a non-null expression + if (isNonNullExpression(node)) { + let expr = node.expression; + while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { + expr = expr.expression; + } + if (expr.flags & NodeFlags.OptionalChain) { + // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. + while (isNonNullExpression(node)) { + node.flags |= NodeFlags.OptionalChain; + node = node.expression; + } + return true; + } + } + return false; } function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { @@ -4698,7 +4712,7 @@ namespace ts { propertyAccess.expression = expression; propertyAccess.questionDotToken = questionDotToken; propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); - if (questionDotToken || hasOptionalChain(expression)) { + if (questionDotToken || tryReparseOptionalChain(expression)) { propertyAccess.flags |= NodeFlags.OptionalChain; if (isPrivateIdentifier(propertyAccess.name)) { parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); @@ -4724,7 +4738,7 @@ namespace ts { } parseExpected(SyntaxKind.CloseBracketToken); - if (questionDotToken || hasOptionalChain(expression)) { + if (questionDotToken || tryReparseOptionalChain(expression)) { indexedAccess.flags |= NodeFlags.OptionalChain; } return finishNode(indexedAccess); @@ -4811,7 +4825,7 @@ namespace ts { callExpr.questionDotToken = questionDotToken; callExpr.typeArguments = typeArguments; callExpr.arguments = parseArgumentList(); - if (questionDotToken || hasOptionalChain(expression)) { + if (questionDotToken || tryReparseOptionalChain(expression)) { callExpr.flags |= NodeFlags.OptionalChain; } expression = finishNode(callExpr); @@ -4823,7 +4837,7 @@ namespace ts { callExpr.expression = expression; callExpr.questionDotToken = questionDotToken; callExpr.arguments = parseArgumentList(); - if (questionDotToken || hasOptionalChain(expression)) { + if (questionDotToken || tryReparseOptionalChain(expression)) { callExpr.flags |= NodeFlags.OptionalChain; } expression = finishNode(callExpr); diff --git a/src/compiler/transformers/es2020.ts b/src/compiler/transformers/es2020.ts index e70ac12aa1b5d..453b3b405f421 100644 --- a/src/compiler/transformers/es2020.ts +++ b/src/compiler/transformers/es2020.ts @@ -42,9 +42,11 @@ namespace ts { } function flattenChain(chain: OptionalChain) { + Debug.assertNotNode(chain, isNonNullChain); const links: OptionalChain[] = [chain]; while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) { chain = cast(skipPartiallyEmittedExpressions(chain.expression), isOptionalChain); + Debug.assertNotNode(chain, isNonNullChain); links.unshift(chain); } return { expression: chain.expression, chain: links }; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3525b12a8a440..740d7e69823c8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1922,6 +1922,7 @@ namespace ts { | PropertyAccessChain | ElementAccessChain | CallChain + | NonNullChain ; /* @internal */ @@ -2016,6 +2017,10 @@ namespace ts { expression: Expression; } + export interface NonNullChain extends NonNullExpression { + _optionalChainBrand: any; + } + // NOTE: MetaProperty is really a MemberExpression, but we consider it a PrimaryExpression // for the same reasons we treat NewExpression as a PrimaryExpression. export interface MetaProperty extends PrimaryExpression { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bdee20ef861bf..97f1fc941345b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2614,11 +2614,7 @@ namespace ts { export function skipParentheses(node: Expression): Expression; export function skipParentheses(node: Node): Node; export function skipParentheses(node: Node): Node { - while (node.kind === SyntaxKind.ParenthesizedExpression) { - node = (node as ParenthesizedExpression).expression; - } - - return node; + return skipOuterExpressions(node, OuterExpressionKinds.Parentheses); } function skipParenthesesUp(node: Node): Node { diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index cc36af32f2c77..184e227e8dbab 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1076,17 +1076,18 @@ namespace ts { return isCallExpression(node) && !!(node.flags & NodeFlags.OptionalChain); } - export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain { + export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain | NonNullChain { const kind = node.kind; return !!(node.flags & NodeFlags.OptionalChain) && (kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression - || kind === SyntaxKind.CallExpression); + || kind === SyntaxKind.CallExpression + || kind === SyntaxKind.NonNullExpression); } /* @internal */ export function isOptionalChainRoot(node: Node): node is OptionalChainRoot { - return isOptionalChain(node) && !!node.questionDotToken; + return isOptionalChain(node) && !isNonNullExpression(node) && !!node.questionDotToken; } /** @@ -1101,17 +1102,18 @@ namespace ts { * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: * * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) - * 2. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) - * 3. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is + * 2. For `a?.b!`, the outermost chain is `a?.b!` (`c` is the end of the chain starting at `a?.`) + * 3. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) + * 4. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is * the end of the chain starting at `c?.`) - * 4. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is + * 5. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is * the end of the chain starting at `a?.`) */ /* @internal */ export function isOutermostOptionalChain(node: OptionalChain) { - return !isOptionalChain(node.parent) // cases 1 and 2 - || isOptionalChainRoot(node.parent) // case 3 - || node !== node.parent.expression; // case 4 + return !isOptionalChain(node.parent) // cases 1, 2, and 3 + || isOptionalChainRoot(node.parent) // case 4 + || node !== node.parent.expression; // case 5 } export function isNullishCoalesce(node: Node) { @@ -1142,11 +1144,7 @@ namespace ts { export function skipPartiallyEmittedExpressions(node: Expression): Expression; export function skipPartiallyEmittedExpressions(node: Node): Node; export function skipPartiallyEmittedExpressions(node: Node) { - while (node.kind === SyntaxKind.PartiallyEmittedExpression) { - node = (node).expression; - } - - return node; + return skipOuterExpressions(node, OuterExpressionKinds.PartiallyEmittedExpressions); } export function isFunctionExpression(node: Node): node is FunctionExpression { @@ -1221,6 +1219,10 @@ namespace ts { return node.kind === SyntaxKind.NonNullExpression; } + export function isNonNullChain(node: Node): node is NonNullChain { + return isNonNullExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + export function isMetaProperty(node: Node): node is MetaProperty { return node.kind === SyntaxKind.MetaProperty; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2a04dc49c877f..4922ec56348f9 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1012,7 +1012,7 @@ namespace ts { export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { let type = checker.getTypeAtLocation(called); if (isOptionalChain(called.parent)) { - type = removeOptionality(type, !!called.parent.questionDotToken, /*isOptionalChain*/ true); + type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); } const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 9320464f11133..392e104e6cd2a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1136,7 +1136,7 @@ declare namespace ts { export interface CallChain extends CallExpression { _optionalChainBrand: any; } - export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain | NonNullChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -1176,6 +1176,9 @@ declare namespace ts { kind: SyntaxKind.NonNullExpression; expression: Expression; } + export interface NonNullChain extends NonNullExpression { + _optionalChainBrand: any; + } export interface MetaProperty extends PrimaryExpression { kind: SyntaxKind.MetaProperty; keywordToken: SyntaxKind.NewKeyword | SyntaxKind.ImportKeyword; @@ -3615,7 +3618,7 @@ declare namespace ts { function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; function isCallChain(node: Node): node is CallChain; - function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain | NonNullChain; function isNullishCoalesce(node: Node): boolean; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; @@ -3642,6 +3645,7 @@ declare namespace ts { function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments; function isAsExpression(node: Node): node is AsExpression; function isNonNullExpression(node: Node): node is NonNullExpression; + function isNonNullChain(node: Node): node is NonNullChain; function isMetaProperty(node: Node): node is MetaProperty; function isTemplateSpan(node: Node): node is TemplateSpan; function isSemicolonClassElement(node: Node): node is SemicolonClassElement; @@ -4130,6 +4134,8 @@ declare namespace ts { function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode): AsExpression; function createNonNullExpression(expression: Expression): NonNullExpression; function updateNonNullExpression(node: NonNullExpression, expression: Expression): NonNullExpression; + function createNonNullChain(expression: Expression): NonNullChain; + function updateNonNullChain(node: NonNullChain, expression: Expression): NonNullChain; function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty; function updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty; function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 86288e69e6041..e96f9da251ccf 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1136,7 +1136,7 @@ declare namespace ts { export interface CallChain extends CallExpression { _optionalChainBrand: any; } - export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain | NonNullChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -1176,6 +1176,9 @@ declare namespace ts { kind: SyntaxKind.NonNullExpression; expression: Expression; } + export interface NonNullChain extends NonNullExpression { + _optionalChainBrand: any; + } export interface MetaProperty extends PrimaryExpression { kind: SyntaxKind.MetaProperty; keywordToken: SyntaxKind.NewKeyword | SyntaxKind.ImportKeyword; @@ -3615,7 +3618,7 @@ declare namespace ts { function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; function isCallChain(node: Node): node is CallChain; - function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain | NonNullChain; function isNullishCoalesce(node: Node): boolean; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; @@ -3642,6 +3645,7 @@ declare namespace ts { function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments; function isAsExpression(node: Node): node is AsExpression; function isNonNullExpression(node: Node): node is NonNullExpression; + function isNonNullChain(node: Node): node is NonNullChain; function isMetaProperty(node: Node): node is MetaProperty; function isTemplateSpan(node: Node): node is TemplateSpan; function isSemicolonClassElement(node: Node): node is SemicolonClassElement; @@ -4130,6 +4134,8 @@ declare namespace ts { function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode): AsExpression; function createNonNullExpression(expression: Expression): NonNullExpression; function updateNonNullExpression(node: NonNullExpression, expression: Expression): NonNullExpression; + function createNonNullChain(expression: Expression): NonNullChain; + function updateNonNullChain(node: NonNullChain, expression: Expression): NonNullChain; function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty; function updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty; function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; diff --git a/tests/baselines/reference/callChain.js b/tests/baselines/reference/callChain.js index 0b26ed119d07e..13fb9c717a397 100644 --- a/tests/baselines/reference/callChain.js +++ b/tests/baselines/reference/callChain.js @@ -38,7 +38,8 @@ declare const o5: () => undefined | (() => void); o5()?.(); // GH#36031 -o2?.b()!.toString +o2?.b()!.toString; +o2?.b()!.toString!; //// [callChain.js] "use strict"; @@ -78,3 +79,4 @@ 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; +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 841c34679e37d..f92688645d426 100644 --- a/tests/baselines/reference/callChain.symbols +++ b/tests/baselines/reference/callChain.symbols @@ -157,7 +157,14 @@ o5()?.(); >o5 : Symbol(o5, Decl(callChain.ts, 35, 13)) // GH#36031 -o2?.b()!.toString +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, --, --)) + +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)) diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types index d511b77869f06..2318a95456ecf 100644 --- a/tests/baselines/reference/callChain.types +++ b/tests/baselines/reference/callChain.types @@ -265,12 +265,22 @@ o5()?.(); >o5 : () => (() => void) | undefined // GH#36031 -o2?.b()!.toString ->o2?.b()!.toString : (radix?: number | undefined) => string ->o2?.b()! : number +o2?.b()!.toString; +>o2?.b()!.toString : ((radix?: number | undefined) => string) | undefined +>o2?.b()! : number | undefined >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 +>toString : ((radix?: number | undefined) => string) | undefined + +o2?.b()!.toString!; +>o2?.b()!.toString! : (radix?: number | undefined) => string +>o2?.b()!.toString : ((radix?: number | undefined) => string) | undefined +>o2?.b()! : number | undefined +>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) | undefined diff --git a/tests/baselines/reference/elementAccessChain.js b/tests/baselines/reference/elementAccessChain.js index c700d392795ef..d643daf0b9686 100644 --- a/tests/baselines/reference/elementAccessChain.js +++ b/tests/baselines/reference/elementAccessChain.js @@ -25,8 +25,10 @@ declare const o6: () => undefined | ({ x: number }); o6()?.["x"]; // GH#36031 -o2?.["b"]!.c -o2?.["b"]!["c"] +o2?.["b"]!.c; +o2?.["b"]!["c"]; +o2?.["b"]!.c!; +o2?.["b"]!["c"]!; //// [elementAccessChain.js] "use strict"; @@ -46,3 +48,5 @@ o2 === null || o2 === void 0 ? void 0 : o2.b["c"]; // GH#36031 o2 === null || o2 === void 0 ? void 0 : o2["b"].c; o2 === null || o2 === void 0 ? void 0 : o2["b"]["c"]; +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 9224498f5b427..b98f6d82c9681 100644 --- a/tests/baselines/reference/elementAccessChain.symbols +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -107,12 +107,19 @@ o6()?.["x"]; >o6 : Symbol(o6, Decl(elementAccessChain.ts, 22, 13)) // GH#36031 -o2?.["b"]!.c +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?.["b"]!["c"]; +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) + +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 db9833253ec37..b865be684332f 100644 --- a/tests/baselines/reference/elementAccessChain.types +++ b/tests/baselines/reference/elementAccessChain.types @@ -142,17 +142,35 @@ o6()?.["x"]; >"x" : "x" // GH#36031 -o2?.["b"]!.c ->o2?.["b"]!.c : string ->o2?.["b"]! : { c: string; } +o2?.["b"]!.c; +>o2?.["b"]!.c : string | undefined +>o2?.["b"]! : { c: string; } | undefined >o2?.["b"] : { c: string; } | undefined >o2 : { b: { c: string; }; } | undefined >"b" : "b" ->c : string +>c : string | undefined + +o2?.["b"]!["c"]; +>o2?.["b"]!["c"] : string | undefined +>o2?.["b"]! : { c: string; } | undefined +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>"c" : "c" + +o2?.["b"]!.c!; +>o2?.["b"]!.c! : string +>o2?.["b"]!.c : string | undefined +>o2?.["b"]! : { c: string; } | undefined +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>c : string | undefined -o2?.["b"]!["c"] ->o2?.["b"]!["c"] : string ->o2?.["b"]! : { c: string; } +o2?.["b"]!["c"]!; +>o2?.["b"]!["c"]! : string +>o2?.["b"]!["c"] : string | undefined +>o2?.["b"]! : { c: string; } | undefined >o2?.["b"] : { c: string; } | undefined >o2 : { b: { c: string; }; } | undefined >"b" : "b" diff --git a/tests/baselines/reference/propertyAccessChain.js b/tests/baselines/reference/propertyAccessChain.js index 4f90fd4d75f24..8c27786432973 100644 --- a/tests/baselines/reference/propertyAccessChain.js +++ b/tests/baselines/reference/propertyAccessChain.js @@ -22,7 +22,8 @@ o6()?.x; o1?.b ? 1 : 0; // GH#36031 -o2?.b!.c +o2?.b!.c; +o2?.b!.c!; //// [propertyAccessChain.js] "use strict"; @@ -37,3 +38,4 @@ o2 === null || o2 === void 0 ? void 0 : o2.b.c; (o1 === null || o1 === void 0 ? void 0 : o1.b) ? 1 : 0; // 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/propertyAccessChain.symbols b/tests/baselines/reference/propertyAccessChain.symbols index e97c1700a5f9d..7d7791eca78c4 100644 --- a/tests/baselines/reference/propertyAccessChain.symbols +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -86,7 +86,14 @@ o1?.b ? 1 : 0; >b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) // GH#36031 -o2?.b!.c +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)) + +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)) diff --git a/tests/baselines/reference/propertyAccessChain.types b/tests/baselines/reference/propertyAccessChain.types index e4b193eb7ef33..bd8023f94ce38 100644 --- a/tests/baselines/reference/propertyAccessChain.types +++ b/tests/baselines/reference/propertyAccessChain.types @@ -90,11 +90,20 @@ o1?.b ? 1 : 0; >0 : 0 // GH#36031 -o2?.b!.c ->o2?.b!.c : string ->o2?.b! : { c: string; } +o2?.b!.c; +>o2?.b!.c : string | undefined +>o2?.b! : { c: string; } | undefined >o2?.b : { c: string; } | undefined >o2 : { b: { c: string; }; } | undefined >b : { c: string; } | undefined ->c : string +>c : string | undefined + +o2?.b!.c!; +>o2?.b!.c! : string +>o2?.b!.c : string | undefined +>o2?.b! : { c: string; } | undefined +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>c : string | undefined diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts index f0c5f5179bcc3..3294181722fa7 100644 --- a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -39,4 +39,5 @@ declare const o5: () => undefined | (() => void); o5()?.(); // GH#36031 -o2?.b()!.toString \ No newline at end of file +o2?.b()!.toString; +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 f525e8cb3111e..9af7bfb88b8d4 100644 --- a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -26,5 +26,7 @@ declare const o6: () => undefined | ({ x: number }); o6()?.["x"]; // GH#36031 -o2?.["b"]!.c -o2?.["b"]!["c"] \ No newline at end of file +o2?.["b"]!.c; +o2?.["b"]!["c"]; +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 96b89bb30b9cd..d2a3c622a9109 100644 --- a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -23,4 +23,5 @@ o6()?.x; o1?.b ? 1 : 0; // GH#36031 -o2?.b!.c \ No newline at end of file +o2?.b!.c; +o2?.b!.c!; \ No newline at end of file From de118eb19dac646ccc1a9e834baee1560ba1269a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 25 Mar 2020 15:26:57 -0700 Subject: [PATCH 3/3] remove dead code and fix comment --- src/compiler/binder.ts | 2 -- src/compiler/utilitiesPublic.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index bcc60fec8267f..b8ab7ed5452d6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1685,8 +1685,6 @@ namespace ts { bindEach(node.typeArguments); bindEach(node.arguments); break; - case SyntaxKind.NonNullExpression: - break; } } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index d4413a1a15d36..d8ea332adf2f2 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1112,7 +1112,7 @@ namespace ts { * Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`: * * 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`) - * 2. For `a?.b!`, the outermost chain is `a?.b!` (`c` is the end of the chain starting at `a?.`) + * 2. For `a?.b!`, the outermost chain is `a?.b` (`b` is the end of the chain starting at `a?.`) * 3. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain) * 4. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is * the end of the chain starting at `c?.`)