Skip to content

Commit b457de4

Browse files
committed
Increase selectivity of subtype relationship for signatures
1 parent 71a9176 commit b457de4

File tree

1 file changed

+37
-22
lines changed

1 file changed

+37
-22
lines changed

src/compiler/checker.ts

Lines changed: 37 additions & 22 deletions
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 {
@@ -13984,7 +13986,7 @@ namespace ts {
1398413986
function isSignatureAssignableTo(source: Signature,
1398513987
target: Signature,
1398613988
ignoreReturnTypes: boolean): boolean {
13987-
return compareSignaturesRelated(source, target, CallbackCheck.None, ignoreReturnTypes, /*reportErrors*/ false,
13989+
return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false,
1398813990
/*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False;
1398913991
}
1399013992

@@ -14004,8 +14006,7 @@ namespace ts {
1400414006
*/
1400514007
function compareSignaturesRelated(source: Signature,
1400614008
target: Signature,
14007-
callbackCheck: CallbackCheck,
14008-
ignoreReturnTypes: boolean,
14009+
checkMode: SignatureCheckMode,
1400914010
reportErrors: boolean,
1401014011
errorReporter: ErrorReporter | undefined,
1401114012
incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined,
@@ -14021,7 +14022,8 @@ namespace ts {
1402114022
}
1402214023

1402314024
const targetCount = getParameterCount(target);
14024-
if (!hasEffectiveRestParameter(target) && getMinArgumentCount(source) > targetCount) {
14025+
if (!hasEffectiveRestParameter(target) &&
14026+
(checkMode & SignatureCheckMode.StrictArity ? getParameterCount(source) : getMinArgumentCount(source)) > targetCount) {
1402514027
return Ternary.False;
1402614028
}
1402714029

@@ -14042,7 +14044,7 @@ namespace ts {
1404214044
}
1404314045

1404414046
const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
14045-
const strictVariance = !callbackCheck && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
14047+
const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration &&
1404614048
kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor;
1404714049
let result = Ternary.True;
1404814050

@@ -14077,14 +14079,14 @@ namespace ts {
1407714079
// similar to return values, callback parameters are output positions. This means that a Promise<T>,
1407814080
// where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant)
1407914081
// with respect to T.
14080-
const sourceSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
14081-
const targetSig = callbackCheck ? undefined : getSingleCallSignature(getNonNullableType(targetType));
14082+
const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
14083+
const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
1408214084
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
1408314085
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
1408414086
const related = callbacks ?
1408514087
// TODO: GH#18217 It will work if they're both `undefined`, but not if only one is
14086-
compareSignaturesRelated(targetSig!, sourceSig!, strictVariance ? CallbackCheck.Strict : CallbackCheck.Bivariant, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
14087-
!callbackCheck && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
14088+
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
14089+
!(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors);
1408814090
if (!related) {
1408914091
if (reportErrors) {
1409014092
errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
@@ -14096,7 +14098,7 @@ namespace ts {
1409614098
result &= related;
1409714099
}
1409814100

14099-
if (!ignoreReturnTypes) {
14101+
if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) {
1410014102
// If a signature resolution is already in-flight, skip issuing a circularity error
1410114103
// here and just use the `any` type directly
1410214104
const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType
@@ -14127,7 +14129,7 @@ namespace ts {
1412714129
// When relating callback signatures, we still need to relate return types bi-variantly as otherwise
1412814130
// the containing type wouldn't be co-variant. For example, interface Foo<T> { add(cb: () => T): void }
1412914131
// wouldn't be co-variant for T without this rule.
14130-
result &= callbackCheck === CallbackCheck.Bivariant && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
14132+
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) ||
1413114133
compareTypes(sourceReturnType, targetReturnType, reportErrors);
1413214134
if (!result && reportErrors && incompatibleErrorReporter) {
1413314135
incompatibleErrorReporter(sourceReturnType, targetReturnType);
@@ -14299,7 +14301,7 @@ namespace ts {
1429914301
return true;
1430014302
}
1430114303
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
14302-
const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, relation));
14304+
const related = relation.get(getRelationKey(source, target, /*isIntersectionConstituent*/ false, /*strictArityChecks*/ false, relation));
1430314305
if (related !== undefined) {
1430414306
return !!(related & RelationComparisonResult.Succeeded);
1430514307
}
@@ -14351,6 +14353,7 @@ namespace ts {
1435114353
let depth = 0;
1435214354
let expandingFlags = ExpandingFlags.None;
1435314355
let overflow = false;
14356+
let strictArityChecks = relation === subtypeRelation;
1435414357
let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid
1435514358
let lastSkippedInfo: [Type, Type] | undefined;
1435614359
let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = [];
@@ -15139,7 +15142,7 @@ namespace ts {
1513915142
if (overflow) {
1514015143
return Ternary.False;
1514115144
}
15142-
const id = getRelationKey(source, target, isIntersectionConstituent, relation);
15145+
const id = getRelationKey(source, target, isIntersectionConstituent, strictArityChecks, relation);
1514315146
const entry = relation.get(id);
1514415147
if (entry !== undefined) {
1514515148
if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) {
@@ -15486,6 +15489,17 @@ namespace ts {
1548615489
// to X. Failing both of those we want to check if the aggregation of A and B's members structurally
1548715490
// relates to X. Thus, we include intersection types on the source side here.
1548815491
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
15492+
// When performing strict arity checks for signatures (under the subtype relationship), if source has
15493+
// every property of target, and target is missing some properties from source, then we disable strict
15494+
// arity checks for the members. This decreases the chances of "ties" in union subtype reduction. For
15495+
// example, it enables us to consider { f(): void } a supertype of { f(x?: string): void, g(): void },
15496+
// which we need for backwards compatibility reasons (specifically, for Object and Number).
15497+
const saveStrictArityChecks = strictArityChecks;
15498+
if (strictArityChecks &&
15499+
!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false) &&
15500+
getUnmatchedProperty(target, source, /*requireOptionalProperties*/ true, /*matchDiscriminantProperties*/ false)) {
15501+
strictArityChecks = false;
15502+
}
1548915503
// Report structural errors only if we haven't reported any errors yet
1549015504
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive;
1549115505
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, isIntersectionConstituent);
@@ -15501,6 +15515,7 @@ namespace ts {
1550115515
}
1550215516
}
1550315517
}
15518+
strictArityChecks = saveStrictArityChecks;
1550415519
if (varianceCheckFailed && result) {
1550515520
errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false
1550615521
}
@@ -16045,7 +16060,7 @@ namespace ts {
1604516060
*/
1604616061
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
1604716062
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
16048-
CallbackCheck.None, /*ignoreReturnTypes*/ false, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
16063+
strictArityChecks ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
1604916064
}
1605016065

1605116066
function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
@@ -16353,18 +16368,18 @@ namespace ts {
1635316368
* To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters.
1635416369
* For other cases, the types ids are used.
1635516370
*/
16356-
function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, relation: Map<RelationComparisonResult>) {
16371+
function getRelationKey(source: Type, target: Type, isIntersectionConstituent: boolean, strictArityChecks: boolean, relation: Map<RelationComparisonResult>) {
1635716372
if (relation === identityRelation && source.id > target.id) {
1635816373
const temp = source;
1635916374
source = target;
1636016375
target = temp;
1636116376
}
16362-
const intersection = isIntersectionConstituent ? "&" : "";
16377+
const delimiter = isIntersectionConstituent ? strictArityChecks ? "|" : ";" : strictArityChecks ? "/" : ",";
1636316378
if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) {
1636416379
const typeParameters: Type[] = [];
16365-
return getTypeReferenceId(<TypeReference>source, typeParameters) + "," + getTypeReferenceId(<TypeReference>target, typeParameters) + intersection;
16380+
return getTypeReferenceId(<TypeReference>source, typeParameters) + delimiter + getTypeReferenceId(<TypeReference>target, typeParameters);
1636616381
}
16367-
return source.id + "," + target.id + intersection;
16382+
return source.id + delimiter + target.id;
1636816383
}
1636916384

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

0 commit comments

Comments
 (0)