Skip to content
Open
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
14 changes: 14 additions & 0 deletions test/repro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type M = { kind: "m" };
type Filtered<T> = { [K in keyof T as T[K] extends M ? K : never]: T[K] };

interface Config<T> { measure: keyof T }

interface Ref<N> { dataset: DS<Record<string, M>, N> }

interface DS<T, N> {
a(config: Config<Filtered<T>>): void;
b: Ref<N>;
}

declare const x: DS<{ total: M }, string>;
export const y: DS<Record<string, M>, string> = x;
140 changes: 57 additions & 83 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -1332,8 +1332,8 @@ func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags {
// instantiations of the generic type for type arguments with known relations. The function
// returns an empty slice when invoked recursively for the given generic type.
func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type) []VarianceFlags {
links := c.varianceLinks.Get(symbol)
if links.variances == nil {
links := c.varianceLinks.Get(symbol)
if links.variances == nil {
var traceArgs map[string]any
if tr := c.tracer; tr != nil {
traceArgs = map[string]any{"arity": len(typeParameters), "id": c.getDeclaredTypeOfSymbol(symbol).id}
Expand All @@ -1348,88 +1348,62 @@ func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type)
}()
}
stackIndex := c.getVarianceStackIndex(symbol)
if stackIndex < 0 {
saveResolutionStart := c.resolutionStart
if len(c.varianceStack) == 0 {
c.resolutionStart = len(c.typeResolutions)
}
c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol, typeParameters})
variances := make([]VarianceFlags, len(typeParameters))
for i, tp := range typeParameters {
modifiers := c.getTypeParameterModifiers(tp)
var variance VarianceFlags
switch {
case modifiers&ast.ModifierFlagsOut != 0:
if modifiers&ast.ModifierFlagsIn != 0 {
variance = VarianceFlagsInvariant
} else {
variance = VarianceFlagsCovariant
}
case modifiers&ast.ModifierFlagsIn != 0:
variance = VarianceFlagsContravariant
default:
saveReliabilityFlags := c.reliabilityFlags
c.reliabilityFlags = 0
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType)
typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType)
variance = core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) |
core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0)
// 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 == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) {
variance = VarianceFlagsIndependent
}
if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 {
variance |= VarianceFlagsUnmeasurable
}
if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 {
variance |= VarianceFlagsUnreliable
}
c.reliabilityFlags = saveReliabilityFlags
}
// If variance computation was restarted due to a circularity we may have already
// computed variances for this generic type. If so, we exit early.
if len(links.variances) != 0 {
break
}
variances[i] = variance
}
// Store the results unless a restarted computation has already stored them.
if len(links.variances) == 0 {
links.variances = variances
}
c.varianceStack = c.varianceStack[:len(c.varianceStack)-1]
if len(c.varianceStack) == 0 {
c.resolutionStart = saveResolutionStart
}
if stackIndex < 0 {
saveResolutionStart := c.resolutionStart
if len(c.varianceStack) == 0 {
c.resolutionStart = len(c.typeResolutions)
}
c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol, typeParameters})
variances := make([]VarianceFlags, len(typeParameters))
for i, tp := range typeParameters {
modifiers := c.getTypeParameterModifiers(tp)
var variance VarianceFlags
switch {
case modifiers&ast.ModifierFlagsOut != 0:
if modifiers&ast.ModifierFlagsIn != 0 {
variance = VarianceFlagsInvariant
} else {
variance = VarianceFlagsCovariant
}
case modifiers&ast.ModifierFlagsIn != 0:
variance = VarianceFlagsContravariant
default:
saveReliabilityFlags := c.reliabilityFlags
c.reliabilityFlags = 0
typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType)
typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType)
variance = core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) |
core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0)
if variance == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) {
variance = VarianceFlagsIndependent
}
if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 {
variance |= VarianceFlagsUnmeasurable
}
if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 {
variance |= VarianceFlagsUnreliable
}
c.reliabilityFlags = saveReliabilityFlags
}
if len(links.variances) != 0 {
break
}
variances[i] = variance
}
if len(links.variances) == 0 {
links.variances = variances
}
c.varianceStack = c.varianceStack[:len(c.varianceStack)-1]
if len(c.varianceStack) == 0 {
c.resolutionStart = saveResolutionStart
}
} else {
// We've detected a circularity. Since we may compute different variances depending on where
// we enter a circularity, we find the generic type with the "smallest" symbol in the circular
// region of the variance stack and restart the computation from there if necessary. This
// ensures stable results for circular generic types.
minIndex := stackIndex
for i := stackIndex + 1; i < len(c.varianceStack); i++ {
if c.compareSymbols(c.varianceStack[i].symbol, c.varianceStack[minIndex].symbol) < 0 {
minIndex = i
}
}
if minIndex > stackIndex {
saveVarianceStack := c.varianceStack
c.varianceStack = nil
c.getVariancesWorker(saveVarianceStack[minIndex].symbol, saveVarianceStack[minIndex].typeParameters)
c.varianceStack = saveVarianceStack
}
// Store an empty slice to mark that we can't compute variances for this type. We treat type
// parameters as co-variant in this case.
if len(links.variances) == 0 {
links.variances = []VarianceFlags{}
}
}
// Handle circularity without unsafe stack restarts that cause regressions
// in recursive generic interfaces.
if len(links.variances) == 0 {
links.variances = []VarianceFlags{}
Comment on lines +1401 to +1404
}
}
}
return links.variances
}
Expand Down