Skip to content

Commit 13234b6

Browse files
authored
Force inner indexed access simplification during inference, if possible (#28420)
* Force inner indexed access simplification during inference, if possible * rename * Refactor to used shared implementation of distribution
1 parent 995f746 commit 13234b6

6 files changed

+171
-13
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9533,6 +9533,17 @@ namespace ts {
95339533
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type;
95349534
}
95359535

9536+
function distributeIndexOverObjectType(objectType: Type, indexType: Type) {
9537+
// (T | U)[K] -> T[K] | U[K]
9538+
if (objectType.flags & TypeFlags.Union) {
9539+
return mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
9540+
}
9541+
// (T & U)[K] -> T[K] & U[K]
9542+
if (objectType.flags & TypeFlags.Intersection) {
9543+
return getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
9544+
}
9545+
}
9546+
95369547
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
95379548
// the type itself if no transformation is possible.
95389549
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
@@ -9550,13 +9561,9 @@ namespace ts {
95509561
}
95519562
// Only do the inner distributions if the index can no longer be instantiated to cause index distribution again
95529563
if (!(indexType.flags & TypeFlags.Instantiable)) {
9553-
// (T | U)[K] -> T[K] | U[K]
9554-
if (objectType.flags & TypeFlags.Union) {
9555-
return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
9556-
}
9557-
// (T & U)[K] -> T[K] & U[K]
9558-
if (objectType.flags & TypeFlags.Intersection) {
9559-
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
9564+
const simplified = distributeIndexOverObjectType(objectType, indexType);
9565+
if (simplified) {
9566+
return type.simplified = simplified;
95609567
}
95619568
}
95629569
// So ultimately:
@@ -13888,10 +13895,17 @@ namespace ts {
1388813895
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
1388913896
const simplified = getSimplifiedType(target);
1389013897
if (simplified !== target) {
13891-
const key = source.id + "," + simplified.id;
13892-
if (!visited || !visited.get(key)) {
13893-
(visited || (visited = createMap<boolean>())).set(key, true);
13894-
inferFromTypes(source, simplified);
13898+
inferFromTypesOnce(source, simplified);
13899+
}
13900+
else if (target.flags & TypeFlags.IndexedAccess) {
13901+
const indexType = getSimplifiedType((target as IndexedAccessType).indexType);
13902+
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
13903+
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
13904+
if (indexType.flags & TypeFlags.Instantiable) {
13905+
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType), indexType);
13906+
if (simplified && simplified !== target) {
13907+
inferFromTypesOnce(source, simplified);
13908+
}
1389513909
}
1389613910
}
1389713911
}
@@ -14014,6 +14028,14 @@ namespace ts {
1401414028
}
1401514029
}
1401614030
}
14031+
14032+
function inferFromTypesOnce(source: Type, target: Type) {
14033+
const key = source.id + "," + target.id;
14034+
if (!visited || !visited.get(key)) {
14035+
(visited || (visited = createMap<boolean>())).set(key, true);
14036+
inferFromTypes(source, target);
14037+
}
14038+
}
1401714039
}
1401814040

