Skip to content

Commit 9d2656f

Browse files
committed
Fix #15463: use intersection types to emulate spread in generic react components (#15851)
* Fix #15463: use intersection types to emulate spread in generic react components * Fix lint errors * reverse condition
1 parent 8534a5a commit 9d2656f

11 files changed

+197
-33
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13207,6 +13207,7 @@ namespace ts {
1320713207
let spread: Type = emptyObjectType;
1320813208
let attributesArray: Symbol[] = [];
1320913209
let hasSpreadAnyType = false;
13210+
let typeToIntersect: Type;
1321013211
let explicitlySpecifyChildrenAttribute = false;
1321113212
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
1321213213

@@ -13238,11 +13239,16 @@ namespace ts {
1323813239
attributesArray = [];
1323913240
attributesTable = createMap<Symbol>();
1324013241
}
13241-
const exprType = getApparentType(checkExpression(attributeDecl.expression));
13242+
const exprType = checkExpression(attributeDecl.expression);
1324213243
if (isTypeAny(exprType)) {
1324313244
hasSpreadAnyType = true;
1324413245
}
13245-
spread = getSpreadType(spread, exprType);
13246+
if (isValidSpreadType(exprType)) {
13247+
spread = getSpreadType(spread, exprType);
13248+
}
13249+
else {
13250+
typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType;
13251+
}
1324613252
}
1324713253
}
1324813254

@@ -13301,7 +13307,13 @@ namespace ts {
1330113307
}
1330213308
}
1330313309

13304-
return hasSpreadAnyType ? anyType : createJsxAttributesType(attributes.symbol, attributesTable);
13310+
if (hasSpreadAnyType) {
13311+
return anyType;
13312+
}
13313+
13314+
const attributeType = createJsxAttributesType(attributes.symbol, attributesTable);
13315+
return typeToIntersect && attributesTable.size ? getIntersectionType([typeToIntersect, attributeType]) :
13316+
typeToIntersect ? typeToIntersect : attributeType;
1330513317

