Skip to content

Commit 2dfb620

Browse files
Merge pull request #27697 from mattmccutchen/issue-27118
Make the assignability rule for conditional types require the check types and distributivity to be identical.
2 parents bbf559b + 1aca1dd commit 2dfb620

File tree

6 files changed

+607
-687
lines changed

6 files changed

+607
-687
lines changed

src/compiler/checker.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -12693,10 +12693,11 @@ namespace ts {
1269312693
else if (source.flags & TypeFlags.Conditional) {
1269412694
if (target.flags & TypeFlags.Conditional) {
1269512695
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
12696-
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
12697-
// and Y1 is related to Y2.
12698-
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
12699-
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
12696+
// they have the same distributivity, T1 and T2 are identical types, U1 and U2 are identical
12697+
// types, X1 is related to X2, and Y1 is related to Y2.
12698+
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive &&
12699+
isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
12700+
isTypeIdenticalTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType)) {
1270012701
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
1270112702
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
1270212703
}

tests/baselines/reference/conditionalTypes2.errors.txt

+65-57
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,38 @@
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
Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
1721
Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
1822
Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
19-
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; }'.
23+
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; }'.
2024
Property 'bat' is missing in type 'Foo & Bar' but required in type '{ foo: string; bat: string; }'.
21-
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; }'.
25+
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; }'.
2226
Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
2327
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
28+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(165,5): error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
29+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(170,5): error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
30+
tests/cases/conformance/types/conditional/conditionalTypes2.ts(177,5): error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.
2431

2532

26-
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ====
33+
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (12 errors) ====
34+
// #27118: Conditional types are now invariant in the check type.
35+
2736
interface Covariant<T> {
2837
foo: T extends string ? T : number;
2938
}
@@ -37,19 +46,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
3746
}
3847

3948
function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>) {
40-
a = b;
49+
a = b; // Error
50+
~
51+
!!! error TS2322: Type 'Covariant<B>' is not assignable to type 'Covariant<A>'.
52+
!!! error TS2322: Types of property 'foo' are incompatible.
53+
!!! error TS2322: Type 'B extends string ? B : number' is not assignable to type 'A extends string ? A : number'.
4154
b = a; // Error
4255
~
4356
!!! error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
44-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
57+
!!! error TS2322: Types of property 'foo' are incompatible.
58+
!!! error TS2322: Type 'A extends string ? A : number' is not assignable to type 'B extends string ? B : number'.
4559
}
4660

4761
function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>) {
4862
a = b; // Error
4963
~
5064
!!! error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
51-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
52-
b = a;
65+
!!! error TS2322: Types of property 'foo' are incompatible.
66+
!!! error TS2322: Type 'B extends string ? keyof B : number' is not assignable to type 'A extends string ? keyof A : number'.
67+
b = a; // Error
68+
~
69+
!!! error TS2322: Type 'Contravariant<A>' is not assignable to type 'Contravariant<B>'.
70+
!!! error TS2322: Types of property 'foo' are incompatible.
71+
!!! error TS2322: Type 'A extends string ? keyof A : number' is not assignable to type 'B extends string ? keyof B : number'.
5372
}
5473

5574
function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>) {
@@ -58,15 +77,11 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
5877
!!! error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
5978
!!! error TS2322: Types of property 'foo' are incompatible.
6079
!!! error TS2322: Type 'B extends string ? keyof B : B' is not assignable to type 'A extends string ? keyof A : A'.
61-
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
62-
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
63-
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
6480
b = a; // Error
6581
~
6682
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
6783
!!! error TS2322: Types of property 'foo' are incompatible.
6884
!!! error TS2322: Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
69-
!!! error TS2322: Type 'A' is not assignable to type 'B'.
7085
}
7186

7287
// Extract<T, Function> is a T that is known to be a Function
@@ -120,52 +135,20 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
120135
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
121136
!!! error TS2345: Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
122137
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
123-
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
124-
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
138+
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
139+
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
125140
fooBat(y); // Error
126141
~
127142
!!! error TS2345: Argument of type 'Extract<T, Foo & Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
128143
!!! error TS2345: Property 'bat' is missing in type 'Foo & Bar' but required in type '{ foo: string; bat: string; }'.
129-
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
144+
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
130145
fooBat(z); // Error
131146
~
132147
!!! error TS2345: Argument of type 'Extract2<T, Foo, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
133148
!!! error TS2345: Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
134149
!!! error TS2345: Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
135150
}
136151

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

