Skip to content

Always cache relations involving intersection types #46523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18207,13 +18207,12 @@ namespace ts {
let result = Ternary.False;
const saveErrorInfo = captureErrorCalculationState();

// Note that these checks are specifically ordered to produce correct results. In particular,
// we need to deconstruct unions before intersections (because unions are always at the top),
// and we need to handle "each" relations before "some" relations for the same kind of type.
if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
result = getConstituentCount(source) * getConstituentCount(target) >= 4 ?
recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags) :
structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck);
if ((source.flags & TypeFlags.Union || target.flags & TypeFlags.Union) && getConstituentCount(source) * getConstituentCount(target) < 4) {
// We skip caching when source or target is a union with no more than three constituents.
result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck);
}
else if (source.flags & TypeFlags.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) {
result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck, recursionFlags);
}
if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) {
if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags)) {
Expand Down Expand Up @@ -18677,17 +18676,17 @@ namespace ts {
const maybeStart = maybeCount;
maybeKeys[maybeCount] = id;
maybeCount++;
const saveExpandingFlags = expandingFlags;
if (recursionFlags & RecursionFlags.Source) {
sourceStack[sourceDepth] = source;
sourceDepth++;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
}
if (recursionFlags & RecursionFlags.Target) {
targetStack[targetDepth] = target;
targetDepth++;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
}
const saveExpandingFlags = expandingFlags;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
let originalHandler: typeof outofbandVarianceMarkerHandler;
let propagatingVarianceFlags: RelationComparisonResult = 0;
if (outofbandVarianceMarkerHandler) {
Expand All @@ -18713,13 +18712,13 @@ namespace ts {
if (outofbandVarianceMarkerHandler) {
outofbandVarianceMarkerHandler = originalHandler;
}
expandingFlags = saveExpandingFlags;
if (recursionFlags & RecursionFlags.Source) {
sourceDepth--;
}
if (recursionFlags & RecursionFlags.Target) {
targetDepth--;
}
expandingFlags = saveExpandingFlags;
if (result) {
if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) {
if (result === Ternary.True || result === Ternary.Maybe) {
Expand Down Expand Up @@ -23150,7 +23149,7 @@ namespace ts {
}

function getConstituentCount(type: Type) {
return type.flags & TypeFlags.UnionOrIntersection ? (type as UnionOrIntersectionType).types.length : 1;
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
}

function extractTypesOfKind(type: Type, kind: TypeFlags) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,14 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t
Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type 'T & "text"'.
Type '"text" | "email"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type '"text"'.
Type '"text" | "email"' is not assignable to type '"text"'.
Type '"email"' is not assignable to type '"text"'.
Type 'T' is not assignable to type 'T & "text"'.
Type '"text" | "email"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T & "text"'.
Type '"text"' is not assignable to type 'T'.
'"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
Type 'T' is not assignable to type '"text"'.
Type '"text" | "email"' is not assignable to type '"text"'.
Type '"email"' is not assignable to type '"text"'.


==== tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.ts (1 errors) ====
Expand Down Expand Up @@ -67,16 +65,14 @@ tests/cases/compiler/complicatedIndexedAccessKeyofReliesOnKeyofNeverUpperBound.t
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'ChannelOfType<T, TextChannel>["type"]'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type '"text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'.
!!! error TS2322: Type '"email"' is not assignable to type '"text"'.
!!! error TS2322: Type 'T' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T & "text"'.
!!! error TS2322: Type '"text"' is not assignable to type 'T'.
!!! error TS2322: '"text"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '"text" | "email"'.
!!! error TS2322: Type 'T' is not assignable to type '"text"'.
!!! error TS2322: Type '"text" | "email"' is not assignable to type '"text"'.
!!! error TS2322: Type '"email"' is not assignable to type '"text"'.
}

const newTextChannel = makeNewChannel('text');
Expand Down
21 changes: 20 additions & 1 deletion tests/baselines/reference/deepComparisons.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,23 @@ tests/cases/compiler/deepComparisons.ts(4,9): error TS2322: Type 'T[K1][K2]' is

function f3<U>() {
let x: Foo1<U> = 0 as any as Bar<U>; // No error!
}
}

// Repro from #46500

type F<T> = {} & (
T extends [any, ...any[]]
? { [K in keyof T]?: F<T[K]> }
: T extends any[]
? F<T[number]>[]
: T extends { [K: string]: any }
? { [K in keyof T]?: F<T[K]> }
: { x: string }
);

declare function f<T = any>(): F<T>;

function g() {
return f() as F<any>;
}

24 changes: 23 additions & 1 deletion tests/baselines/reference/deepComparisons.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,26 @@ type Foo2<T> = { x: Foo1<T> };

function f3<U>() {
let x: Foo1<U> = 0 as any as Bar<U>; // No error!
}
}

// Repro from #46500

type F<T> = {} & (
T extends [any, ...any[]]
? { [K in keyof T]?: F<T[K]> }
: T extends any[]
? F<T[number]>[]
: T extends { [K: string]: any }
? { [K in keyof T]?: F<T[K]> }
: { x: string }
);

declare function f<T = any>(): F<T>;

function g() {
return f() as F<any>;
}


//// [deepComparisons.js]
function f1() {
Expand All @@ -31,3 +50,6 @@ function f2() {
function f3() {
var x = 0; // No error!
}
function g() {
return f();
}
54 changes: 54 additions & 0 deletions tests/baselines/reference/deepComparisons.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,57 @@ function f3<U>() {
>Bar : Symbol(Bar, Decl(deepComparisons.ts, 6, 28))
>U : Symbol(U, Decl(deepComparisons.ts, 16, 12))
}

// Repro from #46500

type F<T> = {} & (
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))

T extends [any, ...any[]]
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))

? { [K in keyof T]?: F<T[K]> }
>K : Symbol(K, Decl(deepComparisons.ts, 24, 13))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))
>K : Symbol(K, Decl(deepComparisons.ts, 24, 13))

