Use inference for filling JSX attributes in getJsxElementInstanceType (#20973)

* Add simple type inference for JSX elements

* Small cleanups from PR feedback
This commit is contained in:
Wesley Wigham
2018-01-03 11:30:41 -08:00
committed by GitHub
parent 5539e11b76
commit e0f20334d2
21 changed files with 77 additions and 128 deletions
+36 -19
View File
@@ -15043,7 +15043,7 @@ namespace ts {
* element is not a class element, or the class element type cannot be determined, returns 'undefined'.
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
*/
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type) {
function getJsxElementInstanceType(node: JsxOpeningLikeElement, valueType: Type, sourceAttributesType: Type) {
Debug.assert(!(valueType.flags & TypeFlags.Union));
if (isTypeAny(valueType)) {
// Short-circuit if the class tag is using an element type 'any'
@@ -15066,7 +15066,8 @@ namespace ts {
for (const signature of signatures) {
if (signature.typeParameters) {
const isJavascript = isInJavaScriptFile(node);
const typeArguments = fillMissingTypeArguments(/*typeArguments*/ undefined, signature.typeParameters, /*minTypeArgumentCount*/ 0, isJavascript);
const inferenceContext = createInferenceContext(signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : 0);
const typeArguments = inferJsxTypeArguments(signature, sourceAttributesType, inferenceContext);
instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript));
}
else {
@@ -15251,6 +15252,7 @@ namespace ts {
*
* @param openingLikeElement a non-intrinsic JSXOPeningLikeElement
* @param shouldIncludeAllStatelessAttributesType a boolean indicating whether to include all attributes types from all stateless function signature
* @param sourceAttributesType Is the attributes type the user passed, and is used to create inferences in the target type if present
* @param elementType an instance type of the given opening-like element. If undefined, the function will check type openinglikeElement's tagname.
* @param elementClassType a JSX-ElementClass type. This is a result of looking up ElementClass interface in the JSX global (imported from react.d.ts)
* @return attributes type if able to resolve the type of node
@@ -15259,13 +15261,14 @@ namespace ts {
*/
function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement,
shouldIncludeAllStatelessAttributesType: boolean,
elementType: Type = checkExpression(openingLikeElement.tagName),
sourceAttributesType: Type | undefined,
elementType: Type,
elementClassType?: Type): Type {
if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
return getUnionType(types.map(type => {
return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType);
return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, sourceAttributesType, type, elementClassType);
}), UnionReduction.Subtype);
}
@@ -15296,7 +15299,7 @@ namespace ts {
}
// Get the element instance type (the result of newing or invoking this tag)
const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType);
const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType, sourceAttributesType || emptyObjectType);
// If we should include all stateless attributes type, then get all attributes type from all stateless function signature.
// Otherwise get only attributes type from the signature picked by choose-overload logic.
@@ -15309,7 +15312,7 @@ namespace ts {
}
// Issue an error if this return type isn't assignable to JSX.ElementClass
if (elementClassType) {
if (elementClassType && sourceAttributesType) {
checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
}
@@ -15392,14 +15395,20 @@ namespace ts {
* @param node a custom JSX opening-like element
* @param shouldIncludeAllStatelessAttributesType a boolean value used by language service to get all possible attributes type from an overload stateless function component
*/
function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean): Type {
const links = getNodeLinks(node);
const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType";
if (!links[linkLocation]) {
const elemClassType = getJsxGlobalElementClassType();
return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, /*elementType*/ undefined, elemClassType);
function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, sourceAttributesType: Type, shouldIncludeAllStatelessAttributesType: boolean): Type {
if (!sourceAttributesType) {
// This ensures we cache non-inference uses of this calculation (ie, contextual types or services)
const links = getNodeLinks(node);
const linkLocation = shouldIncludeAllStatelessAttributesType ? "resolvedJsxElementAllAttributesType" : "resolvedJsxElementAttributesType";
if (!links[linkLocation]) {
const elemClassType = getJsxGlobalElementClassType();
return links[linkLocation] = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), elemClassType);
}
return links[linkLocation];
}
else {
return resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, sourceAttributesType, checkExpression(node.tagName), getJsxGlobalElementClassType());
}
return links[linkLocation];
}
/**
@@ -15414,7 +15423,7 @@ namespace ts {
else {
// Because in language service, the given JSX opening-like element may be incomplete and therefore,
// we can't resolve to exact signature if the element is a stateless function component so the best thing to do is return all attributes type from all overloads.
return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ true);
return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ true);
}
}
@@ -15428,7 +15437,7 @@ namespace ts {
return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
}
else {
return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ false);
return getCustomJsxElementAttributesType(node, /*sourceAttributesType*/ undefined, /*shouldIncludeAllStatelessAttributesType*/ false);
}
}
@@ -15567,16 +15576,17 @@ namespace ts {
// 2. Solved JSX attributes type given by users, sourceAttributesType, which is by resolving "attributes" property of the JSX opening-like element.
// 3. Check if the two are assignable to each other
// targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element.
const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ?
getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) :
getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false);
// sourceAttributesType is a type of an attributes properties.
// i.e <div attr1={10} attr2="string" />
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes".
const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement, checkMode);
// targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element.
const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ?
getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) :
getCustomJsxElementAttributesType(openingLikeElement, sourceAttributesType, /*shouldIncludeAllStatelessAttributesType*/ false);
// If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type.
// but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass.
if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (<ResolvedType>sourceAttributesType).properties.length > 0)) {
@@ -16434,6 +16444,13 @@ namespace ts {
return getSignatureInstantiation(signature, getInferredTypes(context), isInJavaScriptFile(contextualSignature.declaration));
}
function inferJsxTypeArguments(signature: Signature, sourceAttributesType: Type, context: InferenceContext): Type[] {
const paramType = getTypeAtPosition(signature, 0);
inferTypes(context.inferences, sourceAttributesType, paramType);
return getInferredTypes(context);
}
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: ReadonlyArray<Expression>, excludeArgument: boolean[], context: InferenceContext): Type[] {
// Clear out all the inference results from the last time inferTypeArguments was called on this context
for (const inference of context.inferences) {
@@ -1,19 +0,0 @@
tests/cases/conformance/jsx/file.tsx(11,36): error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<B1<{}>> & { children?: ReactNode; }'.
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
import React = require('react');
class B1<T extends { x: string }> extends React.Component<T, {}> {
render() {
return <div>hi</div>;
}
}
class B<U> extends React.Component<U, {}> {
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
~~~~~~
!!! error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<B1<{}>> & { children?: ReactNode; }'.
}
}
@@ -8,7 +8,6 @@ class B1<T extends { x: string }> extends React.Component<T, {}> {
}
class B<U> extends React.Component<U, {}> {
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
}
}
@@ -43,7 +42,6 @@ var B = /** @class */ (function (_super) {
return _super !== null && _super.apply(this, arguments) || this;
}
B.prototype.render = function () {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi"/>;
};
return B;
@@ -30,12 +30,11 @@ class B<U> extends React.Component<U, {}> {
render() {
>render : Symbol(B.render, Decl(file.tsx, 7, 43))
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
>B1 : Symbol(B1, Decl(file.tsx, 0, 32))
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 167, 37))
>this : Symbol(B, Decl(file.tsx, 6, 1))
>props : Symbol(React.Component.props, Decl(react.d.ts, 167, 37))
>x : Symbol(x, Decl(file.tsx, 10, 34))
>x : Symbol(x, Decl(file.tsx, 9, 34))
}
}
@@ -31,7 +31,6 @@ class B<U> extends React.Component<U, {}> {
render() {
>render : () => JSX.Element
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
><B1 {...this.props} x="hi" /> : JSX.Element
>B1 : typeof B1
@@ -1,20 +0,0 @@
tests/cases/conformance/jsx/file.tsx(12,36): error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<B1<{}>> & { children?: ReactNode; }'.
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
import React = require('react');
class B1<T extends { x: string }> extends React.Component<T, {}> {
render() {
return <div>hi</div>;
}
}
class B<U> extends React.Component<U, {}> {
props: U;
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
~~~~~~
!!! error TS2339: Property 'x' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<B1<{}>> & { children?: ReactNode; }'.
}
}
@@ -9,7 +9,6 @@ class B1<T extends { x: string }> extends React.Component<T, {}> {
class B<U> extends React.Component<U, {}> {
props: U;
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
}
}
@@ -44,7 +43,6 @@ var B = /** @class */ (function (_super) {
return _super !== null && _super.apply(this, arguments) || this;
}
B.prototype.render = function () {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi"/>;
};
return B;
@@ -34,12 +34,11 @@ class B<U> extends React.Component<U, {}> {
render() {
>render : Symbol(B.render, Decl(file.tsx, 8, 13))
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
>B1 : Symbol(B1, Decl(file.tsx, 0, 32))
>this.props : Symbol(B.props, Decl(file.tsx, 7, 43))
>this : Symbol(B, Decl(file.tsx, 6, 1))
>props : Symbol(B.props, Decl(file.tsx, 7, 43))
>x : Symbol(x, Decl(file.tsx, 11, 34))
>x : Symbol(x, Decl(file.tsx, 10, 34))
}
}
@@ -35,7 +35,6 @@ class B<U> extends React.Component<U, {}> {
render() {
>render : () => JSX.Element
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
><B1 {...this.props} x="hi" /> : JSX.Element
>B1 : typeof B1
@@ -1,31 +0,0 @@
tests/cases/conformance/jsx/file.tsx(13,9): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
Type '{}' is not assignable to type 'Prop'.
Property 'a' is missing in type '{}'.
tests/cases/conformance/jsx/file.tsx(14,18): error TS2322: Type '{ a: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
Type '{ a: string; }' is not assignable to type 'Prop'.
Property 'b' is missing in type '{ a: string; }'.
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
import React = require('react');
interface Prop {
a: number,
b: string
}
declare class MyComp<P = Prop> extends React.Component<P, {}> {
internalProp: P;
}
// Error
let x = <MyComp />
~~~~~~~~~~
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
!!! error TS2322: Type '{}' is not assignable to type 'Prop'.
!!! error TS2322: Property 'a' is missing in type '{}'.
let x1 = <MyComp a="hi"/>
~~~~~~
!!! error TS2322: Type '{ a: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
!!! error TS2322: Type '{ a: string; }' is not assignable to type 'Prop'.
!!! error TS2322: Property 'b' is missing in type '{ a: string; }'.
@@ -10,7 +10,6 @@ declare class MyComp<P = Prop> extends React.Component<P, {}> {
internalProp: P;
}
// Error
let x = <MyComp />
let x1 = <MyComp a="hi"/>
@@ -18,6 +17,5 @@ let x1 = <MyComp a="hi"/>
"use strict";
exports.__esModule = true;
var React = require("react");
// Error
var x = <MyComp />;
var x1 = <MyComp a="hi"/>;
@@ -26,13 +26,12 @@ declare class MyComp<P = Prop> extends React.Component<P, {}> {
>P : Symbol(P, Decl(file.tsx, 7, 21))
}
// Error
let x = <MyComp />
>x : Symbol(x, Decl(file.tsx, 12, 3))
>x : Symbol(x, Decl(file.tsx, 11, 3))
>MyComp : Symbol(MyComp, Decl(file.tsx, 5, 1))
let x1 = <MyComp a="hi"/>
>x1 : Symbol(x1, Decl(file.tsx, 13, 3))
>x1 : Symbol(x1, Decl(file.tsx, 12, 3))
>MyComp : Symbol(MyComp, Decl(file.tsx, 5, 1))
>a : Symbol(a, Decl(file.tsx, 13, 16))
>a : Symbol(a, Decl(file.tsx, 12, 16))
@@ -26,7 +26,6 @@ declare class MyComp<P = Prop> extends React.Component<P, {}> {
>P : P
}
// Error
let x = <MyComp />
>x : JSX.Element
><MyComp /> : JSX.Element
@@ -1,5 +1,9 @@
tests/cases/conformance/jsx/file.tsx(16,17): error TS2559: Type '{ a: number; b: string; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<{}>> & { children?: ReactNode; }'.
tests/cases/conformance/jsx/file.tsx(17,18): error TS2559: Type '{ a: string; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<{}>> & { children?: ReactNode; }'.
tests/cases/conformance/jsx/file.tsx(13,10): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
Type '{}' is not assignable to type 'Prop'.
Property 'a' is missing in type '{}'.
tests/cases/conformance/jsx/file.tsx(19,18): error TS2322: Type '{ a: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
Type '{ a: string; }' is not assignable to type 'Prop'.
Property 'b' is missing in type '{ a: string; }'.
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
@@ -14,13 +18,19 @@ tests/cases/conformance/jsx/file.tsx(17,18): error TS2559: Type '{ a: string; }'
internalProp: P;
}
// OK: we fille in missing type argument with empty object
// Error
let x1 = <MyComp />
~~~~~~~~~~
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
!!! error TS2322: Type '{}' is not assignable to type 'Prop'.
!!! error TS2322: Property 'a' is missing in type '{}'.
// OK
let x = <MyComp a={10} b="hi" />
// Error
let x = <MyComp a={10} b="hi" />
~~~~~~~~~~~~~
!!! error TS2559: Type '{ a: number; b: string; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<{}>> & { children?: ReactNode; }'.
let x2 = <MyComp a="hi"/>
~~~~~~
!!! error TS2559: Type '{ a: string; }' has no properties in common with type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<{}>> & { children?: ReactNode; }'.
!!! error TS2322: Type '{ a: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp<Prop>> & Prop & { children?: ReactNode; }'.
!!! error TS2322: Type '{ a: string; }' is not assignable to type 'Prop'.
!!! error TS2322: Property 'b' is missing in type '{ a: string; }'.
@@ -10,19 +10,22 @@ declare class MyComp<P extends Prop> extends React.Component<P, {}> {
internalProp: P;
}
// OK: we fille in missing type argument with empty object
// Error
let x1 = <MyComp />
// Error
// OK
let x = <MyComp a={10} b="hi" />
// Error
let x2 = <MyComp a="hi"/>
//// [file.jsx]
"use strict";
exports.__esModule = true;
var React = require("react");
// OK: we fille in missing type argument with empty object
var x1 = <MyComp />;
// Error
var x1 = <MyComp />;
// OK
var x = <MyComp a={10} b="hi"/>;
// Error
var x2 = <MyComp a="hi"/>;
@@ -26,20 +26,21 @@ declare class MyComp<P extends Prop> extends React.Component<P, {}> {
>P : Symbol(P, Decl(file.tsx, 7, 21))
}
// OK: we fille in missing type argument with empty object
// Error
let x1 = <MyComp />
>x1 : Symbol(x1, Decl(file.tsx, 12, 3))
>MyComp : Symbol(MyComp, Decl(file.tsx, 5, 1))
// Error
// OK
let x = <MyComp a={10} b="hi" />
>x : Symbol(x, Decl(file.tsx, 15, 3))
>MyComp : Symbol(MyComp, Decl(file.tsx, 5, 1))
>a : Symbol(a, Decl(file.tsx, 15, 15))
>b : Symbol(b, Decl(file.tsx, 15, 22))
// Error
let x2 = <MyComp a="hi"/>
>x2 : Symbol(x2, Decl(file.tsx, 16, 3))
>x2 : Symbol(x2, Decl(file.tsx, 18, 3))
>MyComp : Symbol(MyComp, Decl(file.tsx, 5, 1))
>a : Symbol(a, Decl(file.tsx, 16, 16))
>a : Symbol(a, Decl(file.tsx, 18, 16))
@@ -26,13 +26,13 @@ declare class MyComp<P extends Prop> extends React.Component<P, {}> {
>P : P
}
// OK: we fille in missing type argument with empty object
// Error
let x1 = <MyComp />
>x1 : JSX.Element
><MyComp /> : JSX.Element
>MyComp : typeof MyComp
// Error
// OK
let x = <MyComp a={10} b="hi" />
>x : JSX.Element
><MyComp a={10} b="hi" /> : JSX.Element
@@ -41,6 +41,7 @@ let x = <MyComp a={10} b="hi" />
>10 : 10
>b : string
// Error
let x2 = <MyComp a="hi"/>
>x2 : JSX.Element
><MyComp a="hi"/> : JSX.Element
@@ -13,7 +13,6 @@ class B1<T extends { x: string }> extends React.Component<T, {}> {
}
class B<U> extends React.Component<U, {}> {
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
}
}
@@ -14,7 +14,6 @@ class B1<T extends { x: string }> extends React.Component<T, {}> {
class B<U> extends React.Component<U, {}> {
props: U;
render() {
// Should be an ok but as of 2.3.3 this will be an error as we will instantiate B1.props to be empty object
return <B1 {...this.props} x="hi" />;
}
}
@@ -15,6 +15,5 @@ declare class MyComp<P = Prop> extends React.Component<P, {}> {
internalProp: P;
}
// Error
let x = <MyComp />
let x1 = <MyComp a="hi"/>
@@ -15,9 +15,11 @@ declare class MyComp<P extends Prop> extends React.Component<P, {}> {
internalProp: P;
}
// OK: we fille in missing type argument with empty object
// Error
let x1 = <MyComp />
// Error
// OK
let x = <MyComp a={10} b="hi" />
// Error
let x2 = <MyComp a="hi"/>