Skip to content

Commit bc9b6d5

Browse files
committed
Make the assignability rule for conditional types require the check
types and distributivity to be identical. Fixes microsoft#27118.
1 parent b2bae85 commit bc9b6d5

File tree

6 files changed

+536
-616
lines changed

6 files changed

+536
-616
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11986,10 +11986,11 @@ namespace ts {
1198611986
else if (source.flags & TypeFlags.Conditional) {
1198711987
if (target.flags & TypeFlags.Conditional) {
1198811988
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
11989-
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
11990-
// and Y1 is related to Y2.
11991-
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
11992-
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
11989+
// they have the same distributivity, T1 and T2 are identical types, U1 and U2 are identical
11990+
// types, X1 is related to X2, and Y1 is related to Y2.
11991+
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive &&
11992+
isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
11993+
isTypeIdenticalTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType)) {
1199311994
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
1199411995
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
1199511996
}

tests/baselines/reference/conditionalTypes2.errors.txt

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
1-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(15,5): error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
2-
Type 'A' is not assignable to type 'B'.
3-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(19,5): error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
4-
Type 'A' is not assignable to type 'B'.
5-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(24,5): error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
1+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(16,5): error TS2322: Type 'Covariant<B>' is not assignable to type 'Covariant<A>'.
2+
Types of property 'foo' are incompatible.
3+
Type 'B extends string ? B : number' is not assignable to type 'A extends string ? A : number'.
4+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(17,5): error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
5+
Types of property 'foo' are incompatible.
6+
Type 'A extends string ? A : number' is not assignable to type 'B extends string ? B : number'.
7+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(21,5): error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
8+
Types of property 'foo' are incompatible.
9+
Type 'B extends string ? keyof B : number' is not assignable to type 'A extends string ? keyof A : number'.
10+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(22,5): error TS2322: Type 'Contravariant<A>' is not assignable to type 'Contravariant<B>'.
11+
Types of property 'foo' are incompatible.
12+
Type 'A extends string ? keyof A : number' is not assignable to type 'B extends string ? keyof B : number'.
13+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(26,5): error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
614
Types of property 'foo' are incompatible.
715
Type 'B extends string ? keyof B : B' is not assignable to type 'A extends string ? keyof A : A'.
8-
Type 'keyof B' is not assignable to type 'keyof A'.
9-
Type 'string | number | symbol' is not assignable to type 'keyof A'.
10-
Type 'string' is not assignable to type 'keyof A'.
11-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
16+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(27,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
1217
Types of property 'foo' are incompatible.
1318
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
14-
Type 'A' is not assignable to type 'B'.
15-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(73,12): error TS2345: Argument of type 'Extract<Extract<T, Foo>, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
19+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2345: Argument of type 'Extract<Extract<T, Foo>, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
1620
Type 'Bar & Extract<T, Foo>' is not assignable to type '{ foo: string; bat: string; }'.
1721
Property 'bat' is missing in type 'Bar & Foo'.
1822
Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
1923
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
2024
Property 'bat' is missing in type 'Bar & Foo'.
21-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(74,12): error TS2345: Argument of type 'Extract<T, Foo & Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
25+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(76,12): error TS2345: Argument of type 'Extract<T, Foo & Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
2226
Type 'Foo & Bar & T' is not assignable to type '{ foo: string; bat: string; }'.
2327
Property 'bat' is missing in type 'Foo & Bar'.
24-
tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2345: Argument of type 'Extract2<T, Foo, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
28+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(77,12): error TS2345: Argument of type 'Extract2<T, Foo, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
2529
Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
2630
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
31+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(122,5): error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
32+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(127,5): error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
33+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(134,5): error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.
2734

2835

29-
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ====
36+
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (12 errors) ====
37+
// #27118: Conditional types are now invariant in the check type.
38+
3039
interface Covariant<T> {
3140
foo: T extends string ? T : number;
3241
}
@@ -40,19 +49,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
4049
}
4150

4251
function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>) {
43-
a = b;
52+
a = b; // Error
53+
~
54+
!!! error TS2322: Type 'Covariant<B>' is not assignable to type 'Covariant<A>'.
55+
!!! error TS2322: Types of property 'foo' are incompatible.
56+
!!! error TS2322: Type 'B extends string ? B : number' is not assignable to type 'A extends string ? A : number'.
4457
b = a; // Error
4558
~
4659
!!! error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
47-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
60+
!!! error TS2322: Types of property 'foo' are incompatible.
61+
!!! error TS2322: Type 'A extends string ? A : number' is not assignable to type 'B extends string ? B : number'.
4862
}
4963

