mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Increase selectivity of subtype relationship for signatures (#35659)
* Increase selectivity of subtype relationship for signatures * Add regression test * Accept new baselines * Use strictSubtypeRelation for union subtype reduction * (x: number | undefined) -> void is subtype of (x?: number | undefined) => void * Accept new baselines * Add tests * Accept new baselines * Address CR feedback * Fix parameter list length check * Accept API baseline changes
This commit is contained in:
@@ -183,10 +183,12 @@ namespace ts {
|
||||
NoTupleBoundsCheck = 1 << 3,
|
||||
}
|
||||
|
||||
const enum CallbackCheck {
|
||||
None,
|
||||
Bivariant,
|
||||
Strict,
|
||||
const enum SignatureCheckMode {
|
||||
BivariantCallback = 1 << 0,
|
||||
StrictCallback = 1 << 1,
|
||||
IgnoreReturnTypes = 1 << 2,
|
||||
StrictArity = 1 << 3,
|
||||
Callback = BivariantCallback | StrictCallback,
|
||||
}
|
||||
|
||||
const enum MappedTypeModifiers {
|
||||
@@ -343,6 +345,7 @@ namespace ts {
|
||||
assignable: assignableRelation.size,
|
||||
identity: identityRelation.size,
|
||||
subtype: subtypeRelation.size,
|
||||
strictSubtype: strictSubtypeRelation.size,
|
||||
}),
|
||||
isUndefinedSymbol: symbol => symbol === undefinedSymbol,
|
||||
isArgumentsSymbol: symbol => symbol === argumentsSymbol,
|
||||
@@ -869,6 +872,7 @@ namespace ts {
|
||||
let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined;
|
||||
|
||||
const subtypeRelation = createMap<RelationComparisonResult>();
|
||||
const strictSubtypeRelation = createMap<RelationComparisonResult>();
|
||||
const assignableRelation = createMap<RelationComparisonResult>();
|
||||
const comparableRelation = createMap<RelationComparisonResult>();
|
||||
const identityRelation = createMap<RelationComparisonResult>();
|
||||
@@ -11428,7 +11432,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
count++;
|
||||
if (isTypeSubtypeOf(source, target) && (
|
||||
if (isTypeRelatedTo(source, target, strictSubtypeRelation) && (
|
||||
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
|
||||
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
|
||||
isTypeDerivedFrom(source, target))) {
|
||||
@@ -14016,7 +14020,7 @@ namespace ts {
|
||||
function isSignatureAssignableTo(source: Signature,
|
||||
target: Signature,
|
||||
ignoreReturnTypes: boolean): boolean {
|
||||
return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false,
|
||||
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false,
|
||||
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
|
||||
}
|
||||
|
||||
@@ -14036,8 +14040,7 @@ namespace ts {
|
||||
*/
|
||||
function compareSignaturesRelated(source: Signature,
|
||||
target: Signature,
|
||||
callbackCheck: CallbackCheck,
|
||||
ignoreReturnTypes: boolean,
|
||||
checkMode: SignatureCheckMode,
|
||||
reportErrors: boolean,
|
||||
errorReporter: ErrorReporter | undefined,
|
||||
incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined,
|
||||
@@ -14053,7 +14056,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
const targetCount = getParameterCount(target);
|
||||
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
|
||||
const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
|
||||
(checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
|
||||
if (sourceHasMoreParameters) {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
@@ -14074,7 +14079,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
|
||||
const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
|
||||
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
|
||||
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
|
||||
let result = Ternary.True;
|
||||
|
||||
@@ -14109,14 +14114,17 @@ namespace ts {
|
||||
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
|
||||
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
|
||||
// with respect to T.
|
||||
const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
|
||||
const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType));
|
||||
const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
|
||||
const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
|
||||
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
|
||||
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
|
||||
const related = callbacks ?
|
||||
// TODO: GH#18217 It will work if they're both `undefined`, but not if only one is
|
||||
compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
|
||||
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
|
||||
let related = callbacks ?
|
||||
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
|
||||
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
|
||||
// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
|
||||
if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
|
||||
related = Ternary.False;
|
||||
}
|
||||
if (!related) {
|
||||
if (reportErrors) {
|
||||
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
|
||||
@@ -14128,7 +14136,7 @@ namespace ts {
|
||||
result &= related;
|
||||
}
|
||||
|
||||
if (!ignoreReturnTypes) {
|
||||
if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) {
|
||||
// If a signature resolution is already in-flight, skip issuing a circularity error
|
||||
// here and just use the `any` type directly
|
||||
const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType
|
||||
@@ -14159,7 +14167,7 @@ namespace ts {
|
||||
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
|
||||
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
|
||||
// wouldn't be co-variant for T without this rule.
|
||||
result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
|
||||
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
|
||||
compareTypes(sourceReturnType, targetReturnType, reportErrors);
|
||||
if (!result && reportErrors && incompatibleErrorReporter) {
|
||||
incompatibleErrorReporter(sourceReturnType, targetReturnType);
|
||||
@@ -15467,7 +15475,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// An empty object type is related to any mapped type that includes a '?' modifier.
|
||||
if (relation !== subtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
|
||||
if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
|
||||
return Ternary.True;
|
||||
}
|
||||
if (isGenericMappedType(target)) {
|
||||
@@ -15509,7 +15517,7 @@ namespace ts {
|
||||
}
|
||||
// Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})`
|
||||
// and not `{} <- fresh({}) <- {[idx: string]: any}`
|
||||
else if (relation === subtypeRelation && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
|
||||
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
|
||||
return Ternary.False;
|
||||
}
|
||||
// Even if relationship doesn't hold for unions, intersections, or generic type references,
|
||||
@@ -15854,7 +15862,7 @@ namespace ts {
|
||||
if (relation === identityRelation) {
|
||||
return propertiesIdenticalTo(source, target, excludedProperties);
|
||||
}
|
||||
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
|
||||
const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
|
||||
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
|
||||
if (unmatchedProperty) {
|
||||
if (reportErrors) {
|
||||
@@ -16077,7 +16085,7 @@ namespace ts {
|
||||
*/
|
||||
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
|
||||
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
|
||||
CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
|
||||
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
|
||||
}
|
||||
|
||||
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
|
||||
@@ -16391,12 +16399,12 @@ namespace ts {
|
||||
source = target;
|
||||
target = temp;
|
||||
}
|
||||
const intersection = isIntersectionConstituent ? "&" : "";
|
||||
const delimiter = isIntersectionConstituent ? ";" : ",";
|
||||
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
|
||||
const typeParameters: Type[] = [];
|
||||
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + intersection;
|
||||
return getTypeReferenceId(<TypeReference>source, typeParameters) + delimiter + getTypeReferenceId(<TypeReference>target, typeParameters);
|
||||
}
|
||||
return source.id + "," + target.id + intersection;
|
||||
return source.id + delimiter + target.id;
|
||||
}
|
||||
|
||||
// Invoke the callback for each underlying property symbol of the given symbol and return the first
|
||||
|
||||
@@ -3183,7 +3183,7 @@ namespace ts {
|
||||
getIdentifierCount(): number;
|
||||
getSymbolCount(): number;
|
||||
getTypeCount(): number;
|
||||
getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
|
||||
getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number };
|
||||
|
||||
/* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection;
|
||||
/* @internal */ getResolvedTypeReferenceDirectives(): Map<ResolvedTypeReferenceDirective | undefined>;
|
||||
@@ -3501,7 +3501,7 @@ namespace ts {
|
||||
/* @internal */ getIdentifierCount(): number;
|
||||
/* @internal */ getSymbolCount(): number;
|
||||
/* @internal */ getTypeCount(): number;
|
||||
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
|
||||
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number };
|
||||
|
||||
/* @internal */ isArrayType(type: Type): boolean;
|
||||
/* @internal */ isTupleType(type: Type): boolean;
|
||||
|
||||
@@ -663,6 +663,7 @@ namespace ts {
|
||||
reportCountStatistic("Assignability cache size", caches.assignable);
|
||||
reportCountStatistic("Identity cache size", caches.identity);
|
||||
reportCountStatistic("Subtype cache size", caches.subtype);
|
||||
reportCountStatistic("Strict subtype cache size", caches.strictSubtype);
|
||||
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user