Skip to content

Commit

Permalink
Merge pull request #27697 from mattmccutchen/issue-27118
Browse files Browse the repository at this point in the history
Make the assignability rule for conditional types require the check types and distributivity to be identical.
  • Loading branch information
RyanCavanaugh authored Feb 11, 2019
2 parents bbf559b + 1aca1dd commit 2dfb620
Show file tree
Hide file tree
Showing 6 changed files with 607 additions and 687 deletions.
9 changes: 5 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12693,10 +12693,11 @@ namespace ts {
else if (source.flags & TypeFlags.Conditional) {
if (target.flags & TypeFlags.Conditional) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
// they have the same distributivity, T1 and T2 are identical types, U1 and U2 are identical
// types, X1 is related to X2, and Y1 is related to Y2.
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive &&
isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
isTypeIdenticalTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType)) {
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
}
Expand Down
122 changes: 65 additions & 57 deletions tests/baselines/reference/conditionalTypes2.errors.txt
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
tests/cases/conformance/types/conditional/conditionalTypes2.ts(15,5): error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
Type 'A' is not assignable to type 'B'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(19,5): error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
Type 'A' is not assignable to type 'B'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(24,5): error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(16,5): error TS2322: Type 'Covariant<B>' is not assignable to type 'Covariant<A>'.
Types of property 'foo' are incompatible.
Type 'B extends string ? B : number' is not assignable to type 'A extends string ? A : number'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(17,5): error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
Types of property 'foo' are incompatible.
Type 'A extends string ? A : number' is not assignable to type 'B extends string ? B : number'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(21,5): error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
Types of property 'foo' are incompatible.
Type 'B extends string ? keyof B : number' is not assignable to type 'A extends string ? keyof A : number'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(22,5): error TS2322: Type 'Contravariant<A>' is not assignable to type 'Contravariant<B>'.
Types of property 'foo' are incompatible.
Type 'A extends string ? keyof A : number' is not assignable to type 'B extends string ? keyof B : number'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(26,5): error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
Types of property 'foo' are incompatible.
Type 'B extends string ? keyof B : B' is not assignable to type 'A extends string ? keyof A : A'.
Type 'keyof B' is not assignable to type 'keyof A'.
Type 'string | number | symbol' is not assignable to type 'keyof A'.
Type 'string' is not assignable to type 'keyof A'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(27,5): error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
Types of property 'foo' are incompatible.
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
Type 'A' is not assignable to type 'B'.
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; }'.
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; }'.
Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
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; }'.
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; }'.
Property 'bat' is missing in type 'Foo & Bar' but required in type '{ foo: string; bat: string; }'.
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; }'.
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; }'.
Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(165,5): error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(170,5): error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(177,5): error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.


==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ====
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (12 errors) ====
// #27118: Conditional types are now invariant in the check type.

interface Covariant<T> {
foo: T extends string ? T : number;
}
Expand All @@ -37,19 +46,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
}

function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>) {
a = b;
a = b; // Error
~
!!! error TS2322: Type 'Covariant<B>' is not assignable to type 'Covariant<A>'.
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'B extends string ? B : number' is not assignable to type 'A extends string ? A : number'.
b = a; // Error
~
!!! error TS2322: Type 'Covariant<A>' is not assignable to type 'Covariant<B>'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'A extends string ? A : number' is not assignable to type 'B extends string ? B : number'.
}

function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>) {
a = b; // Error
~
!!! error TS2322: Type 'Contravariant<B>' is not assignable to type 'Contravariant<A>'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
b = a;
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'B extends string ? keyof B : number' is not assignable to type 'A extends string ? keyof A : number'.
b = a; // Error
~
!!! error TS2322: Type 'Contravariant<A>' is not assignable to type 'Contravariant<B>'.
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'A extends string ? keyof A : number' is not assignable to type 'B extends string ? keyof B : number'.
}