: T extends any[]
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))

? F<T[number]>[]
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))

: T extends { [K: string]: any }
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))
>K : Symbol(K, Decl(deepComparisons.ts, 27, 27))

? { [K in keyof T]?: F<T[K]> }
>K : Symbol(K, Decl(deepComparisons.ts, 28, 21))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
>T : Symbol(T, Decl(deepComparisons.ts, 22, 7))
>K : Symbol(K, Decl(deepComparisons.ts, 28, 21))

: { x: string }
>x : Symbol(x, Decl(deepComparisons.ts, 29, 19))

);

declare function f<T = any>(): F<T>;
>f : Symbol(f, Decl(deepComparisons.ts, 30, 2))
>T : Symbol(T, Decl(deepComparisons.ts, 32, 19))
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
>T : Symbol(T, Decl(deepComparisons.ts, 32, 19))

function g() {
>g : Symbol(g, Decl(deepComparisons.ts, 32, 36))

return f() as F<any>;
>f : Symbol(f, Decl(deepComparisons.ts, 30, 2))
>F : Symbol(F, Decl(deepComparisons.ts, 18, 1))
}

31 changes: 31 additions & 0 deletions tests/baselines/reference/deepComparisons.types
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,34 @@ function f3<U>() {
>0 as any : any
>0 : 0
}

// Repro from #46500

type F<T> = {} & (
>F : F<T>

T extends [any, ...any[]]
? { [K in keyof T]?: F<T[K]> }
: T extends any[]
? F<T[number]>[]
: T extends { [K: string]: any }
>K : string

? { [K in keyof T]?: F<T[K]> }
: { x: string }
>x : string

);

declare function f<T = any>(): F<T>;
>f : <T = any>() => F<T>

