Skip to content

Add rule that a type is assignable to A extends B ? C : D if it is assignable to both C and D. #27589

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

Closed
wants to merge 1 commit into from
Closed
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
29 changes: 29 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11817,6 +11817,20 @@ namespace ts {
return relation === definitelyAssignableRelation ? undefined : getConstraintOfType(type);
}

function mayBeNever(type: Type): boolean {
if (type.flags & TypeFlags.Intersection) {
return some((<IntersectionType>type).types, mayBeNever);
}
if (type.flags & TypeFlags.Union) {
return every((<UnionType>type).types, mayBeNever);
}
if (type.flags & TypeFlags.Instantiable) {
// Maybe we could be smarter in some cases.
return true;
}
return !!(type.flags & TypeFlags.Never);
}

function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
const flags = source.flags & target.flags;
if (relation === identityRelation && !(flags & TypeFlags.Object)) {
Expand Down Expand Up @@ -11918,6 +11932,21 @@ namespace ts {
}
}
}
else if (target.flags & TypeFlags.Conditional) {
// A type T1 is related to a conditional type 'T2 extends U2 ? X2 : Y2' if the
// conditional type has no infer type parameters, T1 is related to both X2 and
// Y2, and (the conditional type is non-distributive or we know that T2 cannot
// instantiate to never).
if (!(<ConditionalType>target).root.inferTypeParameters &&
(!(<ConditionalType>target).root.isDistributive || !mayBeNever((<ConditionalType>target).checkType))) {
if (result = isRelatedTo(source, getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
result &= isRelatedTo(source, getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
}
if (result) {
return result;
}
}
}

if (source.flags & TypeFlags.TypeVariable) {
if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
Expand Down
40 changes: 32 additions & 8 deletions tests/baselines/reference/conditionalTypes1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,35 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(104,5): error TS2
tests/cases/conformance/types/conditional/conditionalTypes1.ts(106,5): error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>'.
Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'keyof T' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'never'.
Type 'keyof T' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(108,5): error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>'.
Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'keyof T' is not assignable to type 'never'.
Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'never'.
Type 'keyof T' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(114,5): error TS2322: Type 'keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'string | number | symbol' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'string' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'string' is not assignable to type 'keyof T'.
Type 'keyof T' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(115,5): error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'keyof T' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'never'.
Type 'keyof T' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(116,5): error TS2322: Type 'keyof T' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'string | number | symbol' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'string' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'string' is not assignable to type 'never'.
Type 'keyof T' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(117,5): error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'keyof T' is not assignable to type 'never'.
Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'never'.
Type 'keyof T' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(134,10): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(135,5): error TS2542: Index signature in type 'DeepReadonlyArray<Part>' only permits reading.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(136,22): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
Expand Down Expand Up @@ -188,14 +200,18 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
!!! error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>'.
!!! error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'never'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
!!! error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'never'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
z = x;
z = y; // Error
~
!!! error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>'.
!!! error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
}

function f8<T>(x: keyof T, y: FunctionPropertyNames<T>, z: NonFunctionPropertyNames<T>) {
Expand All @@ -206,21 +222,29 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
!!! error TS2322: Type 'keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'string' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'string' is not assignable to type 'keyof T'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'never'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
y = z; // Error
~
!!! error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'never'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
!!! error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
z = x; // Error
~
!!! error TS2322: Type 'keyof T' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'string' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
z = y; // Error
~
!!! error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
}

type DeepReadonly<T> =
Expand Down
35 changes: 34 additions & 1 deletion tests/baselines/reference/conditionalTypes2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(74,12): error TS2
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; }'.
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(163,11): error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(165,11): error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive<T>'.
tests/cases/conformance/types/conditional/conditionalTypes2.ts(167,11): error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive<T & string>'.


==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ====
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (10 errors) ====
interface Covariant<T> {
foo: T extends string ? T : number;
}
Expand Down Expand Up @@ -206,4 +209,34 @@ 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;

// #26933

type Distributive<T> = T extends {a: number} ? { a: number } : { b: number };

function testAssignabilityToConditionalType<T>() {
const o = { a: 1, b: 2 };
const x: [T] extends [string] ? { y: number } : { a: number, b: number } = undefined!;
// Simple case: OK
const o1: [T] extends [number] ? { a: number } : { b: number } = o;
// Simple case where source happens to be a conditional type: also OK
const x1: [T] extends [number]
? ([T] extends [string] ? { y: number } : { a: number })
: ([T] extends [string] ? { y: number } : { b: number })
= x;
// Infer type parameters: no good
const o2: [T] extends [[infer U]] ? U : { b: number } = o;
~~
!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'.
// Distributive where T might instantiate to never: no good
const o3: Distributive<T> = o;
~~
!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive<T>'.
// Distributive where T & string might instantiate to never: also no good
const o4: Distributive<T & string> = o;
~~
!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive<T & string>'.
// Distributive where {a: T} cannot instantiate to never: OK
const o5: Distributive<{a: T}> = o;
}

48 changes: 48 additions & 0 deletions tests/baselines/reference/conditionalTypes2.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ type B2<T, V> =

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

// #26933

type Distributive<T> = T extends {a: number} ? { a: number } : { b: number };

function testAssignabilityToConditionalType<T>() {
const o = { a: 1, b: 2 };
const x: [T] extends [string] ? { y: number } : { a: number, b: number } = undefined!;
// Simple case: OK
const o1: [T] extends [number] ? { a: number } : { b: number } = o;
// Simple case where source happens to be a conditional type: also OK
const x1: [T] extends [number]
? ([T] extends [string] ? { y: number } : { a: number })
: ([T] extends [string] ? { y: number } : { b: number })
= x;
// Infer type parameters: no good
const o2: [T] extends [[infer U]] ? U : { b: number } = o;
// Distributive where T might instantiate to never: no good
const o3: Distributive<T> = o;
// Distributive where T & string might instantiate to never: also no good
const o4: Distributive<T & string> = o;
// Distributive where {a: T} cannot instantiate to never: OK
const o5: Distributive<{a: T}> = o;
}


//// [conditionalTypes2.js]
Expand Down Expand Up @@ -222,6 +246,22 @@ function foo(value) {
toString2(value);
}
}
function testAssignabilityToConditionalType() {
var o = { a: 1, b: 2 };
var x = undefined;
// Simple case: OK
var o1 = o;
// Simple case where source happens to be a conditional type: also OK
var x1 = x;
// Infer type parameters: no good
var o2 = o;
// Distributive where T might instantiate to never: no good
var o3 = o;
// Distributive where T & string might instantiate to never: also no good
var o4 = o;
// Distributive where {a: T} cannot instantiate to never: OK
var o5 = o;
}