function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>) {
Expand All @@ -58,15 +77,11 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
!!! error TS2322: Type 'Invariant<B>' is not assignable to type 'Invariant<A>'.
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'B extends string ? keyof B : B' is not assignable to type 'A extends string ? keyof A : A'.
!!! error TS2322: Type 'keyof B' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'keyof A'.
!!! error TS2322: Type 'string' is not assignable to type 'keyof A'.
b = a; // Error
~
!!! error TS2322: Type 'Invariant<A>' is not assignable to type 'Invariant<B>'.
!!! error TS2322: Types of property 'foo' are incompatible.
!!! error TS2322: Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
!!! error TS2322: Type 'A' is not assignable to type 'B'.
}

// Extract<T, Function> is a T that is known to be a Function
Expand Down Expand Up @@ -120,52 +135,20 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
!!! error TS2345: Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo' but required in type '{ foo: string; bat: string; }'.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
fooBat(y); // Error
~
!!! error TS2345: Argument of type 'Extract<T, Foo & Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
!!! error TS2345: Property 'bat' is missing in type 'Foo & Bar' but required in type '{ foo: string; bat: string; }'.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:62:43: 'bat' is declared here.
!!! related TS2728 tests/cases/conformance/types/conditional/conditionalTypes2.ts:64:43: 'bat' is declared here.
fooBat(z); // Error
~
!!! error TS2345: Argument of type 'Extract2<T, Foo, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
!!! error TS2345: Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
!!! error TS2345: Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
}

// Repros from #22860

class Opt<T> {
toVector(): Vector<T> {
return <any>undefined;
}
}

interface Seq<T> {
tail(): Opt<Seq<T>>;
}

class Vector<T> implements Seq<T> {
tail(): Opt<Vector<T>> {
return <any>undefined;
}
partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T, U>>];
partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
return <any>undefined;
}
}

interface A1<T> {
bat: B1<A1<T>>;
}

interface B1<T> extends A1<T> {
bat: B1<B1<T>>;
boom: T extends any ? true : true
}

// Repro from #22899

declare function toString1(value: object | Function): string ;
Expand Down Expand Up @@ -246,4 +229,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
};
type PCCA = ProductComplementComplement['a'];
type PCCB = ProductComplementComplement['b'];

// Repros from #27118

type MyElement<A> = [A] extends [[infer E]] ? E : never;
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
return arg; // Unsound, should be error
~~~~~~~~~~~
!!! error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
}

type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
return arg; // Unsound, should be error
~~~~~~~~~~~
!!! error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
}

type Dist<T> = T extends number ? number : string;
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
type Nondist<T> = Aux<{a: T}>;
function oops3<T>(arg: Dist<T>): Nondist<T> {
return arg; // Unsound, should be error
~~~~~~~~~~~
!!! error TS2322: Type 'Dist<T>' is not assignable to type 'Aux<{ a: T; }>'.
}

121 changes: 47 additions & 74 deletions tests/baselines/reference/conditionalTypes2.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//// [conditionalTypes2.ts]
// #27118: Conditional types are now invariant in the check type.

interface Covariant<T> {
foo: T extends string ? T : number;
}
Expand All @@ -12,13 +14,13 @@ interface Invariant<T> {
}

function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>) {
a = b;
a = b; // Error
b = a; // Error
}

function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>) {
a = b; // Error
b = a;
b = a; // Error
}

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

// Repros from #22860

class Opt<T> {
toVector(): Vector<T> {
return <any>undefined;
}
}

interface Seq<T> {
tail(): Opt<Seq<T>>;
}

class Vector<T> implements Seq<T> {
tail(): Opt<Vector<T>> {
return <any>undefined;
}
partition2<U extends T>(predicate:(v:T)=>v is U): [Vector<U>,Vector<Exclude<T, U>>];
partition2(predicate:(x:T)=>boolean): [Vector<T>,Vector<T>];
partition2<U extends T>(predicate:(v:T)=>boolean): [Vector<U>,Vector<any>] {
return <any>undefined;
}
}

interface A1<T> {
bat: B1<A1<T>>;
}

