Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 2643e65

Browse filesBrowse files
weswighamsandersn
andauthored
Add missing relationship allowing a type to be assignable to a conditional when assignable to both branches (microsoft#30639)
* Finally add that missing relationship allowing a type to be assignable to both branches of a conditional * Explicitly write out Ternary.Maybe * Add slightly modified example from microsoft#25413 * fix sick sentence * Loosen check to skip false branch constraint check to consider `infer` parameters as always satisfied in the extends clause * Simplify things a bit, only instantiate once Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
1 parent b2d1f53 commit 2643e65
Copy full SHA for 2643e65

14 files changed

+5,727Lines changed: 5727 additions & 0 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14607,6 +14607,13 @@ namespace ts {
1460714607
return type[cache] = type;
1460814608
}
1460914609

14610+
function isConditionalTypeAlwaysTrueDisregardingInferTypes(type: ConditionalType) {
14611+
const extendsInferParamMapper = type.root.inferTypeParameters && createTypeMapper(type.root.inferTypeParameters, map(type.root.inferTypeParameters, () => wildcardType));
14612+
const checkType = type.checkType;
14613+
const extendsType = type.extendsType;
14614+
return isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(instantiateType(extendsType, extendsInferParamMapper)));
14615+
}
14616+
1461014617
function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
1461114618
const checkType = type.checkType;
1461214619
const extendsType = type.extendsType;
@@ -18139,6 +18146,36 @@ namespace ts {
1813918146
}
1814018147
}
1814118148
}
18149+
else if (target.flags & TypeFlags.Conditional) {
18150+
const c = target as ConditionalType;
18151+
// Check if the conditional is always true or always false but still deferred for distribution purposes
18152+
const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType));
18153+
const skipFalse = !skipTrue && isConditionalTypeAlwaysTrueDisregardingInferTypes(c);
18154+
18155+
// Instantiate with a replacement mapper if the conditional is distributive, replacing the check type with a clone of itself,
18156+
// this way {x: string | number, y: string | number} -> (T extends T ? { x: T, y: T } : never) appropriately _fails_ when
18157+
// T = string | number (since that will end up distributing and producing `{x: string, y: string} | {x: number, y: number}`,
18158+
// to which `{x: string | number, y: string | number}` isn't assignable)
18159+
let distributionMapper: TypeMapper | undefined;
18160+
const checkVar = getActualTypeVariable(c.root.checkType);
18161+
if (c.root.isDistributive && checkVar.flags & TypeFlags.TypeParameter) {
18162+
const newParam = cloneTypeParameter(checkVar);
18163+
distributionMapper = prependTypeMapping(checkVar, newParam, c.mapper);
18164+
newParam.mapper = distributionMapper;
18165+
}
18166+
18167+
// TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't)
18168+
let localResult: Ternary | undefined;
18169+
if (skipTrue || (localResult = isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.trueType), distributionMapper) : getTrueTypeFromConditionalType(c), /*reportErrors*/ false))) {
18170+
if (!skipFalse) {
18171+
localResult = (localResult || Ternary.Maybe) & isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.falseType), distributionMapper) : getFalseTypeFromConditionalType(c), /*reportErrors*/ false);
18172+
}
18173+
}
18174+
if (localResult) {
18175+
resetErrorInfo(saveErrorInfo);
18176+
return localResult;
18177+
}
18178+
}
1814218179
else if (target.flags & TypeFlags.TemplateLiteral && source.flags & TypeFlags.StringLiteral) {
1814318180
if (isPatternLiteralType(target)) {
1814418181
// match all non-`string` segments
Collapse file
+149Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(28,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
2+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(29,21): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
3+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(39,3): error TS2322: Type '{ x: T; y: T; }' is not assignable to type 'T extends T ? { x: T; y: T; } : never'.
4+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(46,3): error TS2322: Type 'string' is not assignable to type 'Foo<T>'.
5+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(63,9): error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'.
6+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(95,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Unwrap<this["prop"]>'.
7+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(106,3): error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNot<Q>'.
8+
Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNot<Q>'.
9+
tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNotDistributive<Q>'.
10+
Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive<Q>'.
11+
12+
13+
==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (8 errors) ====
14+
export type FilterPropsByType<T, TT> = {
15+
[K in keyof T]: T[K] extends TT ? K : never
16+
}[keyof T];
17+
18+
function select<
19+
T extends string | number,
20+
TList extends object,
21+
TValueProp extends FilterPropsByType<TList, T>
22+
>(property: T, list: TList[], valueProp: TValueProp) {}
23+
24+
export function func<XX extends string>(x: XX, tipos: { value: XX }[]) {
25+
select(x, tipos, "value");
26+
}
27+
28+
declare function onlyNullablePlease<T extends null extends T ? any : never>(
29+
value: T
30+
): void;
31+
32+
declare function onlyNullablePlease2<
33+
T extends [null] extends [T] ? any : never
34+
>(value: T): void;
35+
36+
declare var z: string | null;
37+
onlyNullablePlease(z); // works as expected
38+
onlyNullablePlease2(z); // works as expected
39+
40+
declare var y: string;
41+
onlyNullablePlease(y); // error as expected
42+
~
43+
!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
44+
onlyNullablePlease2(y); // error as expected
45+
~
46+
!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
47+
48+
function f<T>(t: T) {
49+
var x: T | null = Math.random() > 0.5 ? null : t;
50+
onlyNullablePlease(x); // should work
51+
onlyNullablePlease2(x); // should work
52+
}
53+
54+
function f2<T>(t1: { x: T; y: T }, t2: T extends T ? { x: T; y: T } : never) {
55+
t1 = t2; // OK
56+
t2 = t1; // should fail
57+
~~
58+
!!! error TS2322: Type '{ x: T; y: T; }' is not assignable to type 'T extends T ? { x: T; y: T; } : never'.
59+
}
60+
61+
type Foo<T> = T extends true ? string : "a";
62+
63+
function test<T>(x: Foo<T>, s: string) {
64+
x = "a"; // Currently an error, should be ok
65+
x = s; // Error
66+
~
67+
!!! error TS2322: Type 'string' is not assignable to type 'Foo<T>'.
68+
}
69+
70+
// #26933
71+
type Distributive<T> = T extends { a: number } ? { a: number } : { b: number };
72+
function testAssignabilityToConditionalType<T>() {
73+
const o = { a: 1, b: 2 };
74+
const x: [T] extends [string]
75+
? { y: number }
76+
: { a: number; b: number } = undefined!;
77+
// Simple case: OK
78+
const o1: [T] extends [number] ? { a: number } : { b: number } = o;
79+
// Simple case where source happens to be a conditional type: also OK
80+
const x1: [T] extends [number]
81+
? ([T] extends [string] ? { y: number } : { a: number })
82+
: ([T] extends [string] ? { y: number } : { b: number }) = x;
83+
// Infer type parameters: no good
84+
const o2: [T] extends [[infer U]] ? U : { b: number } = o;
85+
~~
86+
!!! error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'.
87+
88+
// The next 4 are arguable - if you choose to ignore the `never` distribution case,
89+
// then they're all good. The `never` case _is_ a bit of an outlier - we say distributive types
90+
// look approximately like the sum of their branches, but the `never` case bucks that.
91+
// There's an argument for the result of dumping `never` into a distributive conditional
92+
// being not `never`, but instead the intersection of the branches - a much more precise bound
93+
// on that "impossible" input.
94+
95+
// Distributive where T might instantiate to never: no good
96+
const o3: Distributive<T> = o;
97+
// Distributive where T & string might instantiate to never: also no good
98+
const o4: Distributive<T & string> = o;
99+
// Distributive where {a: T} cannot instantiate to never: OK
100+
const o5: Distributive<{ a: T }> = o;
101+
// Distributive where check type is a conditional which returns a non-never type upon instantiation with `never` but can still return never otherwise: no good
102+
const o6: Distributive<[T] extends [never] ? { a: number } : never> = o;
103+
}
104+
105+
type Wrapped<T> = { ___secret: T };
106+
type Unwrap<T> = T extends Wrapped<infer U> ? U : T;
107+
108+
declare function set<T, K extends keyof T>(
109+
obj: T,
110+
key: K,
111+
value: Unwrap<T[K]>
112+
): Unwrap<T[K]>;
113+
114+
class Foo2 {
115+
prop!: Wrapped<string>;
116+
117+
method() {
118+
set(this, "prop", "hi"); // <-- type error
119+
~~~~
120+
!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'Unwrap<this["prop"]>'.
121+
}
122+
}
123+
124+
set(new Foo2(), "prop", "hi"); // <-- typechecks
125+
126+
type InferBecauseWhyNot<T> = [T] extends [(p: infer P1) => any]
127+
? P1 | T
128+
: never;
129+
130+
function f3<Q extends (arg: any) => any>(x: Q): InferBecauseWhyNot<Q> {
131+
return x;
132+
~~~~~~~~~
133+
!!! error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNot<Q>'.
134+
!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNot<Q>'.
135+
}
136+
137+
type InferBecauseWhyNotDistributive<T> = T extends (p: infer P1) => any
138+
? P1 | T
139+
: never;
140+
141+
function f4<Q extends (arg: any) => any>(
142+
x: Q
143+
): InferBecauseWhyNotDistributive<Q> {
144+
return x; // should fail
145+
~~~~~~~~~
146+
!!! error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNotDistributive<Q>'.
147+
!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive<Q>'.
148+
}
149+
Collapse file
+185Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//// [conditionalTypeAssignabilityWhenDeferred.ts]
2+
export type FilterPropsByType<T, TT> = {
3+
[K in keyof T]: T[K] extends TT ? K : never
4+
}[keyof T];
5+
6+
function select<
7+
T extends string | number,
8+
TList extends object,
9+
TValueProp extends FilterPropsByType<TList, T>
10+
>(property: T, list: TList[], valueProp: TValueProp) {}
11+
12+
export function func<XX extends string>(x: XX, tipos: { value: XX }[]) {
13+
select(x, tipos, "value");
14+
}
15+
16+
declare function onlyNullablePlease<T extends null extends T ? any : never>(
17+
value: T
18+
): void;
19+
20+
declare function onlyNullablePlease2<
21+
T extends [null] extends [T] ? any : never
22+
>(value: T): void;
23+
24+
declare var z: string | null;
25+
onlyNullablePlease(z); // works as expected
26+
onlyNullablePlease2(z); // works as expected
27+
28+
declare var y: string;
29+
onlyNullablePlease(y); // error as expected
30+
onlyNullablePlease2(y); // error as expected
31+
32+
function f<T>(t: T) {
33+
var x: T | null = Math.random() > 0.5 ? null : t;
34+
onlyNullablePlease(x); // should work
35+
onlyNullablePlease2(x); // should work
36+
}
37+
38+
function f2<T>(t1: { x: T; y: T }, t2: T extends T ? { x: T; y: T } : never) {
39+
t1 = t2; // OK
40+
t2 = t1; // should fail
41+
}
42+
43+
type Foo<T> = T extends true ? string : "a";
44+
45+
function test<T>(x: Foo<T>, s: string) {
46+
x = "a"; // Currently an error, should be ok
47+
x = s; // Error
48+
}
49+
50+
// #26933
51+
type Distributive<T> = T extends { a: number } ? { a: number } : { b: number };
52+
function testAssignabilityToConditionalType<T>() {
53+
const o = { a: 1, b: 2 };
54+
const x: [T] extends [string]
55+
? { y: number }
56+
: { a: number; b: number } = undefined!;
57+
// Simple case: OK
58+
const o1: [T] extends [number] ? { a: number } : { b: number } = o;
59+
// Simple case where source happens to be a conditional type: also OK
60+
const x1: [T] extends [number]
61+
? ([T] extends [string] ? { y: number } : { a: number })
62+
: ([T] extends [string] ? { y: number } : { b: number }) = x;
63+
// Infer type parameters: no good
64+
const o2: [T] extends [[infer U]] ? U : { b: number } = o;
65+
66+
// The next 4 are arguable - if you choose to ignore the `never` distribution case,
67+
// then they're all good. The `never` case _is_ a bit of an outlier - we say distributive types
68+
// look approximately like the sum of their branches, but the `never` case bucks that.
69+
// There's an argument for the result of dumping `never` into a distributive conditional
70+
// being not `never`, but instead the intersection of the branches - a much more precise bound
71+
// on that "impossible" input.
72+
73+
// Distributive where T might instantiate to never: no good
74+
const o3: Distributive<T> = o;
75+
// Distributive where T & string might instantiate to never: also no good
76+
const o4: Distributive<T & string> = o;
77+
// Distributive where {a: T} cannot instantiate to never: OK
78+
const o5: Distributive<{ a: T }> = o;
79+
// Distributive where check type is a conditional which returns a non-never type upon instantiation with `never` but can still return never otherwise: no good
80+
const o6: Distributive<[T] extends [never] ? { a: number } : never> = o;
81+
}
82+
83+
type Wrapped<T> = { ___secret: T };
84+
type Unwrap<T> = T extends Wrapped<infer U> ? U : T;
85+
86+
declare function set<T, K extends keyof T>(
87+
obj: T,
88+
key: K,
89+
value: Unwrap<T[K]>
90+
): Unwrap<T[K]>;
91+
92+
class Foo2 {
93+
prop!: Wrapped<string>;
94+
95+
method() {
96+
set(this, "prop", "hi"); // <-- type error
97+
}
98+
}
99+
100+
set(new Foo2(), "prop", "hi"); // <-- typechecks
101+
102+
type InferBecauseWhyNot<T> = [T] extends [(p: infer P1) => any]
103+
? P1 | T
104+
: never;
105+
106+
function f3<Q extends (arg: any) => any>(x: Q): InferBecauseWhyNot<Q> {
107+
return x;
108+
}
109+
110+
type InferBecauseWhyNotDistributive<T> = T extends (p: infer P1) => any
111+
? P1 | T
112+
: never;
113+
114+
function f4<Q extends (arg: any) => any>(
115+
x: Q
116+
): InferBecauseWhyNotDistributive<Q> {
117+
return x; // should fail
118+
}
119+
120+
121+
//// [conditionalTypeAssignabilityWhenDeferred.js]
122+
"use strict";
123+
exports.__esModule = true;
124+
exports.func = void 0;
125+
function select(property, list, valueProp) { }
126+
function func(x, tipos) {
127+
select(x, tipos, "value");
128+
}
129+
exports.func = func;
130+
onlyNullablePlease(z); // works as expected
131+
onlyNullablePlease2(z); // works as expected
132+
onlyNullablePlease(y); // error as expected
133+
onlyNullablePlease2(y); // error as expected
134+
function f(t) {
135+
var x = Math.random() > 0.5 ? null : t;
136+
onlyNullablePlease(x); // should work
137+
onlyNullablePlease2(x); // should work
138+
}
139+
function f2(t1, t2) {
140+
t1 = t2; // OK
141+
t2 = t1; // should fail
142+
}
143+
function test(x, s) {
144+
x = "a"; // Currently an error, should be ok
145+
x = s; // Error
146+
}
147+
function testAssignabilityToConditionalType() {
148+
var o = { a: 1, b: 2 };
149+
var x = undefined;
150+
// Simple case: OK
151+
var o1 = o;
152+
// Simple case where source happens to be a conditional type: also OK
153+
var x1 = x;
154+
// Infer type parameters: no good
155+
var o2 = o;
156+
// The next 4 are arguable - if you choose to ignore the `never` distribution case,
157+
// then they're all good. The `never` case _is_ a bit of an outlier - we say distributive types
158+
// look approximately like the sum of their branches, but the `never` case bucks that.
159+
// There's an argument for the result of dumping `never` into a distributive conditional
160+
// being not `never`, but instead the intersection of the branches - a much more precise bound
161+
// on that "impossible" input.
162+
// Distributive where T might instantiate to never: no good
163+
var o3 = o;
164+
// Distributive where T & string might instantiate to never: also no good
165+
var o4 = o;
166+
// Distributive where {a: T} cannot instantiate to never: OK
167+
var o5 = o;
168+
// Distributive where check type is a conditional which returns a non-never type upon instantiation with `never` but can still return never otherwise: no good
169+
var o6 = o;
170+
}
171+
var Foo2 = /** @class */ (function () {
172+
function Foo2() {
173+
}
174+
Foo2.prototype.method = function () {
175+
set(this, "prop", "hi"); // <-- type error
176+
};
177+
return Foo2;
178+
}());
179+
set(new Foo2(), "prop", "hi"); // <-- typechecks
180+
function f3(x) {
181+
return x;
182+
}
183+
function f4(x) {
184+
return x; // should fail
185+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.