//// [conditionalTypes2.d.ts]
Expand Down Expand Up @@ -304,3 +344,11 @@ 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 Distributive<T> = T extends {
a: number;
} ? {
a: number;
} : {
b: number;
};
declare function testAssignabilityToConditionalType<T>(): void;
85 changes: 85 additions & 0 deletions tests/baselines/reference/conditionalTypes2.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,88 @@ type C2<T, V, E> =
>E : Symbol(E, Decl(conditionalTypes2.ts, 144, 13))
>T : Symbol(T, Decl(conditionalTypes2.ts, 144, 8))

// #26933

type Distributive<T> = T extends {a: number} ? { a: number } : { b: number };
>Distributive : Symbol(Distributive, Decl(conditionalTypes2.ts, 145, 63))
>T : Symbol(T, Decl(conditionalTypes2.ts, 149, 18))
>T : Symbol(T, Decl(conditionalTypes2.ts, 149, 18))
>a : Symbol(a, Decl(conditionalTypes2.ts, 149, 34))
>a : Symbol(a, Decl(conditionalTypes2.ts, 149, 48))
>b : Symbol(b, Decl(conditionalTypes2.ts, 149, 64))

function testAssignabilityToConditionalType<T>() {
>testAssignabilityToConditionalType : Symbol(testAssignabilityToConditionalType, Decl(conditionalTypes2.ts, 149, 77))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))

const o = { a: 1, b: 2 };
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))
>a : Symbol(a, Decl(conditionalTypes2.ts, 152, 15))
>b : Symbol(b, Decl(conditionalTypes2.ts, 152, 21))

const x: [T] extends [string] ? { y: number } : { a: number, b: number } = undefined!;
>x : Symbol(x, Decl(conditionalTypes2.ts, 153, 9))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>y : Symbol(y, Decl(conditionalTypes2.ts, 153, 37))
>a : Symbol(a, Decl(conditionalTypes2.ts, 153, 53))
>b : Symbol(b, Decl(conditionalTypes2.ts, 153, 64))
>undefined : Symbol(undefined)

// Simple case: OK
const o1: [T] extends [number] ? { a: number } : { b: number } = o;
>o1 : Symbol(o1, Decl(conditionalTypes2.ts, 155, 9))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>a : Symbol(a, Decl(conditionalTypes2.ts, 155, 38))
>b : Symbol(b, Decl(conditionalTypes2.ts, 155, 54))
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))

// Simple case where source happens to be a conditional type: also OK
const x1: [T] extends [number]
>x1 : Symbol(x1, Decl(conditionalTypes2.ts, 157, 9))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))

? ([T] extends [string] ? { y: number } : { a: number })
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>y : Symbol(y, Decl(conditionalTypes2.ts, 158, 35))
>a : Symbol(a, Decl(conditionalTypes2.ts, 158, 51))

: ([T] extends [string] ? { y: number } : { b: number })
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>y : Symbol(y, Decl(conditionalTypes2.ts, 159, 35))
>b : Symbol(b, Decl(conditionalTypes2.ts, 159, 51))

= x;
>x : Symbol(x, Decl(conditionalTypes2.ts, 153, 9))

// Infer type parameters: no good
const o2: [T] extends [[infer U]] ? U : { b: number } = o;
>o2 : Symbol(o2, Decl(conditionalTypes2.ts, 162, 9))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>U : Symbol(U, Decl(conditionalTypes2.ts, 162, 33))
>U : Symbol(U, Decl(conditionalTypes2.ts, 162, 33))
>b : Symbol(b, Decl(conditionalTypes2.ts, 162, 45))
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))

// Distributive where T might instantiate to never: no good
const o3: Distributive<T> = o;
>o3 : Symbol(o3, Decl(conditionalTypes2.ts, 164, 9))
>Distributive : Symbol(Distributive, Decl(conditionalTypes2.ts, 145, 63))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))

// Distributive where T & string might instantiate to never: also no good
const o4: Distributive<T & string> = o;
>o4 : Symbol(o4, Decl(conditionalTypes2.ts, 166, 9))
>Distributive : Symbol(Distributive, Decl(conditionalTypes2.ts, 145, 63))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))

// Distributive where {a: T} cannot instantiate to never: OK
const o5: Distributive<{a: T}> = o;
>o5 : Symbol(o5, Decl(conditionalTypes2.ts, 168, 9))
>Distributive : Symbol(Distributive, Decl(conditionalTypes2.ts, 145, 63))
>a : Symbol(a, Decl(conditionalTypes2.ts, 168, 28))
>T : Symbol(T, Decl(conditionalTypes2.ts, 151, 44))
>o : Symbol(o, Decl(conditionalTypes2.ts, 152, 9))
}

Loading