From 6e72aa76c9b4e34869300bf1c94cac761cd98ad2 Mon Sep 17 00:00:00 2001 From: shahinsamiur Date: Wed, 21 Oct 2026 11:08:34 +0600 Subject: [PATCH] fix(checker): resolve TS2322 regression in circular generic interface variance calculation --- test/repro.ts | 14 ++++ tsc/internal/checker/relater.go | 140 +++++++++++++------------------- 2 files changed, 71 insertions(+), 83 deletions(-) create mode 100644 test/repro.ts diff --git a/test/repro.ts b/test/repro.ts new file mode 100644 index 0000000000000..1bb3542c10125 --- /dev/null +++ b/test/repro.ts @@ -0,0 +1,14 @@ +type M = { kind: "m" }; +type Filtered = { [K in keyof T as T[K] extends M ? K : never]: T[K] }; + +interface Config { measure: keyof T } + +interface Ref { dataset: DS, N> } + +interface DS { + a(config: Config>): void; + b: Ref; +} + +declare const x: DS<{ total: M }, string>; +export const y: DS, string> = x; \ No newline at end of file diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index d3b6c86014498..137e1b255b00e 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -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} @@ -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{} + } + } } return links.variances }