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:
Wesley Wigham 2018-10-26 16:01:32 -07:00 committed by GitHub
parent 36dfd775b3
commit 972c403cd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 49 deletions

View file

@ -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));

View file

@ -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,

View 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>;

View 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");

View 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))

View 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; }>

View file

@ -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} />

View 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>;