Skip to content

Commit e2e9380

Browse files
rbucktonsandersn
authored andcommitted
Cherry-pick PR microsoft#34588 into release-3.7
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 a22ad16 commit e2e9380

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);
@@ -16743,8 +16755,8 @@ namespace ts {
1674316755
return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type;
1674416756
}
1674516757

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

1675016762
function getOptionalExpressionType(exprType: Type, expression: Expression) {
@@ -22811,7 +22823,7 @@ namespace ts {
2281122823
function checkPropertyAccessChain(node: PropertyAccessChain) {
2281222824
const leftType = checkExpression(node.expression);
2281322825
const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
22814-
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType);
22826+
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType);
2281522827
}
2281622828

2281722829
function checkQualifiedName(node: QualifiedName) {
@@ -23243,7 +23255,7 @@ namespace ts {
2324323255
function checkElementAccessChain(node: ElementAccessChain) {
2324423256
const exprType = checkExpression(node.expression);
2324523257
const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
23246-
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType);
23258+
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType);
2324723259
}
2324823260

2324923261
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
@@ -23348,7 +23360,7 @@ namespace ts {
2334823360
// interface B extends A { (x: 'foo'): string }
2334923361
// const b: B;
2335023362
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
23351-
function reorderCandidates(signatures: readonly Signature[], result: Signature[], isOptionalCall: boolean): void {
23363+
function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void {
2335223364
let lastParent: Node | undefined;
2335323365
let lastSymbol: Symbol | undefined;
2335423366
let cutoffIndex = 0;
@@ -23390,7 +23402,7 @@ namespace ts {
2339023402
spliceIndex = index;
2339123403
}
2339223404

23393-
result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature);
23405+
result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature);
2339423406
}
2339523407
}
2339623408

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

24059-
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature {
24071+
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature {
2406024072
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
2406124073
const isDecorator = node.kind === SyntaxKind.Decorator;
2406224074
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -24075,7 +24087,7 @@ namespace ts {
2407524087

2407624088
const candidates = candidatesOutArray || [];
2407724089
// reorderCandidates fills up the candidates array directly
24078-
reorderCandidates(signatures, candidates, isOptionalCall);
24090+
reorderCandidates(signatures, candidates, callChainFlags);
2407924091
if (!candidates.length) {
2408024092
if (reportErrors) {
2408124093
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
@@ -24462,22 +24474,25 @@ namespace ts {
2446224474
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
2446324475
if (baseTypeNode) {
2446424476
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
24465-
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false);
24477+
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None);
2446624478
}
2446724479
}
2446824480
return resolveUntypedCall(node);
2446924481
}
2447024482

24471-
let isOptional: boolean;
24483+
let callChainFlags: SignatureFlags;
2447224484
let funcType = checkExpression(node.expression);
2447324485
if (isCallChain(node)) {
2447424486
const nonOptionalType = getOptionalExpressionType(funcType, node.expression);
24475-
isOptional = nonOptionalType !== funcType;
24487+
callChainFlags = nonOptionalType === funcType ? SignatureFlags.None :
24488+
isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain :
24489+
SignatureFlags.IsInnerCallChain;
2447624490
funcType = nonOptionalType;
2447724491
}
2447824492
else {
24479-
isOptional = false;
24493+
callChainFlags = SignatureFlags.None;
2448024494
}
24495+
2448124496
funcType = checkNonNullTypeWithReporter(
2448224497
funcType,
2448324498
node.expression,
@@ -24553,7 +24568,7 @@ namespace ts {
2455324568
return resolveErrorCall(node);
2455424569
}
2455524570

24556-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional);
24571+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags);
2455724572
}
2455824573

2455924574
function isGenericFunctionReturningFunction(signature: Signature) {
@@ -24624,7 +24639,7 @@ namespace ts {
2462424639
return resolveErrorCall(node);
2462524640
}
2462624641

24627-
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24642+
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2462824643
}
2462924644

2463024645
// If expressionType's apparent type is an object type with no construct signatures but
@@ -24633,7 +24648,7 @@ namespace ts {
2463324648
// operation is Any. It is an error to have a Void this type.
2463424649
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
2463524650
if (callSignatures.length) {
24636-
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24651+
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2463724652
if (!noImplicitAny) {
2463824653
if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
2463924654
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
@@ -24848,7 +24863,7 @@ namespace ts {
2484824863
return resolveErrorCall(node);
2484924864
}
2485024865

24851-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24866+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
2485224867
}
2485324868

2485424869
/**
@@ -24911,7 +24926,7 @@ namespace ts {
2491124926
return resolveErrorCall(node);
2491224927
}
2491324928

24914-
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage);
24929+
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
2491524930
}
2491624931

2491724932
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
@@ -24963,7 +24978,7 @@ namespace ts {
2496324978
return resolveErrorCall(node);
2496424979
}
2496524980

24966-
return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false);
24981+
return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None);
2496724982
}
2496824983

2496924984
/**
@@ -27436,6 +27451,20 @@ namespace ts {
2743627451
}
2743727452
}
2743827453

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

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

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)