Skip to content

Commit 972c403

Browse files
authored
JSX uses mixed signatures and union sigs use subtype on partial match (microsoft#28141)
* JSX uses mixed signatures and union sigs use subtype on partial match * Small improvement
1 parent 36dfd77 commit 972c403

8 files changed

+169
-49
lines changed

src/compiler/checker.ts

+40-20
Original file line numberDiff line numberDiff line change
@@ -6567,7 +6567,7 @@ namespace ts {
65676567

65686568
function findMatchingSignature(signatureList: ReadonlyArray<Signature>, signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined {
65696569
for (const s of signatureList) {
6570-
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, compareTypesIdentical)) {
6570+
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) {
65716571
return s;
65726572
}
65736573
}
@@ -6603,8 +6603,7 @@ namespace ts {
66036603
// Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional
66046604
// parameters and may differ in return types. When signatures differ in return types, the resulting return
66056605
// type is the union of the constituent return types.
6606-
function getUnionSignatures(types: ReadonlyArray<Type>, kind: SignatureKind): Signature[] {
6607-
const signatureLists = map(types, t => getSignaturesOfType(t, kind));
6606+
function getUnionSignatures(signatureLists: ReadonlyArray<ReadonlyArray<Signature>>): Signature[] {
66086607
let result: Signature[] | undefined;
66096608
for (let i = 0; i < signatureLists.length; i++) {
66106609
for (const signature of signatureLists[i]) {
@@ -6650,8 +6649,8 @@ namespace ts {
66506649
function resolveUnionTypeMembers(type: UnionType) {
66516650
// The members and properties collections are empty for union types. To get all properties of a union
66526651
// type use getPropertiesOfType (only the language service uses this).
6653-
const callSignatures = getUnionSignatures(type.types, SignatureKind.Call);
6654-
const constructSignatures = getUnionSignatures(type.types, SignatureKind.Construct);
6652+
const callSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Call)));
6653+
const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct)));
66556654
const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String);
66566655
const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number);
66576656
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
@@ -10691,6 +10690,10 @@ namespace ts {
1069110690
return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False;
1069210691
}
1069310692

10693+
function compareTypesSubtypeOf(source: Type, target: Type): Ternary {
10694+
return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False;
10695+
}
10696+
1069410697
function isTypeSubtypeOf(source: Type, target: Type): boolean {
1069510698
return isTypeRelatedTo(source, target, subtypeRelation);
1069610699
}
@@ -12880,7 +12883,7 @@ namespace ts {
1288012883
for (let i = 0; i < targetLen; i++) {
1288112884
const s = getTypeAtPosition(source, i);
1288212885
const t = getTypeAtPosition(target, i);
12883-
const related = compareTypes(s, t);
12886+
const related = compareTypes(t, s);
1288412887
if (!related) {
1288512888
return Ternary.False;
1288612889
}
@@ -17108,7 +17111,7 @@ namespace ts {
1710817111
}
1710917112

1711017113
function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) {
17111-
return isJsxStatelessFunctionReference(node) ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node);
17114+
return getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node);
1711217115
}
1711317116

1711417117
function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
@@ -18004,13 +18007,17 @@ namespace ts {
1800418007
return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace);
1800518008
}
1800618009

