Skip to content

Commit c4f96b6

Browse files
committed
Cache propagating variance flags in the relationship cache
1 parent 345777e commit c4f96b6

6 files changed

+202
-25
lines changed

src/compiler/checker.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12264,8 +12264,9 @@ namespace ts {
1226412264
return true;
1226512265
}
1226612266
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
12267-
const relation = enumRelation.get(id);
12268-
if (relation !== undefined && !(relation === RelationComparisonResult.Failed && errorReporter)) {
12267+
const entry = enumRelation.get(id);
12268+
const relation = entry! & RelationComparisonResult.ResultMask;
12269+
if (entry !== undefined && !(relation === RelationComparisonResult.Failed && errorReporter)) {
1226912270
return relation === RelationComparisonResult.Succeeded;
1227012271
}
1227112272
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
@@ -12345,7 +12346,7 @@ namespace ts {
1234512346
if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) {
1234612347
const related = relation.get(getRelationKey(source, target, relation));
1234712348
if (related !== undefined) {
12348-
return related === RelationComparisonResult.Succeeded;
12349+
return (related & RelationComparisonResult.ResultMask) === RelationComparisonResult.Succeeded;
1234912350
}
1235012351
}
1235112352
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
@@ -13011,18 +13012,6 @@ namespace ts {
1301113012
return result;
1301213013
}
1301313014

13014-
function propagateSidebandVarianceFlags(typeArguments: readonly Type[], variances: VarianceFlags[]) {
13015-
for (let i = 0; i < variances.length; i++) {
13016-
const v = variances[i];
13017-
if (v & VarianceFlags.Unmeasurable) {
13018-
instantiateType(typeArguments[i], reportUnmeasurableMarkers);
13019-
}
13020-
if (v & VarianceFlags.Unreliable) {
13021-
instantiateType(typeArguments[i], reportUnreliableMarkers);
13022-
}
13023-
}
13024-
}
13025-
1302613015
// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
1302713016
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
1302813017
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
@@ -13033,21 +13022,22 @@ namespace ts {
1303313022
return Ternary.False;
1303413023
}
1303513024
const id = getRelationKey(source, target, relation);
13036-
const related = relation.get(id);
13037-
if (related !== undefined) {
13025+
const entry = relation.get(id);
13026+
const related = entry! & RelationComparisonResult.ResultMask;
13027+
if (entry !== undefined) {
1303813028
if (reportErrors && related === RelationComparisonResult.Failed) {
1303913029
// We are elaborating errors and the cached result is an unreported failure. The result will be reported
1304013030
// as a failure, and should be updated as a reported failure by the bottom of this function.
1304113031
}
1304213032
else {
1304313033
if (outofbandVarianceMarkerHandler) {
1304413034
// We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component
13045-
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
13046-
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
13047-
propagateSidebandVarianceFlags(source.aliasTypeArguments, getAliasVariances(source.aliasSymbol));
13035+
const saved = entry & RelationComparisonResult.ReportsMask;
13036+
if (saved & RelationComparisonResult.ReportsUnmeasurable) {
13037+
instantiateType(source, reportUnmeasurableMarkers);
1304813038
}
13049-
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target && length((<TypeReference>source).typeArguments)) {
13050-
propagateSidebandVarianceFlags((<TypeReference>source).typeArguments!, getVariances((<TypeReference>source).target));
13039+
if (saved & RelationComparisonResult.ReportsUnreliable) {
13040+
instantiateType(source, reportUnreliableMarkers);
1305113041
}
1305213042
}
1305313043
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
@@ -13079,22 +13069,34 @@ namespace ts {
1307913069
const saveExpandingFlags = expandingFlags;
1308013070
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source;
1308113071
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target;
13072+
let originalHandler: typeof outofbandVarianceMarkerHandler;
13073+
let propagatingVarianceFlags: RelationComparisonResult = 0;
13074+
if (outofbandVarianceMarkerHandler) {
13075+
originalHandler = outofbandVarianceMarkerHandler;
13076+
outofbandVarianceMarkerHandler = onlyUnreliable => {
13077+
propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
13078+
return originalHandler!(onlyUnreliable);
13079+
};
13080+
}
1308213081
const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent) : Ternary.Maybe;
13082+
if (outofbandVarianceMarkerHandler) {
13083+
outofbandVarianceMarkerHandler = originalHandler;
13084+
}
1308313085
expandingFlags = saveExpandingFlags;
1308413086
depth--;
1308513087
if (result) {
1308613088
if (result === Ternary.True || depth === 0) {
1308713089
// If result is definitely true, record all maybe keys as having succeeded
1308813090
for (let i = maybeStart; i < maybeCount; i++) {
13089-
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded);
13091+
relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags);
1309013092
}
1309113093
maybeCount = maybeStart;
1309213094
}
1309313095
}
1309413096
else {
1309513097
// A false result goes straight into global cache (when something is false under
1309613098
// assumptions it will also be false without assumptions)
13097-
relation.set(id, reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed);
13099+
relation.set(id, (reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed) | propagatingVarianceFlags);
1309813100
maybeCount = maybeStart;
1309913101
}
1310013102
return result;

src/compiler/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,12 @@ namespace ts {
612612
export const enum RelationComparisonResult {
613613
Succeeded = 1, // Should be truthy
614614
Failed = 2,
615-
FailedAndReported = 3
615+
FailedAndReported = 3,
616+
ResultMask = 0x3,
617+
618+
ReportsUnmeasurable = 1 << 2,
619+
ReportsUnreliable = 1 << 3,
620+
ReportsMask = ReportsUnmeasurable | ReportsUnreliable
616621
}
617622

618623
export interface Node extends TextRange {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [varianceRepeatedlyPropegatesWithUnreliableFlag.ts]
2+
type A = { a: number };
3+
type B = { b: number };
4+
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
5+
type P1<T> = { data: X<T> };
6+
type P2<T> = { data: X<T> };
7+
8+
interface I<T> {
9+
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
10+
}
11+
12+
const i: I<A & B> = null as any;
13+
const p2: P2<A> = null as any;
14+
15+
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
16+
i.fn(null as any, p2);
17+
18+
const _i: I<A> = i;
19+
20+
//// [varianceRepeatedlyPropegatesWithUnreliableFlag.js]
21+
var i = null;
22+
var p2 = null;
23+
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
24+
i.fn(null, p2);
25+
var _i = i;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
=== tests/cases/compiler/varianceRepeatedlyPropegatesWithUnreliableFlag.ts ===
2+
type A = { a: number };
3+
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
4+
>a : Symbol(a, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 10))
5+
6+
type B = { b: number };
7+
>B : Symbol(B, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 23))
8+
>b : Symbol(b, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 10))
9+
10+
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
11+
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
12+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
13+
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 16))
14+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
15+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
16+
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 16))
17+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
18+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 7))
19+
20+
type P1<T> = { data: X<T> };
21+
>P1 : Symbol(P1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 71))
22+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 8))
23+
>data : Symbol(data, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 14))
24+
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
25+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 8))
26+
27+
type P2<T> = { data: X<T> };
28+
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
29+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 8))
30+
>data : Symbol(data, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 14))
31+
>X : Symbol(X, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 1, 23))
32+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 8))
33+
34+
interface I<T> {
35+
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
36+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
37+
38+
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
39+
>fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
40+
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
41+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
42+
>p1 : Symbol(p1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 26))
43+
>P1 : Symbol(P1, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 2, 71))
44+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
45+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
46+
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
47+
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 45))
48+
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
49+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
50+
>T : Symbol(T, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 12))
51+
>K : Symbol(K, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 7, 7))
52+
}
53+
54+
const i: I<A & B> = null as any;
55+
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))
56+
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
57+
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
58+
>B : Symbol(B, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 23))
59+
60+
const p2: P2<A> = null as any;
61+
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 11, 5))
62+
>P2 : Symbol(P2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 3, 28))
63+
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
64+
65+
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
66+
i.fn(null as any, p2);
67+
>i.fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
68+
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))
69+
>fn : Symbol(I.fn, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 6, 16))
70+
>p2 : Symbol(p2, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 11, 5))
71+
72+
const _i: I<A> = i;
73+
>_i : Symbol(_i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 16, 5))
74+
>I : Symbol(I, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 4, 28))
75+
>A : Symbol(A, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 0, 0))
76+
>i : Symbol(i, Decl(varianceRepeatedlyPropegatesWithUnreliableFlag.ts, 10, 5))
77+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/compiler/varianceRepeatedlyPropegatesWithUnreliableFlag.ts ===
2+
type A = { a: number };
3+
>A : A
4+
>a : number
5+
6+
type B = { b: number };
7+
>B : B
8+
>b : number
9+
10+
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
11+
>X : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]
12+
13+
type P1<T> = { data: X<T> };
14+
>P1 : P1<T>
15+
>data : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]
16+
17+
type P2<T> = { data: X<T> };
18+
>P2 : P2<T>
19+
>data : ({ [K in keyof T]: T[K]; } & Record<string, void>)[keyof T]
20+
21+
interface I<T> {
22+
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
23+
>fn : <K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>) => void
24+
>p1 : P1<Pick<T, K>>
25+
>p2 : P2<Pick<T, K>>
26+
}
27+
28+
const i: I<A & B> = null as any;
29+
>i : I<A & B>
30+
>null as any : any
31+
>null : null
32+
33+
const p2: P2<A> = null as any;
34+
>p2 : P2<A>
35+
>null as any : any
36+
>null : null
37+
38+
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
39+
i.fn(null as any, p2);
40+
>i.fn(null as any, p2) : void
41+
>i.fn : <K extends "a" | "b">(p1: P1<Pick<A & B, K>>, p2: P2<Pick<A & B, K>>) => void
42+
>i : I<A & B>
43+
>fn : <K extends "a" | "b">(p1: P1<Pick<A & B, K>>, p2: P2<Pick<A & B, K>>) => void
44+
>null as any : any
45+
>null : null
46+
>p2 : P2<A>
47+
48+
const _i: I<A> = i;
49+
>_i : I<A>
50+
>i : I<A & B>
51+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type A = { a: number };
2+
type B = { b: number };
3+
type X<T> = ({ [K in keyof T]: T[K] } & Record<string, void>)[keyof T];
4+
type P1<T> = { data: X<T> };
5+
type P2<T> = { data: X<T> };
6+
7+
interface I<T> {
8+
fn<K extends keyof T>(p1: P1<Pick<T, K>>, p2: P2<Pick<T, K>>): void;
9+
}
10+
11+
const i: I<A & B> = null as any;
12+
const p2: P2<A> = null as any;
13+
14+
// Commenting out the below line will remove the error on the `const _i: I<A> = i;`
15+
i.fn(null as any, p2);
16+
17+
const _i: I<A> = i;

0 commit comments

Comments
 (0)