mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-26 20:14:05 -05:00
Propagate 'undefined' instead of the optional type marker at an optional chain boundary (#34588)
* Propagate 'undefined' instead of the optional type marker at an optional chain boundary * Update src/compiler/types.ts Co-Authored-By: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
@@ -1556,10 +1556,6 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function isOutermostOptionalChain(node: OptionalChain) {
|
||||
return !isOptionalChain(node.parent) || isOptionalChainRoot(node.parent) || node !== node.parent.expression;
|
||||
}
|
||||
|
||||
function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
|
||||
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
|
||||
if (!isOptionalChain(node) || isOutermostOptionalChain(node)) {
|
||||
|
||||
@@ -8691,14 +8691,23 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getOptionalCallSignature(signature: Signature) {
|
||||
return signatureIsOptionalCall(signature) ? signature :
|
||||
(signature.optionalCallSignatureCache || (signature.optionalCallSignatureCache = createOptionalCallSignature(signature)));
|
||||
function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature {
|
||||
if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) {
|
||||
return signature;
|
||||
}
|
||||
if (!signature.optionalCallSignatureCache) {
|
||||
signature.optionalCallSignatureCache = {};
|
||||
}
|
||||
const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer";
|
||||
return signature.optionalCallSignatureCache[key]
|
||||
|| (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags));
|
||||
}
|
||||
|
||||
function createOptionalCallSignature(signature: Signature) {
|
||||
function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) {
|
||||
Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain,
|
||||
"An optional call signature can either be for an inner call chain or an outer call chain, but not both.");
|
||||
const result = cloneSignature(signature);
|
||||
result.flags |= SignatureFlags.IsOptionalCall;
|
||||
result.flags |= callChainFlags;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -10313,9 +10322,12 @@ namespace ts {
|
||||
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
|
||||
getReturnTypeFromAnnotation(signature.declaration!) ||
|
||||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
|
||||
if (signatureIsOptionalCall(signature)) {
|
||||
if (signature.flags & SignatureFlags.IsInnerCallChain) {
|
||||
type = addOptionalTypeMarker(type);
|
||||
}
|
||||
else if (signature.flags & SignatureFlags.IsOuterCallChain) {
|
||||
type = getOptionalType(type);
|
||||
}
|
||||
if (!popTypeResolution()) {
|
||||
if (signature.declaration) {
|
||||
const typeNode = getEffectiveReturnTypeNode(signature.declaration);
|
||||
@@ -16767,8 +16779,8 @@ namespace ts {
|
||||
return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type;
|
||||
}
|
||||
|
||||
function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) {
|
||||
return wasOptional ? addOptionalTypeMarker(type) : type;
|
||||
function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) {
|
||||
return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type;
|
||||
}
|
||||
|
||||
function getOptionalExpressionType(exprType: Type, expression: Expression) {
|
||||
@@ -22835,7 +22847,7 @@ namespace ts {
|
||||
function checkPropertyAccessChain(node: PropertyAccessChain) {
|
||||
const leftType = checkExpression(node.expression);
|
||||
const nonOptionalType = getOptionalExpressionType(leftType, node.expression);
|
||||
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType);
|
||||
return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType);
|
||||
}
|
||||
|
||||
function checkQualifiedName(node: QualifiedName) {
|
||||
@@ -23267,7 +23279,7 @@ namespace ts {
|
||||
function checkElementAccessChain(node: ElementAccessChain) {
|
||||
const exprType = checkExpression(node.expression);
|
||||
const nonOptionalType = getOptionalExpressionType(exprType, node.expression);
|
||||
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType);
|
||||
return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType);
|
||||
}
|
||||
|
||||
function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type {
|
||||
@@ -23372,7 +23384,7 @@ namespace ts {
|
||||
// interface B extends A { (x: 'foo'): string }
|
||||
// const b: B;
|
||||
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
|
||||
function reorderCandidates(signatures: readonly Signature[], result: Signature[], isOptionalCall: boolean): void {
|
||||
function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void {
|
||||
let lastParent: Node | undefined;
|
||||
let lastSymbol: Symbol | undefined;
|
||||
let cutoffIndex = 0;
|
||||
@@ -23414,7 +23426,7 @@ namespace ts {
|
||||
spliceIndex = index;
|
||||
}
|
||||
|
||||
result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature);
|
||||
result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24080,7 +24092,7 @@ namespace ts {
|
||||
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
|
||||
}
|
||||
|
||||
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature {
|
||||
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature {
|
||||
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
|
||||
const isDecorator = node.kind === SyntaxKind.Decorator;
|
||||
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
|
||||
@@ -24099,7 +24111,7 @@ namespace ts {
|
||||
|
||||
const candidates = candidatesOutArray || [];
|
||||
// reorderCandidates fills up the candidates array directly
|
||||
reorderCandidates(signatures, candidates, isOptionalCall);
|
||||
reorderCandidates(signatures, candidates, callChainFlags);
|
||||
if (!candidates.length) {
|
||||
if (reportErrors) {
|
||||
diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures));
|
||||
@@ -24486,22 +24498,25 @@ namespace ts {
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
|
||||
if (baseTypeNode) {
|
||||
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
|
||||
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false);
|
||||
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None);
|
||||
}
|
||||
}
|
||||
return resolveUntypedCall(node);
|
||||
}
|
||||
|
||||
let isOptional: boolean;
|
||||
let callChainFlags: SignatureFlags;
|
||||
let funcType = checkExpression(node.expression);
|
||||
if (isCallChain(node)) {
|
||||
const nonOptionalType = getOptionalExpressionType(funcType, node.expression);
|
||||
isOptional = nonOptionalType !== funcType;
|
||||
callChainFlags = nonOptionalType === funcType ? SignatureFlags.None :
|
||||
isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain :
|
||||
SignatureFlags.IsInnerCallChain;
|
||||
funcType = nonOptionalType;
|
||||
}
|
||||
else {
|
||||
isOptional = false;
|
||||
callChainFlags = SignatureFlags.None;
|
||||
}
|
||||
|
||||
funcType = checkNonNullTypeWithReporter(
|
||||
funcType,
|
||||
node.expression,
|
||||
@@ -24577,7 +24592,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags);
|
||||
}
|
||||
|
||||
function isGenericFunctionReturningFunction(signature: Signature) {
|
||||
@@ -24648,7 +24663,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
|
||||
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
||||
}
|
||||
|
||||
// If expressionType's apparent type is an object type with no construct signatures but
|
||||
@@ -24657,7 +24672,7 @@ namespace ts {
|
||||
// operation is Any. It is an error to have a Void this type.
|
||||
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
|
||||
if (callSignatures.length) {
|
||||
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
|
||||
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
||||
if (!noImplicitAny) {
|
||||
if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
|
||||
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
|
||||
@@ -24872,7 +24887,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24935,7 +24950,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage);
|
||||
}
|
||||
|
||||
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
|
||||
@@ -24987,7 +25002,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false);
|
||||
return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27460,6 +27475,20 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) {
|
||||
const signature = getSingleCallSignature(funcType);
|
||||
if (signature && !signature.typeParameters) {
|
||||
return getReturnTypeOfSignature(signature);
|
||||
}
|
||||
}
|
||||
|
||||
function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) {
|
||||
const funcType = checkExpression(expr.expression);
|
||||
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
|
||||
const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType);
|
||||
return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
|
||||
* with computing the type and may not fully check all contained sub-expressions for errors.
|
||||
@@ -27471,21 +27500,10 @@ namespace ts {
|
||||
// Optimize for the common case of a call to a function with a single non-generic call
|
||||
// signature where we can just fetch the return type without checking the arguments.
|
||||
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
|
||||
let isOptional: boolean;
|
||||
let funcType: Type;
|
||||
if (isCallChain(expr)) {
|
||||
funcType = checkExpression(expr.expression);
|
||||
const nonOptionalType = getOptionalExpressionType(funcType, expr.expression);
|
||||
isOptional = funcType !== nonOptionalType;
|
||||
funcType = checkNonNullType(nonOptionalType, expr.expression);
|
||||
}
|
||||
else {
|
||||
isOptional = false;
|
||||
funcType = checkNonNullExpression(expr.expression);
|
||||
}
|
||||
const signature = getSingleCallSignature(funcType);
|
||||
if (signature && !signature.typeParameters) {
|
||||
return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional);
|
||||
const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) :
|
||||
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
|
||||
if (type) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) {
|
||||
@@ -36198,7 +36216,4 @@ namespace ts {
|
||||
return !!(s.flags & SignatureFlags.HasLiteralTypes);
|
||||
}
|
||||
|
||||
export function signatureIsOptionalCall(s: Signature) {
|
||||
return !!(s.flags & SignatureFlags.IsOptionalCall);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4673,14 +4673,17 @@ namespace ts {
|
||||
/* @internal */
|
||||
export const enum SignatureFlags {
|
||||
None = 0,
|
||||
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
|
||||
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
|
||||
IsOptionalCall = 1 << 2, // Indicates signature comes from a CallChain
|
||||
HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter
|
||||
HasLiteralTypes = 1 << 1, // Indicates signature is specialized
|
||||
IsInnerCallChain = 1 << 2, // Indicates signature comes from a CallChain nested in an outer OptionalChain
|
||||
IsOuterCallChain = 1 << 3, // Indicates signature comes from a CallChain that is the outermost chain of an optional expression
|
||||
|
||||
// We do not propagate `IsOptionalCall` to instantiated signatures, as that would result in us
|
||||
// We do not propagate `IsInnerCallChain` to instantiated signatures, as that would result in us
|
||||
// attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when
|
||||
// instantiating the return type.
|
||||
PropagatingFlags = HasRestParameter | HasLiteralTypes,
|
||||
|
||||
CallChainFlags = IsInnerCallChain | IsOuterCallChain,
|
||||
}
|
||||
|
||||
export interface Signature {
|
||||
@@ -4712,7 +4715,7 @@ namespace ts {
|
||||
/* @internal */
|
||||
canonicalSignatureCache?: Signature; // Canonical version of signature (deferred)
|
||||
/* @internal */
|
||||
optionalCallSignatureCache?: Signature; // Optional chained call version of signature (deferred)
|
||||
optionalCallSignatureCache?: { inner?: Signature, outer?: Signature }; // Optional chained call version of signature (deferred)
|
||||
/* @internal */
|
||||
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
|
||||
/* @internal */
|
||||
|
||||
@@ -5947,6 +5947,11 @@ namespace ts {
|
||||
|| kind === SyntaxKind.CallExpression);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isOptionalChainRoot(node: Node): node is OptionalChainRoot {
|
||||
return isOptionalChain(node) && !!node.questionDotToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`).
|
||||
*/
|
||||
@@ -5955,6 +5960,23 @@ namespace ts {
|
||||
return isOptionalChainRoot(node.parent) && node.parent.expression === node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
}
|
||||
|
||||
export function isNullishCoalesce(node: Node) {
|
||||
return node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.QuestionQuestionToken;
|
||||
}
|
||||
@@ -7276,11 +7298,6 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.GetAccessor;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isOptionalChainRoot(node: Node): node is OptionalChainRoot {
|
||||
return isOptionalChain(node) && !!node.questionDotToken;
|
||||
}
|
||||
|
||||
/** True if has jsdoc nodes attached to it. */
|
||||
/* @internal */
|
||||
// TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times
|
||||
|
||||
Reference in New Issue
Block a user