diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 54e7c3cdbd439..b8ab7ed5452d6 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: @@ -1668,15 +1671,17 @@ 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; @@ -1695,7 +1700,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); @@ -1718,7 +1723,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); } @@ -1727,7 +1741,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 1b2e6931e02c4..d3375d87faaa3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26158,8 +26158,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 660f136133510..2de848aac2ddd 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -166,7 +166,7 @@ namespace ts { } } - export function assertNotNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; + export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 43c44599413b1..bd5f85d873ac2 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 04ec83a622231..3466664d99af8 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 802756971d2e7..f757e5934cc13 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4752,12 +4752,34 @@ namespace ts { && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); } + 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) { 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 || tryReparseOptionalChain(expression)) { propertyAccess.flags |= NodeFlags.OptionalChain; if (isPrivateIdentifier(propertyAccess.name)) { parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); @@ -4783,7 +4805,7 @@ namespace ts { } parseExpected(SyntaxKind.CloseBracketToken); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || tryReparseOptionalChain(expression)) { indexedAccess.flags |= NodeFlags.OptionalChain; } return finishNode(indexedAccess); @@ -4870,7 +4892,7 @@ namespace ts { callExpr.questionDotToken = questionDotToken; callExpr.typeArguments = typeArguments; callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + if (questionDotToken || tryReparseOptionalChain(expression)) { callExpr.flags |= NodeFlags.OptionalChain; } expression = finishNode(callExpr); @@ -4882,7 +4904,7 @@ namespace ts { callExpr.expression = expression; callExpr.questionDotToken = questionDotToken; callExpr.arguments = parseArgumentList(); - if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + 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 bc5ea6771e6cb..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(chain.expression, isOptionalChain); + 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 519b9609383f1..0eae5aa9d19bd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1933,6 +1933,7 @@ namespace ts { | PropertyAccessChain | ElementAccessChain | CallChain + | NonNullChain ; /* @internal */ @@ -2027,6 +2028,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 ba4cc0106b9b1..9e89f12a6752c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2617,11 +2617,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 516b091bde5d5..d8ea332adf2f2 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1086,17 +1086,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; } /** @@ -1111,17 +1112,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` (`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?.`) - * 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) { @@ -1152,11 +1154,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 { @@ -1231,6 +1229,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 2e1b8a5795d20..1bda138472d16 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1355,7 +1355,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 0cb0c0fa0dc49..1f2c02120cc5c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1137,7 +1137,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; } @@ -1177,6 +1177,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; @@ -3633,7 +3636,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; @@ -3660,6 +3663,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; @@ -4151,6 +4155,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 91eafc2894e1c..028810b82f401 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1137,7 +1137,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; } @@ -1177,6 +1177,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; @@ -3633,7 +3636,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; @@ -3660,6 +3663,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; @@ -4151,6 +4155,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 f67166dcde372..13fb9c717a397 100644 --- a/tests/baselines/reference/callChain.js +++ b/tests/baselines/reference/callChain.js @@ -35,7 +35,11 @@ const v: number | undefined = o4?.(incr); // GH#33744 declare const o5: () => undefined | (() => void); -o5()?.(); +o5()?.(); + +// GH#36031 +o2?.b()!.toString; +o2?.b()!.toString!; //// [callChain.js] "use strict"; @@ -73,3 +77,6 @@ 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; +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..f92688645d426 100644 --- a/tests/baselines/reference/callChain.symbols +++ b/tests/baselines/reference/callChain.symbols @@ -156,3 +156,18 @@ 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, --, --)) + +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..2318a95456ecf 100644 --- a/tests/baselines/reference/callChain.types +++ b/tests/baselines/reference/callChain.types @@ -264,3 +264,23 @@ o5()?.(); >o5() : (() => void) | undefined >o5 : () => (() => void) | undefined +// GH#36031 +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) | 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 5dd4e79b918f3..d643daf0b9686 100644 --- a/tests/baselines/reference/elementAccessChain.js +++ b/tests/baselines/reference/elementAccessChain.js @@ -22,7 +22,13 @@ 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"]; +o2?.["b"]!.c!; +o2?.["b"]!["c"]!; //// [elementAccessChain.js] "use strict"; @@ -39,3 +45,8 @@ 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"]; +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..b98f6d82c9681 100644 --- a/tests/baselines/reference/elementAccessChain.symbols +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -106,3 +106,20 @@ 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)) + +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)) + diff --git a/tests/baselines/reference/elementAccessChain.types b/tests/baselines/reference/elementAccessChain.types index a186074e86bed..b865be684332f 100644 --- a/tests/baselines/reference/elementAccessChain.types +++ b/tests/baselines/reference/elementAccessChain.types @@ -141,3 +141,38 @@ o6()?.["x"]; >o6 : () => { x: number; } | undefined >"x" : "x" +// GH#36031 +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 | 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 | undefined +>o2?.["b"]! : { c: string; } | undefined +>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..8c27786432973 100644 --- a/tests/baselines/reference/propertyAccessChain.js +++ b/tests/baselines/reference/propertyAccessChain.js @@ -19,7 +19,11 @@ declare const o6: () => undefined | ({ x: number }); o6()?.x; // GH#34109 -o1?.b ? 1 : 0; +o1?.b ? 1 : 0; + +// GH#36031 +o2?.b!.c; +o2?.b!.c!; //// [propertyAccessChain.js] "use strict"; @@ -32,3 +36,6 @@ 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; +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..7d7791eca78c4 100644 --- a/tests/baselines/reference/propertyAccessChain.symbols +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -85,3 +85,18 @@ 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)) + +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..bd8023f94ce38 100644 --- a/tests/baselines/reference/propertyAccessChain.types +++ b/tests/baselines/reference/propertyAccessChain.types @@ -89,3 +89,21 @@ o1?.b ? 1 : 0; >1 : 1 >0 : 0 +// GH#36031 +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 | 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 bbfc3aa42f929..3294181722fa7 100644 --- a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -36,4 +36,8 @@ 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; +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..9af7bfb88b8d4 100644 --- a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -23,4 +23,10 @@ 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"]; +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..d2a3c622a9109 100644 --- a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -20,4 +20,8 @@ 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; +o2?.b!.c!; \ No newline at end of file