diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ecd3312fa30cd..8ea8237729b20 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11817,6 +11817,20 @@ namespace ts { return relation === definitelyAssignableRelation ? undefined : getConstraintOfType(type); } + function mayBeNever(type: Type): boolean { + if (type.flags & TypeFlags.Intersection) { + return some((type).types, mayBeNever); + } + if (type.flags & TypeFlags.Union) { + return every((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)) { @@ -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 (!(target).root.inferTypeParameters && + (!(target).root.isDistributive || !mayBeNever((target).checkType))) { + if (result = isRelatedTo(source, getTrueTypeFromConditionalType(target), reportErrors)) { + result &= isRelatedTo(source, getFalseTypeFromConditionalType(target), reportErrors); + } + if (result) { + return result; + } + } + } if (source.flags & TypeFlags.TypeVariable) { if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { diff --git a/tests/baselines/reference/conditionalTypes1.errors.txt b/tests/baselines/reference/conditionalTypes1.errors.txt index 7ba3a2aa9344a..d010ab96f1be0 100644 --- a/tests/baselines/reference/conditionalTypes1.errors.txt +++ b/tests/baselines/reference/conditionalTypes1.errors.txt @@ -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' is not assignable to type 'Pick'. 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' is not assignable to type 'Pick'. 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' 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. @@ -188,14 +200,18 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS !!! error TS2322: Type 'Pick' is not assignable to type 'Pick'. !!! 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' is not assignable to type 'Pick'. !!! 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(x: keyof T, y: FunctionPropertyNames, z: NonFunctionPropertyNames) { @@ -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 = diff --git a/tests/baselines/reference/conditionalTypes2.errors.txt b/tests/baselines/reference/conditionalTypes2.errors.txt index 49a36be96dbfd..8755b6765d1f4 100644 --- a/tests/baselines/reference/conditionalTypes2.errors.txt +++ b/tests/baselines/reference/conditionalTypes2.errors.txt @@ -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' 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'. +tests/cases/conformance/types/conditional/conditionalTypes2.ts(167,11): error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive'. -==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ==== +==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (10 errors) ==== interface Covariant { foo: T extends string ? T : number; } @@ -206,4 +209,34 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(75,12): error TS2 type C2 = T extends object ? { [Q in keyof T]: C2; } : T; + + // #26933 + + type Distributive = T extends {a: number} ? { a: number } : { b: number }; + + function testAssignabilityToConditionalType() { + 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 = o; + ~~ +!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive'. + // Distributive where T & string might instantiate to never: also no good + const o4: Distributive = o; + ~~ +!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type 'Distributive'. + // Distributive where {a: T} cannot instantiate to never: OK + const o5: Distributive<{a: T}> = o; + } \ No newline at end of file diff --git a/tests/baselines/reference/conditionalTypes2.js b/tests/baselines/reference/conditionalTypes2.js index 1f95e7eb8d9f2..aed9c43d993c6 100644 --- a/tests/baselines/reference/conditionalTypes2.js +++ b/tests/baselines/reference/conditionalTypes2.js @@ -145,6 +145,30 @@ type B2 = type C2 = T extends object ? { [Q in keyof T]: C2; } : T; + +// #26933 + +type Distributive = T extends {a: number} ? { a: number } : { b: number }; + +function testAssignabilityToConditionalType() { + 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 = o; + // Distributive where T & string might instantiate to never: also no good + const o4: Distributive = o; + // Distributive where {a: T} cannot instantiate to never: OK + const o5: Distributive<{a: T}> = o; +} //// [conditionalTypes2.js] @@ -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] @@ -304,3 +344,11 @@ declare type B2 = T extends object ? T extends any[] ? T : { declare type C2 = T extends object ? { [Q in keyof T]: C2; } : T; +declare type Distributive = T extends { + a: number; +} ? { + a: number; +} : { + b: number; +}; +declare function testAssignabilityToConditionalType(): void; diff --git a/tests/baselines/reference/conditionalTypes2.symbols b/tests/baselines/reference/conditionalTypes2.symbols index c52179e81b01c..8558b3063b6f6 100644 --- a/tests/baselines/reference/conditionalTypes2.symbols +++ b/tests/baselines/reference/conditionalTypes2.symbols @@ -551,3 +551,88 @@ type C2 = >E : Symbol(E, Decl(conditionalTypes2.ts, 144, 13)) >T : Symbol(T, Decl(conditionalTypes2.ts, 144, 8)) +// #26933 + +type Distributive = 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() { +>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 = 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 = 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)) +} + diff --git a/tests/baselines/reference/conditionalTypes2.types b/tests/baselines/reference/conditionalTypes2.types index b1fbfc1f4834d..15188cd820c78 100644 --- a/tests/baselines/reference/conditionalTypes2.types +++ b/tests/baselines/reference/conditionalTypes2.types @@ -341,3 +341,75 @@ type C2 = T extends object ? { [Q in keyof T]: C2; } : T; +// #26933 + +type Distributive = T extends {a: number} ? { a: number } : { b: number }; +>Distributive : Distributive +>a : number +>a : number +>b : number + +function testAssignabilityToConditionalType() { +>testAssignabilityToConditionalType : () => void + + const o = { a: 1, b: 2 }; +>o : { a: number; b: number; } +>{ a: 1, b: 2 } : { a: number; b: number; } +>a : number +>1 : 1 +>b : number +>2 : 2 + + const x: [T] extends [string] ? { y: number } : { a: number, b: number } = undefined!; +>x : [T] extends [string] ? { y: number; } : { a: number; b: number; } +>y : number +>a : number +>b : number +>undefined! : never +>undefined : undefined + + // Simple case: OK + const o1: [T] extends [number] ? { a: number } : { b: number } = o; +>o1 : [T] extends [number] ? { a: number; } : { b: number; } +>a : number +>b : number +>o : { a: number; b: number; } + + // Simple case where source happens to be a conditional type: also OK + const x1: [T] extends [number] +>x1 : [T] extends [number] ? [T] extends [string] ? { y: number; } : { a: number; } : [T] extends [string] ? { y: number; } : { b: number; } + + ? ([T] extends [string] ? { y: number } : { a: number }) +>y : number +>a : number + + : ([T] extends [string] ? { y: number } : { b: number }) +>y : number +>b : number + + = x; +>x : [T] extends [string] ? { y: number; } : { a: number; b: number; } + + // Infer type parameters: no good + const o2: [T] extends [[infer U]] ? U : { b: number } = o; +>o2 : [T] extends [[infer U]] ? U : { b: number; } +>b : number +>o : { a: number; b: number; } + + // Distributive where T might instantiate to never: no good + const o3: Distributive = o; +>o3 : Distributive +>o : { a: number; b: number; } + + // Distributive where T & string might instantiate to never: also no good + const o4: Distributive = o; +>o4 : Distributive +>o : { a: number; b: number; } + + // Distributive where {a: T} cannot instantiate to never: OK + const o5: Distributive<{a: T}> = o; +>o5 : Distributive<{ a: T; }> +>a : T +>o : { a: number; b: number; } +} + diff --git a/tests/cases/conformance/types/conditional/conditionalTypes2.ts b/tests/cases/conformance/types/conditional/conditionalTypes2.ts index be75894546944..99830fd090301 100644 --- a/tests/cases/conformance/types/conditional/conditionalTypes2.ts +++ b/tests/cases/conformance/types/conditional/conditionalTypes2.ts @@ -147,3 +147,27 @@ type B2 = type C2 = T extends object ? { [Q in keyof T]: C2; } : T; + +// #26933 + +type Distributive = T extends {a: number} ? { a: number } : { b: number }; + +function testAssignabilityToConditionalType() { + 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 = o; + // Distributive where T & string might instantiate to never: also no good + const o4: Distributive = o; + // Distributive where {a: T} cannot instantiate to never: OK + const o5: Distributive<{a: T}> = o; +}