Skip to content

Commit e60c210

Browse files
authored
Improve JSX invalid children type error (#52105)
1 parent 400e2c2 commit e60c210

18 files changed

+3945
-3
lines changed

src/compiler/checker.ts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19072,6 +19072,68 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1907219072
return reportedError;
1907319073
}
1907419074

19075+
/**
19076+
* Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined,
19077+
* or that it's an array or tuple-like type, if `Iterable` is not defined.
19078+
*/
19079+
function elaborateIterableOrArrayLikeTargetElementwise(
19080+
iterator: ElaborationIterator,
19081+
source: Type,
19082+
target: Type,
19083+
relation: Map<string, RelationComparisonResult>,
19084+
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
19085+
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined
19086+
) {
19087+
const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType);
19088+
const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t));
19089+
// If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined.
19090+
const iterationType = nonTupleOrArrayLikeTargetParts !== neverType
19091+
? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined)
19092+
: undefined;
19093+
19094+
let reportedError = false;
19095+
for (let status = iterator.next(); !status.done; status = iterator.next()) {
19096+
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
19097+
let targetPropType = iterationType;
19098+
const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined;
19099+
if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables
19100+
targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType;
19101+
}
19102+
if (!targetPropType) continue;
19103+
let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType);
19104+
if (!sourcePropType) continue;
19105+
const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined);
19106+
if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) {
19107+
const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer);
19108+
reportedError = true;
19109+
if (!elaborated) {
19110+
// Issue error on the prop itself, since the prop couldn't elaborate the error
19111+
const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {};
19112+
// Use the expression type, if available
19113+
const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType;
19114+
if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) {
19115+
const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType));
19116+
diagnostics.add(diag);
19117+
resultObj.errors = [diag];
19118+
}
19119+
else {
19120+
const targetIsOptional = !!(propName && (getPropertyOfType(tupleOrArrayLikeTargetParts, propName) || unknownSymbol).flags & SymbolFlags.Optional);
19121+
const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional);
19122+
targetPropType = removeMissingType(targetPropType, targetIsOptional);
19123+
sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional);
19124+
const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
19125+
if (result && specificSource !== sourcePropType) {
19126+
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
19127+
checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj);
19128+
}
19129+
}
19130+
}
19131+
}
19132+
}
19133+
return reportedError;
19134+
}
19135+
19136+
1907519137
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1907619138
if (!length(node.properties)) return;
1907719139
for (const prop of node.properties) {
@@ -19138,13 +19200,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1913819200
return result;
1913919201
}
1914019202
const moreThanOneRealChildren = length(validChildren) > 1;
19141-
const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
19142-
const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
19203+
let arrayLikeTargetParts: Type;
19204+
let nonArrayLikeTargetParts: Type;
19205+
const iterableType = getGlobalIterableType(/*reportErrors*/ false);
19206+
if (iterableType !== emptyGenericType) {
19207+
const anyIterable = createIterableType(anyType);
19208+
arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable));
19209+
nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable));
19210+
}
19211+
else {
19212+
arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
19213+
nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));
19214+
}
1914319215
if (moreThanOneRealChildren) {
1914419216
if (arrayLikeTargetParts !== neverType) {
1914519217
const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal));
1914619218
const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic);
19147-
result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result;
19219+
result = elaborateIterableOrArrayLikeTargetElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result;
1914819220
}
1914919221
else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) {
1915019222
// arity mismatch
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/compiler/index.tsx(6,5): error TS2322: Type 'unknown' is not assignable to type 'ReactNode'.
2+
3+
4+
==== tests/cases/compiler/index.tsx (1 errors) ====
5+
/// <reference path="/.lib/react18/react18.d.ts" />
6+
/// <reference path="/.lib/react18/global.d.ts" />
7+
8+
const a = (
9+
<main>
10+
{(<div />) as unknown}
11+
~~~~~~~~~~~~~~~~~~~~~~
12+
!!! error TS2322: Type 'unknown' is not assignable to type 'ReactNode'.
13+
<span />
14+
</main>
15+
);
16+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [index.tsx]
2+
/// <reference path="/.lib/react18/react18.d.ts" />
3+
/// <reference path="/.lib/react18/global.d.ts" />
4+
5+
const a = (
6+
<main>
7+
{(<div />) as unknown}
8+
<span />
9+
</main>
10+
);
11+
12+
13+
//// [index.js]
14+
"use strict";
15+
/// <reference path="react18/react18.d.ts" />
16+
/// <reference path="react18/global.d.ts" />
17+
const a = (React.createElement("main", null,
18+
(React.createElement("div", null)),
19+
React.createElement("span", null)));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/compiler/index.tsx ===
2+
/// <reference path="react18/react18.d.ts" />
3+
/// <reference path="react18/global.d.ts" />
4+
5+
const a = (
6+
>a : Symbol(a, Decl(index.tsx, 3, 5))
7+
8+
<main>
9+
>main : Symbol(JSX.IntrinsicElements.main, Decl(react18.d.ts, 3206, 102))
10+
11+
{(<div />) as unknown}
12+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react18.d.ts, 3174, 110))
13+
14+
<span />
15+
>span : Symbol(JSX.IntrinsicElements.span, Decl(react18.d.ts, 3238, 110))
16+
17+
</main>
18+
>main : Symbol(JSX.IntrinsicElements.main, Decl(react18.d.ts, 3206, 102))
19+
20+
);
21+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=== tests/cases/compiler/index.tsx ===
2+
/// <reference path="react18/react18.d.ts" />
3+
/// <reference path="react18/global.d.ts" />
4+
5+
const a = (
6+
>a : JSX.Element
7+
>( <main> {(<div />) as unknown} <span /> </main>) : JSX.Element
8+
9+
<main>
10+
><main> {(<div />) as unknown} <span /> </main> : JSX.Element
11+
>main : any
12+
13+
{(<div />) as unknown}
14+
>(<div />) as unknown : unknown
15+
>(<div />) : JSX.Element
16+
><div /> : JSX.Element
17+
>div : any
18+
19+
<span />
20+
><span /> : JSX.Element
21+
>span : any
22+
23+
</main>
24+
>main : any
25+
26+
);
27+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/compiler/index.tsx(11,5): error TS2769: No overload matches this call.
2+
Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
3+
Type 'unknown' is not assignable to type 'string | boolean'.
4+
Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
5+
Type 'string' is not assignable to type 'number | boolean'.
6+
7+
8+
==== tests/cases/compiler/index.tsx (1 errors) ====
9+
/// <reference path="/.lib/react18/react18.d.ts" />
10+
/// <reference path="/.lib/react18/global.d.ts" />
11+
12+
// target is ES5, so no `Iterable` type is present.
13+
14+
interface PropsType {
15+
children: [string, number] | boolean[];
16+
}
17+
declare class Foo extends React.Component<PropsType, {}> {}
18+
const b = (
19+
<Foo>
20+
~~~~~
21+
!!! error TS2769: No overload matches this call.
22+
!!! error TS2769: Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
23+
!!! error TS2769: Type 'unknown' is not assignable to type 'string | boolean'.
24+
!!! error TS2769: Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
25+
!!! error TS2769: Type 'string' is not assignable to type 'number | boolean'.
26+
{<div/> as unknown}
27+
{"aa"}
28+
</Foo>
29+
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [index.tsx]
2+
/// <reference path="/.lib/react18/react18.d.ts" />
3+
/// <reference path="/.lib/react18/global.d.ts" />
4+
5+
// target is ES5, so no `Iterable` type is present.
6+
7+
interface PropsType {
8+
children: [string, number] | boolean[];
9+
}
10+
declare class Foo extends React.Component<PropsType, {}> {}
11+
const b = (
12+
<Foo>
13+
{<div/> as unknown}
14+
{"aa"}
15+
</Foo>
16+
);
17+
18+
//// [index.js]
19+
"use strict";
20+
/// <reference path="react18/react18.d.ts" />
21+
/// <reference path="react18/global.d.ts" />
22+
var b = (React.createElement(Foo, null,
23+
React.createElement("div", null),
24+
"aa"));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/index.tsx ===
2+
/// <reference path="react18/react18.d.ts" />
3+
/// <reference path="react18/global.d.ts" />
4+
5+
// target is ES5, so no `Iterable` type is present.
6+
7+
interface PropsType {
8+
>PropsType : Symbol(PropsType, Decl(index.tsx, 0, 0))
9+
10+
children: [string, number] | boolean[];
11+
>children : Symbol(PropsType.children, Decl(index.tsx, 5, 21))
12+
}
13+
declare class Foo extends React.Component<PropsType, {}> {}
14+
>Foo : Symbol(Foo, Decl(index.tsx, 7, 1))
15+
>React.Component : Symbol(React.Component, Decl(react18.d.ts, 427, 50), Decl(react18.d.ts, 430, 90))
16+
>React : Symbol(React, Decl(react18.d.ts, 62, 15))
17+
>Component : Symbol(React.Component, Decl(react18.d.ts, 427, 50), Decl(react18.d.ts, 430, 90))
18+
>PropsType : Symbol(PropsType, Decl(index.tsx, 0, 0))
19+
20+
const b = (
21+
>b : Symbol(b, Decl(index.tsx, 9, 5))
22+
23+
<Foo>
24+
>Foo : Symbol(Foo, Decl(index.tsx, 7, 1))
25+
26+
{<div/> as unknown}
27+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react18.d.ts, 3174, 110))
28+
29+
{"aa"}
30+
</Foo>
31+
>Foo : Symbol(Foo, Decl(index.tsx, 7, 1))
32+
33+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/index.tsx ===
2+
/// <reference path="react18/react18.d.ts" />
3+
/// <reference path="react18/global.d.ts" />
4+
5+
// target is ES5, so no `Iterable` type is present.
6+
7+
interface PropsType {
8+
children: [string, number] | boolean[];
9+
>children : [string, number] | boolean[]
10+
}
11+
declare class Foo extends React.Component<PropsType, {}> {}
12+
>Foo : Foo
13+
>React.Component : React.Component<PropsType, {}, any>
14+
>React : typeof React
15+
>Component : typeof React.Component
16+
17+
const b = (
18+
>b : JSX.Element
19+
>( <Foo> {<div/> as unknown} {"aa"} </Foo>) : JSX.Element
20+
21+
<Foo>
22+
><Foo> {<div/> as unknown} {"aa"} </Foo> : JSX.Element
23+
>Foo : typeof Foo
24+
25+
{<div/> as unknown}
26+
><div/> as unknown : unknown
27+
><div/> : JSX.Element
28+
>div : any
29+
30+
{"aa"}
31+
>"aa" : "aa"
32+
33+
</Foo>
34+
>Foo : typeof Foo
35+
36+
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
tests/cases/compiler/other.tsx(10,5): error TS2769: No overload matches this call.
2+
Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
3+
Type 'unknown' is not assignable to type 'string | boolean'.
4+
Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
5+
Type 'string' is not assignable to type 'number | boolean'.
6+
7+
8+
==== tests/cases/compiler/other.tsx (1 errors) ====
9+
/// <reference path="/.lib/react18/react18.d.ts" />
10+
/// <reference path="/.lib/react18/global.d.ts" />
11+
12+
13+
interface PropsType {
14+
children: [string, number?] | Iterable<boolean>;
15+
}
16+
declare class Foo extends React.Component<PropsType, {}> {}
17+
const b = (
18+
<Foo>
19+
~~~~~
20+
!!! error TS2769: No overload matches this call.
21+
!!! error TS2769: Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
22+
!!! error TS2769: Type 'unknown' is not assignable to type 'string | boolean'.
23+
!!! error TS2769: Overload 2 of 2, '(props: PropsType, context: any): Foo', gave the following error.
24+
!!! error TS2769: Type 'string' is not assignable to type 'number | boolean'.
25+
{<div/> as unknown}
26+
{"aa"}
27+
</Foo>
28+
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [other.tsx]
2+
/// <reference path="/.lib/react18/react18.d.ts" />
3+
/// <reference path="/.lib/react18/global.d.ts" />
4+
5+
6+
interface PropsType {
7+
children: [string, number?] | Iterable<boolean>;
8+
}
9+
declare class Foo extends React.Component<PropsType, {}> {}
10+
const b = (
11+
<Foo>
12+
{<div/> as unknown}
13+
{"aa"}
14+
</Foo>
15+
);
16+
17+
//// [other.js]
18+
"use strict";
19+
/// <reference path="react18/react18.d.ts" />
20+
/// <reference path="react18/global.d.ts" />
21+
const b = (React.createElement(Foo, null,
22+
React.createElement("div", null),
23+
"aa"));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/other.tsx ===
2+
/// <reference path="react18/react18.d.ts" />
3+
/// <reference path="react18/global.d.ts" />
4+
5+
6+
interface PropsType {
7+
>PropsType : Symbol(PropsType, Decl(other.tsx, 0, 0))
8+
9+
children: [string, number?] | Iterable<boolean>;
10+
>children : Symbol(PropsType.children, Decl(other.tsx, 4, 21))
11+
>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --))
12+
}
13+
declare class Foo extends React.Component<PropsType, {}> {}
14+
>Foo : Symbol(Foo, Decl(other.tsx, 6, 1))
15+
>React.Component : Symbol(React.Component, Decl(react18.d.ts, 427, 50), Decl(react18.d.ts, 430, 90))
16+
>React : Symbol(React, Decl(react18.d.ts, 62, 15))
17+
>Component : Symbol(React.Component, Decl(react18.d.ts, 427, 50), Decl(react18.d.ts, 430, 90))
18+
>PropsType : Symbol(PropsType, Decl(other.tsx, 0, 0))
19+
20+
const b = (
21+
>b : Symbol(b, Decl(other.tsx, 8, 5))
22+
23+
<Foo>
24+
>Foo : Symbol(Foo, Decl(other.tsx, 6, 1))
25+
26+
{<div/> as unknown}
27+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react18.d.ts, 3174, 110))
28+
29+
{"aa"}
30+
</Foo>
31+
>Foo : Symbol(Foo, Decl(other.tsx, 6, 1))
32+
33+
);

0 commit comments

Comments
 (0)