Skip to content

Commit b382952

Browse files
authored
Merge pull request #23039 from Microsoft/fixConditionalConstraints
Improve conditional type constraints
2 parents 9b558f9 + 3d91736 commit b382952

File tree

7 files changed

+806
-76
lines changed

7 files changed

+806
-76
lines changed

src/compiler/checker.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6245,7 +6245,12 @@ namespace ts {
62456245
}
62466246

62476247
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
6248-
return getUnionType([getInferredTrueTypeFromConditionalType(type), getFalseTypeFromConditionalType(type)]);
6248+
if (!type.resolvedDefaultConstraint) {
6249+
const rootTrueType = type.root.trueType;
6250+
const rootTrueConstraint = rootTrueType.flags & TypeFlags.Substitution ? (<SubstitutionType>rootTrueType).substitute : rootTrueType;
6251+
type.resolvedDefaultConstraint = getUnionType([instantiateType(rootTrueConstraint, type.combinedMapper || type.mapper), getFalseTypeFromConditionalType(type)]);
6252+
}
6253+
return type.resolvedDefaultConstraint;
62496254
}
62506255

62516256
function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type {
@@ -6258,7 +6263,10 @@ namespace ts {
62586263
const constraint = getConstraintOfType(type.checkType);
62596264
if (constraint) {
62606265
const mapper = createTypeMapper([<TypeParameter>type.root.checkType], [constraint]);
6261-
return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
6266+
const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
6267+
if (!(instantiated.flags & TypeFlags.Never)) {
6268+
return instantiated;
6269+
}
62626270
}
62636271
}
62646272
return undefined;
@@ -8410,12 +8418,6 @@ namespace ts {
84108418
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
84118419
}
84128420

8413-
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
8414-
return type.combinedMapper ?
8415-
type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper)) :
8416-
getTrueTypeFromConditionalType(type);
8417-
}
8418-
84198421
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] {
84208422
let result: TypeParameter[];
84218423
if (node.locals) {
@@ -8428,13 +8430,25 @@ namespace ts {
84288430
return result;
84298431
}
84308432

8433+
function getTopConditionalType(node: Node): ConditionalTypeNode {
8434+
let result: ConditionalTypeNode;
8435+
while (node) {
8436+
if (node.kind === SyntaxKind.ConditionalType) {
8437+
result = <ConditionalTypeNode>node;
8438+
}
8439+
node = node.parent;
8440+
}
8441+
return result;
8442+
}
8443+
84318444
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
84328445
const links = getNodeLinks(node);
84338446
if (!links.resolvedType) {
84348447
const checkType = getTypeFromTypeNode(node.checkType);
84358448
const aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
84368449
const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true);
8437-
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node));
8450+
const topNode = getTopConditionalType(node);
8451+
const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, topNode));
84388452
const root: ConditionalRoot = {
84398453
node,
84408454
checkType,

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3882,7 +3882,7 @@ namespace ts {
38823882
resolvedTrueType?: Type;
38833883
resolvedFalseType?: Type;
38843884
/* @internal */
3885-
resolvedInferredTrueType?: Type;
3885+
resolvedDefaultConstraint?: Type;
38863886
/* @internal */
38873887
mapper?: TypeMapper;
38883888
/* @internal */

tests/baselines/reference/conditionalTypes2.errors.txt

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,21 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS23
1010
Types of property 'foo' are incompatible.
1111
Type 'A extends string ? keyof A : A' is not assignable to type 'B extends string ? keyof B : B'.
1212
Type 'A' is not assignable to type 'B'.
13+
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; }'.
14+
Type 'Bar & Extract<T, Foo>' is not assignable to type '{ foo: string; bat: string; }'.
15+
Property 'bat' is missing in type 'Bar & Foo'.
16+
Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
17+
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
18+
Property 'bat' is missing in type 'Bar & Foo'.
19+
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; }'.
20+
Type 'Foo & Bar & T' is not assignable to type '{ foo: string; bat: string; }'.
21+
Property 'bat' is missing in type 'Foo & Bar'.
22+
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; }'.
23+
Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
24+
Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
1325

1426

15-
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (4 errors) ====
27+
==== tests/cases/conformance/types/conditional/conditionalTypes2.ts (7 errors) ====
1628
interface Covariant<T> {
1729
foo: T extends string ? T : number;
1830
}
@@ -56,6 +68,71 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS23
5668
!!! error TS2322: Type 'A' is not assignable to type 'B'.
5769
}
5870

