Skip to content

Commit 98a661b

Browse files
committed
Use a single relation stack for nested dependent variance calculations
1 parent 312a6f0 commit 98a661b

File tree

5 files changed

+2291
-13
lines changed

5 files changed

+2291
-13
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16864,7 +16864,7 @@ namespace ts {
1686416864
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
1686516865
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
1686616866
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
16867-
const variances = getAliasVariances(source.aliasSymbol);
16867+
const variances = getAliasVariances(source.aliasSymbol, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
1686816868
if (variances === emptyArray) {
1686916869
return Ternary.Maybe;
1687016870
}
@@ -17095,7 +17095,7 @@ namespace ts {
1709517095
// We have type references to the same generic type, and the type references are not marker
1709617096
// type references (which are intended by be compared structurally). Obtain the variance
1709717097
// information for the type parameters and relate the type arguments accordingly.
17098-
const variances = getVariances((<TypeReference>source).target);
17098+
const variances = getVariances((<TypeReference>source).target, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
1709917099
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
1710017100
// effectively means we measure variance only from type parameter occurrences that aren't nested in
1710117101
// recursive instantiations of the generic type.
@@ -17988,21 +17988,21 @@ namespace ts {
1798817988
return result;
1798917989
}
1799017990

17991-
function getAliasVariances(symbol: Symbol) {
17991+
function getAliasVariances(symbol: Symbol, compareTypes: (source: Type, target: Type) => Ternary) {
1799217992
const links = getSymbolLinks(symbol);
1799317993
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
1799417994
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
1799517995
type.aliasTypeArgumentsContainsMarker = true;
1799617996
return type;
17997-
});
17997+
}, compareTypes);
1799817998
}
1799917999

1800018000
// Return an array containing the variance of each type parameter. The variance is effectively
1800118001
// a digest of the type comparisons that occur for each type argument when instantiations of the
1800218002
// generic type are structurally compared. We infer the variance information by comparing
1800318003
// instantiations of the generic type for type arguments with known relations. The function
1800418004
// returns the emptyArray singleton when invoked recursively for the given generic type.
18005-
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
18005+
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type, compareTypes: (source: Type, target: Type) => Ternary): VarianceFlags[] {
1800618006
let variances = cache.variances;
1800718007
if (!variances) {
1800818008
// The emptyArray singleton is used to signal a recursive invocation.
@@ -18018,13 +18018,20 @@ namespace ts {
1801818018
// invariance, covariance, contravariance or bivariance.
1801918019
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
1802018020
const typeWithSub = createMarkerType(cache, tp, markerSubType);
18021-
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
18022-
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
18021+
// Note: We consider a `Maybe` result to be affirmative below; if we're already trying to figure out if A<+?> is assignable to A<-?>
18022+
// and we end up needing to check some type we're already comparing in the body of the calling comparison, we can assume it to be true
18023+
// for the scope of the remainder of the comparison. (Simply because disproving it is the point of the _other_ comparisons we're performing)
18024+
// The issue comes with invalidating the cached variance result if the root comparison turns out to be negative. _Right now_, we're simply...
18025+
// not. This will _probably_ cause some subtle bugs, however coming up with an example exposing one such bug is nontrivial.
18026+
// But! In the event such a motiviating example should come to light, the fix shouldn't be _too_ bad - here in `getVariancesWorker` we'd
18027+
// just need a `Maybe` tracking stack just like we have in `recursiveTypeRelatedTo`.
18028+
let variance = (compareTypes(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
18029+
(compareTypes(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
1802318030
// If the instantiations appear to be related bivariantly it may be because the
1802418031
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
1802518032
// type). To determine this we compare instantiations where the type parameter is
1802618033
// replaced with marker types that are known to be unrelated.
18027-
if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
18034+
if (variance === VarianceFlags.Bivariant && compareTypes(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
1802818035
variance = VarianceFlags.Independent;
1802918036
}
1803018037
outofbandVarianceMarkerHandler = oldHandler;
@@ -18043,12 +18050,12 @@ namespace ts {
1804318050
return variances;
1804418051
}
1804518052

18046-
function getVariances(type: GenericType): VarianceFlags[] {
18053+
function getVariances(type: GenericType, compareTypes: (source: Type, target: Type) => Ternary): VarianceFlags[] {
1804718054
// Arrays and tuples are known to be covariant, no need to spend time computing this.
1804818055
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
1804918056
return arrayVariances;
1805018057
}
18051-
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
18058+
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference, compareTypes);
1805218059
}
1805318060

1805418061
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
@@ -19313,7 +19320,7 @@ namespace ts {
1931319320
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
1931419321
// Source and target are types originating in the same generic type alias declaration.
1931519322
// Simply infer from source type arguments to target type arguments.
19316-
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
19323+
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol, compareTypesAssignable));
1931719324
return;
1931819325
}
1931919326
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
@@ -19435,7 +19442,7 @@ namespace ts {
1943519442
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
1943619443
!((<TypeReference>source).node && (<TypeReference>target).node)) {
1943719444
// If source and target are references to the same generic type, infer from type arguments
19438-
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
19445+
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
1943919446
}
1944019447
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
1944119448
contravariant = !contravariant;
@@ -19758,7 +19765,7 @@ namespace ts {
1975819765
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
1975919766
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
1976019767
// If source and target are references to the same generic type, infer from type arguments
19761-
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
19768+
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
1976219769
return;
1976319770
}
1976419771
if (isGenericMappedType(source) && isGenericMappedType(target)) {

0 commit comments

Comments
 (0)