Skip to content

Commit

Permalink
Fix #15463: use intersection types to emulate spread in generic react…
Browse files Browse the repository at this point in the history
… components (#15851)

* Fix #15463: use intersection types to emulate spread in generic react components

* Fix lint errors

* reverse condition
  • Loading branch information
mhegazy committed May 15, 2017
1 parent 8534a5a commit 9d2656f
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 33 deletions.
18 changes: 15 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13207,6 +13207,7 @@ namespace ts {
let spread: Type = emptyObjectType;
let attributesArray: Symbol[] = [];
let hasSpreadAnyType = false;
let typeToIntersect: Type;
let explicitlySpecifyChildrenAttribute = false;
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();

Expand Down Expand Up @@ -13238,11 +13239,16 @@ namespace ts {
attributesArray = [];
attributesTable = createMap<Symbol>();
}
const exprType = getApparentType(checkExpression(attributeDecl.expression));
const exprType = checkExpression(attributeDecl.expression);
if (isTypeAny(exprType)) {
hasSpreadAnyType = true;
}
spread = getSpreadType(spread, exprType);
if (isValidSpreadType(exprType)) {
spread = getSpreadType(spread, exprType);
}
else {
typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType;
}
}
}

Expand Down Expand Up @@ -13301,7 +13307,13 @@ namespace ts {
}
}

return hasSpreadAnyType ? anyType : createJsxAttributesType(attributes.symbol, attributesTable);
if (hasSpreadAnyType) {
return anyType;
}

const attributeType = createJsxAttributesType(attributes.symbol, attributesTable);
return typeToIntersect && attributesTable.size ? getIntersectionType([typeToIntersect, attributeType]) :
typeToIntersect ? typeToIntersect : attributeType;