171154
declare function toString1(value: object | Function): string ;
@@ -246,4 +229,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
246229
};
247230
type PCCA = ProductComplementComplement['a'];
248231
type PCCB = ProductComplementComplement['b'];
232+
233+
// Repros from #27118
234+
235+
type MyElement<A> = [A] extends [[infer E]] ? E : never;
236+
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
237+
return arg; // Unsound, should be error
238+
~~~~~~~~~~~
239+
!!! error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
240+
}
241+
242+
type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
243+
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
244+
return arg; // Unsound, should be error
245+
~~~~~~~~~~~
246+
!!! error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
247+
}
248+
249+
type Dist<T> = T extends number ? number : string;
250+
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
251+
type Nondist<T> = Aux<{a: T}>;
252+
function oops3<T>(arg: Dist<T>): Nondist<T> {
253+
return arg; // Unsound, should be error
254+
~~~~~~~~~~~
255+
!!! error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.
256+
}
249257

tests/baselines/reference/conditionalTypes2.js

+47-74
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 ;
@@ -188,17 +158,37 @@ type ProductComplementComplement = {
188158
};
189159
type PCCA = ProductComplementComplement['a'];
190160
type PCCB = ProductComplementComplement['b'];
161+
162+
// Repros from #27118
163+
164+
type MyElement<A> = [A] extends [[infer E]] ? E : never;
165+
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
166+
return arg; // Unsound, should be error
167+
}
168+
169+
type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
170+
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
171+
return arg; // Unsound, should be error
172+
}
173+
174+
type Dist<T> = T extends number ? number : string;
175+
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
176+
type Nondist<T> = Aux<{a: T}>;
177+
function oops3<T>(arg: Dist<T>): Nondist<T> {
178+
return arg; // Unsound, should be error
179+
}
191180

192181

193182
//// [conditionalTypes2.js]
194183
"use strict";
184+
// #27118: Conditional types are now invariant in the check type.
195185
function f1(a, b) {
196-
a = b;
186+
a = b; // Error
197187
b = a; // Error
198188
}
199189
function f2(a, b) {
200190
a = b; // Error
201-
b = a;
191+
b = a; // Error
202192
}
203193
function f3(a, b) {
204194
a = b; // Error
@@ -239,32 +229,21 @@ function f21(x, y, z) {
239229
fooBat(y); // Error
240230
fooBat(z); // Error
241231
}
242-
// Repros from #22860
243-
var Opt = /** @class */ (function () {
244-
function Opt() {
245-
}
246-
Opt.prototype.toVector = function () {
247-
return undefined;
248-
};
249-
return Opt;
250-
}());
251-
var Vector = /** @class */ (function () {
252-
function Vector() {
253-
}
254-
Vector.prototype.tail = function () {
255-
return undefined;
256-
};
257-
Vector.prototype.partition2 = function (predicate) {
258-
return undefined;
259-
};
260-
return Vector;
261-
}());
262232
function foo(value) {
263233
if (isFunction(value)) {
264234
toString1(value);
265235
toString2(value);
266236
}
267237
}
238+
function oops(arg) {
239+
return arg; // Unsound, should be error
240+
}
241+
function oops2(arg) {
242+
return arg; // Unsound, should be error
243+
}
244+
function oops3(arg) {
245+
return arg; // Unsound, should be error
246+
}
268247

269248

270249
//// [conditionalTypes2.d.ts]
@@ -302,24 +281,6 @@ declare function fooBat(x: {
302281
declare type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
303282
declare function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
304283
declare function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
305-
declare class Opt<T> {
306-
toVector(): Vector<T>;
307-
}
308-
interface Seq<T> {
309-
tail(): Opt<Seq<T>>;
310-
}
311-
declare class Vector<T> implements Seq<T> {
312-
tail(): Opt<Vector<T>>;
313-
partition2<U extends T>(predicate: (v: T) => v is U): [Vector<U>, Vector<Exclude<T, U>>];
314-
partition2(predicate: (x: T) => boolean): [Vector<T>, Vector<T>];
315-
}
316-
interface A1<T> {
317-
bat: B1<A1<T>>;
318-
}
319-
interface B1<T> extends A1<T> {
320-
bat: B1<B1<T>>;
321-
boom: T extends any ? true : true;
322-
}
323284
declare function toString1(value: object | Function): string;
324285
declare function toString2(value: Function): string;
325286
declare function foo<T>(value: T): void;
@@ -392,3 +353,15 @@ declare type ProductComplementComplement = {
392353
};
393354
declare type PCCA = ProductComplementComplement['a'];
394355
declare type PCCB = ProductComplementComplement['b'];
356+
declare type MyElement<A> = [A] extends [[infer E]] ? E : never;
357+
declare function oops<A, B extends A>(arg: MyElement<A>): MyElement<B>;
358+
declare type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
359+
declare function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A>;
360+
declare type Dist<T> = T extends number ? number : string;
361+
declare type Aux<A extends {
362+
a: unknown;
363+
}> = A["a"] extends number ? number : string;
364+
declare type Nondist<T> = Aux<{
365+
a: T;
366+
}>;
367+
declare function oops3<T>(arg: Dist<T>): Nondist<T>;

0 commit comments

Comments
 (0)