interface B1<T> extends A1<T> {
bat: B1<B1<T>>;
boom: T extends any ? true : true
}

// Repro from #22899

declare function toString1(value: object | Function): string ;
Expand Down Expand Up @@ -188,17 +158,37 @@ type ProductComplementComplement = {
};
type PCCA = ProductComplementComplement['a'];
type PCCB = ProductComplementComplement['b'];

// Repros from #27118

type MyElement<A> = [A] extends [[infer E]] ? E : never;
function oops<A, B extends A>(arg: MyElement<A>): MyElement<B> {
return arg; // Unsound, should be error
}

type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A> {
return arg; // Unsound, should be error
}

type Dist<T> = T extends number ? number : string;
type Aux<A extends { a: unknown }> = A["a"] extends number ? number : string;
type Nondist<T> = Aux<{a: T}>;
function oops3<T>(arg: Dist<T>): Nondist<T> {
return arg; // Unsound, should be error
}


//// [conditionalTypes2.js]
"use strict";
// #27118: Conditional types are now invariant in the check type.
function f1(a, b) {
a = b;
a = b; // Error
b = a; // Error
}
function f2(a, b) {
a = b; // Error
b = a;
b = a; // Error
}
function f3(a, b) {
a = b; // Error
Expand Down Expand Up @@ -239,32 +229,21 @@ function f21(x, y, z) {
fooBat(y); // Error
fooBat(z); // Error
}
// Repros from #22860
var Opt = /** @class */ (function () {
function Opt() {
}
Opt.prototype.toVector = function () {
return undefined;
};
return Opt;
}());
var Vector = /** @class */ (function () {
function Vector() {
}
Vector.prototype.tail = function () {
return undefined;
};
Vector.prototype.partition2 = function (predicate) {
return undefined;
};
return Vector;
}());
function foo(value) {
if (isFunction(value)) {
toString1(value);
toString2(value);
}
}
function oops(arg) {
return arg; // Unsound, should be error
}
function oops2(arg) {
return arg; // Unsound, should be error
}
function oops3(arg) {
return arg; // Unsound, should be error
}


//// [conditionalTypes2.d.ts]
Expand Down Expand Up @@ -302,24 +281,6 @@ declare function fooBat(x: {
declare type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
declare function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
declare function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
declare class Opt<T> {
toVector(): Vector<T>;
}
interface Seq<T> {
tail(): Opt<Seq<T>>;
}
declare class Vector<T> implements Seq<T> {
tail(): Opt<Vector<T>>;
partition2<U extends T>(predicate: (v: T) => v is U): [Vector<U>, Vector<Exclude<T, U>>];
partition2(predicate: (x: T) => boolean): [Vector<T>, Vector<T>];
}
interface A1<T> {
bat: B1<A1<T>>;
}
interface B1<T> extends A1<T> {
bat: B1<B1<T>>;
boom: T extends any ? true : true;
}
declare function toString1(value: object | Function): string;
declare function toString2(value: Function): string;
declare function foo<T>(value: T): void;
Expand Down Expand Up @@ -392,3 +353,15 @@ declare type ProductComplementComplement = {
};
declare type PCCA = ProductComplementComplement['a'];
declare type PCCB = ProductComplementComplement['b'];
declare type MyElement<A> = [A] extends [[infer E]] ? E : never;
declare function oops<A, B extends A>(arg: MyElement<A>): MyElement<B>;
declare type MyAcceptor<A> = [A] extends [[infer E]] ? (arg: E) => void : never;
declare function oops2<A, B extends A>(arg: MyAcceptor<B>): MyAcceptor<A>;
declare type Dist<T> = T extends number ? number : string;
declare type Aux<A extends {
a: unknown;
}> = A["a"] extends number ? number : string;
declare type Nondist<T> = Aux<{
a: T;
}>;
declare function oops3<T>(arg: Dist<T>): Nondist<T>;
Loading

0 comments on commit 2dfb620

Please sign in to comment.