Skip to content

Commit 2e38783

Browse files
TypeScript Botsandersn
authored andcommitted
Cherry-pick PR #34588 into release-3.7 (#34988)
Component commits: 99328e9 Propagate 'undefined' instead of the optional type marker at an optional chain boundary 7aa6eee Merge branch 'master' into fix34579 # Conflicts: # src/compiler/utilities.ts 61e6765 Update src/compiler/types.ts Co-Authored-By: Nathan Shively-Sanders <[email protected]>
1 parent 12e8b08 commit 2e38783

File tree

9 files changed

+435
-59
lines changed

9 files changed

+435
-59
lines changed

src/compiler/binder.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,10 +1556,6 @@ namespace ts {
15561556
}
15571557
}
15581558

1559-
function isOutermostOptionalChain(node: OptionalChain) {
1560-
return !isOptionalChain(node.parent) || isOptionalChainRoot(node.parent) || node !== node.parent.expression;
1561-
}
1562-
15631559
function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
15641560
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
15651561
if (!isOptionalChain(node) || isOutermostOptionalChain(node)) {

src/compiler/checker.ts

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8670,14 +8670,23 @@ namespace ts {
86708670
return result;
86718671
}
86728672

8673-
function getOptionalCallSignature(signature: Signature) {
8674-
return signatureIsOptionalCall(signature) ? signature :
8675-
(signature.optionalCallSignatureCache || (signature.optionalCallSignatureCache = createOptionalCallSignature(signature)));
8673+
function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature {
8674+
if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) {
8675+
return signature;
8676+
}
8677+
if (!signature.optionalCallSignatureCache) {
8678+
signature.optionalCallSignatureCache = {};
8679+
}
8680+
const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer";
8681+
return signature.optionalCallSignatureCache[key]
8682+
|| (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags));
86768683
}
86778684

8678-
function createOptionalCallSignature(signature: Signature) {
8685+
function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) {
8686+
Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain,
8687+
"An optional call signature can either be for an inner call chain or an outer call chain, but not both.");
86798688
const result = cloneSignature(signature);
8680-
result.flags |= SignatureFlags.IsOptionalCall;
8689+
result.flags |= callChainFlags;
86818690
return result;
86828691
}
86838692