1330613318
/**
1330713319
* Create anonymous type from given attributes symbol table.

tests/baselines/reference/tsxAttributeResolution5.errors.txt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
tests/cases/conformance/jsx/file.tsx(21,16): error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
2-
Types of property 'x' are incompatible.
3-
Type 'number' is not assignable to type 'string'.
4-
tests/cases/conformance/jsx/file.tsx(25,16): error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
5-
Property 'x' is missing in type '{ y: string; }'.
1+
tests/cases/conformance/jsx/file.tsx(21,16): error TS2322: Type 'T' is not assignable to type 'Attribs1'.
2+
Type '{ x: number; }' is not assignable to type 'Attribs1'.
3+
Types of property 'x' are incompatible.
4+
Type 'number' is not assignable to type 'string'.
5+
tests/cases/conformance/jsx/file.tsx(25,16): error TS2322: Type 'T' is not assignable to type 'Attribs1'.
6+
Type '{ y: string; }' is not assignable to type 'Attribs1'.
7+
Property 'x' is missing in type '{ y: string; }'.
68
tests/cases/conformance/jsx/file.tsx(29,8): error TS2322: Type '{}' is not assignable to type 'Attribs1'.
79
Property 'x' is missing in type '{}'.
810

@@ -30,16 +32,18 @@ tests/cases/conformance/jsx/file.tsx(29,8): error TS2322: Type '{}' is not assig
3032
function make2<T extends {x: number}> (obj: T) {
3133
return <test1 {...obj} />; // Error (x is number, not string)
3234
~~~~~~~~
33-
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
34-
!!! error TS2322: Types of property 'x' are incompatible.
35-
!!! error TS2322: Type 'number' is not assignable to type 'string'.
35+
!!! error TS2322: Type 'T' is not assignable to type 'Attribs1'.
36+
!!! error TS2322: Type '{ x: number; }' is not assignable to type 'Attribs1'.
37+
!!! error TS2322: Types of property 'x' are incompatible.
38+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
3639
}
3740

3841
function make3<T extends {y: string}> (obj: T) {
3942
return <test1 {...obj} />; // Error, missing x
4043
~~~~~~~~
41-
!!! error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
42-
!!! error TS2322: Property 'x' is missing in type '{ y: string; }'.
44+
!!! error TS2322: Type 'T' is not assignable to type 'Attribs1'.
45+
!!! error TS2322: Type '{ y: string; }' is not assignable to type 'Attribs1'.
46+
!!! error TS2322: Property 'x' is missing in type '{ y: string; }'.
4347
}
4448

4549

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [file.tsx]
2+
import React = require('react');
3+
4+
export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
5+
return class extends React.PureComponent<P, void> {
6+
public render(): JSX.Element {
7+
return (
8+
<Ctor {...this.props } />
9+
);
10+
}
11+
};
12+
}
13+
14+
//// [file.jsx]
15+
"use strict";
16+
var __extends = (this && this.__extends) || (function () {
17+
var extendStatics = Object.setPrototypeOf ||
18+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
19+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
20+
return function (d, b) {
21+
extendStatics(d, b);
22+
function __() { this.constructor = d; }
23+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
24+
};
25+
})();
26+
exports.__esModule = true;
27+
var React = require("react");
28+
function makeP(Ctor) {
29+
return (function (_super) {
30+
__extends(class_1, _super);
31+
function class_1() {
32+
return _super !== null && _super.apply(this, arguments) || this;
33+
}
34+
class_1.prototype.render = function () {
35+
return (<Ctor {...this.props}/>);
36+
};
37+
return class_1;
38+
}(React.PureComponent));
39+
}
40+
exports.makeP = makeP;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : Symbol(React, Decl(file.tsx, 0, 0))
4+
5+
export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
6+
>makeP : Symbol(makeP, Decl(file.tsx, 0, 32))
7+
>P : Symbol(P, Decl(file.tsx, 2, 22))
8+
>Ctor : Symbol(Ctor, Decl(file.tsx, 2, 25))
9+
>React : Symbol(React, Decl(file.tsx, 0, 0))
10+
>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 204, 5))
11+
>P : Symbol(P, Decl(file.tsx, 2, 22))
12+
>React : Symbol(React, Decl(file.tsx, 0, 0))
13+
>ComponentClass : Symbol(React.ComponentClass, Decl(react.d.ts, 204, 5))
14+
>P : Symbol(P, Decl(file.tsx, 2, 22))
15+
16+
return class extends React.PureComponent<P, void> {
17+
>React.PureComponent : Symbol(React.PureComponent, Decl(react.d.ts, 179, 5))
18+
>React : Symbol(React, Decl(file.tsx, 0, 0))
19+
>PureComponent : Symbol(React.PureComponent, Decl(react.d.ts, 179, 5))
20+
>P : Symbol(P, Decl(file.tsx, 2, 22))
21+
22+
public render(): JSX.Element {
23+
>render : Symbol((Anonymous class).render, Decl(file.tsx, 3, 52))
24+
>JSX : Symbol(JSX, Decl(react.d.ts, 2352, 1))
25+
>Element : Symbol(JSX.Element, Decl(react.d.ts, 2355, 27))
26+
27+
return (
28+
<Ctor {...this.props } />
29+
>Ctor : Symbol(Ctor, Decl(file.tsx, 2, 25))
30+
>this.props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
31+
>this : Symbol((Anonymous class), Decl(file.tsx, 3, 7))
32+
>props : Symbol(React.Component.props, Decl(react.d.ts, 166, 37))
33+
34+
);
35+
}
36+
};
37+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/conformance/jsx/file.tsx ===
2+
import React = require('react');
3+
>React : typeof React
4+
5+
export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
6+
>makeP : <P>(Ctor: React.ComponentClass<P>) => React.ComponentClass<P>
7+
>P : P
8+
>Ctor : React.ComponentClass<P>
9+
>React : any
10+
>ComponentClass : React.ComponentClass<P>
11+
>P : P
12+
>React : any
13+
>ComponentClass : React.ComponentClass<P>
14+
>P : P
15+
16+
return class extends React.PureComponent<P, void> {
17+
>class extends React.PureComponent<P, void> { public render(): JSX.Element { return ( <Ctor {...this.props } /> ); } } : typeof (Anonymous class)
18+
>React.PureComponent : React.PureComponent<P, void>
19+
>React : typeof React
20+
>PureComponent : typeof React.PureComponent
21+
>P : P
22+
23+
public render(): JSX.Element {
24+
>render : () => JSX.Element
25+
>JSX : any
26+
>Element : JSX.Element
27+
28+
return (
29+
>( <Ctor {...this.props } /> ) : JSX.Element
30+
31+
<Ctor {...this.props } />
32+
><Ctor {...this.props } /> : JSX.Element
33+
>Ctor : React.ComponentClass<P>
34+
>this.props : P & { children?: React.ReactNode; }
35+
>this : this
36+
>props : P & { children?: React.ReactNode; }
37+
38+
);
39+
}
40+
};
41+
}

tests/baselines/reference/tsxStatelessFunctionComponentsWithTypeArguments2.errors.txt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
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; }'.
2-
Type '{ ignore-prop: 10; prop: number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
1+
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; }'.
2+
Type 'T & { ignore-prop: 10; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
33
Types of property '"ignore-prop"' are incompatible.
44
Type '10' is not assignable to type 'string'.
5-
tests/cases/conformance/jsx/file.tsx(13,34): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
6-
Type '{}' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
7-
Property 'prop' is missing in type '{}'.
5+
tests/cases/conformance/jsx/file.tsx(13,34): error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
6+
Type 'T' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
87
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; }'.
98
Type '{ func: (a: number, b: string) => void; }' is not assignable to type '{ func: (arg: number) => void; }'.
109
Types of property 'func' are incompatible.
@@ -25,8 +24,8 @@ tests/cases/conformance/jsx/file.tsx(31,10): error TS2453: The type argument for
2524
function Bar<T extends {prop: number}>(arg: T) {
2625
let a1 = <ComponentSpecific1 {...arg} ignore-prop={10} />;
2726
~~~~~~~~~~~~~~~~~~~~~~~~~
28-
!!! error TS2322: Type '{ ignore-prop: 10; prop: number; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
29-
!!! error TS2322: Type '{ ignore-prop: 10; prop: number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
27+
!!! error TS2322: Type 'T & { ignore-prop: 10; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
28+
!!! error TS2322: Type 'T & { ignore-prop: 10; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
3029
!!! error TS2322: Types of property '"ignore-prop"' are incompatible.
3130
!!! error TS2322: Type '10' is not assignable to type 'string'.
3231
}
@@ -35,9 +34,8 @@ tests/cases/conformance/jsx/file.tsx(31,10): error TS2453: The type argument for
3534
function Baz<T>(arg: T) {
3635
let a0 = <ComponentSpecific1 {...arg} />
3736
~~~~~~~~
38-
!!! error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
39-
!!! error TS2322: Type '{}' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
40-
!!! error TS2322: Property 'prop' is missing in type '{}'.
37+
!!! error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { prop: {}; "ignore-prop": string; }'.
38+
!!! error TS2322: Type 'T' is not assignable to type '{ prop: {}; "ignore-prop": string; }'.
4139
}
4240

4341
declare function Link<U>(l: {func: (arg: U)=>void}): JSX.Element;

tests/baselines/reference/tsxStatelessFunctionComponentsWithTypeArguments4.errors.txt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
tests/cases/conformance/jsx/file.tsx(9,33): error TS2322: Type '{ a: number; }' is not assignable to type 'IntrinsicAttributes & { b: {}; a: number; }'.
22
Type '{ a: number; }' is not assignable to type '{ b: {}; a: number; }'.
33
Property 'b' is missing in type '{ a: number; }'.
4-
tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
5-
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
6-
Property 'a' is missing in type '{ b: number; }'.
4+
tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
5+
Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
6+
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
7+
Type 'T' is not assignable to type '{ b: number; a: {}; }'.
8+
Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
9+
Property 'a' is missing in type '{ b: number; }'.
710

811

912
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
@@ -22,7 +25,10 @@ tests/cases/conformance/jsx/file.tsx(10,33): error TS2322: Type '{ b: number; }'
2225
!!! error TS2322: Property 'b' is missing in type '{ a: number; }'.
2326
let a2 = <OverloadComponent {...arg1} ignore-prop /> // missing a
2427
~~~~~~~~~~~~~~~~~~~~~
25-
!!! error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
26-
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
27-
!!! error TS2322: Property 'a' is missing in type '{ b: number; }'.
28+
!!! error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
29+
!!! error TS2322: Type '{ b: number; }' is not assignable to type 'IntrinsicAttributes & { b: number; a: {}; }'.
30+
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
31+
!!! error TS2322: Type 'T' is not assignable to type '{ b: number; a: {}; }'.
32+
!!! error TS2322: Type '{ b: number; }' is not assignable to type '{ b: number; a: {}; }'.
33+
!!! error TS2322: Property 'a' is missing in type '{ b: number; }'.
2834
}

tests/baselines/reference/tsxStatelessFunctionComponentsWithTypeArguments5.errors.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
tests/cases/conformance/jsx/file.tsx(15,14): error TS2605: JSX element type 'Element' is not a constructor function for JSX elements.
2+
Property 'render' is missing in type 'Element'.
3+
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.
4+
Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate '"hello"'.
15
tests/cases/conformance/jsx/file.tsx(16,42): error TS2339: Property 'prop1' does not exist on type 'IntrinsicAttributes & { prop: number; }'.
26

37

4-
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
8+
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
59
import React = require('react')
610

711
declare function Component<U>(l: U): JSX.Element;
@@ -17,6 +21,12 @@ tests/cases/conformance/jsx/file.tsx(16,42): error TS2339: Property 'prop1' does
1721
let a1 = <ComponentSpecific {...arg} ignore-prop="hi" />; // U is number
1822
let a2 = <ComponentSpecific1 {...arg} ignore-prop={10} />; // U is number
1923
let a3 = <ComponentSpecific {...arg} prop="hello" />; // U is "hello"
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
!!! error TS2605: JSX element type 'Element' is not a constructor function for JSX elements.
26+
!!! error TS2605: Property 'render' is missing in type 'Element'.
27+
~~~~~~~~~~~~~~~~~
28+
!!! error TS2453: The type argument for type parameter 'U' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
29+
!!! error TS2453: Type argument candidate 'number' is not a valid type argument because it is not a supertype of candidate '"hello"'.
2030
let a4 = <ComponentSpecific {...arg} prop1="hello" />; // U is "hello"
2131
~~~~~~~~~~~~~
2232
!!! error TS2339: Property 'prop1' does not exist on type 'IntrinsicAttributes & { prop: number; }'.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @filename: file.tsx
2+
// @jsx: preserve
3+
// @noLib: true
4+
// @libFiles: react.d.ts,lib.d.ts
5+
6+
import React = require('react');
7+
8+
export function makeP<P>(Ctor: React.ComponentClass<P>): React.ComponentClass<P> {
9+
return class extends React.PureComponent<P, void> {
10+
public render(): JSX.Element {
11+
return (
12+
<Ctor {...this.props } />
13+
);
14+
}
15+
};
16+
}

tests/cases/fourslash/tsxQuickInfo6.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515

1616
verify.quickInfos({
1717
1: "function ComponentSpecific<number>(l: {\n prop: number;\n}): any",
18-
2: "function ComponentSpecific<\"hello\">(l: {\n prop: \"hello\";\n}): any"
18+
2: "function ComponentSpecific<U>(l: {\n prop: U;\n}): any"
1919
});

tests/cases/fourslash/tsxQuickInfo7.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ verify.quickInfos({
2424
3: "function OverloadComponent<boolean, string>(attr: {\n b: string;\n a: boolean;\n}): any (+2 overloads)",
2525
4: "function OverloadComponent<number>(attr: {\n b: number;\n a?: string;\n \"ignore-prop\": boolean;\n}): any (+2 overloads)",
2626
5: "function OverloadComponent(): any (+2 overloads)",
27-
6: "function OverloadComponent<boolean, number>(attr: {\n b: number;\n a: boolean;\n}): any (+2 overloads)",
28-
7: "function OverloadComponent<boolean, string>(attr: {\n b: string;\n a: boolean;\n}): any (+2 overloads)"
27+
6: "function OverloadComponent(): any (+2 overloads)",
28+
7: "function OverloadComponent(): any (+2 overloads)",
2929
});

0 commit comments

Comments
 (0)