diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4305227dab79b..288ea95faba94 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6567,7 +6567,7 @@ namespace ts { function findMatchingSignature(signatureList: ReadonlyArray, 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, kind: SignatureKind): Signature[] { - const signatureLists = map(types, t => getSignaturesOfType(t, kind)); + function getUnionSignatures(signatureLists: ReadonlyArray>): 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; } @@ -17094,7 +17097,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) { @@ -17990,13 +17993,17 @@ namespace ts { return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); } - function getUninstantiatedJsxSignaturesOfType(elementType: Type) { + function getUninstantiatedJsxSignaturesOfType(elementType: Type): ReadonlyArray { // 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; } @@ -18022,20 +18029,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); + } } /** @@ -18125,7 +18141,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); } } @@ -19160,12 +19176,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 = getApparentType(checkExpression(node.tagName)); + if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { + return JsxReferenceKind.Component; + } + if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { + return JsxReferenceKind.Function; } - const tagType = checkExpression(node.tagName); - return !length(getSignaturesOfType(getApparentType(tagType), SignatureKind.Construct)); + return JsxReferenceKind.Mixed; } /** @@ -20168,13 +20190,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)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d382436ae429c..d6ef05d7007e6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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, diff --git a/tests/baselines/reference/tsxInvokeComponentType.errors.txt b/tests/baselines/reference/tsxInvokeComponentType.errors.txt new file mode 100644 index 0000000000000..75f9e4ad5d6fd --- /dev/null +++ b/tests/baselines/reference/tsxInvokeComponentType.errors.txt @@ -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) ==== + /// + import React, { ComponentType } from "react"; + + declare const Elem: ComponentType<{ someKey: string }>; + + const bad = ; + ~~~~ +!!! 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 = ; + + declare const Elem2: ComponentType<{ opt?: number }>; + const alsoOk = text; + \ No newline at end of file diff --git a/tests/baselines/reference/tsxInvokeComponentType.js b/tests/baselines/reference/tsxInvokeComponentType.js new file mode 100644 index 0000000000000..613ef8f26c574 --- /dev/null +++ b/tests/baselines/reference/tsxInvokeComponentType.js @@ -0,0 +1,25 @@ +//// [tsxInvokeComponentType.tsx] +/// +import React, { ComponentType } from "react"; + +declare const Elem: ComponentType<{ someKey: string }>; + +const bad = ; + +const good = ; + +declare const Elem2: ComponentType<{ opt?: number }>; +const alsoOk = text; + + +//// [tsxInvokeComponentType.js] +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +exports.__esModule = true; +/// +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"); diff --git a/tests/baselines/reference/tsxInvokeComponentType.symbols b/tests/baselines/reference/tsxInvokeComponentType.symbols new file mode 100644 index 0000000000000..34501acc6738b --- /dev/null +++ b/tests/baselines/reference/tsxInvokeComponentType.symbols @@ -0,0 +1,30 @@ +=== tests/cases/compiler/tsxInvokeComponentType.tsx === +/// +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 = ; +>bad : Symbol(bad, Decl(tsxInvokeComponentType.tsx, 5, 5)) +>Elem : Symbol(Elem, Decl(tsxInvokeComponentType.tsx, 3, 13)) + +const good = ; +>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 = text; +>alsoOk : Symbol(alsoOk, Decl(tsxInvokeComponentType.tsx, 10, 5)) +>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13)) +>Elem2 : Symbol(Elem2, Decl(tsxInvokeComponentType.tsx, 9, 13)) + diff --git a/tests/baselines/reference/tsxInvokeComponentType.types b/tests/baselines/reference/tsxInvokeComponentType.types new file mode 100644 index 0000000000000..a8cad986828b1 --- /dev/null +++ b/tests/baselines/reference/tsxInvokeComponentType.types @@ -0,0 +1,31 @@ +=== tests/cases/compiler/tsxInvokeComponentType.tsx === +/// +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 = ; +>bad : JSX.Element +> : JSX.Element +>Elem : React.ComponentType<{ someKey: string; }> + +const good = ; +>good : JSX.Element +> : 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 = text; +>alsoOk : JSX.Element +>text : JSX.Element +>Elem2 : React.ComponentType<{ opt?: number | undefined; }> +>Elem2 : React.ComponentType<{ opt?: number | undefined; }> + diff --git a/tests/baselines/reference/tsxUnionTypeComponent1.errors.txt b/tests/baselines/reference/tsxUnionTypeComponent1.errors.txt deleted file mode 100644 index 564f7ec3d391c..0000000000000 --- a/tests/baselines/reference/tsxUnionTypeComponent1.errors.txt +++ /dev/null @@ -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 | React.ComponentClass; - } - - class MyComponent extends React.Component { - render() { - const { AnyComponent } = this.props; - return (); - ~~~~~~~~~~~~ -!!! error TS2604: JSX element type 'AnyComponent' does not have any construct or call signatures. - } - } - - // Stateless Component As Props - }/> - - // Component Class as Props - class MyButtonComponent extends React.Component<{},{}> { - } - - - - \ No newline at end of file diff --git a/tests/cases/compiler/tsxInvokeComponentType.tsx b/tests/cases/compiler/tsxInvokeComponentType.tsx new file mode 100644 index 0000000000000..dcb4a30a002d4 --- /dev/null +++ b/tests/cases/compiler/tsxInvokeComponentType.tsx @@ -0,0 +1,14 @@ +// @jsx: react +// @strict: true +// @esModuleInterop: true +/// +import React, { ComponentType } from "react"; + +declare const Elem: ComponentType<{ someKey: string }>; + +const bad = ; + +const good = ; + +declare const Elem2: ComponentType<{ opt?: number }>; +const alsoOk = text;