71+
// Extract<T, Function> is a T that is known to be a Function
72+
function isFunction<T>(value: T): value is Extract<T, Function> {
73+
return typeof value === "function";
74+
}
75+
76+
function getFunction<T>(item: T) {
77+
if (isFunction(item)) {
78+
return item;
79+
}
80+
throw new Error();
81+
}
82+
83+
function f10<T>(x: T) {
84+
if (isFunction(x)) {
85+
const f: Function = x;
86+
const t: T = x;
87+
}
88+
}
89+
90+
function f11(x: string | (() => string) | undefined) {
91+
if (isFunction(x)) {
92+
x();
93+
}
94+
}
95+
96+
function f12(x: string | (() => string) | undefined) {
97+
const f = getFunction(x); // () => string
98+
f();
99+
}
100+
101+
type Foo = { foo: string };
102+
type Bar = { bar: string };
103+
104+
declare function fooBar(x: { foo: string, bar: string }): void;
105+
declare function fooBat(x: { foo: string, bat: string }): void;
106+
107+
type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
108+
109+
function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>) {
110+
fooBar(x);
111+
fooBar(y);
112+
fooBar(z);
113+
}
114+
115+
function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>) {
116+
fooBat(x); // Error
117+
~
118+
!!! error TS2345: Argument of type 'Extract<Extract<T, Foo>, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
119+
!!! error TS2345: Type 'Bar & Extract<T, Foo>' is not assignable to type '{ foo: string; bat: string; }'.
120+
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo'.
121+
!!! error TS2345: Type 'Extract<Foo & T, Bar>' is not assignable to type '{ foo: string; bat: string; }'.
122+
!!! error TS2345: Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
123+
!!! error TS2345: Property 'bat' is missing in type 'Bar & Foo'.
124+
fooBat(y); // Error
125+
~
126+
!!! error TS2345: Argument of type 'Extract<T, Foo & Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
127+
!!! error TS2345: Type 'Foo & Bar & T' is not assignable to type '{ foo: string; bat: string; }'.
128+
!!! error TS2345: Property 'bat' is missing in type 'Foo & Bar'.
129+
fooBat(z); // Error
130+
~
131+
!!! error TS2345: Argument of type 'Extract2<T, Foo, Bar>' is not assignable to parameter of type '{ foo: string; bat: string; }'.
132+
!!! error TS2345: Type 'T extends Bar ? T : never' is not assignable to type '{ foo: string; bat: string; }'.
133+
!!! error TS2345: Type 'Bar & Foo & T' is not assignable to type '{ foo: string; bat: string; }'.
134+
}
135+
59136
// Repros from #22860
60137

61138
class Opt<T> {
@@ -87,4 +164,16 @@ tests/cases/conformance/types/conditional/conditionalTypes2.ts(25,5): error TS23
87164
bat: B1<B1<T>>;
88165
boom: T extends any ? true : true
89166
}
167+
168+
// Repro from #22899
169+
170+
declare function toString1(value: object | Function): string ;
171+
declare function toString2(value: Function): string ;
172+
173+
function foo<T>(value: T) {
174+
if (isFunction(value)) {
175+
toString1(value);
176+
toString2(value);
177+
}
178+
}
90179

tests/baselines/reference/conditionalTypes2.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,56 @@ function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>) {
2626
b = a; // Error
2727
}
2828

29+
// Extract<T, Function> is a T that is known to be a Function
30+
function isFunction<T>(value: T): value is Extract<T, Function> {
31+
return typeof value === "function";
32+
}
33+
34+
function getFunction<T>(item: T) {
35+
if (isFunction(item)) {
36+
return item;
37+
}
38+
throw new Error();
39+
}
40+
41+
function f10<T>(x: T) {
42+
if (isFunction(x)) {
43+
const f: Function = x;
44+
const t: T = x;
45+
}
46+
}
47+
48+
function f11(x: string | (() => string) | undefined) {
49+
if (isFunction(x)) {
50+
x();
51+
}
52+
}
53+
54+
function f12(x: string | (() => string) | undefined) {
55+
const f = getFunction(x); // () => string
56+
f();
57+
}
58+
59+
type Foo = { foo: string };
60+
type Bar = { bar: string };
61+
62+
declare function fooBar(x: { foo: string, bar: string }): void;
63+
declare function fooBat(x: { foo: string, bat: string }): void;
64+
65+
type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
66+
67+
function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>) {
68+
fooBar(x);
69+
fooBar(y);
70+
fooBar(z);
71+
}
72+
73+
function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>) {
74+
fooBat(x); // Error
75+
fooBat(y); // Error
76+
fooBat(z); // Error
77+
}
78+
2979
// Repros from #22860
3080

