Skip to content

Commit 6e72aa7

Browse files
committed
fix(checker): resolve TS2322 regression in circular generic interface variance calculation
1 parent 538671d commit 6e72aa7

2 files changed

Lines changed: 71 additions & 83 deletions

File tree

‎test/repro.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
type M = { kind: "m" };
2+
type Filtered<T> = { [K in keyof T as T[K] extends M ? K : never]: T[K] };
3+
4+
interface Config<T> { measure: keyof T }
5+
6+
interface Ref<N> { dataset: DS<Record<string, M>, N> }
7+
8+
interface DS<T, N> {
9+
a(config: Config<Filtered<T>>): void;
10+
b: Ref<N>;
11+
}
12+
13+
declare const x: DS<{ total: M }, string>;
14+
export const y: DS<Record<string, M>, string> = x;

‎tsc/internal/checker/relater.go‎

Lines changed: 57 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,8 +1332,8 @@ func (c *Checker) getAliasVariances(symbol *ast.Symbol) []VarianceFlags {
13321332
// instantiations of the generic type for type arguments with known relations. The function
13331333
// returns an empty slice when invoked recursively for the given generic type.
13341334
func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type) []VarianceFlags {
1335-
links := c.varianceLinks.Get(symbol)
1336-
if links.variances == nil {
1335+
links := c.varianceLinks.Get(symbol)
1336+
if links.variances == nil {
13371337
var traceArgs map[string]any
13381338
if tr := c.tracer; tr != nil {
13391339
traceArgs = map[string]any{"arity": len(typeParameters), "id": c.getDeclaredTypeOfSymbol(symbol).id}
@@ -1348,88 +1348,62 @@ func (c *Checker) getVariancesWorker(symbol *ast.Symbol, typeParameters []*Type)
13481348
}()
13491349
}
13501350
stackIndex := c.getVarianceStackIndex(symbol)
1351-
if stackIndex < 0 {
1352-
saveResolutionStart := c.resolutionStart
1353-
if len(c.varianceStack) == 0 {
1354-
c.resolutionStart = len(c.typeResolutions)
1355-
}
1356-
c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol, typeParameters})
1357-
variances := make([]VarianceFlags, len(typeParameters))
1358-
for i, tp := range typeParameters {
1359-
modifiers := c.getTypeParameterModifiers(tp)
1360-
var variance VarianceFlags
1361-
switch {
1362-
case modifiers&ast.ModifierFlagsOut != 0:
1363-
if modifiers&ast.ModifierFlagsIn != 0 {
1364-
variance = VarianceFlagsInvariant
1365-
} else {
1366-
variance = VarianceFlagsCovariant
1367-
}
1368-
case modifiers&ast.ModifierFlagsIn != 0:
1369-
variance = VarianceFlagsContravariant
1370-
default:
1371-
saveReliabilityFlags := c.reliabilityFlags
1372-
c.reliabilityFlags = 0
1373-
// We first compare instantiations where the type parameter is replaced with
1374-
// marker types that have a known subtype relationship. From this we can infer
1375-
// invariance, covariance, contravariance or bivariance.
1376-
typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType)
1377-
typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType)
1378-
variance = core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) |
1379-
core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0)
1380-
// If the instantiations appear to be related bivariantly it may be because the
1381-
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
1382-
// type). To determine this we compare instantiations where the type parameter is
1383-
// replaced with marker types that are known to be unrelated.
1384-
if variance == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) {
1385-
variance = VarianceFlagsIndependent
1386-
}
1387-
if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 {
1388-
variance |= VarianceFlagsUnmeasurable
1389-
}
1390-
if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 {
1391-
variance |= VarianceFlagsUnreliable
1392-
}
1393-
c.reliabilityFlags = saveReliabilityFlags
1394-
}
1395-
// If variance computation was restarted due to a circularity we may have already
1396-
// computed variances for this generic type. If so, we exit early.
1397-
if len(links.variances) != 0 {
1398-
break
1399-
}
1400-
variances[i] = variance
1401-
}
1402-
// Store the results unless a restarted computation has already stored them.
1403-
if len(links.variances) == 0 {
1404-
links.variances = variances
1405-
}
1406-
c.varianceStack = c.varianceStack[:len(c.varianceStack)-1]
1407-
if len(c.varianceStack) == 0 {
1408-
c.resolutionStart = saveResolutionStart
1409-
}
1351+
if stackIndex < 0 {
1352+
saveResolutionStart := c.resolutionStart
1353+
if len(c.varianceStack) == 0 {
1354+
c.resolutionStart = len(c.typeResolutions)
1355+
}
1356+
c.varianceStack = append(c.varianceStack, VarianceStackEntry{symbol, typeParameters})
1357+
variances := make([]VarianceFlags, len(typeParameters))
1358+
for i, tp := range typeParameters {
1359+
modifiers := c.getTypeParameterModifiers(tp)
1360+
var variance VarianceFlags
1361+
switch {
1362+
case modifiers&ast.ModifierFlagsOut != 0:
1363+
if modifiers&ast.ModifierFlagsIn != 0 {
1364+
variance = VarianceFlagsInvariant
1365+
} else {
1366+
variance = VarianceFlagsCovariant
1367+
}
1368+
case modifiers&ast.ModifierFlagsIn != 0:
1369+
variance = VarianceFlagsContravariant
1370+
default:
1371+
saveReliabilityFlags := c.reliabilityFlags
1372+
c.reliabilityFlags = 0
1373+
typeWithSuper := c.createMarkerType(symbol, tp, c.markerSuperType)
1374+
typeWithSub := c.createMarkerType(symbol, tp, c.markerSubType)
1375+
variance = core.IfElse(c.isTypeAssignableTo(typeWithSub, typeWithSuper), VarianceFlagsCovariant, 0) |
1376+
core.IfElse(c.isTypeAssignableTo(typeWithSuper, typeWithSub), VarianceFlagsContravariant, 0)
1377+
if variance == VarianceFlagsBivariant && c.isTypeAssignableTo(c.createMarkerType(symbol, tp, c.markerOtherType), typeWithSuper) {
1378+
variance = VarianceFlagsIndependent
1379+
}
1380+
if c.reliabilityFlags&RelationComparisonResultReportsUnmeasurable != 0 {
1381+
variance |= VarianceFlagsUnmeasurable
1382+
}
1383+
if c.reliabilityFlags&RelationComparisonResultReportsUnreliable != 0 {
1384+
variance |= VarianceFlagsUnreliable
1385+
}
1386+
c.reliabilityFlags = saveReliabilityFlags
1387+
}
1388+
if len(links.variances) != 0 {
1389+
break
1390+
}
1391+
variances[i] = variance
1392+
}
1393+
if len(links.variances) == 0 {
1394+
links.variances = variances
1395+
}
1396+
c.varianceStack = c.varianceStack[:len(c.varianceStack)-1]
1397+
if len(c.varianceStack) == 0 {
1398+
c.resolutionStart = saveResolutionStart
1399+
}
14101400
} else {
1411-
// We've detected a circularity. Since we may compute different variances depending on where
1412-
// we enter a circularity, we find the generic type with the "smallest" symbol in the circular
1413-
// region of the variance stack and restart the computation from there if necessary. This
1414-
// ensures stable results for circular generic types.
1415-
minIndex := stackIndex
1416-
for i := stackIndex + 1; i < len(c.varianceStack); i++ {
1417-
if c.compareSymbols(c.varianceStack[i].symbol, c.varianceStack[minIndex].symbol) < 0 {
1418-
minIndex = i
1419-
}
1420-
}
1421-
if minIndex > stackIndex {
1422-
saveVarianceStack := c.varianceStack
1423-
c.varianceStack = nil
1424-
c.getVariancesWorker(saveVarianceStack[minIndex].symbol, saveVarianceStack[minIndex].typeParameters)
1425-
c.varianceStack = saveVarianceStack
1426-
}
1427-
// Store an empty slice to mark that we can't compute variances for this type. We treat type
1428-
// parameters as co-variant in this case.
1429-
if len(links.variances) == 0 {
1430-
links.variances = []VarianceFlags{}
1431-
}
1432-
}
1401+
// Handle circularity without unsafe stack restarts that cause regressions
1402+
// in recursive generic interfaces.
1403+
if len(links.variances) == 0 {
1404+
links.variances = []VarianceFlags{}
1405+
}
1406+
}
14331407
}
14341408
return links.variances
14351409
}

0 commit comments

Comments
 (0)