Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 40 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -17990,13 +17993,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;
}

Expand All @@ -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);
Copy link

@Jessidhia Jessidhia Oct 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note) as of React 16, components are also allowed to return number / string / boolean (in other words, anything other than undefined, symbol or a non-Element, non-Array object), but at least TS@latest will reject SFC which include those return types. It does validate with classes, just not functions.

If fixed this probably goes in another PR though.

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

/**
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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));
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions tests/baselines/reference/tsxInvokeComponentType.errors.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 25 additions & 0 deletions tests/baselines/reference/tsxInvokeComponentType.js
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions tests/baselines/reference/tsxInvokeComponentType.symbols
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions tests/baselines/reference/tsxInvokeComponentType.types
Original file line number Diff line number Diff line change
@@ -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; }>

29 changes: 0 additions & 29 deletions tests/baselines/reference/tsxUnionTypeComponent1.errors.txt

This file was deleted.

14 changes: 14 additions & 0 deletions tests/cases/compiler/tsxInvokeComponentType.tsx
Original file line number Diff line number Diff line change
@@ -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>;