Skip to content

Make the assignability rule for conditional types require the check types and distributivity to be identical. #27697

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 1 commit into from
Feb 11, 2019
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
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