@@ -10292,9 +10301,12 @@ namespace ts {
1029210301
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
1029310302
getReturnTypeFromAnnotation(signature.declaration!) ||
1029410303
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
10295-
if (signatureIsOptionalCall(signature)) {
10304+
if (signature.flags & SignatureFlags.IsInnerCallChain) {
1029610305
type = addOptionalTypeMarker(type);
1029710306
}
10307+
else if (signature.flags & SignatureFlags.IsOuterCallChain) {
10308+
type = getOptionalType(type);
10309+
}
1029810310
if (!popTypeResolution()) {
1029910311
if (signature.declaration) {
1030010312
const typeNode = getEffectiveReturnTypeNode(signature.declaration);
@@ -16744,8 +16756,8 @@ namespace ts {
1674416756
return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type;
1674516757
}
1674616758

16747-
function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) {
16748-
return wasOptional ? addOptionalTypeMarker(type) : type;
16759+
function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) {
16760+
return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type;
1674916761
}
1675016762

1675116763
function getOptionalExpressionType(exprType: Type, expression: Expression) {
@@ -22812,7 +22824,7 @@ namespace ts {
2281222824
function checkPropertyAccessChain(node: PropertyAccessChain) {
2281322825
const leftType = checkExpression(node.expression);
2281422826
const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
22815-
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType);
22827+
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType);
2281622828
}
2281722829

2281822830
function checkQualifiedName(node: QualifiedName) {
@@ -23244,7 +23256,7 @@ namespace ts {
2324423256
function checkElementAccessChain(node: ElementAccessChain) {
2324523257
const exprType = checkExpression(node.expression);
2324623258
const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
23247-
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType);
23259+
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType);
2324823260
}
2324923261

2325023262
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
@@ -23349,7 +23361,7 @@ namespace ts {
2334923361
// interface B extends A { (x: 'foo'): string }
2335023362
// const b: B;
2335123363
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
23352-
function reorderCandidates(signatures: readonly Signature[], result: Signature[], isOptionalCall: boolean): void {
23364+
function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void {
2335323365
let lastParent: Node | undefined;
2335423366
let lastSymbol: Symbol | undefined;
2335523367
let cutoffIndex = 0;
@@ -23391,7 +23403,7 @@ namespace ts {
2339123403
spliceIndex = index;
2339223404
}
2339323405

23394-
result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature);
23406+
result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature);
2339523407
}
2339623408
}
2339723409

@@ -24057,7 +24069,7 @@ namespace ts {
2405724069
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
2405824070
}
2405924071

24060-
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature {
24072+
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature {
2406124073
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
2406224074
const isDecorator = node.kind === SyntaxKind.Decorator;
2406324075
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -24076,7 +24088,7 @@ namespace ts {
2407624088

2407724089
const candidates = candidatesOutArray || [];
2407824090
// reorderCandidates fills up the candidates array directly
24079-
reorderCandidates(signatures, candidates, isOptionalCall);
24091+
reorderCandidates(signatures, candidates, callChainFlags);
2408024092
if (!candidates.length) {
2408124093
if (reportErrors) {
2408224094
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
@@ -24463,22 +24475,25 @@ namespace ts {
2446324475
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
2446424476
if (baseTypeNode) {
2446524477
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
24466-
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false);
24478+
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None);
2446724479
}
2446824480
}
2446924481
return resolveUntypedCall(node);
2447024482
}
2447124483

24472-
let isOptional: boolean;
24484+
let callChainFlags: SignatureFlags;
2447324485
let funcType = checkExpression(node.expression);
2447424486
if (isCallChain(node)) {
2447524487
const nonOptionalType = getOptionalExpressionType(funcType, node.expression);
24476-
isOptional = nonOptionalType !== funcType;
24488+
callChainFlags = nonOptionalType === funcType ? SignatureFlags.None :
24489+
isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain :
24490+
SignatureFlags.IsInnerCallChain;
2447724491
funcType = nonOptionalType;
2447824492
}
2447924493
else {
24480-
isOptional = false;
24494+
callChainFlags = SignatureFlags.None;
2448124495
}
24496+
2448224497
funcType = checkNonNullTypeWithReporter(
2448324498
funcType,
2448424499
node.expression,
@@ -24554,7 +24569,7 @@ namespace ts {
2455424569
return resolveErrorCall(node);
2455524570
}
2455624571

24557-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional);
24572+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags);
2455824573
}
2455924574

2456024575
function isGenericFunctionReturningFunction(signature: Signature) {
@@ -24625,7 +24640,7 @@ namespace ts {
2462524640
return resolveErrorCall(node);
2462624641
}
2462724642

24628-
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24643+
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2462924644
}
2463024645

2463124646
// If expressionType's apparent type is an object type with no construct signatures but
@@ -24634,7 +24649,7 @@ namespace ts {
2463424649
// operation is Any. It is an error to have a Void this type.
2463524650
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
2463624651
if (callSignatures.length) {
24637-
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24652+
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2463824653
if (!noImplicitAny) {
2463924654
if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
2464024655
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
@@ -24849,7 +24864,7 @@ namespace ts {
2484924864
return resolveErrorCall(node);
2485024865
}
2485124866

24852-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24867+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2485324868
}
2485424869

2485524870
/**
@@ -24912,7 +24927,7 @@ namespace ts {
2491224927
return resolveErrorCall(node);
2491324928
}
2491424929

24915-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage);
24930+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
2491624931
}
2491724932

2491824933
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
@@ -24964,7 +24979,7 @@ namespace ts {
2496424979
return resolveErrorCall(node);
2496524980
}
2496624981

24967-
return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24982+
return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None);
2496824983
}
2496924984

2497024985
/**
@@ -27437,6 +27452,20 @@ namespace ts {
2743727452
}
2743827453
}
2743927454

27455+
function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) {
27456+
const signature = getSingleCallSignature(funcType);
27457+
if (signature && !signature.typeParameters) {
27458+
return getReturnTypeOfSignature(signature);
27459+
}
27460+
}
27461+
27462+
function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) {
27463+
const funcType = checkExpression(expr.expression);
27464+
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
27465+
const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType);
27466+
return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType);
27467+
}
27468+
2744027469
/**
2744127470
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
2744227471
* with computing the type and may not fully check all contained sub-expressions for errors.
@@ -27448,21 +27477,10 @@ namespace ts {
2744827477
// Optimize for the common case of a call to a function with a single non-generic call
2744927478
// signature where we can just fetch the return type without checking the arguments.
2745027479
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
27451-
let isOptional: boolean;
27452-
let funcType: Type;
27453-
if (isCallChain(expr)) {
27454-
funcType = checkExpression(expr.expression);
27455-
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
27456-
isOptional = funcType !== nonOptionalType;
27457-
funcType = checkNonNullType(nonOptionalType, expr.expression);
27458-
}
27459-
else {
27460-
isOptional = false;
27461-
funcType = checkNonNullExpression(expr.expression);
27462-
}
27463-
const signature = getSingleCallSignature(funcType);
27464-
if (signature && !signature.typeParameters) {
27465-
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional);
27480+
const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) :
27481+
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
27482+
if (type) {
27483+
return type;
2746627484
}
2746727485
}
2746827486
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
@@ -36175,7 +36193,4 @@ namespace ts {
3617536193
return !!(s.flags & SignatureFlags.HasLiteralTypes);
3617636194
}
3617736195

36178-
export function signatureIsOptionalCall(s: Signature) {
36179-
return !!(s.flags & SignatureFlags.IsOptionalCall);
36180-
}
3618136196
}

src/compiler/types.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4673,14 +4673,17 @@ namespace ts {
46734673
/* @internal */
46744674
export const enum SignatureFlags {
46754675
None = 0,
4676-
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
4677-
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
4678-
IsOptionalCall = 1 << 2, // Indicates signature comes from a CallChain
4676+
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
4677+
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
4678+
IsInnerCallChain = 1 << 2, // Indicates signature comes from a CallChain nested in an outer OptionalChain
4679+
IsOuterCallChain = 1 << 3, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
46794680

4680-
// We do not propagate `IsOptionalCall` to instantiated signatures, as that would result in us
4681+
// We do not propagate `IsInnerCallChain` to instantiated signatures, as that would result in us
46814682
// attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when
46824683
// instantiating the return type.
46834684
PropagatingFlags = HasRestParameter | HasLiteralTypes,
4685+
4686+
CallChainFlags = IsInnerCallChain | IsOuterCallChain,
46844687
}
46854688

46864689
export interface Signature {
@@ -4712,7 +4715,7 @@ namespace ts {
47124715
/* @internal */
47134716
canonicalSignatureCache?: Signature; // Canonical version of signature (deferred)
47144717
/* @internal */
4715-
optionalCallSignatureCache?: Signature; // Optional chained call version of signature (deferred)
4718+
optionalCallSignatureCache?: { inner?: Signature, outer?: Signature }; // Optional chained call version of signature (deferred)
47164719
/* @internal */
47174720
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
47184721
/* @internal */

src/compiler/utilities.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5917,6 +5917,11 @@ namespace ts {
59175917
|| kind === SyntaxKind.CallExpression);
59185918
}
59195919

5920+
/* @internal */
5921+
export function isOptionalChainRoot(node: Node): node is OptionalChainRoot {
5922+
return isOptionalChain(node) && !!node.questionDotToken;
5923+
}
5924+
59205925
/**
59215926
* Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`).
59225927
*/
@@ -5925,6 +5930,23 @@ namespace ts {
59255930
return isOptionalChainRoot(node.parent) && node.parent.expression === node;
59265931
}
59275932

5933+
/**
5934+
* Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`:
5935+
*
5936+
* 1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`)
5937+
* 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)
5938+
* 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
5939+
* the end of the chain starting at `c?.`)
5940+
* 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
5941+
* the end of the chain starting at `a?.`)
5942+
*/
5943+
/* @internal */
5944+
export function isOutermostOptionalChain(node: OptionalChain) {
5945+
return !isOptionalChain(node.parent) // cases 1 and 2
5946+
|| isOptionalChainRoot(node.parent) // case 3
5947+
|| node !== node.parent.expression; // case 4
5948+
}
5949+
59285950
export function isNullishCoalesce(node: Node) {
59295951
return node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken;
59305952
}
@@ -7246,11 +7268,6 @@ namespace ts {
72467268
return node.kind === SyntaxKind.GetAccessor;
72477269
}
72487270

7249-
/* @internal */
7250-
export function isOptionalChainRoot(node: Node): node is OptionalChainRoot {
7251-
return isOptionalChain(node) && !!node.questionDotToken;
7252-
}
7253-
72547271
/** True if has jsdoc nodes attached to it. */
72557272
/* @internal */
72567273
// TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times

tests/baselines/reference/callChain.3.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ const n4: number | undefined = a?.m?.({x: absorb()}); // likewise
4545
>a?.m : (<T>(obj: { x: T; }) => T) | undefined
4646
>a : { m?<T>(obj: { x: T; }): T; } | undefined
4747
>m : (<T>(obj: { x: T; }) => T) | undefined
48-
>{x: absorb()} : { x: number | undefined; }
49-
>x : number | undefined
50-
>absorb() : number | undefined
48+
>{x: absorb()} : { x: number; }
49+
>x : number
50+
>absorb() : number
5151
>absorb : <T>() => T
5252

5353
// Also a test showing `!` vs `?` for good measure

0 commit comments

Comments
 (0)