Skip to content

Commit c9e57e1

Browse files
committed
Use a single relation stack for nested dependent variance calculations
1 parent 7893c9f commit c9e57e1

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
@@ -16821,7 +16821,7 @@ namespace ts {
1682116821
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
1682216822
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
1682316823
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
16824-
const variances = getAliasVariances(source.aliasSymbol);
16824+
const variances = getAliasVariances(source.aliasSymbol, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
1682516825
if (variances === emptyArray) {
1682616826
return Ternary.Maybe;
1682716827
}
@@ -17031,7 +17031,7 @@ namespace ts {
1703117031
// We have type references to the same generic type, and the type references are not marker
1703217032
// type references (which are intended by be compared structurally). Obtain the variance
1703317033
// information for the type parameters and relate the type arguments accordingly.
17034-
const variances = getVariances((<TypeReference>source).target);
17034+
const variances = getVariances((<TypeReference>source).target, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
1703517035
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
1703617036
// effectively means we measure variance only from type parameter occurrences that aren't nested in
1703717037
// recursive instantiations of the generic type.
@@ -17903,21 +17903,21 @@ namespace ts {
1790317903
return result;
1790417904
}
1790517905

17906-
function getAliasVariances(symbol: Symbol) {
17906+
function getAliasVariances(symbol: Symbol, compareTypes: (source: Type, target: Type) => Ternary) {
1790717907
const links = getSymbolLinks(symbol);
1790817908
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
1790917909
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
1791017910
type.aliasTypeArgumentsContainsMarker = true;
1791117911
return type;
17912-
});
17912+
}, compareTypes);
1791317913
}
1791417914

1791517915
// Return an array containing the variance of each type parameter. The variance is effectively
1791617916
// a digest of the type comparisons that occur for each type argument when instantiations of the
1791717917
// generic type are structurally compared. We infer the variance information by comparing
1791817918
// instantiations of the generic type for type arguments with known relations. The function
1791917919
// returns the emptyArray singleton when invoked recursively for the given generic type.
17920-
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
17920+
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[] {
1792117921
let variances = cache.variances;
1792217922
if (!variances) {
1792317923
// The emptyArray singleton is used to signal a recursive invocation.
@@ -17933,13 +17933,20 @@ namespace ts {
1793317933
// invariance, covariance, contravariance or bivariance.
1793417934
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
1793517935
const typeWithSub = createMarkerType(cache, tp, markerSubType);
17936-
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
17937-
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
17936+
// Note: We consider a `Maybe` result to be affirmative below; if we're already trying to figure out if A<+?> is assignable to A<-?>
17937+
// 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
17938+
// for the scope of the remainder of the comparison. (Simply because disproving it is the point of the _other_ comparisons we're performing)
17939+
// The issue comes with invalidating the cached variance result if the root comparison turns out to be negative. _Right now_, we're simply...
17940+
// not. This will _probably_ cause some subtle bugs, however coming up with an example exposing one such bug is nontrivial.
17941+
// But! In the event such a motiviating example should come to light, the fix shouldn't be _too_ bad - here in `getVariancesWorker` we'd
17942+
// just need a `Maybe` tracking stack just like we have in `recursiveTypeRelatedTo`.
17943+
let variance = (compareTypes(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
17944+
(compareTypes(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);
1793817945
// If the instantiations appear to be related bivariantly it may be because the
1793917946
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
1794017947
// type). To determine this we compare instantiations where the type parameter is
1794117948
// replaced with marker types that are known to be unrelated.
17942-
if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
17949+
if (variance === VarianceFlags.Bivariant && compareTypes(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
1794317950
variance = VarianceFlags.Independent;
1794417951
}
1794517952
outofbandVarianceMarkerHandler = oldHandler;
@@ -17958,12 +17965,12 @@ namespace ts {
1795817965
return variances;
1795917966
}
1796017967

17961-
function getVariances(type: GenericType): VarianceFlags[] {
17968+
function getVariances(type: GenericType, compareTypes: (source: Type, target: Type) => Ternary): VarianceFlags[] {
1796217969
// Arrays and tuples are known to be covariant, no need to spend time computing this.
1796317970
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
1796417971
return arrayVariances;
1796517972
}
17966-
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
17973+
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference, compareTypes);
1796717974
}
1796817975

1796917976
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
@@ -19209,7 +19216,7 @@ namespace ts {
1920919216
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
1921019217
// Source and target are types originating in the same generic type alias declaration.
1921119218
// Simply infer from source type arguments to target type arguments.
19212-
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
19219+
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol, compareTypesAssignable));
1921319220
return;
1921419221
}
1921519222
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
@@ -19331,7 +19338,7 @@ namespace ts {
1933119338
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
1933219339
!((<TypeReference>source).node && (<TypeReference>target).node)) {
1933319340
// If source and target are references to the same generic type, infer from type arguments
19334-
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
19341+
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
1933519342
}
1933619343
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
1933719344
contravariant = !contravariant;
@@ -19642,7 +19649,7 @@ namespace ts {
1964219649
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
1964319650
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
1964419651
// If source and target are references to the same generic type, infer from type arguments
19645-
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
19652+
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
1964619653
return;
1964719654
}
1964819655
if (isGenericMappedType(source) && isGenericMappedType(target)) {

0 commit comments

Comments
 (0)