function g() {
>g : () => F<any>

return f() as F<any>;
>f() as F<any> : F<any>
>f() : F<any>
>f : <T = any>() => F<T>
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(20,1): e
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(23,1): error TS2322: Type 'A | B' is not assignable to type '(A & B) | (C & D)'.
Type 'A' is not assignable to type '(A & B) | (C & D)'.
Type 'A' is not assignable to type 'A & B'.
Type 'A' is not assignable to type 'B'.
tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(25,1): error TS2322: Type 'C | D' is not assignable to type '(A & B) | (C & D)'.
Type 'C' is not assignable to type '(A & B) | (C & D)'.
Type 'C' is not assignable to type 'C & D'.
Expand Down Expand Up @@ -80,7 +79,6 @@ tests/cases/conformance/types/intersection/intersectionAndUnionTypes.ts(37,1): e
!!! error TS2322: Type 'A | B' is not assignable to type '(A & B) | (C & D)'.
!!! error TS2322: Type 'A' is not assignable to type '(A & B) | (C & D)'.
!!! error TS2322: Type 'A' is not assignable to type 'A & B'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
x = cnd; // Ok
x = cod;
~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(82,5): error
Type 'string | number | symbol' is not assignable to type 'keyof U'.
Type 'string' is not assignable to type 'keyof U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(83,5): error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
Type 'keyof T' is not assignable to type 'keyof T & keyof U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(86,5): error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
Type 'keyof T' is not assignable to type 'keyof T & keyof U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(87,5): error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
Type 'keyof T' is not assignable to type 'keyof T & keyof U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(103,9): error TS2322: Type 'Extract<keyof T, string>' is not assignable to type 'K'.
'Extract<keyof T, string>' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string'.
Type 'string & keyof T' is not assignable to type 'K'.
Expand Down Expand Up @@ -209,14 +212,17 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(142,5): error
k1 = k4; // Error
~~
!!! error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'keyof T & keyof U'.

k2 = k1;
k2 = k3; // Error
~~
!!! error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'keyof T & keyof U'.
k2 = k4; // Error
~~
!!! error TS2322: Type 'keyof T | keyof U' is not assignable to type 'keyof T & keyof U'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'keyof T & keyof U'.

k3 = k1;
k3 = k2;
Expand Down
4 changes: 0 additions & 4 deletions tests/baselines/reference/unionTypeCallSignatures6.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(38,4): error TS2
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(39,1): error TS2684: The 'this' context of type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' is not assignable to method's 'this' of type 'B'.
Property 'b' is missing in type 'A & C & { f0: F0 | F3; f1: F1 | F3; f2: F1 | F4; f3: F3 | F4; f4: F3 | F5; }' but required in type 'B'.
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(48,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
Type 'void' is not assignable to type 'A'.
tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(55,1): error TS2769: No overload matches this call.
Overload 1 of 2, '(this: A & B & C): void', gave the following error.
The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B & C'.
Type 'void' is not assignable to type 'A'.
Overload 2 of 2, '(this: A & B): void', gave the following error.
The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
Type 'void' is not assignable to type 'A'.


==== tests/cases/conformance/types/union/unionTypeCallSignatures6.ts (6 errors) ====
Expand Down Expand Up @@ -79,7 +77,6 @@ tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(55,1): error TS2
f3(); // error
~~~~
!!! error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
!!! error TS2684: Type 'void' is not assignable to type 'A'.

interface F7 {
(this: A & B & C): void;
Expand All @@ -94,5 +91,4 @@ tests/cases/conformance/types/union/unionTypeCallSignatures6.ts(55,1): error TS2
!!! error TS2769: Type 'void' is not assignable to type 'A'.
!!! error TS2769: Overload 2 of 2, '(this: A & B): void', gave the following error.
!!! error TS2769: The 'this' context of type 'void' is not assignable to method's 'this' of type 'A & B'.
!!! error TS2769: Type 'void' is not assignable to type 'A'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loss of this ending of the elaboration is a little unfortunate since the line above it is kind of an earfull.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but that's basically the effect we get from caching the relation.


20 changes: 19 additions & 1 deletion tests/cases/compiler/deepComparisons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,22 @@ type Foo2<T> = { x: Foo1<T> };

function f3<U>() {
let x: Foo1<U> = 0 as any as Bar<U>; // No error!
}
}

// Repro from #46500

type F<T> = {} & (
T extends [any, ...any[]]
? { [K in keyof T]?: F<T[K]> }
: T extends any[]
? F<T[number]>[]
: T extends { [K: string]: any }
? { [K in keyof T]?: F<T[K]> }
: { x: string }
);

declare function f<T = any>(): F<T>;

function g() {
return f() as F<any>;
}