Skip to content

Commit 4d4ea66

Browse files
authored
update contextual discrimination to include omitted members (#43633)
This diff extends the types checked by discriminateContextualTypeByObjectMembers and discriminateContextualTypeByJSXAttributes to also include any optional components in the type union. fixes #41759 although it doesn't address the better error reporting for their last repro, which I'm not sure how to address.
1 parent 0ad158e commit 4d4ea66

9 files changed

+588
-6
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25322,9 +25322,15 @@ namespace ts {
2532225322

2532325323
function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) {
2532425324
return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType,
25325-
map(
25326-
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)),
25327-
prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String])
25325+
concatenate(
25326+
map(
25327+
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)),
25328+
prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String])
25329+
),
25330+
map(
25331+
filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
25332+
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
25333+
)
2532825334
),
2532925335
isTypeAssignableTo,
2533025336
contextualType
@@ -25333,9 +25339,15 @@ namespace ts {
2533325339

2533425340
function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) {
2533525341
return discriminateTypeByDiscriminableItems(contextualType,
25336-
map(
25337-
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))),
25338-
prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
25342+
concatenate(
25343+
map(
25344+
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))),
25345+
prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
25346+
),
25347+
map(
25348+
filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
25349+
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
25350+
)
2533925351
),
2534025352
isTypeAssignableTo,
2534125353
contextualType
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//// [discriminantPropertyInference.ts]
2+
// Repro from #41759
3+
4+
type DiscriminatorTrue = {
5+
disc: true;
6+
cb: (x: string) => void;
7+
}
8+
9+
type DiscriminatorFalse = {
10+
disc?: false;
11+
cb: (x: number) => void;
12+
}
13+
14+
type Props = DiscriminatorTrue | DiscriminatorFalse;
15+
16+
declare function f(options: DiscriminatorTrue | DiscriminatorFalse): any;
17+
18+
// simple inference
19+
f({
20+
disc: true,
21+
cb: s => parseInt(s)
22+
});
23+
24+
// simple inference
25+
f({
26+
disc: false,
27+
cb: n => n.toFixed()
28+
});
29+
30+
// simple inference when strict-null-checks are enabled
31+
f({
32+
disc: undefined,
33+
cb: n => n.toFixed()
34+
});
35+
36+
// requires checking type information since discriminator is missing from object
37+
f({
38+
cb: n => n.toFixed()
39+
});
40+
41+
42+
//// [discriminantPropertyInference.js]
43+
// Repro from #41759
44+
// simple inference
45+
f({
46+
disc: true,
47+
cb: function (s) { return parseInt(s); }
48+
});
49+
// simple inference
50+
f({
51+
disc: false,
52+
cb: function (n) { return n.toFixed(); }
53+
});
54+
// simple inference when strict-null-checks are enabled
55+
f({
56+
disc: undefined,
57+
cb: function (n) { return n.toFixed(); }
58+
});
59+
// requires checking type information since discriminator is missing from object
60+
f({
61+
cb: function (n) { return n.toFixed(); }
62+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
=== tests/cases/compiler/discriminantPropertyInference.ts ===
2+
// Repro from #41759
3+
4+
type DiscriminatorTrue = {
5+
>DiscriminatorTrue : Symbol(DiscriminatorTrue, Decl(discriminantPropertyInference.ts, 0, 0))
6+
7+
disc: true;
8+
>disc : Symbol(disc, Decl(discriminantPropertyInference.ts, 2, 26))
9+
10+
cb: (x: string) => void;
11+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 3, 15))
12+
>x : Symbol(x, Decl(discriminantPropertyInference.ts, 4, 9))
13+
}
14+
15+
type DiscriminatorFalse = {
16+
>DiscriminatorFalse : Symbol(DiscriminatorFalse, Decl(discriminantPropertyInference.ts, 5, 1))
17+
18+
disc?: false;
19+
>disc : Symbol(disc, Decl(discriminantPropertyInference.ts, 7, 27))
20+
21+
cb: (x: number) => void;
22+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 8, 17))
23+
>x : Symbol(x, Decl(discriminantPropertyInference.ts, 9, 9))
24+
}
25+
26+
type Props = DiscriminatorTrue | DiscriminatorFalse;
27+
>Props : Symbol(Props, Decl(discriminantPropertyInference.ts, 10, 1))
28+
>DiscriminatorTrue : Symbol(DiscriminatorTrue, Decl(discriminantPropertyInference.ts, 0, 0))
29+
>DiscriminatorFalse : Symbol(DiscriminatorFalse, Decl(discriminantPropertyInference.ts, 5, 1))
30+
31+
declare function f(options: DiscriminatorTrue | DiscriminatorFalse): any;
32+
>f : Symbol(f, Decl(discriminantPropertyInference.ts, 12, 52))
33+
>options : Symbol(options, Decl(discriminantPropertyInference.ts, 14, 19))
34+
>DiscriminatorTrue : Symbol(DiscriminatorTrue, Decl(discriminantPropertyInference.ts, 0, 0))
35+
>DiscriminatorFalse : Symbol(DiscriminatorFalse, Decl(discriminantPropertyInference.ts, 5, 1))
36+
37+
// simple inference
38+
f({
39+
>f : Symbol(f, Decl(discriminantPropertyInference.ts, 12, 52))
40+
41+
disc: true,
42+
>disc : Symbol(disc, Decl(discriminantPropertyInference.ts, 17, 3))
43+
44+
cb: s => parseInt(s)
45+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 18, 15))
46+
>s : Symbol(s, Decl(discriminantPropertyInference.ts, 19, 7))
47+
>parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --))
48+
>s : Symbol(s, Decl(discriminantPropertyInference.ts, 19, 7))
49+
50+
});
51+
52+
// simple inference
53+
f({
54+
>f : Symbol(f, Decl(discriminantPropertyInference.ts, 12, 52))
55+
56+
disc: false,
57+
>disc : Symbol(disc, Decl(discriminantPropertyInference.ts, 23, 3))
58+
59+
cb: n => n.toFixed()
60+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 24, 16))
61+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 25, 7))
62+
>n.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
63+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 25, 7))
64+
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
65+
66+
});
67+
68+
// simple inference when strict-null-checks are enabled
69+
f({
70+
>f : Symbol(f, Decl(discriminantPropertyInference.ts, 12, 52))
71+
72+
disc: undefined,
73+
>disc : Symbol(disc, Decl(discriminantPropertyInference.ts, 29, 3))
74+
>undefined : Symbol(undefined)
75+
76+
cb: n => n.toFixed()
77+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 30, 20))
78+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 31, 7))
79+
>n.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
80+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 31, 7))
81+
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
82+
83+
});
84+
85+
// requires checking type information since discriminator is missing from object
86+
f({
87+
>f : Symbol(f, Decl(discriminantPropertyInference.ts, 12, 52))
88+
89+
cb: n => n.toFixed()
90+
>cb : Symbol(cb, Decl(discriminantPropertyInference.ts, 35, 3))
91+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 36, 7))
92+
>n.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
93+
>n : Symbol(n, Decl(discriminantPropertyInference.ts, 36, 7))
94+
>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --))
95+
96+
});
97+
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
=== tests/cases/compiler/discriminantPropertyInference.ts ===
2+
// Repro from #41759
3+
4+
type DiscriminatorTrue = {
5+
>DiscriminatorTrue : DiscriminatorTrue
6+
7+
disc: true;
8+
>disc : true
9+
>true : true
10+
11+
cb: (x: string) => void;
12+
>cb : (x: string) => void
13+
>x : string
14+
}
15+
16+
type DiscriminatorFalse = {
17+
>DiscriminatorFalse : DiscriminatorFalse
18+
19+
disc?: false;
20+
>disc : false | undefined
21+
>false : false
22+
23+
cb: (x: number) => void;
24+
>cb : (x: number) => void
25+
>x : number
26+
}
27+
28+
type Props = DiscriminatorTrue | DiscriminatorFalse;
29+
>Props : Props
30+
31+
declare function f(options: DiscriminatorTrue | DiscriminatorFalse): any;
32+
>f : (options: DiscriminatorTrue | DiscriminatorFalse) => any
33+
>options : DiscriminatorTrue | DiscriminatorFalse
34+
35+
// simple inference
36+
f({
37+
>f({ disc: true, cb: s => parseInt(s)}) : any
38+
>f : (options: DiscriminatorTrue | DiscriminatorFalse) => any
39+
>{ disc: true, cb: s => parseInt(s)} : { disc: true; cb: (s: string) => number; }
40+
41+
disc: true,
42+
>disc : true
43+
>true : true
44+
45+
cb: s => parseInt(s)
46+
>cb : (s: string) => number
47+
>s => parseInt(s) : (s: string) => number
48+
>s : string
49+
>parseInt(s) : number
50+
>parseInt : (string: string, radix?: number | undefined) => number
51+
>s : string
52+
53+
});
54+
55+
// simple inference
56+
f({
57+
>f({ disc: false, cb: n => n.toFixed()}) : any
58+
>f : (options: DiscriminatorTrue | DiscriminatorFalse) => any
59+
>{ disc: false, cb: n => n.toFixed()} : { disc: false; cb: (n: number) => string; }
60+
61+
disc: false,
62+
>disc : false
63+
>false : false
64+
65+
cb: n => n.toFixed()
66+
>cb : (n: number) => string
67+
>n => n.toFixed() : (n: number) => string
68+
>n : number
69+
>n.toFixed() : string
70+
>n.toFixed : (fractionDigits?: number | undefined) => string
71+
>n : number
72+
>toFixed : (fractionDigits?: number | undefined) => string
73+
74+
});
75+
76+
// simple inference when strict-null-checks are enabled
77+
f({
78+
>f({ disc: undefined, cb: n => n.toFixed()}) : any
79+
>f : (options: DiscriminatorTrue | DiscriminatorFalse) => any
80+
>{ disc: undefined, cb: n => n.toFixed()} : { disc: undefined; cb: (n: number) => string; }
81+
82+
disc: undefined,
83+
>disc : undefined
84+
>undefined : undefined
85+
86+
cb: n => n.toFixed()
87+
>cb : (n: number) => string
88+
>n => n.toFixed() : (n: number) => string
89+
>n : number
90+
>n.toFixed() : string
91+
>n.toFixed : (fractionDigits?: number | undefined) => string
92+
>n : number
93+
>toFixed : (fractionDigits?: number | undefined) => string
94+
95+
});
96+
97+
// requires checking type information since discriminator is missing from object
98+
f({
99+
>f({ cb: n => n.toFixed()}) : any
100+
>f : (options: DiscriminatorTrue | DiscriminatorFalse) => any
101+
>{ cb: n => n.toFixed()} : { cb: (n: number) => string; }
102+
103+
cb: n => n.toFixed()
104+
>cb : (n: number) => string
105+
>n => n.toFixed() : (n: number) => string
106+
>n : number
107+
>n.toFixed() : string
108+
>n.toFixed : (fractionDigits?: number | undefined) => string
109+
>n : number
110+
>toFixed : (fractionDigits?: number | undefined) => string
111+
112+
});
113+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tsxDiscriminantPropertyInference.tsx]
2+
// Repro from #41759
3+
namespace JSX {
4+
export interface Element {}
5+
}
6+
7+
type DiscriminatorTrue = {
8+
disc: true;
9+
cb: (x: string) => void;
10+
}
11+
12+
type DiscriminatorFalse = {
13+
disc?: false;
14+
cb: (x: number) => void;
15+
}
16+
17+
type Props = DiscriminatorTrue | DiscriminatorFalse;
18+
19+
declare function Comp(props: DiscriminatorTrue | DiscriminatorFalse): JSX.Element;
20+
21+
// simple inference
22+
void (<Comp disc cb={s => parseInt(s)} />);
23+
24+
// simple inference
25+
void (<Comp disc={false} cb={n => n.toFixed()} />);
26+
27+
// simple inference when strict-null-checks are enabled
28+
void (<Comp disc={undefined} cb={n => n.toFixed()} />);
29+
30+
// requires checking type information since discriminator is missing from object
31+
void (<Comp cb={n => n.toFixed()} />);
32+
33+
34+
//// [tsxDiscriminantPropertyInference.jsx]
35+
// simple inference
36+
void (<Comp disc cb={function (s) { return parseInt(s); }}/>);
37+
// simple inference
38+
void (<Comp disc={false} cb={function (n) { return n.toFixed(); }}/>);
39+
// simple inference when strict-null-checks are enabled
40+
void (<Comp disc={undefined} cb={function (n) { return n.toFixed(); }}/>);
41+
// requires checking type information since discriminator is missing from object
42+
void (<Comp cb={function (n) { return n.toFixed(); }}/>);

0 commit comments

Comments
 (0)