Skip to content

Commit 2cbfb51

Browse files
authored
Fixed JSX attributes discriminating based on optional children (#53980)
1 parent a956bbc commit 2cbfb51

File tree

4 files changed

+278
-1
lines changed

4 files changed

+278
-1
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29336,14 +29336,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2933629336
}
2933729337

2933829338
function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) {
29339+
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
2933929340
return discriminateTypeByDiscriminableItems(contextualType,
2934029341
concatenate(
2934129342
map(
2934229343
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))),
2934329344
prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
2934429345
),
2934529346
map(
29346-
filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
29347+
filter(getPropertiesOfType(contextualType), s => {
29348+
if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) {
29349+
return false;
29350+
}
29351+
const element = node.parent.parent;
29352+
if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) {
29353+
return false;
29354+
}
29355+
return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName);
29356+
}),
2934729357
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
2934829358
)
2934929359
),
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
=== tests/cases/compiler/contextuallyTypedJsxChildren.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import React from 'react';
5+
>React : Symbol(React, Decl(contextuallyTypedJsxChildren.tsx, 2, 6))
6+
7+
// repro from https://github.com/microsoft/TypeScript/issues/53941
8+
declare namespace DropdownMenu {
9+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
10+
11+
interface BaseProps {
12+
>BaseProps : Symbol(BaseProps, Decl(contextuallyTypedJsxChildren.tsx, 5, 32))
13+
14+
icon: string;
15+
>icon : Symbol(BaseProps.icon, Decl(contextuallyTypedJsxChildren.tsx, 6, 23))
16+
17+
label: string;
18+
>label : Symbol(BaseProps.label, Decl(contextuallyTypedJsxChildren.tsx, 7, 17))
19+
}
20+
interface PropsWithChildren extends BaseProps {
21+
>PropsWithChildren : Symbol(PropsWithChildren, Decl(contextuallyTypedJsxChildren.tsx, 9, 3))
22+
>BaseProps : Symbol(BaseProps, Decl(contextuallyTypedJsxChildren.tsx, 5, 32))
23+
24+
children(props: { onClose: () => void }): JSX.Element;
25+
>children : Symbol(PropsWithChildren.children, Decl(contextuallyTypedJsxChildren.tsx, 10, 49))
26+
>props : Symbol(props, Decl(contextuallyTypedJsxChildren.tsx, 11, 13))
27+
>onClose : Symbol(onClose, Decl(contextuallyTypedJsxChildren.tsx, 11, 21))
28+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
29+
>Element : Symbol(JSX.Element, Decl(react16.d.ts, 2494, 23))
30+
31+
controls?: never | undefined;
32+
>controls : Symbol(PropsWithChildren.controls, Decl(contextuallyTypedJsxChildren.tsx, 11, 58))
33+
}
34+
interface PropsWithControls extends BaseProps {
35+
>PropsWithControls : Symbol(PropsWithControls, Decl(contextuallyTypedJsxChildren.tsx, 13, 3))
36+
>BaseProps : Symbol(BaseProps, Decl(contextuallyTypedJsxChildren.tsx, 5, 32))
37+
38+
controls: Control[];
39+
>controls : Symbol(PropsWithControls.controls, Decl(contextuallyTypedJsxChildren.tsx, 14, 49))
40+
>Control : Symbol(Control, Decl(contextuallyTypedJsxChildren.tsx, 17, 3))
41+
42+
children?: never | undefined;
43+
>children : Symbol(PropsWithControls.children, Decl(contextuallyTypedJsxChildren.tsx, 15, 24))
44+
}
45+
interface Control {
46+
>Control : Symbol(Control, Decl(contextuallyTypedJsxChildren.tsx, 17, 3))
47+
48+
title: string;
49+
>title : Symbol(Control.title, Decl(contextuallyTypedJsxChildren.tsx, 18, 21))
50+
}
51+
type Props = PropsWithChildren | PropsWithControls;
52+
>Props : Symbol(Props, Decl(contextuallyTypedJsxChildren.tsx, 20, 3))
53+
>PropsWithChildren : Symbol(PropsWithChildren, Decl(contextuallyTypedJsxChildren.tsx, 9, 3))
54+
>PropsWithControls : Symbol(PropsWithControls, Decl(contextuallyTypedJsxChildren.tsx, 13, 3))
55+
}
56+
declare const DropdownMenu: React.ComponentType<DropdownMenu.Props>;
57+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
58+
>React : Symbol(React, Decl(contextuallyTypedJsxChildren.tsx, 2, 6))
59+
>ComponentType : Symbol(React.ComponentType, Decl(react16.d.ts, 117, 60))
60+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
61+
>Props : Symbol(DropdownMenu.Props, Decl(contextuallyTypedJsxChildren.tsx, 20, 3))
62+
63+
<DropdownMenu icon="move" label="Select a direction">
64+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
65+
>icon : Symbol(icon, Decl(contextuallyTypedJsxChildren.tsx, 25, 13))
66+
>label : Symbol(label, Decl(contextuallyTypedJsxChildren.tsx, 25, 25))
67+
68+
{({ onClose }) => (
69+
>onClose : Symbol(onClose, Decl(contextuallyTypedJsxChildren.tsx, 26, 5))
70+
71+
<div>
72+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
73+
74+
<button onClick={onClose}>Click me</button>
75+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react16.d.ts, 2532, 96))
76+
>onClick : Symbol(onClick, Decl(contextuallyTypedJsxChildren.tsx, 28, 13))
77+
>onClose : Symbol(onClose, Decl(contextuallyTypedJsxChildren.tsx, 26, 5))
78+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react16.d.ts, 2532, 96))
79+
80+
</div>
81+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
82+
83+
)}
84+
</DropdownMenu>;
85+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
86+
87+
<DropdownMenu
88+
>DropdownMenu : Symbol(DropdownMenu, Decl(contextuallyTypedJsxChildren.tsx, 2, 26), Decl(contextuallyTypedJsxChildren.tsx, 23, 13))
89+
90+
icon="move"
91+
>icon : Symbol(icon, Decl(contextuallyTypedJsxChildren.tsx, 33, 13))
92+
93+
label="Select a direction"
94+
>label : Symbol(label, Decl(contextuallyTypedJsxChildren.tsx, 34, 13))
95+
96+
children={({ onClose }) => (
97+
>children : Symbol(children, Decl(contextuallyTypedJsxChildren.tsx, 35, 28))
98+
>onClose : Symbol(onClose, Decl(contextuallyTypedJsxChildren.tsx, 36, 14))
99+
100+
<div>
101+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
102+
103+
<button onClick={onClose}>Click me</button>
104+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react16.d.ts, 2532, 96))
105+
>onClick : Symbol(onClick, Decl(contextuallyTypedJsxChildren.tsx, 38, 13))
106+
>onClose : Symbol(onClose, Decl(contextuallyTypedJsxChildren.tsx, 36, 14))
107+
>button : Symbol(JSX.IntrinsicElements.button, Decl(react16.d.ts, 2532, 96))
108+
109+
</div>
110+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))
111+
112+
)}
113+
/>;
114+
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
=== tests/cases/compiler/contextuallyTypedJsxChildren.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import React from 'react';
5+
>React : typeof React
6+
7+
// repro from https://github.com/microsoft/TypeScript/issues/53941
8+
declare namespace DropdownMenu {
9+
interface BaseProps {
10+
icon: string;
11+
>icon : string
12+
13+
label: string;
14+
>label : string
15+
}
16+
interface PropsWithChildren extends BaseProps {
17+
children(props: { onClose: () => void }): JSX.Element;
18+
>children : (props: { onClose: () => void;}) => JSX.Element
19+
>props : { onClose: () => void; }
20+
>onClose : () => void
21+
>JSX : any
22+
23+
controls?: never | undefined;
24+
>controls : undefined
25+
}
26+
interface PropsWithControls extends BaseProps {
27+
controls: Control[];
28+
>controls : Control[]
29+
30+
children?: never | undefined;
31+
>children : undefined
32+
}
33+
interface Control {
34+
title: string;
35+
>title : string
36+
}
37+
type Props = PropsWithChildren | PropsWithControls;
38+
>Props : PropsWithChildren | PropsWithControls
39+
}
40+
declare const DropdownMenu: React.ComponentType<DropdownMenu.Props>;
41+
>DropdownMenu : React.ComponentType<DropdownMenu.Props>
42+
>React : any
43+
>DropdownMenu : any
44+
45+
<DropdownMenu icon="move" label="Select a direction">
46+
><DropdownMenu icon="move" label="Select a direction"> {({ onClose }) => ( <div> <button onClick={onClose}>Click me</button> </div> )}</DropdownMenu> : JSX.Element
47+
>DropdownMenu : React.ComponentType<DropdownMenu.Props>
48+
>icon : string
49+
>label : string
50+
51+
{({ onClose }) => (
52+
>({ onClose }) => ( <div> <button onClick={onClose}>Click me</button> </div> ) : ({ onClose }: { onClose: () => void; }) => JSX.Element
53+
>onClose : () => void
54+
>( <div> <button onClick={onClose}>Click me</button> </div> ) : JSX.Element
55+
56+
<div>
57+
><div> <button onClick={onClose}>Click me</button> </div> : JSX.Element
58+
>div : any
59+
60+
<button onClick={onClose}>Click me</button>
61+
><button onClick={onClose}>Click me</button> : JSX.Element
62+
>button : any
63+
>onClick : () => void
64+
>onClose : () => void
65+
>button : any
66+
67+
</div>
68+
>div : any
69+
70+
)}
71+
</DropdownMenu>;
72+
>DropdownMenu : React.ComponentType<DropdownMenu.Props>
73+
74+
<DropdownMenu
75+
><DropdownMenu icon="move" label="Select a direction" children={({ onClose }) => ( <div> <button onClick={onClose}>Click me</button> </div> )}/> : JSX.Element
76+
>DropdownMenu : React.ComponentType<DropdownMenu.Props>
77+
78+
icon="move"
79+
>icon : string
80+
81+
label="Select a direction"
82+
>label : string
83+
84+
children={({ onClose }) => (
85+
>children : ({ onClose }: { onClose: () => void; }) => JSX.Element
86+
>({ onClose }) => ( <div> <button onClick={onClose}>Click me</button> </div> ) : ({ onClose }: { onClose: () => void; }) => JSX.Element
87+
>onClose : () => void
88+
>( <div> <button onClick={onClose}>Click me</button> </div> ) : JSX.Element
89+
90+
<div>
91+
><div> <button onClick={onClose}>Click me</button> </div> : JSX.Element
92+
>div : any
93+
94+
<button onClick={onClose}>Click me</button>
95+
><button onClick={onClose}>Click me</button> : JSX.Element
96+
>button : any
97+
>onClick : () => void
98+
>onClose : () => void
99+
>button : any
100+
101+
</div>
102+
>div : any
103+
104+
)}
105+
/>;
106+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @strict: true
2+
// @jsx: react
3+
// @esModuleInterop: true
4+
// @noEmit: true
5+
6+
/// <reference path="/.lib/react16.d.ts" />
7+
8+
import React from 'react';
9+
10+
// repro from https://github.com/microsoft/TypeScript/issues/53941
11+
declare namespace DropdownMenu {
12+
interface BaseProps {
13+
icon: string;
14+
label: string;
15+
}
16+
interface PropsWithChildren extends BaseProps {
17+
children(props: { onClose: () => void }): JSX.Element;
18+
controls?: never | undefined;
19+
}
20+
interface PropsWithControls extends BaseProps {
21+
controls: Control[];
22+
children?: never | undefined;
23+
}
24+
interface Control {
25+
title: string;
26+
}
27+
type Props = PropsWithChildren | PropsWithControls;
28+
}
29+
declare const DropdownMenu: React.ComponentType<DropdownMenu.Props>;
30+
31+
<DropdownMenu icon="move" label="Select a direction">
32+
{({ onClose }) => (
33+
<div>
34+
<button onClick={onClose}>Click me</button>
35+
</div>
36+
)}
37+
</DropdownMenu>;
38+
39+
<DropdownMenu
40+
icon="move"
41+
label="Select a direction"
42+
children={({ onClose }) => (
43+
<div>
44+
<button onClick={onClose}>Click me</button>
45+
</div>
46+
)}
47+
/>;

0 commit comments

Comments
 (0)