JSX uses mixed signatures and union sigs use subtype on partial match (#28141)
* JSX uses mixed signatures and union sigs use subtype on partial match * Small improvement
This commit is contained in:
parent
36dfd775b3
commit
972c403cd8
|
@ -6567,7 +6567,7 @@ namespace ts {
|
|||
|
||||
function findMatchingSignature(signatureList: ReadonlyArray<Signature>, signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined {
|
||||
for (const s of signatureList) {
|
||||
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, compareTypesIdentical)) {
|
||||
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
@ -6603,8 +6603,7 @@ namespace ts {
|
|||
// Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional
|
||||
// parameters and may differ in return types. When signatures differ in return types, the resulting return
|
||||
// type is the union of the constituent return types.
|
||||
function getUnionSignatures(types: ReadonlyArray<Type>, kind: SignatureKind): Signature[] {
|
||||
const signatureLists = map(types, t => getSignaturesOfType(t, kind));
|
||||
function getUnionSignatures(signatureLists: ReadonlyArray<ReadonlyArray<Signature>>): Signature[] {
|
||||
let result: Signature[] | undefined;
|
||||
for (let i = 0; i < signatureLists.length; i++) {
|
||||
for (const signature of signatureLists[i]) {
|
||||
|
@ -6650,8 +6649,8 @@ namespace ts {
|
|||
function resolveUnionTypeMembers(type: UnionType) {
|
||||
// The members and properties collections are empty for union types. To get all properties of a union
|
||||
// type use getPropertiesOfType (only the language service uses this).
|
||||
const callSignatures = getUnionSignatures(type.types, SignatureKind.Call);
|
||||
const constructSignatures = getUnionSignatures(type.types, SignatureKind.Construct);
|
||||
const callSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Call)));
|
||||
const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct)));
|
||||
const stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String);
|
||||
const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number);
|
||||
setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo);
|
||||
|
@ -10691,6 +10690,10 @@ namespace ts {
|
|||
return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False;
|
||||
}
|
||||
|
||||
function compareTypesSubtypeOf(source: Type, target: Type): Ternary {
|
||||
return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False;
|
||||
}
|
||||
|
||||
function isTypeSubtypeOf(source: Type, target: Type): boolean {
|
||||
return isTypeRelatedTo(source, target, subtypeRelation);
|
||||
}
|
||||
|
@ -12880,7 +12883,7 @@ namespace ts {
|
|||
for (let i = 0; i < targetLen; i++) {
|
||||
const s = getTypeAtPosition(source, i);
|
||||
const t = getTypeAtPosition(target, i);
|
||||
const related = compareTypes(s, t);
|
||||
const related = compareTypes(t, s);
|
||||
if (!related) {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
@ -17108,7 +17111,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) {
|
||||
return isJsxStatelessFunctionReference(node) ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node);
|
||||
return getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node);
|
||||
}
|
||||
|
||||
function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) {
|
||||
|
@ -18004,13 +18007,17 @@ namespace ts {
|
|||
return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace);
|
||||
}
|
||||
|
||||
function getUninstantiatedJsxSignaturesOfType(elementType: Type) {
|
||||
function getUninstantiatedJsxSignaturesOfType(elementType: Type): ReadonlyArray<Signature> {
|
||||
// Resolve the signatures, preferring constructor
|
||||
let signatures = getSignaturesOfType(elementType, SignatureKind.Construct);
|
||||
if (signatures.length === 0) {
|
||||
// No construct signatures, try call signatures
|
||||
signatures = getSignaturesOfType(elementType, SignatureKind.Call);
|
||||
}
|
||||
if (signatures.length === 0 && elementType.flags & TypeFlags.Union) {
|
||||
// If each member has some combination of new/call signatures; make a union signature list for those
|
||||
signatures = getUnionSignatures(map((elementType as UnionType).types, getUninstantiatedJsxSignaturesOfType));
|
||||
}
|
||||
return signatures;
|
||||
}
|
||||
|
||||
|
@ -18036,20 +18043,29 @@ namespace ts {
|
|||
return anyType;
|
||||
}
|
||||
|
||||
function checkJsxReturnAssignableToAppropriateBound(isSFC: boolean, elemInstanceType: Type, openingLikeElement: Node) {
|
||||
if (isSFC) {
|
||||
function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: Node) {
|
||||
if (refKind === JsxReferenceKind.Function) {
|
||||
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
|
||||
if (sfcReturnConstraint) {
|
||||
checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (refKind === JsxReferenceKind.Component) {
|
||||
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
|
||||
if (classConstraint) {
|
||||
// Issue an error if this return type isn't assignable to JSX.ElementClass or JSX.Element, failing that
|
||||
checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
||||
}
|
||||
}
|
||||
else { // Mixed
|
||||
const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement);
|
||||
const classConstraint = getJsxElementClassTypeAt(openingLikeElement);
|
||||
if (!sfcReturnConstraint || !classConstraint) {
|
||||
return;
|
||||
}
|
||||
const combined = getUnionType([sfcReturnConstraint, classConstraint]);
|
||||
checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18139,7 +18155,7 @@ namespace ts {
|
|||
|
||||
if (isNodeOpeningLikeElement) {
|
||||
const sig = getResolvedSignature(node as JsxOpeningLikeElement);
|
||||
checkJsxReturnAssignableToAppropriateBound(isJsxStatelessFunctionReference(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
|
||||
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node as JsxOpeningLikeElement), getReturnTypeOfSignature(sig), node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19174,12 +19190,18 @@ namespace ts {
|
|||
return typeArgumentTypes;
|
||||
}
|
||||
|
||||
function isJsxStatelessFunctionReference(node: JsxOpeningLikeElement) {
|
||||
function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind {
|
||||
if (isJsxIntrinsicIdentifier(node.tagName)) {
|
||||
return true;
|
||||
return JsxReferenceKind.Mixed;
|
||||
}
|
||||
const tagType = checkExpression(node.tagName);
|
||||
return !length(getSignaturesOfType(getApparentType(tagType), SignatureKind.Construct));
|
||||
const tagType = getApparentType(checkExpression(node.tagName));
|
||||
if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) {
|
||||
return JsxReferenceKind.Component;
|
||||
}
|
||||
if (length(getSignaturesOfType(tagType, SignatureKind.Call))) {
|
||||
return JsxReferenceKind.Function;
|
||||
}
|
||||
return JsxReferenceKind.Mixed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20182,13 +20204,11 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
|
||||
const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct);
|
||||
if (exprTypes.flags & TypeFlags.String || isUntypedFunctionCall(exprTypes, apparentType, callSignatures.length, constructSignatures.length)) {
|
||||
const signatures = getUninstantiatedJsxSignaturesOfType(apparentType);
|
||||
if (exprTypes.flags & TypeFlags.String || isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) {
|
||||
return resolveUntypedCall(node);
|
||||
}
|
||||
|
||||
const signatures = getUninstantiatedJsxSignaturesOfType(apparentType);
|
||||
if (signatures.length === 0) {
|
||||
// We found no signatures at all, which is an error
|
||||
error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName));
|
||||
|
|
|
@ -4217,6 +4217,13 @@ namespace ts {
|
|||
substitute: Type; // Type to substitute for type parameter
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum JsxReferenceKind {
|
||||
Component,
|
||||
Function,
|
||||
Mixed
|
||||
}
|
||||
|
||||
export const enum SignatureKind {
|
||||
Call,
|
||||
Construct,
|
||||
|
|
22
tests/baselines/reference/tsxInvokeComponentType.errors.txt
Normal file
22
tests/baselines/reference/tsxInvokeComponentType.errors.txt
Normal file
|
@ -0,0 +1,22 @@
|
|||
tests/cases/compiler/tsxInvokeComponentType.tsx(6,14): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { someKey: string; } & { children?: ReactNode; }'.
|
||||
Type '{}' is not assignable to type '{ someKey: string; }'.
|
||||
Property 'someKey' is missing in type '{}'.
|
||||
|
||||
|
||||
==== tests/cases/compiler/tsxInvokeComponentType.tsx (1 errors) ====
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
import React, { ComponentType } from "react";
|
||||
|
||||
declare const Elem: ComponentType<{ someKey: string }>;
|
||||
|
||||
const bad = <Elem />;
|
||||
~~~~
|
||||
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { someKey: string; } & { children?: ReactNode; }'.
|
||||
!!! error TS2322: Type '{}' is not assignable to type '{ someKey: string; }'.
|
||||
!!! error TS2322: Property 'someKey' is missing in type '{}'.
|
||||
|
||||
const good = <Elem someKey="ok" />;
|
||||
|
||||
declare const Elem2: ComponentType<{ opt?: number }>;
|
||||
const alsoOk = <Elem2>text</Elem2>;
|
||||
|
25
tests/baselines/reference/tsxInvokeComponentType.js
Normal file
25
tests/baselines/reference/tsxInvokeComponentType.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
//// [tsxInvokeComponentType.tsx]
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
import React, { ComponentType } from "react";
|
||||
|
||||
declare const Elem: ComponentType<{ someKey: string }>;
|
||||
|
||||
const bad = <Elem />;
|
||||
|
||||
const good = <Elem someKey="ok" />;
|
||||
|
||||
declare const Elem2: ComponentType<{ opt?: number }>;
|
||||
const alsoOk = <Elem2>text</Elem2>;
|
||||
|
||||
|
||||
//// [tsxInvokeComponentType.js]
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
exports.__esModule = true;
|
||||
/// <reference path="react16.d.ts" />
|
||||
var react_1 = __importDefault(require("react"));
|
||||
var bad = react_1["default"].createElement(Elem, null);
|
||||
var good = react_1["default"].createElement(Elem, { someKey: "ok" });
|
||||
var alsoOk = react_1["default"].createElement(Elem2, null, "text");
|
30
tests/baselines/reference/tsxInvokeComponentType.symbols
Normal file
30
tests/baselines/reference/tsxInvokeComponentType.symbols
Normal file
|
@ -0,0 +1,30 @@
|
|||
=== tests/cases/compiler/tsxInvokeComponentType.tsx ===
|
||||
/// <reference path="react16.d.ts" />
|
||||
import React, { ComponentType } from "react";
|
||||
>React : Symbol(React, Decl(tsxInvokeComponentType.tsx, 1, 6))
|
||||
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
|
||||
|
||||
declare const Elem: ComponentType<{ someKey: string }>;
|
||||
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
|
||||
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
|
||||
>someKey : Symbol(someKey, Decl(tsxInvokeComponentType.tsx, 3, 35))
|
||||
|
||||
const bad = <Elem />;
|
||||
>bad : Symbol(bad, Decl(tsxInvokeComponentType.tsx, 5, 5))
|
||||
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
|
||||
|
||||
const good = <Elem someKey="ok" />;
|
||||
>good : Symbol(good, Decl(tsxInvokeComponentType.tsx, 7, 5))
|
||||
>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13))
|
||||
>someKey : Symbol(someKey, Decl(tsxInvokeComponentType.tsx, 7, 18))
|
||||
|
||||
declare const Elem2: ComponentType<{ opt?: number }>;
|
||||
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
|
||||
>ComponentType : Symbol(ComponentType, Decl(tsxInvokeComponentType.tsx, 1, 15))
|
||||
>opt : Symbol(opt, Decl(tsxInvokeComponentType.tsx, 9, 36))
|
||||
|
||||
const alsoOk = <Elem2>text</Elem2>;
|
||||
>alsoOk : Symbol(alsoOk, Decl(tsxInvokeComponentType.tsx, 10, 5))
|
||||
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
|
||||
>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13))
|
||||
|
31
tests/baselines/reference/tsxInvokeComponentType.types
Normal file
31
tests/baselines/reference/tsxInvokeComponentType.types
Normal file
|
@ -0,0 +1,31 @@
|
|||
=== tests/cases/compiler/tsxInvokeComponentType.tsx ===
|
||||
/// <reference path="react16.d.ts" />
|
||||
import React, { ComponentType } from "react";
|
||||
>React : typeof React
|
||||
>ComponentType : any
|
||||
|
||||
declare const Elem: ComponentType<{ someKey: string }>;
|
||||
>Elem : React.ComponentType<{ someKey: string; }>
|
||||
>someKey : string
|
||||
|
||||
const bad = <Elem />;
|
||||
>bad : JSX.Element
|
||||
><Elem /> : JSX.Element
|
||||
>Elem : React.ComponentType<{ someKey: string; }>
|
||||
|
||||
const good = <Elem someKey="ok" />;
|
||||
>good : JSX.Element
|
||||
><Elem someKey="ok" /> : JSX.Element
|
||||
>Elem : React.ComponentType<{ someKey: string; }>
|
||||
>someKey : string
|
||||
|
||||
declare const Elem2: ComponentType<{ opt?: number }>;
|
||||
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
|
||||
>opt : number | undefined
|
||||
|
||||
const alsoOk = <Elem2>text</Elem2>;
|
||||
>alsoOk : JSX.Element
|
||||
><Elem2>text</Elem2> : JSX.Element
|
||||
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
|
||||
>Elem2 : React.ComponentType<{ opt?: number | undefined; }>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
tests/cases/conformance/jsx/file.tsx(10,18): error TS2604: JSX element type 'AnyComponent' does not have any construct or call signatures.
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
|
||||
import React = require('react');
|
||||
|
||||
interface ComponentProps {
|
||||
AnyComponent: React.StatelessComponent<any> | React.ComponentClass<any>;
|
||||
}
|
||||
|
||||
class MyComponent extends React.Component<ComponentProps, {}> {
|
||||
render() {
|
||||
const { AnyComponent } = this.props;
|
||||
return (<AnyComponent />);
|
||||
~~~~~~~~~~~~
|
||||
!!! error TS2604: JSX element type 'AnyComponent' does not have any construct or call signatures.
|
||||
}
|
||||
}
|
||||
|
||||
// Stateless Component As Props
|
||||
<MyComponent AnyComponent={() => <button>test</button>}/>
|
||||
|
||||
// Component Class as Props
|
||||
class MyButtonComponent extends React.Component<{},{}> {
|
||||
}
|
||||
|
||||
<MyComponent AnyComponent={MyButtonComponent} />
|
||||
|
||||
|
14
tests/cases/compiler/tsxInvokeComponentType.tsx
Normal file
14
tests/cases/compiler/tsxInvokeComponentType.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
// @jsx: react
|
||||
// @strict: true
|
||||
// @esModuleInterop: true
|
||||
/// <reference path="/.lib/react16.d.ts" />
|
||||
import React, { ComponentType } from "react";
|
||||
|
||||
declare const Elem: ComponentType<{ someKey: string }>;
|
||||
|
||||
const bad = <Elem />;
|
||||
|
||||
const good = <Elem someKey="ok" />;
|
||||
|
||||
declare const Elem2: ComponentType<{ opt?: number }>;
|
||||
const alsoOk = <Elem2>text</Elem2>;
|
Loading…
Reference in a new issue