Skip to content

Use a single relation stack for nested dependent variance calculations #39232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16864,7 +16864,7 @@ namespace ts {
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
const variances = getAliasVariances(source.aliasSymbol);
const variances = getAliasVariances(source.aliasSymbol, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
if (variances === emptyArray) {
return Ternary.Maybe;
}
Expand Down Expand Up @@ -17095,7 +17095,7 @@ namespace ts {
// We have type references to the same generic type, and the type references are not marker
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
const variances = getVariances((<TypeReference>source).target, relation === assignableRelation ? isRelatedTo : compareTypesAssignable);
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
// effectively means we measure variance only from type parameter occurrences that aren't nested in
// recursive instantiations of the generic type.
Expand Down Expand Up @@ -17988,25 +17988,26 @@ namespace ts {
return result;
}

function getAliasVariances(symbol: Symbol) {
function getAliasVariances(symbol: Symbol, compareTypes: (source: Type, target: Type) => Ternary) {
const links = getSymbolLinks(symbol);
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
type.aliasTypeArgumentsContainsMarker = true;
return type;
});
}, compareTypes);
}

// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton when invoked recursively for the given generic type.
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
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[] {
let variances = cache.variances;
if (!variances) {
// The emptyArray singleton is used to signal a recursive invocation.
cache.variances = emptyArray;
let encounteredMaybeResult = false;
variances = [];
for (const tp of typeParameters) {
let unmeasurable = false;
Expand All @@ -18018,14 +18019,28 @@ namespace ts {
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
const typeWithSub = createMarkerType(cache, tp, markerSubType);
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0);

const subResult = compareTypes(typeWithSub, typeWithSuper);
const superResult = compareTypes(typeWithSuper, typeWithSub);
let variance = (subResult ? VarianceFlags.Covariant : 0) |
(superResult ? VarianceFlags.Contravariant : 0);
if (subResult === Ternary.Maybe || superResult === Ternary.Maybe) {
variance |= VarianceFlags.Unmeasurable;
encounteredMaybeResult = true;
}
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
variance = VarianceFlags.Independent;
if (variance === VarianceFlags.Bivariant) {
const otherResult = compareTypes(createMarkerType(cache, tp, markerOtherType), typeWithSuper);
if (otherResult === Ternary.Maybe) {
variance |= VarianceFlags.Unmeasurable;
encounteredMaybeResult = true;
}
if (otherResult) {
variance = VarianceFlags.Independent;
}
}
outofbandVarianceMarkerHandler = oldHandler;
if (unmeasurable || unreliable) {
Expand All @@ -18038,17 +18053,22 @@ namespace ts {
}
variances.push(variance);
}
cache.variances = variances;
if (!encounteredMaybeResult) {
cache.variances = variances;
}
else {
cache.variances = undefined;
}
}
return variances;
}

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

// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
Expand Down Expand Up @@ -19313,7 +19333,7 @@ namespace ts {
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
// Source and target are types originating in the same generic type alias declaration.
// Simply infer from source type arguments to target type arguments.
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol, compareTypesAssignable));
return;
}
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
Expand Down Expand Up @@ -19435,7 +19455,7 @@ namespace ts {
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
!((<TypeReference>source).node && (<TypeReference>target).node)) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
contravariant = !contravariant;
Expand Down Expand Up @@ -19758,7 +19778,7 @@ namespace ts {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target, compareTypesAssignable));
return;
}
if (isGenericMappedType(source) && isGenericMappedType(target)) {
Expand Down
Loading