3181
class Opt<T> {
@@ -57,6 +107,18 @@ interface B1<T> extends A1<T> {
57107
bat: B1<B1<T>>;
58108
boom: T extends any ? true : true
59109
}
110+
111+
// Repro from #22899
112+
113+
declare function toString1(value: object | Function): string ;
114+
declare function toString2(value: Function): string ;
115+
116+
function foo<T>(value: T) {
117+
if (isFunction(value)) {
118+
toString1(value);
119+
toString2(value);
120+
}
121+
}
60122

61123

62124
//// [conditionalTypes2.js]
@@ -73,6 +135,41 @@ function f3(a, b) {
73135
a = b; // Error
74136
b = a; // Error
75137
}
138+
// Extract<T, Function> is a T that is known to be a Function
139+
function isFunction(value) {
140+
return typeof value === "function";
141+
}
142+
function getFunction(item) {
143+
if (isFunction(item)) {
144+
return item;
145+
}
146+
throw new Error();
147+
}
148+
function f10(x) {
149+
if (isFunction(x)) {
150+
var f = x;
151+
var t = x;
152+
}
153+
}
154+
function f11(x) {
155+
if (isFunction(x)) {
156+
x();
157+
}
158+
}
159+
function f12(x) {
160+
var f = getFunction(x); // () => string
161+
f();
162+
}
163+
function f20(x, y, z) {
164+
fooBar(x);
165+
fooBar(y);
166+
fooBar(z);
167+
}
168+
function f21(x, y, z) {
169+
fooBat(x); // Error
170+
fooBat(y); // Error
171+
fooBat(z); // Error
172+
}
76173
// Repros from #22860
77174
var Opt = /** @class */ (function () {
78175
function Opt() {
@@ -93,6 +190,12 @@ var Vector = /** @class */ (function () {
93190
};
94191
return Vector;
95192
}());
193+
function foo(value) {
194+
if (isFunction(value)) {
195+
toString1(value);
196+
toString2(value);
197+
}
198+
}
96199

97200

98201
//// [conditionalTypes2.d.ts]
@@ -108,6 +211,28 @@ interface Invariant<T> {
108211
declare function f1<A, B extends A>(a: Covariant<A>, b: Covariant<B>): void;
109212
declare function f2<A, B extends A>(a: Contravariant<A>, b: Contravariant<B>): void;
110213
declare function f3<A, B extends A>(a: Invariant<A>, b: Invariant<B>): void;
214+
declare function isFunction<T>(value: T): value is Extract<T, Function>;
215+
declare function getFunction<T>(item: T): Extract<T, Function>;
216+
declare function f10<T>(x: T): void;
217+
declare function f11(x: string | (() => string) | undefined): void;
218+
declare function f12(x: string | (() => string) | undefined): void;
219+
declare type Foo = {
220+
foo: string;
221+
};
222+
declare type Bar = {
223+
bar: string;
224+
};
225+
declare function fooBar(x: {
226+
foo: string;
227+
bar: string;
228+
}): void;
229+
declare function fooBat(x: {
230+
foo: string;
231+
bat: string;
232+
}): void;
233+
declare type Extract2<T, U, V> = T extends U ? T extends V ? T : never : never;
234+
declare function f20<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
235+
declare function f21<T>(x: Extract<Extract<T, Foo>, Bar>, y: Extract<T, Foo & Bar>, z: Extract2<T, Foo, Bar>): void;
111236
declare class Opt<T> {
112237
toVector(): Vector<T>;
113238
}
@@ -126,3 +251,6 @@ interface B1<T> extends A1<T> {
126251
bat: B1<B1<T>>;
127252
boom: T extends any ? true : true;
128253
}
254+
declare function toString1(value: object | Function): string;
255+
declare function toString2(value: Function): string;
256+
declare function foo<T>(value: T): void;

0 commit comments

Comments
 (0)