Skip to content

Commit afc8a26

Browse files
committed
Always perform structural comparison when variance check fails
1 parent 589e1f4 commit afc8a26

File tree

2 files changed

+35
-86
lines changed

2 files changed

+35
-86
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,9 @@ namespace ts {
282282
const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
283283
const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
284284

285-
const markerSuperType = createType(TypeFlags.MarkerType);
286-
const markerSubType = createType(TypeFlags.MarkerType);
287-
const markerOtherType = createType(TypeFlags.MarkerType);
285+
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
286+
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
287+
markerSubType.constraint = markerSuperType;
288288

289289
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
290290
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
@@ -8948,10 +8948,6 @@ namespace ts {
89488948

89498949
if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
89508950

8951-
if (source.flags & TypeFlags.MarkerType && target.flags & TypeFlags.MarkerType && !(source.flags & TypeFlags.Object || target.flags & TypeFlags.Object)) {
8952-
return source === markerSubType && target === markerSuperType ? Ternary.True : Ternary.False;
8953-
}
8954-
89558951
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) {
89568952
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
89578953
if (reportErrors) {
@@ -9219,41 +9215,15 @@ namespace ts {
92199215
// in the process of computing variance information for recursive types and when
92209216
// comparing 'this' type arguments.
92219217
const variance = i < variances.length ? variances[i] : Variance.Covariant;
9222-
// We simply ignore omnivariant type arguments (because they're never witnessed).
9223-
if (variance !== Variance.Omnivariant) {
9224-
const s = sources[i];
9225-
const t = targets[i];
9226-
let related = Ternary.True;
9227-
if (variance === Variance.Covariant) {
9228-
related = isRelatedTo(s, t, reportErrors);
9229-
}
9230-
else if (variance === Variance.Contravariant) {
9231-
related = isRelatedTo(t, s, reportErrors);
9232-
}
9233-
else if (variance === Variance.Bivariant) {
9234-
// In the bivariant case we first compare contravariantly without reporting
9235-
// errors. Then, if that doesn't succeed, we compare covariantly with error
9236-
// reporting. Thus, error elaboration will be based on the the covariant check,
9237-
// which is generally easier to reason about.
9238-
related = isRelatedTo(t, s, /*reportErrors*/ false);
9239-
if (!related) {
9240-
related = isRelatedTo(s, t, reportErrors);
9241-
}
9242-
}
9243-
else {
9244-
// In the invariant case we first compare covariantly, and only when that
9245-
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
9246-
// will typically be based on the covariant check.
9247-
related = isRelatedTo(s, t, reportErrors);
9248-
if (related) {
9249-
related &= isRelatedTo(t, s, reportErrors);
9250-
}
9251-
}
9252-
if (!related) {
9253-
return Ternary.False;
9254-
}
9255-
result &= related;
9218+
const s = sources[i];
9219+
const t = targets[i];
9220+
const related = variance === Variance.Covariant ? isRelatedTo(s, t, reportErrors) :
9221+
variance === Variance.Contravariant ? isRelatedTo(t, s, reportErrors) :
9222+
Ternary.False;
9223+
if (!related) {
9224+
return Ternary.False;
92569225
}
9226+
result &= related;
92579227
}
92589228
return result;
92599229
}
@@ -9418,21 +9388,14 @@ namespace ts {
94189388
!(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) {
94199389
// We have type references to the same generic type, and the type references are not marker
94209390
// type references (which are intended by be compared structurally). Obtain the variance
9421-
// information for the type parameters and relate the type arguments accordingly.
9391+
// information for the type parameters and relate the type arguments accordingly. If we do
9392+
// not succeed, fall through and do a structural comparison instead (there are instances
9393+
// where the variance information isn't accurate, e.g. when type parameters are used only
9394+
// in bivariant positions or when a type argument is 'any' or 'void'.)
94229395
const variances = getVariances((<TypeReference>source).target);
94239396
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
94249397
return result;
94259398
}
9426-
// The type arguments did not relate appropriately, but it may be because getVariances was
9427-
// invoked recursively and returned emptyArray (in which case typeArgumentsRelatedTo defaulted
9428-
// to covariance for all type arguments). It might also be the case that the target type has a
9429-
// 'void' type argument for a covariant type parameter that is only used in return positions
9430-
// within the generic type (in which case any type argument is permitted on the source side).
9431-
// In those cases we proceed with a structural comparison. Otherwise, we know for certain the
9432-
// instantiations aren't related and we can return here.
9433-
if (variances !== emptyArray && !hasCovariantVoidArgument(<TypeReference>target, variances)) {
9434-
return Ternary.False;
9435-
}
94369399
}
94379400
// Even if relationship doesn't hold for unions, intersections, or generic type references,
94389401
// it may hold in a structural comparison.
@@ -9851,13 +9814,18 @@ namespace ts {
98519814
return result;
98529815
}
98539816

9854-
// Return an array containing the variance of each type parameter. The variance is effectively
9855-
// a digest of the type comparisons that occur for each type argument when instantiations of the
9856-
// generic type are structurally compared. We infer the variance information by comparing
9857-
// instantiations of the generic type for type arguments with known relations. Note that the
9858-
// function returns the emptyArray singleton to signal that it has been invoked recursively for
9859-
// the given generic type.
9817+
// Return an array containing the variance of each type parameter. The variance information is
9818+
// computed by comparing instantiations of the generic type for type arguments with known relations.
9819+
// A type parameter is marked as covariant if a covariant comparison succeeds; otherwise, it is
9820+
// marked contravariant if a contravarint comparison succeeds; otherwise, it is marked invariant.
9821+
// One form of variance doesn't exclude another, so this information simply serves to indicate
9822+
// a "primary" relationship that can be checked as an optimization ahead of a full structural
9823+
// comparison. The function returns the emptyArray singleton if we're not in strictFunctionTypes
9824+
// mode or if the function has been invoked recursively for the given generic type.
98609825
function getVariances(type: GenericType): Variance[] {
9826+
if (!strictFunctionTypes) {
9827+
return emptyArray;
9828+
}
98619829
const typeParameters = type.typeParameters || emptyArray;
98629830
let variances = type.variances;
98639831
if (!variances) {
@@ -9870,20 +9838,14 @@ namespace ts {
98709838
type.variances = emptyArray;
98719839
variances = [];
98729840
for (const tp of typeParameters) {
9873-
// We first compare instantiations where the type parameter is replaced with
9874-
// marker types that have a known subtype relationship. From this we can infer
9875-
// invariance, covariance, contravariance or bivariance.
9841+
// We compare instantiations where the type parameter is replaced with marker types
9842+
// that have a known subtype relationship. From this we infer covariance, contravariance
9843+
// or invariance.
98769844
const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType);
98779845
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
9878-
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
9879-
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
9880-
// If the instantiations appear to be related bivariantly, it may be because the
9881-
// type parameter is omnivariant (i.e. it isn't witnessed anywhere in the generic
9882-
// type). To determine this we compare instantiations where the type parameter is
9883-
// replaced with marker types that are known to be unrelated.
9884-
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
9885-
variance = Variance.Omnivariant;
9886-
}
9846+
const variance = isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant :
9847+
isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant :
9848+
Variance.Invariant;
98879849
variances.push(variance);
98889850
}
98899851
}
@@ -9892,17 +9854,6 @@ namespace ts {
98929854
return variances;
98939855
}
98949856

9895-
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
9896-
// See comment at call in recursiveTypeRelatedTo for when this case matters.
9897-
function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean {
9898-
for (let i = 0; i < variances.length; i++) {
9899-
if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) {
9900-
return true;
9901-
}
9902-
}
9903-
return false;
9904-
}
9905-
99069857
function isUnconstrainedTypeParameter(type: Type) {
99079858
return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(<TypeParameter>type);
99089859
}
@@ -10709,9 +10660,9 @@ namespace ts {
1070910660
const sourceTypes = (<TypeReference>source).typeArguments || emptyArray;
1071010661
const targetTypes = (<TypeReference>target).typeArguments || emptyArray;
1071110662
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
10712-
const variances = strictFunctionTypes ? getVariances((<TypeReference>source).target) : undefined;
10663+
const variances = getVariances((<TypeReference>source).target);
1071310664
for (let i = 0; i < count; i++) {
10714-
if (variances && i < variances.length && variances[i] === Variance.Contravariant) {
10665+
if (i < variances.length && variances[i] === Variance.Contravariant) {
1071510666
inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
1071610667
}
1071710668
else {

src/compiler/types.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,11 +3345,9 @@ namespace ts {
33453345
}
33463346

33473347
export const enum Variance {
3348-
Invariant = 0, // Both covariant and contravariant
3348+
Invariant = 0, // Neither covariant nor contravariant
33493349
Covariant = 1, // Covariant
33503350
Contravariant = 2, // Contravariant
3351-
Bivariant = 3, // Either covariant or contravariant
3352-
Omnivariant = 4 // Unwitnessed type parameter
33533351
}
33543352

33553353
// Generic class and interface types

0 commit comments

Comments
 (0)