/**
* Create anonymous type from given attributes symbol table.
Expand Down
24 changes: 14 additions & 10 deletions tests/baselines/reference/tsxAttributeResolution5.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
tests/cases/conformance/jsx/file.tsx(21,16): error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
Types of property 'x' are incompatible.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsx/file.tsx(25,16): error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
Property 'x' is missing in type '{ y: string; }'.
tests/cases/conformance/jsx/file.tsx(21,16): error TS2322: Type 'T' is not assignable to type 'Attribs1'.
Type '{ x: number; }' is not assignable to type 'Attribs1'.
Types of property 'x' are incompatible.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/jsx/file.tsx(25,16): error TS2322: Type 'T' is not assignable to type 'Attribs1'.
Type '{ y: string; }' is not assignable to type 'Attribs1'.
Property 'x' is missing in type '{ y: string; }'.
tests/cases/conformance/jsx/file.tsx(29,8): error TS2322: Type '{}' is not assignable to type 'Attribs1'.
Property 'x' is missing in type '{}'.

Expand Down Expand Up @@ -30,16 +32,18 @@ tests/cases/conformance/jsx/file.tsx(29,8): error TS2322: Type '{}' is not assig
function make2<T extends {x: number}> (obj: T) {
return <test1 {...obj} />; // Error (x is number, not string)
~~~~~~~~
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
!!! error TS2322: Type 'T' is not assignable to type 'Attribs1'.
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}

function make3<T extends {y: string}> (obj: T) {
return <test1 {...obj} />; // Error, missing x
~~~~~~~~
!!! error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
!!! error TS2322: Property 'x' is missing in type '{ y: string; }'.
!!! error TS2322: Type 'T' is not assignable to type 'Attribs1'.
!!! error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
!!! error TS2322: Property 'x' is missing in type '{ y: string; }'.
}


Expand Down
40 changes: 40 additions & 0 deletions tests/baselines/reference/tsxGenericAttributesType9.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [file.tsx]
import React = require('react');

export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
return class extends React.PureComponent<P, void> {
public render(): JSX.Element {
return (
<Ctor {...this.props } />
);
}
};
}

//// [file.jsx]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
var React = require("react");
function makeP(Ctor) {
return (function (_super) {
__extends(class_1, _super);
function class_1() {
return _super !== null && _super.apply(this, arguments) || this;
}
class_1.prototype.render = function () {
return (<Ctor {...this.props}/>);
};
return class_1;
}(React.PureComponent));
}
exports.makeP = makeP;
37 changes: 37 additions & 0 deletions tests/baselines/reference/tsxGenericAttributesType9.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : Symbol(React, Decl(file.tsx, 0, 0))

export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
>makeP : Symbol(makeP, Decl(file.tsx, 0, 32))
>P : Symbol(P, Decl(file.tsx, 2, 22))
>Ctor : Symbol(Ctor, Decl(file.tsx, 2, 25))
>React : Symbol(React, Decl(file.tsx, 0, 0))
>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 204, 5))
>P : Symbol(P, Decl(file.tsx, 2, 22))
>React : Symbol(React, Decl(file.tsx, 0, 0))
>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 204, 5))
>P : Symbol(P, Decl(file.tsx, 2, 22))

return class extends React.PureComponent<P, void> {
>React.PureComponent : Symbol(React.PureComponent, Decl(react.d.ts, 179, 5))
>React : Symbol(React, Decl(file.tsx, 0, 0))
>PureComponent : Symbol(React.PureComponent, Decl(react.d.ts, 179, 5))
>P : Symbol(P, Decl(file.tsx, 2, 22))

public render(): JSX.Element {
>render : Symbol((Anonymous class).render, Decl(file.tsx, 3, 52))
>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1))
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27))

return (
<Ctor {...this.props } />
>Ctor : Symbol(Ctor, Decl(file.tsx, 2, 25))
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
>this : Symbol((Anonymous class), Decl(file.tsx, 3, 7))
>props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))

);
}
};
}
41 changes: 41 additions & 0 deletions tests/baselines/reference/tsxGenericAttributesType9.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=== tests/cases/conformance/jsx/file.tsx ===
import React = require('react');
>React : typeof React

export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
>makeP : <P>(Ctor: React.ComponentClass<P>) => React.ComponentClass<P>
>P : P
>Ctor : React.ComponentClass<P>
>React : any
>ComponentClass : React.ComponentClass<P>
>P : P
>React : any
>ComponentClass : React.ComponentClass<P>
>P : P

return class extends React.PureComponent<P, void> {
>class extends React.PureComponent<P, void> { public render(): JSX.Element { return ( <Ctor {...this.props } /> ); } } : typeof (Anonymous class)
>React.PureComponent : React.PureComponent<P, void>
>React : typeof React
>PureComponent : typeof React.PureComponent
>P : P

public render(): JSX.Element {
>render : () => JSX.Element
>JSX : any
>Element : JSX.Element

return (
>( <Ctor {...this.props } /> ) : JSX.Element

<Ctor {...this.props } />
><Ctor {...this.props } /> : JSX.Element
>Ctor : React.ComponentClass<P>
>this.props : P & { children?: React.ReactNode; }
>this : this
>props : P & { children?: React.ReactNode; }

);
}
};
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
tests/cases/conformance/jsx/file.tsx(8,34): error TS2322: Type '{ ignore-prop: 10; prop: number; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
Type '{ ignore-prop: 10; prop: number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
tests/cases/conformance/jsx/file.tsx(8,34): error TS2322: Type 'T & { ignore-prop: 10; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
Type 'T & { ignore-prop: 10; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
Types of property '"ignore-prop"' are incompatible.
Type '10' is not assignable to type 'string'.
tests/cases/conformance/jsx/file.tsx(13,34): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
Type '{}' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
Property 'prop' is missing in type '{}'.
tests/cases/conformance/jsx/file.tsx(13,34): error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
Type 'T' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
tests/cases/conformance/jsx/file.tsx(20,19): error TS2322: Type '{ func: (a: number, b: string) => void; }' is not assignable to type 'IntrinsicAttributes & { func: (arg: number) => void; }'.
Type '{ func: (a: number, b: string) => void; }' is not assignable to type '{ func: (arg: number) => void; }'.
Types of property 'func' are incompatible.
Expand All @@ -25,8 +24,8 @@ tests/cases/conformance/jsx/file.tsx(31,10): error TS2453: The type argument for
function Bar<T extends {prop: number}>(arg: T) {
let a1 = <ComponentSpecific1 {...arg} ignore-prop={10} />;
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ ignore-prop: 10; prop: number; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
!!! error TS2322: Type '{ ignore-prop: 10; prop: number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
!!! error TS2322: Type 'T & { ignore-prop: 10; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
!!! error TS2322: Type 'T & { ignore-prop: 10; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
!!! error TS2322: Types of property '"ignore-prop"' are incompatible.
!!! error TS2322: Type '10' is not assignable to type 'string'.
}
Expand All @@ -35,9 +34,8 @@ tests/cases/conformance/jsx/file.tsx(31,10): error TS2453: The type argument for
function Baz<T>(arg: T) {
let a0 = <ComponentSpecific1 {...arg} />
~~~~~~~~
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
!!! error TS2322: Type '{}' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
!!! error TS2322: Property 'prop' is missing in type '{}'.
!!! error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
!!! error TS2322: Type 'T' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
}

declare function Link<U>(l: {func: (arg: U)=>void}): JSX.Element;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
tests/cases/conformance/jsx/file.tsx(9,33): error TS2322: Type '{ a: number; }' is not assignable to type 'IntrinsicAttributes & { b: {}; a: number; }'.
Type '{ a: number; }' is not assignable to type '{ b: {}; a: number; }'.
Property 'b' is missing in type '{ a: number; }'.
tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
Property 'a' is missing in type '{ b: number; }'.
tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
Type 'T' is not assignable to type '{ b: number; a: {}; }'.
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
Property 'a' is missing in type '{ b: number; }'.


==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
Expand All @@ -22,7 +25,10 @@ tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type '{ b: number; }'
!!! error TS2322: Property 'b' is missing in type '{ a: number; }'.
let a2 = <OverloadComponent {...arg1} ignore-prop /> // missing a
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
!!! error TS2322: Property 'a' is missing in type '{ b: number; }'.
!!! error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
!!! error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
!!! error TS2322: Type 'T' is not assignable to type '{ b: number; a: {}; }'.
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
!!! error TS2322: Property 'a' is missing in type '{ b: number; }'.
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
tests/cases/conformance/jsx/file.tsx(15,14): error TS2605: JSX element type 'Element' is not a constructor function for JSX elements.
Property 'render' is missing in type 'Element'.
tests/cases/conformance/jsx/file.tsx(15,15): error TS2453: The type argument for type parameter 'U' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate '"hello"'.
tests/cases/conformance/jsx/file.tsx(16,42): error TS2339: Property 'prop1' does not exist on type 'IntrinsicAttributes & { prop: number; }'.


==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
import React = require('react')

declare function Component<U>(l: U): JSX.Element;
Expand All @@ -17,6 +21,12 @@ tests/cases/conformance/jsx/file.tsx(16,42): error TS2339: Property 'prop1' does
let a1 = <ComponentSpecific {...arg} ignore-prop="hi" />; // U is number
let a2 = <ComponentSpecific1 {...arg} ignore-prop={10} />; // U is number
let a3 = <ComponentSpecific {...arg} prop="hello" />; // U is "hello"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2605: JSX element type 'Element' is not a constructor function for JSX elements.
!!! error TS2605: Property 'render' is missing in type 'Element'.
~~~~~~~~~~~~~~~~~
!!! error TS2453: The type argument for type parameter 'U' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate '"hello"'.
let a4 = <ComponentSpecific {...arg} prop1="hello" />; // U is "hello"
~~~~~~~~~~~~~
!!! error TS2339: Property 'prop1' does not exist on type 'IntrinsicAttributes & { prop: number; }'.
Expand Down
16 changes: 16 additions & 0 deletions tests/cases/conformance/jsx/tsxGenericAttributesType9.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @filename: file.tsx
// @jsx: preserve
// @noLib: true
// @libFiles: react.d.ts,lib.d.ts

import React = require('react');

export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
return class extends React.PureComponent<P, void> {
public render(): JSX.Element {
return (
<Ctor {...this.props } />
);
}
};
}
2 changes: 1 addition & 1 deletion tests/cases/fourslash/tsxQuickInfo6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@

verify.quickInfos({
1: "function ComponentSpecific<number>(l: {\n prop: number;\n}): any",
2: "function ComponentSpecific<\"hello\">(l: {\n prop: \"hello\";\n}): any"
2: "function ComponentSpecific<U>(l: {\n prop: U;\n}): any"
});
4 changes: 2 additions & 2 deletions tests/cases/fourslash/tsxQuickInfo7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ verify.quickInfos({
3: "function OverloadComponent<boolean, string>(attr: {\n b: string;\n a: boolean;\n}): any (+2 overloads)",
4: "function OverloadComponent<number>(attr: {\n b: number;\n a?: string;\n \"ignore-prop\": boolean;\n}): any (+2 overloads)",
5: "function OverloadComponent(): any (+2 overloads)",
6: "function OverloadComponent<boolean, number>(attr: {\n b: number;\n a: boolean;\n}): any (+2 overloads)",
7: "function OverloadComponent<boolean, string>(attr: {\n b: string;\n a: boolean;\n}): any (+2 overloads)"
6: "function OverloadComponent(): any (+2 overloads)",
7: "function OverloadComponent(): any (+2 overloads)",
});

0 comments on commit 9d2656f

Please sign in to comment.