5064
function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>) {
5165
a = b; // Error
5266
~
5367
!!! error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
54-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
55-
b = a;
68+
!!! error TS2322: Types of property 'foo' are incompatible.
69+
!!! error TS2322: Type 'B extends string ? keyof B : number' is not assignable to type 'A extends string ? keyof A : number'.
70+
b = a; // Error
71+
~
72+
!!! error TS2322: Type 'Contravariant<A>' is not assignable to type 'Contravariant<B>'.
73+
!!! error TS2322: Types of property 'foo' are incompatible.
74+
!!! error TS2322: Type 'A extends string ? keyof A : number' is not assignable to type 'B extends string ? keyof B : number'.
5675
}
5776

5877
function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>) {
@@ -61,15 +80,11 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
6180
!!! error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
6281
!!! error TS2322: Types of property 'foo' are incompatible.
6382
!!! error TS2322: Type 'B extends string ? keyof B : B' is not assignable to type 'A extends string ? keyof A : A'.
64-
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
65-
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
66-
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
6783
b = a; // Error
6884
~
6985
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
7086
!!! error TS2322: Types of property 'foo' are incompatible.
7187
!!! error TS2322: Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
72-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
7388
}
7489

7590
// Extract<T, Function> is a T that is known to be a Function
@@ -137,38 +152,6 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
137152
!!! error TS2345: Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
138153
}
139154

140-
// Repros from #22860
141-
142-
class Opt<T> {
143-
toVector(): Vector<T> {
144-
return <any>undefined;
145-
}
146-
}
147-
148-
interface Seq<T> {
149-
tail(): Opt<Seq<T>>;
150-
}
151-
152-
class Vector<T> implements Seq<T> {
153-
tail(): Opt<Vector<T>> {
154-
return <any>undefined;
155-
}
156-
partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T, U>>];
157-
partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
158-
partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
159-
return <any>undefined;
160-
}
161-
}
162-
163-
interface A1<T> {
164-
bat: B1<A1<T>>;
165-
}
166-
167-
interface B1<T> extends A1<T> {
168-
bat: B1<B1<T>>;
169-
boom: T extends any ? true : true
170-
}
171-
172155
// Repro from #22899
173156

174157
declare function toString1(value: object | Function): string ;
@@ -206,4 +189,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
206189

207190
type C2<T, V, E> =
208191
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;
192+
193+
// Repros from #27118
194+
195+
type MyElement<A> = [A] extends [[infer E]] ? E : never;
196+
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
197+
return arg; // Unsound, should be error
198+
~~~~~~~~~~~
199+
!!! error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
200+
}
201+
202+
type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
203+
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
204+
return arg; // Unsound, should be error
205+
~~~~~~~~~~~
206+
!!! error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
207+
}
208+
209+
type Dist<T> = T extends number ? number : string;
210+
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
211+
type Nondist<T> = Aux<{a: T}>;
212+
function oops3<T>(arg: Dist<T>): Nondist<T> {
213+
return arg; // Unsound, should be error
214+
~~~~~~~~~~~
215+
!!! error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.
216+
}
209217

tests/baselines/reference/conditionalTypes2.js

Lines changed: 47 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//// [conditionalTypes2.ts]
2+
// #27118: Conditional types are now invariant in the check type.
3+
24
interface Covariant<T> {
35
foo: T extends string ? T : number;
46
}
@@ -12,13 +14,13 @@ interface Invariant<T> {
1214
}
1315

1416
function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>) {
15-
a = b;
17+
a = b; // Error
1618
b = a; // Error
1719
}
1820

1921
function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>) {
2022
a = b; // Error
21-
b = a;
23+
b = a; // Error
2224
}
2325

2426
function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>) {
@@ -76,38 +78,6 @@ function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: E
7678
fooBat(z); // Error
7779
}
7880

79-
// Repros from #22860
80-
81-
class Opt<T> {
82-
toVector(): Vector<T> {
83-
return <any>undefined;
84-
}
85-
}
86-
87-
interface Seq<T> {
88-
tail(): Opt<Seq<T>>;
89-
}
90-
91-
class Vector<T> implements Seq<T> {
92-
tail(): Opt<Vector<T>> {
93-
return <any>undefined;
94-
}
95-
partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T, U>>];
96-
partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
97-
partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
98-
return <any>undefined;
99-
}
100-
}
101-
102-
interface A1<T> {
103-
bat: B1<A1<T>>;
104-
}
105-
106-
interface B1<T> extends A1<T> {
107-
bat: B1<B1<T>>;
108-
boom: T extends any ? true : true
109-
}
110-
11181
// Repro from #22899
11282

