Skip to content

Commit f9dcd9e

Browse files
authored
Don't cache Ternary.Maybe results when recursion is encountered during variance measurement (#41218)
* Don't record Ternary.Maybe results in cache during recursive variance measurement * Add regression test * Accept new baselines * Use Ternary.Unknown to signal variance recursion * Add comments * Fix comment
1 parent 3754bb4 commit f9dcd9e

File tree

7 files changed

+123
-13
lines changed

7 files changed

+123
-13
lines changed

src/compiler/checker.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17287,9 +17287,12 @@ namespace ts {
1728717287
depth--;
1728817288
if (result) {
1728917289
if (result === Ternary.True || depth === 0) {
17290-
// If result is definitely true, record all maybe keys as having succeeded
17291-
for (let i = maybeStart; i < maybeCount; i++) {
17292-
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
17290+
if (result === Ternary.True || result === Ternary.Maybe) {
17291+
// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
17292+
// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
17293+
for (let i = maybeStart; i < maybeCount; i++) {
17294+
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
17295+
}
1729317296
}
1729417297
maybeCount = maybeStart;
1729517298
}
@@ -17359,7 +17362,7 @@ namespace ts {
1735917362
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
1736017363
const variances = getAliasVariances(source.aliasSymbol);
1736117364
if (variances === emptyArray) {
17362-
return Ternary.Maybe;
17365+
return Ternary.Unknown;
1736317366
}
1736417367
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
1736517368
if (varianceResult !== undefined) {
@@ -17632,7 +17635,7 @@ namespace ts {
1763217635
// effectively means we measure variance only from type parameter occurrences that aren't nested in
1763317636
// recursive instantiations of the generic type.
1763417637
if (variances === emptyArray) {
17635-
return Ternary.Maybe;
17638+
return Ternary.Unknown;
1763617639
}
1763717640
const varianceResult = relateVariances(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), variances, intersectionState);
1763817641
if (varianceResult !== undefined) {

src/compiler/types.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5531,17 +5531,17 @@ namespace ts {
55315531

55325532
/**
55335533
* Ternary values are defined such that
5534-
* x & y is False if either x or y is False.
5535-
* x & y is Maybe if either x or y is Maybe, but neither x or y is False.
5536-
* x & y is True if both x and y are True.
5537-
* x | y is False if both x and y are False.
5538-
* x | y is Maybe if either x or y is Maybe, but neither x or y is True.
5539-
* x | y is True if either x or y is True.
5534+
* x & y picks the lesser in the order False < Unknown < Maybe < True, and
5535+
* x | y picks the greater in the order False < Unknown < Maybe < True.
5536+
* Generally, Ternary.Maybe is used as the result of a relation that depends on itself, and
5537+
* Ternary.Unknown is used as the result of a variance check that depends on itself. We make
5538+
* a distinction because we don't want to cache circular variance check results.
55405539
*/
55415540
/* @internal */
55425541
export const enum Ternary {
55435542
False = 0,
5544-
Maybe = 1,
5543+
Unknown = 1,
5544+
Maybe = 3,
55455545
True = -1
55465546
}
55475547

tests/baselines/reference/varianceMeasurement.errors.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ tests/cases/compiler/varianceMeasurement.ts(57,7): error TS2322: Type 'Fn<string
2222
Type 'unknown' is not assignable to type 'string'.
2323
tests/cases/compiler/varianceMeasurement.ts(62,7): error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
2424
Type 'number' is not assignable to type '0'.
25+
tests/cases/compiler/varianceMeasurement.ts(75,7): error TS2322: Type 'C<unknown, number>' is not assignable to type 'C<unknown, string>'.
26+
Type 'number' is not assignable to type 'string'.
2527

2628

27-
==== tests/cases/compiler/varianceMeasurement.ts (8 errors) ====
29+
==== tests/cases/compiler/varianceMeasurement.ts (9 errors) ====
2830
// The type below should be invariant in T but is measured as covariant because
2931
// we don't analyze recursive references.
3032

@@ -119,4 +121,20 @@ tests/cases/compiler/varianceMeasurement.ts(62,7): error TS2322: Type 'Fn<string
119121
~~~
120122
!!! error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
121123
!!! error TS2322: Type 'number' is not assignable to type '0'.
124+
125+
// Repro from #39947
126+
127+
interface I<Dummy, V> {
128+
c: C<Dummy, V>;
129+
}
130+
131+
class C<Dummy, V> {
132+
declare sub: I<Dummy, V>;
133+
declare covariance: V;
134+
}
135+
136+
const c1: C<unknown, string> = new C<unknown, number>(); // Error
137+
~~
138+
!!! error TS2322: Type 'C<unknown, number>' is not assignable to type 'C<unknown, string>'.
139+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
122140

tests/baselines/reference/varianceMeasurement.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ const fn2: Fn<'a', number> = fn;
6161
// Covariant in B
6262
const fn3: Fn<string, unknown> = fn;
6363
const fn4: Fn<string, 0> = fn; // Error
64+
65+
// Repro from #39947
66+
67+
interface I<Dummy, V> {
68+
c: C<Dummy, V>;
69+
}
70+
71+
class C<Dummy, V> {
72+
declare sub: I<Dummy, V>;
73+
declare covariance: V;
74+
}
75+
76+
const c1: C<unknown, string> = new C<unknown, number>(); // Error
6477

6578

6679
//// [varianceMeasurement.js]
@@ -81,3 +94,9 @@ var fn2 = fn;
8194
// Covariant in B
8295
var fn3 = fn;
8396
var fn4 = fn; // Error
97+
var C = /** @class */ (function () {
98+
function C() {
99+
}
100+
return C;
101+
}());
102+
var c1 = new C(); // Error

tests/baselines/reference/varianceMeasurement.symbols

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,38 @@ const fn4: Fn<string, 0> = fn; // Error
183183
>Fn : Symbol(Fn, Decl(varianceMeasurement.ts, 44, 31))
184184
>fn : Symbol(fn, Decl(varianceMeasurement.ts, 53, 13))
185185

186+
// Repro from #39947
187+
188+
interface I<Dummy, V> {
189+
>I : Symbol(I, Decl(varianceMeasurement.ts, 61, 30))
190+
>Dummy : Symbol(Dummy, Decl(varianceMeasurement.ts, 65, 12))
191+
>V : Symbol(V, Decl(varianceMeasurement.ts, 65, 18))
192+
193+
c: C<Dummy, V>;
194+
>c : Symbol(I.c, Decl(varianceMeasurement.ts, 65, 23))
195+
>C : Symbol(C, Decl(varianceMeasurement.ts, 67, 1))
196+
>Dummy : Symbol(Dummy, Decl(varianceMeasurement.ts, 65, 12))
197+
>V : Symbol(V, Decl(varianceMeasurement.ts, 65, 18))
198+
}
199+
200+
class C<Dummy, V> {
201+
>C : Symbol(C, Decl(varianceMeasurement.ts, 67, 1))
202+
>Dummy : Symbol(Dummy, Decl(varianceMeasurement.ts, 69, 8))
203+
>V : Symbol(V, Decl(varianceMeasurement.ts, 69, 14))
204+
205+
declare sub: I<Dummy, V>;
206+
>sub : Symbol(C.sub, Decl(varianceMeasurement.ts, 69, 19))
207+
>I : Symbol(I, Decl(varianceMeasurement.ts, 61, 30))
208+
>Dummy : Symbol(Dummy, Decl(varianceMeasurement.ts, 69, 8))
209+
>V : Symbol(V, Decl(varianceMeasurement.ts, 69, 14))
210+
211+
declare covariance: V;
212+
>covariance : Symbol(C.covariance, Decl(varianceMeasurement.ts, 70, 27))
213+
>V : Symbol(V, Decl(varianceMeasurement.ts, 69, 14))
214+
}
215+
216+
const c1: C<unknown, string> = new C<unknown, number>(); // Error
217+
>c1 : Symbol(c1, Decl(varianceMeasurement.ts, 74, 5))
218+
>C : Symbol(C, Decl(varianceMeasurement.ts, 67, 1))
219+
>C : Symbol(C, Decl(varianceMeasurement.ts, 67, 1))
220+

tests/baselines/reference/varianceMeasurement.types

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,25 @@ const fn4: Fn<string, 0> = fn; // Error
131131
>fn4 : Fn<string, 0>
132132
>fn : Fn<string, number>
133133

134+
// Repro from #39947
135+
136+
interface I<Dummy, V> {
137+
c: C<Dummy, V>;
138+
>c : C<Dummy, V>
139+
}
140+
141+
class C<Dummy, V> {
142+
>C : C<Dummy, V>
143+
144+
declare sub: I<Dummy, V>;
145+
>sub : I<Dummy, V>
146+
147+
declare covariance: V;
148+
>covariance : V
149+
}
150+
151+
const c1: C<unknown, string> = new C<unknown, number>(); // Error
152+
>c1 : C<unknown, string>
153+
>new C<unknown, number>() : C<unknown, number>
154+
>C : typeof C
155+

tests/cases/compiler/varianceMeasurement.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,16 @@ const fn2: Fn<'a', number> = fn;
6262
// Covariant in B
6363
const fn3: Fn<string, unknown> = fn;
6464
const fn4: Fn<string, 0> = fn; // Error
65+
66+
// Repro from #39947
67+
68+
interface I<Dummy, V> {
69+
c: C<Dummy, V>;
70+
}
71+
72+
class C<Dummy, V> {
73+
declare sub: I<Dummy, V>;
74+
declare covariance: V;
75+
}
76+
77+
const c1: C<unknown, string> = new C<unknown, number>(); // Error

0 commit comments

Comments
 (0)