Skip to content

Commit 1a10e71

Browse files
authored
Improve variance measurement (#36261)
* No covariance default for recursive references in variance measurement * Add tests * Accept new baselines
1 parent e2e1f6f commit 1a10e71

8 files changed

+600
-63
lines changed

src/compiler/checker.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ namespace ts {
306306

307307
const emptySymbols = createSymbolTable();
308308
const identityMapper: (type: Type) => Type = identity;
309+
const arrayVariances = [VarianceFlags.Covariant];
309310

310311
const compilerOptions = host.getCompilerOptions();
311312
const languageVersion = getEmitScriptTarget(compilerOptions);
@@ -15406,6 +15407,9 @@ namespace ts {
1540615407
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
1540715408
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
1540815409
const variances = getAliasVariances(source.aliasSymbol);
15410+
if (variances === emptyArray) {
15411+
return Ternary.Maybe;
15412+
}
1540915413
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
1541015414
if (varianceResult !== undefined) {
1541115415
return varianceResult;
@@ -15602,6 +15606,12 @@ namespace ts {
1560215606
// type references (which are intended by be compared structurally). Obtain the variance
1560315607
// information for the type parameters and relate the type arguments accordingly.
1560415608
const variances = getVariances((<TypeReference>source).target);
15609+
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
15610+
// effectively means we measure variance only from type parameter occurrences that aren't nested in
15611+
// recursive instantiations of the generic type.
15612+
if (variances === emptyArray) {
15613+
return Ternary.Maybe;
15614+
}
1560515615
const varianceResult = relateVariances(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), variances, intersectionState);
1560615616
if (varianceResult !== undefined) {
1560715617
return varianceResult;
@@ -16421,8 +16431,7 @@ namespace ts {
1642116431
// a digest of the type comparisons that occur for each type argument when instantiations of the
1642216432
// generic type are structurally compared. We infer the variance information by comparing
1642316433
// instantiations of the generic type for type arguments with known relations. The function
16424-
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
16425-
// has been invoked recursively for the given generic type.
16434+
// returns the emptyArray singleton when invoked recursively for the given generic type.
1642616435
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
1642716436
let variances = cache.variances;
1642816437
if (!variances) {
@@ -16465,9 +16474,9 @@ namespace ts {
1646516474
}
1646616475

1646716476
function getVariances(type: GenericType): VarianceFlags[] {
16468-
// Arrays and tuples are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters)
16477+
// Arrays and tuples are known to be covariant, no need to spend time computing this.
1646916478
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
16470-
return emptyArray;
16479+
return arrayVariances;
1647116480
}
1647216481
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
1647316482
}

tests/baselines/reference/checkInfiniteExpansionTermination.errors.txt

-32
This file was deleted.

tests/baselines/reference/recursiveTypeComparison.errors.txt

-27
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
tests/cases/compiler/varianceMeasurement.ts(10,7): error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<"a">'.
2+
Type 'string' is not assignable to type '"a"'.
3+
tests/cases/compiler/varianceMeasurement.ts(21,7): error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<"a">'.
4+
Types of property 'x' are incompatible.
5+
Type 'string' is not assignable to type '"a"'.
6+
tests/cases/compiler/varianceMeasurement.ts(22,7): error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<unknown>'.
7+
The types of 'y.x' are incompatible between these types.
8+
Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
9+
Types of parameters 'arg' and 'arg' are incompatible.
10+
Type 'unknown' is not assignable to type 'string'.
11+
tests/cases/compiler/varianceMeasurement.ts(33,7): error TS2322: Type 'Foo3<string>' is not assignable to type 'Foo3<"a">'.
12+
Type 'string' is not assignable to type '"a"'.
13+
tests/cases/compiler/varianceMeasurement.ts(44,7): error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<"a">'.
14+
Types of property 'x' are incompatible.
15+
Type 'string' is not assignable to type '"a"'.
16+
tests/cases/compiler/varianceMeasurement.ts(45,7): error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<unknown>'.
17+
The types of 'y.x' are incompatible between these types.
18+
Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
19+
Types of parameters 'arg' and 'arg' are incompatible.
20+
Type 'unknown' is not assignable to type 'string'.
21+
tests/cases/compiler/varianceMeasurement.ts(57,7): error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<unknown, number>'.
22+
Type 'unknown' is not assignable to type 'string'.
23+
tests/cases/compiler/varianceMeasurement.ts(62,7): error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
24+
Type 'number' is not assignable to type '0'.
25+
26+
27+
==== tests/cases/compiler/varianceMeasurement.ts (8 errors) ====
28+
// The type below should be invariant in T but is measured as covariant because
29+
// we don't analyze recursive references.
30+
31+
interface Foo1<T> {
32+
x: T;
33+
y: Foo1<(arg: T) => void>;
34+
}
35+
36+
declare const f10: Foo1<string>;
37+
const f11: Foo1<'a'> = f10;
38+
~~~
39+
!!! error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<"a">'.
40+
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
41+
const f12: Foo1<unknown> = f10;
42+
43+
// The type below is invariant in T and is measured as such.
44+
45+
interface Foo2<T> {
46+
x: T;
47+
y: { x: (arg: T) => void, y: Foo2<(arg: T) => void>; }
48+
}
49+
50+
declare const f20: Foo2<string>;
51+
const f21: Foo2<'a'> = f20;
52+
~~~
53+
!!! error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<"a">'.
54+
!!! error TS2322: Types of property 'x' are incompatible.
55+
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
56+
const f22: Foo2<unknown> = f20;
57+
~~~
58+
!!! error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<unknown>'.
59+
!!! error TS2322: The types of 'y.x' are incompatible between these types.
60+
!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
61+
!!! error TS2322: Types of parameters 'arg' and 'arg' are incompatible.
62+
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
63+
64+
// The type below should be invariant in T but is measured as covariant because
65+
// we don't analyze recursive references.
66+
67+
type Foo3<T> = {
68+
x: T;
69+
y: Foo3<(arg: T) => void>;
70+
}
71+
72+
declare const f30: Foo3<string>;
73+
const f31: Foo3<'a'> = f30;
74+
~~~
75+
!!! error TS2322: Type 'Foo3<string>' is not assignable to type 'Foo3<"a">'.
76+
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
77+
const f32: Foo3<unknown> = f30;
78+
79+
// The type below is invariant in T and is measured as such.
80+
81+
type Foo4<T> = {
82+
x: T;
83+
y: { x: (arg: T) => void, y: Foo4<(arg: T) => void>; }
84+
}
85+
86+
declare const f40: Foo4<string>;
87+
const f41: Foo4<'a'> = f40;
88+
~~~
89+
!!! error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<"a">'.
90+
!!! error TS2322: Types of property 'x' are incompatible.
91+
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
92+
const f42: Foo4<unknown> = f40;
93+
~~~
94+
!!! error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<unknown>'.
95+
!!! error TS2322: The types of 'y.x' are incompatible between these types.
96+
!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
97+
!!! error TS2322: Types of parameters 'arg' and 'arg' are incompatible.
98+
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
99+
100+
// Repro from #3580
101+
102+
interface Fn<A, B> {
103+
(a: A): B;
104+
then<C>(next: Fn<B, C>): Fn<A, C>;
105+
}
106+
107+
declare const fn: Fn<string, number>;
108+
109+
// Contravariant in A
110+
const fn1: Fn<unknown, number> = fn; // Error
111+
~~~
112+
!!! error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<unknown, number>'.
113+
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
114+
const fn2: Fn<'a', number> = fn;
115+
116+
// Covariant in B
117+
const fn3: Fn<string, unknown> = fn;
118+
const fn4: Fn<string, 0> = fn; // Error
119+
~~~
120+
!!! error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
121+
!!! error TS2322: Type 'number' is not assignable to type '0'.
122+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//// [varianceMeasurement.ts]
2+
// The type below should be invariant in T but is measured as covariant because
3+
// we don't analyze recursive references.
4+
5+
interface Foo1<T> {
6+
x: T;
7+
y: Foo1<(arg: T) => void>;
8+
}
9+
10+
declare const f10: Foo1<string>;
11+
const f11: Foo1<'a'> = f10;
12+
const f12: Foo1<unknown> = f10;
13+
14+
// The type below is invariant in T and is measured as such.
15+
16+
interface Foo2<T> {
17+
x: T;
18+
y: { x: (arg: T) => void, y: Foo2<(arg: T) => void>; }
19+
}
20+
21+
declare const f20: Foo2<string>;
22+
const f21: Foo2<'a'> = f20;
23+
const f22: Foo2<unknown> = f20;
24+
25+
// The type below should be invariant in T but is measured as covariant because
26+
// we don't analyze recursive references.
27+
28+
type Foo3<T> = {
29+
x: T;
30+
y: Foo3<(arg: T) => void>;
31+
}
32+
33+
declare const f30: Foo3<string>;
34+
const f31: Foo3<'a'> = f30;
35+
const f32: Foo3<unknown> = f30;
36+
37+
// The type below is invariant in T and is measured as such.
38+
39+
type Foo4<T> = {
40+
x: T;
41+
y: { x: (arg: T) => void, y: Foo4<(arg: T) => void>; }
42+
}
43+
44+
declare const f40: Foo4<string>;
45+
const f41: Foo4<'a'> = f40;
46+
const f42: Foo4<unknown> = f40;
47+
48+
// Repro from #3580
49+
50+
interface Fn<A, B> {
51+
(a: A): B;
52+
then<C>(next: Fn<B, C>): Fn<A, C>;
53+
}
54+
55+
declare const fn: Fn<string, number>;
56+
57+
// Contravariant in A
58+
const fn1: Fn<unknown, number> = fn; // Error
59+
const fn2: Fn<'a', number> = fn;
60+
61+
// Covariant in B
62+
const fn3: Fn<string, unknown> = fn;
63+
const fn4: Fn<string, 0> = fn; // Error
64+
65+
66+
//// [varianceMeasurement.js]
67+
"use strict";
68+
// The type below should be invariant in T but is measured as covariant because
69+
// we don't analyze recursive references.
70+
var f11 = f10;
71+
var f12 = f10;
72+
var f21 = f20;
73+
var f22 = f20;
74+
var f31 = f30;
75+
var f32 = f30;
76+
var f41 = f40;
77+
var f42 = f40;
78+
// Contravariant in A
79+
var fn1 = fn; // Error
80+
var fn2 = fn;
81+
// Covariant in B
82+
var fn3 = fn;
83+
var fn4 = fn; // Error

0 commit comments

Comments
 (0)