1401914041
function inferFromContravariantTypes(source: Type, target: Type) {

tests/baselines/reference/checkJsxIntersectionElementPropsType.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ class C<T> extends Component<{ x?: boolean; } & T> {}
2020
>x : boolean | undefined
2121

2222
const y = new C({foobar: "example"});
23-
>y : C<{ foobar: {}; }>
24-
>new C({foobar: "example"}) : C<{ foobar: {}; }>
23+
>y : C<{ foobar: string; }>
24+
>new C({foobar: "example"}) : C<{ foobar: string; }>
2525
>C : typeof C
2626
>{foobar: "example"} : { foobar: string; }
2727
>foobar : string
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [tsxReactPropsInferenceSucceedsOnIntersections.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
import React from "react";
5+
6+
export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
7+
outline?: boolean;
8+
} & T;
9+
10+
declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
11+
12+
interface CustomButtonProps extends ButtonProps {
13+
customProp: string;
14+
}
15+
16+
const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;
17+
18+
19+
//// [tsxReactPropsInferenceSucceedsOnIntersections.js]
20+
"use strict";
21+
/// <reference path="react16.d.ts" />
22+
var __assign = (this && this.__assign) || function () {
23+
__assign = Object.assign || function(t) {
24+
for (var s, i = 1, n = arguments.length; i < n; i++) {
25+
s = arguments[i];
26+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
27+
t[p] = s[p];
28+
}
29+
return t;
30+
};
31+
return __assign.apply(this, arguments);
32+
};
33+
var __importDefault = (this && this.__importDefault) || function (mod) {
34+
return (mod && mod.__esModule) ? mod : { "default": mod };
35+
};
36+
exports.__esModule = true;
37+
var react_1 = __importDefault(require("react"));
38+
var CustomButton = function (props) { return react_1["default"].createElement(Button, __assign({}, props)); };
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/tsxReactPropsInferenceSucceedsOnIntersections.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import React from "react";
5+
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
6+
7+
export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
8+
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))
9+
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 24))
10+
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
11+
>ButtonHTMLAttributes : Symbol(React.ButtonHTMLAttributes, Decl(react16.d.ts, 1437, 9))
12+
>HTMLButtonElement : Symbol(HTMLButtonElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
13+
14+
outline?: boolean;
15+
>outline : Symbol(outline, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 83))
16+
17+
} & T;
18+
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 24))
19+
20+
declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
21+
>Button : Symbol(Button, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 6, 6))
22+
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 21))
23+
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
24+
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
25+
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
26+
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))
27+
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 21))
28+
29+
interface CustomButtonProps extends ButtonProps {
30+
>CustomButtonProps : Symbol(CustomButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 72))
31+
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))
32+
33+
customProp: string;
34+
>customProp : Symbol(CustomButtonProps.customProp, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 10, 49))
35+
}
36+
37+
const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;
38+
>CustomButton : Symbol(CustomButton, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 5))
39+
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
40+
>SFC : Symbol(React.SFC, Decl(react16.d.ts, 400, 9))
41+
>CustomButtonProps : Symbol(CustomButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 72))
42+
>props : Symbol(props, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 50))
43+
>Button : Symbol(Button, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 6, 6))
44+
>props : Symbol(props, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 50))
45+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/compiler/tsxReactPropsInferenceSucceedsOnIntersections.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import React from "react";
5+
>React : typeof React
6+
7+
export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
8+
>ButtonProps : ButtonProps<T>
9+
>React : any
10+
11+
outline?: boolean;
12+
>outline : boolean | undefined
13+
14+
} & T;
15+
16+
declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
17+
>Button : Button<T>
18+
>React.Component : React.Component<ButtonProps<T>, {}, any>
19+
>React : typeof React
20+
>Component : typeof React.Component
21+
22+
interface CustomButtonProps extends ButtonProps {
23+
customProp: string;
24+
>customProp : string
25+
}
26+
27+
const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;
28+
>CustomButton : React.StatelessComponent<CustomButtonProps>
29+
>React : any
30+
>props => <Button {...props} /> : (props: CustomButtonProps & { children?: React.ReactNode; }) => JSX.Element
31+
>props : CustomButtonProps & { children?: React.ReactNode; }
32+
><Button {...props} /> : JSX.Element
33+
>Button : typeof Button
34+
>props : CustomButtonProps & { children?: React.ReactNode; }
35+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @jsx: react
2+
// @esModuleInterop: true
3+
// @strictNullChecks: true
4+
/// <reference path="/.lib/react16.d.ts" />
5+
6+
import React from "react";
7+
8+
export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
9+
outline?: boolean;
10+
} & T;
11+
12+
declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
13+
14+
interface CustomButtonProps extends ButtonProps {
15+
customProp: string;
16+
}
17+
18+
const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;

0 commit comments

Comments
 (0)