11383
declare function toString1(value: object | Function): string ;
@@ -145,17 +115,37 @@ type B2<T, V> =
145115

146116
type C2<T, V, E> =
147117
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;
118+
119+
// Repros from #27118
120+
121+
type MyElement<A> = [A] extends [[infer E]] ? E : never;
122+
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
123+
return arg; // Unsound, should be error
124+
}
125+
126+
type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
127+
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
128+
return arg; // Unsound, should be error
129+
}
130+
131+
type Dist<T> = T extends number ? number : string;
132+
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
133+
type Nondist<T> = Aux<{a: T}>;
134+
function oops3<T>(arg: Dist<T>): Nondist<T> {
135+
return arg; // Unsound, should be error
136+
}
148137

149138

150139
//// [conditionalTypes2.js]
151140
"use strict";
141+
// #27118: Conditional types are now invariant in the check type.
152142
function f1(a, b) {
153-
a = b;
143+
a = b; // Error
154144
b = a; // Error
155145
}
156146
function f2(a, b) {
157147
a = b; // Error
158-
b = a;
148+
b = a; // Error
159149
}
160150
function f3(a, b) {
161151
a = b; // Error
@@ -196,32 +186,21 @@ function f21(x, y, z) {
196186
fooBat(y); // Error
197187
fooBat(z); // Error
198188
}
199-
// Repros from #22860
200-
var Opt = /** @class */ (function () {
201-
function Opt() {
202-
}
203-
Opt.prototype.toVector = function () {
204-
return undefined;
205-
};
206-
return Opt;
207-
}());
208-
var Vector = /** @class */ (function () {
209-
function Vector() {
210-
}
211-
Vector.prototype.tail = function () {
212-
return undefined;
213-
};
214-
Vector.prototype.partition2 = function (predicate) {
215-
return undefined;
216-
};
217-
return Vector;
218-
}());
219189
function foo(value) {
220190
if (isFunction(value)) {
221191
toString1(value);
222192
toString2(value);
223193
}
224194
}
195+
function oops(arg) {
196+
return arg; // Unsound, should be error
197+
}
198+
function oops2(arg) {
199+
return arg; // Unsound, should be error
200+
}
201+
function oops3(arg) {
202+
return arg; // Unsound, should be error
203+
}
225204

226205

227206
//// [conditionalTypes2.d.ts]
@@ -259,24 +238,6 @@ declare function fooBat(x: {
259238
declare type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
260239
declare function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
261240
declare function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
262-
declare class Opt<T> {
263-
toVector(): Vector<T>;
264-
}
265-
interface Seq<T> {
266-
tail(): Opt<Seq<T>>;
267-
}
268-
declare class Vector<T> implements Seq<T> {
269-
tail(): Opt<Vector<T>>;
270-
partition2<U extends T>(predicate: (v: T) => v is U): [Vector<U>, Vector<Exclude<T, U>>];
271-
partition2(predicate: (x: T) => boolean): [Vector<T>, Vector<T>];
272-
}
273-
interface A1<T> {
274-
bat: B1<A1<T>>;
275-
}
276-
interface B1<T> extends A1<T> {
277-
bat: B1<B1<T>>;
278-
boom: T extends any ? true : true;
279-
}
280241
declare function toString1(value: object | Function): string;
281242
declare function toString2(value: Function): string;
282243
declare function foo<T>(value: T): void;
@@ -304,3 +265,15 @@ declare type B2<T, V> = T extends object ? T extends any[] ? T : {
304265
declare type C2<T, V, E> = T extends object ? {
305266
[Q in keyof T]: C2<T[Q], V, E>;
306267
} : T;
268+
declare type MyElement<A> = [A] extends [[infer E]] ? E : never;
269+
declare function oops<A, B extends A>(arg: MyElement<A>): MyElement<B>;
270+
declare type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
271+
declare function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A>;
272+
declare type Dist<T> = T extends number ? number : string;
273+
declare type Aux<A extends {
274+
a: unknown;
275+
}> = A["a"] extends number ? number : string;
276+
declare type Nondist<T> = Aux<{
277+
a: T;
278+
}>;
279+
declare function oops3<T>(arg: Dist<T>): Nondist<T>;

0 commit comments

Comments
 (0)