18007-
function getUninstantiatedJsxSignaturesOfType(elementType: Type) {
18010+
function getUninstantiatedJsxSignaturesOfType(elementType: Type): ReadonlyArray<Signature> {
1800818011
// Resolve the signatures, preferring constructor
1800918012
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
1801018013
if (signatures.length === 0) {
1801118014
// No construct signatures, try call signatures
1801218015
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
1801318016
}
18017+
if (signatures.length === 0 && elementType.flags & TypeFlags.Union) {
18018+
// If each member has some combination of new/call signatures; make a union signature list for those
18019+
signatures = getUnionSignatures(map((elementType as UnionType).types, getUninstantiatedJsxSignaturesOfType));
18020+
}
1801418021
return signatures;
1801518022
}
1801618023

@@ -18036,20 +18043,29 @@ namespace ts {
1803618043
return anyType;
1803718044
}
1803818045

18039-
function checkJsxReturnAssignableToAppropriateBound(isSFC: boolean, elemInstanceType: Type, openingLikeElement: Node) {
18040-
if (isSFC) {
18046+
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) {
18047+
if (refKind === JsxReferenceKind.Function) {
1804118048
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
1804218049
if (sfcReturnConstraint) {
1804318050
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
1804418051
}
1804518052
}
18046-
else {
18053+
else if (refKind === JsxReferenceKind.Component) {
1804718054
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
1804818055
if (classConstraint) {
1804918056
// Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that
1805018057
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
1805118058
}
1805218059
}
18060+
else { // Mixed
18061+
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
18062+
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
18063+
if (!sfcReturnConstraint || !classConstraint) {
18064+
return;
18065+
}
18066+
const combined = getUnionType([sfcReturnConstraint, classConstraint]);
18067+
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
18068+
}
1805318069
}
1805418070

1805518071
/**
@@ -18139,7 +18155,7 @@ namespace ts {
1813918155

1814018156
if (isNodeOpeningLikeElement) {
1814118157
const sig = getResolvedSignature(node as JsxOpeningLikeElement);
18142-
checkJsxReturnAssignableToAppropriateBound(isJsxStatelessFunctionReference(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
18158+
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
1814318159
}
1814418160
}
1814518161

@@ -19174,12 +19190,18 @@ namespace ts {
1917419190
return typeArgumentTypes;
1917519191
}
1917619192

19177-
function isJsxStatelessFunctionReference(node: JsxOpeningLikeElement) {
19193+
function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind {
1917819194
if (isJsxIntrinsicIdentifier(node.tagName)) {
19179-
return true;
19195+
return JsxReferenceKind.Mixed;
19196+
}
19197+
const tagType = getApparentType(checkExpression(node.tagName));
19198+
if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) {
19199+
return JsxReferenceKind.Component;
19200+
}
19201+
if (length(getSignaturesOfType(tagType, SignatureKind.Call))) {
19202+
return JsxReferenceKind.Function;
1918019203
}
19181-
const tagType = checkExpression(node.tagName);
19182-
return !length(getSignaturesOfType(getApparentType(tagType), SignatureKind.Construct));
19204+
return JsxReferenceKind.Mixed;
1918319205
}
1918419206

1918519207
/**
@@ -20182,13 +20204,11 @@ namespace ts {
2018220204
}
2018320205
}
2018420206

20185-
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
20186-
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
20187-
if (exprTypes.flags & TypeFlags.String || isUntypedFunctionCall(exprTypes, apparentType, callSignatures.length, constructSignatures.length)) {
20207+
const signatures = getUninstantiatedJsxSignaturesOfType(apparentType);
20208+
if (exprTypes.flags & TypeFlags.String || isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) {
2018820209
return resolveUntypedCall(node);
2018920210
}
2019020211

20191-
const signatures = getUninstantiatedJsxSignaturesOfType(apparentType);
2019220212
if (signatures.length === 0) {
2019320213
// We found no signatures at all, which is an error
2019420214
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));

src/compiler/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -4217,6 +4217,13 @@ namespace ts {
42174217
substitute: Type; // Type to substitute for type parameter
42184218
}
42194219

4220+
/* @internal */
4221+
export const enum JsxReferenceKind {
4222+
Component,
4223+
Function,
4224+
Mixed
4225+
}
4226+
42204227
export const enum SignatureKind {
42214228
Call,
42224229
Construct,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
tests/cases/compiler/tsxInvokeComponentType.tsx(6,14): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { someKey: string; } & { children?: ReactNode; }'.
2+
Type '{}' is not assignable to type '{ someKey: string; }'.
3+
Property 'someKey' is missing in type '{}'.
4+
5+
6+
==== tests/cases/compiler/tsxInvokeComponentType.tsx (1 errors) ====
7+
/// <reference path="/.lib/react16.d.ts" />
8+
import React, { ComponentType } from "react";
9+
10+
declare const Elem: ComponentType<{ someKey: string }>;
11+
12+
const bad = <Elem />;
13+
~~~~
14+
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { someKey: string; } & { children?: ReactNode; }'.
15+
!!! error TS2322: Type '{}' is not assignable to type '{ someKey: string; }'.
16+
!!! error TS2322: Property 'someKey' is missing in type '{}'.
17+
18+
const good = <Elem someKey="ok" />;
19+
20+
declare const Elem2: ComponentType<{ opt?: number }>;
21+
const alsoOk = <Elem2>text</Elem2>;
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tsxInvokeComponentType.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import React, { ComponentType } from "react";
4+
5+
declare const Elem: ComponentType<{ someKey: string }>;
6+
7+
const bad = <Elem />;
8+
9+
const good = <Elem someKey="ok" />;
10+
11+
declare const Elem2: ComponentType<{ opt?: number }>;
12+
const alsoOk = <Elem2>text</Elem2>;
13+
14+
15+
//// [tsxInvokeComponentType.js]
16+
"use strict";
17+
var __importDefault = (this && this.__importDefault) || function (mod) {
18+
return (mod && mod.__esModule) ? mod : { "default": mod };
19+
};
20+
exports.__esModule = true;
21+
/// <reference path="react16.d.ts" />
22+
var react_1 = __importDefault(require("react"));
23+
var bad = react_1["default"].createElement(Elem, null);
24+
var good = react_1["default"].createElement(Elem, { someKey: "ok" });
25+
var alsoOk = react_1["default"].createElement(Elem2, null, "text");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/compiler/tsxInvokeComponentType.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import React, { ComponentType } from "react";
4+
>React : Symbol(React, Decl(tsxInvokeComponentType.tsx, 1, 6))
5+
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
6+
7+
declare const Elem: ComponentType<{ someKey: string }>;
8+
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
9+
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
10+
>someKey : Symbol(someKey, Decl(tsxInvokeComponentType.tsx, 3, 35))
11+
12+
const bad = <Elem />;
13+
>bad : Symbol(bad, Decl(tsxInvokeComponentType.tsx, 5, 5))
14+
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
15+
16+
const good = <Elem someKey="ok" />;
17+
>good : Symbol(good, Decl(tsxInvokeComponentType.tsx, 7, 5))
18+
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
19+
>someKey : Symbol(someKey, Decl(tsxInvokeComponentType.tsx, 7, 18))
20+
21+
declare const Elem2: ComponentType<{ opt?: number }>;
22+
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
23+
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
24+
>opt : Symbol(opt, Decl(tsxInvokeComponentType.tsx, 9, 36))
25+
26+
const alsoOk = <Elem2>text</Elem2>;
27+
>alsoOk : Symbol(alsoOk, Decl(tsxInvokeComponentType.tsx, 10, 5))
28+
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
29+
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/compiler/tsxInvokeComponentType.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import React, { ComponentType } from "react";
4+
>React : typeof React
5+
>ComponentType : any
6+
7+
declare const Elem: ComponentType<{ someKey: string }>;
8+
>Elem : React.ComponentType<{ someKey: string; }>
9+
>someKey : string
10+
11+
const bad = <Elem />;
12+
>bad : JSX.Element
13+
><Elem /> : JSX.Element
14+
>Elem : React.ComponentType<{ someKey: string; }>
15+
16+
const good = <Elem someKey="ok" />;
17+
>good : JSX.Element
18+
><Elem someKey="ok" /> : JSX.Element
19+
>Elem : React.ComponentType<{ someKey: string; }>
20+
>someKey : string
21+
22+
declare const Elem2: ComponentType<{ opt?: number }>;
23+
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
24+
>opt : number | undefined
25+
26+
const alsoOk = <Elem2>text</Elem2>;
27+
>alsoOk : JSX.Element
28+
><Elem2>text</Elem2> : JSX.Element
29+
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
30+
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
31+

tests/baselines/reference/tsxUnionTypeComponent1.errors.txt

-29
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @jsx: react
2+
// @strict: true
3+
// @esModuleInterop: true
4+
/// <reference path="/.lib/react16.d.ts" />
5+
import React, { ComponentType } from "react";
6+
7+
declare const Elem: ComponentType<{ someKey: string }>;
8+
9+
const bad = <Elem />;
10+
11+
const good = <Elem someKey="ok" />;
12+
13+
declare const Elem2: ComponentType<{ opt?: number }>;
14+
const alsoOk = <Elem2>text</Elem2>;

0 commit comments

Comments
 (0)