Skip to content

Commit

Permalink
Make the assignability rule for conditional types require the check
Browse files Browse the repository at this point in the history
types and distributivity to be identical.

Fixes microsoft#27118.
  • Loading branch information
mattmccutchen committed Oct 10, 2018
1 parent b2bae85 commit bc9b6d5
Show file tree
Hide file tree
Showing 6 changed files with 536 additions and 616 deletions.
9 changes: 5 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11986,10 +11986,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
116 changes: 62 additions & 54 deletions tests/baselines/reference/conditionalTypes2.errors.txt
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
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; }'.
Type 'Bar & Extract<T, Foo>' is not assignable to type '{ foo: string; bat: string; }'.
Property 'bat' is missing in type 'Bar & Foo'.
Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
Property 'bat' is missing in type 'Bar & Foo'.
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; }'.
Type 'Foo & Bar & T' is not assignable to type '{ foo: string; bat: string; }'.
Property 'bat' is missing in type 'Foo & Bar'.
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(122,5): error TS2322: Type 'MyElement<A>' is not assignable to type 'MyElement<B>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(127,5): error TS2322: Type 'MyAcceptor<B>' is not assignable to type 'MyAcceptor<A>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(134,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 @@ -40,19 +49,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 @@ -61,15 +80,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 @@ -137,38 +152,6 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2
!!! 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 @@ -206,4 +189,29 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2

type C2<T, V, E> =
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;

// 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 @@ -145,17 +115,37 @@ type B2<T, V> =

type C2<T, V, E> =
T extends object ? { [Q in keyof T]: C2<T[Q], V, E>; } : T;

// 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 @@ -196,32 +186,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 @@ -259,24 +238,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 @@ -304,3 +265,15 @@ declare type B2<T, V> = T extends object ? T extends any[] ? T : {
declare type C2<T, V, E> = T extends object ? {
[Q in keyof T]: C2<T[Q], V, E>;
} : T;
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 bc9b6d5

Please sign in to comment.