Skip to content

Commit 2f0d07c

Browse files
authored
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
1 parent 3d2b92c commit 2f0d07c

18 files changed

+875
-47
lines changed

src/compiler/checker.ts

+33-25
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,12 @@ namespace ts {
183183
NoTupleBoundsCheck = 1 << 3,
184184
}
185185

186-
const enum CallbackCheck {
187-
None,
188-
Bivariant,
189-
Strict,
186+
const enum SignatureCheckMode {
187+
BivariantCallback = 1 << 0,
188+
StrictCallback = 1 << 1,
189+
IgnoreReturnTypes = 1 << 2,
190+
StrictArity = 1 << 3,
191+
Callback = BivariantCallback | StrictCallback,
190192
}
191193

192194
const enum MappedTypeModifiers {
@@ -343,6 +345,7 @@ namespace ts {
343345
assignable: assignableRelation.size,
344346
identity: identityRelation.size,
345347
subtype: subtypeRelation.size,
348+
strictSubtype: strictSubtypeRelation.size,
346349
}),
347350
isUndefinedSymbol: symbol => symbol === undefinedSymbol,
348351
isArgumentsSymbol: symbol => symbol === argumentsSymbol,
@@ -869,6 +872,7 @@ namespace ts {
869872
let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined;
870873

871874
const subtypeRelation = createMap<RelationComparisonResult>();
875+
const strictSubtypeRelation = createMap<RelationComparisonResult>();
872876
const assignableRelation = createMap<RelationComparisonResult>();
873877
const comparableRelation = createMap<RelationComparisonResult>();
874878
const identityRelation = createMap<RelationComparisonResult>();
@@ -11428,7 +11432,7 @@ namespace ts {
1142811432
}
1142911433
}
1143011434
count++;
11431-
if (isTypeSubtypeOf(source, target) && (
11435+
if (isTypeRelatedTo(source, target, strictSubtypeRelation) && (
1143211436
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
1143311437
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
1143411438
isTypeDerivedFrom(source, target))) {
@@ -14016,7 +14020,7 @@ namespace ts {
1401614020
function isSignatureAssignableTo(source: Signature,
1401714021
target: Signature,
1401814022
ignoreReturnTypes: boolean): boolean {
14019-
return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false,
14023+
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false,
1402014024
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
1402114025
}
1402214026

@@ -14036,8 +14040,7 @@ namespace ts {
1403614040
*/
1403714041
function compareSignaturesRelated(source: Signature,
1403814042
target: Signature,
14039-
callbackCheck: CallbackCheck,
14040-
ignoreReturnTypes: boolean,
14043+
checkMode: SignatureCheckMode,
1404114044
reportErrors: boolean,
1404214045
errorReporter: ErrorReporter | undefined,
1404314046
incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined,
@@ -14053,7 +14056,9 @@ namespace ts {
1405314056
}
1405414057

1405514058
const targetCount = getParameterCount(target);
14056-
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
14059+
const sourceHasMoreParameters = !hasEffectiveRestParameter(target) &&
14060+
(checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount);
14061+
if (sourceHasMoreParameters) {
1405714062
return Ternary.False;
1405814063
}
1405914064

@@ -14074,7 +14079,7 @@ namespace ts {
1407414079
}
1407514080

1407614081
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
14077-
const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
14082+
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
1407814083
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
1407914084
let result = Ternary.True;
1408014085

@@ -14109,14 +14114,17 @@ namespace ts {
1410914114
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
1411014115
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
1411114116
// with respect to T.
14112-
const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
14113-
const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType));
14117+
const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
14118+
const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
1411414119
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
1411514120
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
14116-
const related = callbacks ?
14117-
// TODO: GH#18217 It will work if they're both `undefined`, but not if only one is
14118-
compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
14119-
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
14121+
let related = callbacks ?
14122+
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
14123+
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
14124+
// With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void
14125+
if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) {
14126+
related = Ternary.False;
14127+
}
1412014128
if (!related) {
1412114129
if (reportErrors) {
1412214130
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -14128,7 +14136,7 @@ namespace ts {
1412814136
result &= related;
1412914137
}
1413014138

14131-
if (!ignoreReturnTypes) {
14139+
if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) {
1413214140
// If a signature resolution is already in-flight, skip issuing a circularity error
1413314141
// here and just use the `any` type directly
1413414142
const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType
@@ -14159,7 +14167,7 @@ namespace ts {
1415914167
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
1416014168
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
1416114169
// wouldn't be co-variant for T without this rule.
14162-
result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
14170+
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
1416314171
compareTypes(sourceReturnType, targetReturnType, reportErrors);
1416414172
if (!result && reportErrors && incompatibleErrorReporter) {
1416514173
incompatibleErrorReporter(sourceReturnType, targetReturnType);
@@ -15467,7 +15475,7 @@ namespace ts {
1546715475
}
1546815476
else {
1546915477
// An empty object type is related to any mapped type that includes a '?' modifier.
15470-
if (relation !== subtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
15478+
if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
1547115479
return Ternary.True;
1547215480
}
1547315481
if (isGenericMappedType(target)) {
@@ -15509,7 +15517,7 @@ namespace ts {
1550915517
}
1551015518
// Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})`
1551115519
// and not `{} <- fresh({}) <- {[idx: string]: any}`
15512-
else if (relation === subtypeRelation && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
15520+
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
1551315521
return Ternary.False;
1551415522
}
1551515523
// Even if relationship doesn't hold for unions, intersections, or generic type references,
@@ -15854,7 +15862,7 @@ namespace ts {
1585415862
if (relation === identityRelation) {
1585515863
return propertiesIdenticalTo(source, target, excludedProperties);
1585615864
}
15857-
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
15865+
const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
1585815866
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
1585915867
if (unmatchedProperty) {
1586015868
if (reportErrors) {
@@ -16077,7 +16085,7 @@ namespace ts {
1607716085
*/
1607816086
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
1607916087
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
16080-
CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
16088+
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
1608116089
}
1608216090

1608316091
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
@@ -16391,12 +16399,12 @@ namespace ts {
1639116399
source = target;
1639216400
target = temp;
1639316401
}
16394-
const intersection = isIntersectionConstituent ? "&" : "";
16402+
const delimiter = isIntersectionConstituent ? ";" : ",";
1639516403
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
1639616404
const typeParameters: Type[] = [];
16397-
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + intersection;
16405+
return getTypeReferenceId(<TypeReference>source, typeParameters) + delimiter + getTypeReferenceId(<TypeReference>target, typeParameters);
1639816406
}
16399-
return source.id + "," + target.id + intersection;
16407+
return source.id + delimiter + target.id;
1640016408
}
1640116409

1640216410
// Invoke the callback for each underlying property symbol of the given symbol and return the first

src/compiler/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3183,7 +3183,7 @@ namespace ts {
31833183
getIdentifierCount(): number;
31843184
getSymbolCount(): number;
31853185
getTypeCount(): number;
3186-
getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
3186+
getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number };
31873187

31883188
/* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection;
31893189
/* @internal */ getResolvedTypeReferenceDirectives(): Map<ResolvedTypeReferenceDirective | undefined>;
@@ -3501,7 +3501,7 @@ namespace ts {
35013501
/* @internal */ getIdentifierCount(): number;
35023502
/* @internal */ getSymbolCount(): number;
35033503
/* @internal */ getTypeCount(): number;
3504-
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
3504+
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number };
35053505

35063506
/* @internal */ isArrayType(type: Type): boolean;
35073507
/* @internal */ isTupleType(type: Type): boolean;

src/executeCommandLine/executeCommandLine.ts

+1
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ namespace ts {
663663
reportCountStatistic("Assignability cache size", caches.assignable);
664664
reportCountStatistic("Identity cache size", caches.identity);
665665
reportCountStatistic("Subtype cache size", caches.subtype);
666+
reportCountStatistic("Strict subtype cache size", caches.strictSubtype);
666667
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
667668
}
668669
else {

tests/baselines/reference/api/tsserverlibrary.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,7 @@ declare namespace ts {
19351935
assignable: number;
19361936
identity: number;
19371937
subtype: number;
1938+
strictSubtype: number;
19381939
};
19391940
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
19401941
isSourceFileDefaultLibrary(file: SourceFile): boolean;

tests/baselines/reference/api/typescript.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,7 @@ declare namespace ts {
19351935
assignable: number;
19361936
identity: number;
19371937
subtype: number;
1938+
strictSubtype: number;
19381939
};
19391940
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
19401941
isSourceFileDefaultLibrary(file: SourceFile): boolean;

tests/baselines/reference/bestCommonTypeOfConditionalExpressions.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ var r5 = true ? b : a; // typeof b
6464
>a : { x: number; y?: number; }
6565

6666
var r6 = true ? (x: number) => { } : (x: Object) => { }; // returns number => void
67-
>r6 : (x: number) => void
68-
>true ? (x: number) => { } : (x: Object) => { } : (x: number) => void
67+
>r6 : ((x: number) => void) | ((x: Object) => void)
68+
>true ? (x: number) => { } : (x: Object) => { } : ((x: number) => void) | ((x: Object) => void)
6969
>true : true
7070
>(x: number) => { } : (x: number) => void
7171
>x : number
@@ -75,16 +75,16 @@ var r6 = true ? (x: number) => { } : (x: Object) => { }; // returns number => vo
7575
var r7: (x: Object) => void = true ? (x: number) => { } : (x: Object) => { };
7676
>r7 : (x: Object) => void
7777
>x : Object
78-
>true ? (x: number) => { } : (x: Object) => { } : (x: number) => void
78+
>true ? (x: number) => { } : (x: Object) => { } : ((x: number) => void) | ((x: Object) => void)
7979
>true : true
8080
>(x: number) => { } : (x: number) => void
8181
>x : number
8282
>(x: Object) => { } : (x: Object) => void
8383
>x : Object
8484

8585
var r8 = true ? (x: Object) => { } : (x: number) => { }; // returns Object => void
86-
>r8 : (x: Object) => void
87-
>true ? (x: Object) => { } : (x: number) => { } : (x: Object) => void
86+
>r8 : ((x: Object) => void) | ((x: number) => void)
87+
>true ? (x: Object) => { } : (x: number) => { } : ((x: Object) => void) | ((x: number) => void)
8888
>true : true
8989
>(x: Object) => { } : (x: Object) => void
9090
>x : Object

tests/baselines/reference/functionWithMultipleReturnStatements2.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function f4() {
5858
}
5959

6060
function f5() {
61-
>f5 : () => Object
61+
>f5 : () => Object | 1
6262

6363
return 1;
6464
>1 : 1
@@ -136,7 +136,7 @@ function f10() {
136136

137137
// returns number => void
138138
function f11() {
139-
>f11 : () => (x: number) => void
139+
>f11 : () => ((x: number) => void) | ((x: Object) => void)
140140

141141
if (true) {
142142
>true : true
@@ -154,7 +154,7 @@ function f11() {
154154

155155
// returns Object => void
156156
function f12() {
157-
>f12 : () => (x: Object) => void
157+
>f12 : () => ((x: Object) => void) | ((x: number) => void)
158158

159159
if (true) {
160160
>true : true

tests/baselines/reference/subtypesOfTypeParameterWithConstraints2.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -566,16 +566,16 @@ function f20<T extends Number>(x: T) {
566566
>x : T
567567

568568
var r19 = true ? new Object() : x; // ok
569-
>r19 : Object
570-
>true ? new Object() : x : Object
569+
>r19 : Object | T
570+
>true ? new Object() : x : Object | T
571571
>true : true
572572
>new Object() : Object
573573
>Object : ObjectConstructor
574574
>x : T
575575

576576
var r19 = true ? x : new Object(); // ok
577-
>r19 : Object
578-
>true ? x : new Object() : Object
577+
>r19 : Object | T
578+
>true ? x : new Object() : Object | T
579579
>true : true
580580
>x : T
581581
>new Object() : Object

0 commit comments

Comments
 (0)