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
44 changes: 33 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9526,6 +9526,17 @@ namespace ts {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type;
}

function distributeIndexOverObjectType(objectType: Type, indexType: Type) {
// (T | U)[K] -> T[K] | U[K]
if (objectType.flags & TypeFlags.Union) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You're a bad person for not testing this branch.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Excuse me, I'm just reusing an operation already in use elsehwere ☑️

return mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
}
// (T & U)[K] -> T[K] & U[K]
if (objectType.flags & TypeFlags.Intersection) {
return getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
}
}

// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
// the type itself if no transformation is possible.
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
Expand All @@ -9543,13 +9554,9 @@ namespace ts {
}
// Only do the inner distributions if the index can no longer be instantiated to cause index distribution again
if (!(indexType.flags & TypeFlags.Instantiable)) {
// (T | U)[K] -> T[K] | U[K]
if (objectType.flags & TypeFlags.Union) {
return type.simplified = mapType(objectType, t => getSimplifiedType(getIndexedAccessType(t, indexType)));
}
// (T & U)[K] -> T[K] & U[K]
if (objectType.flags & TypeFlags.Intersection) {
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType))));
const simplified = distributeIndexOverObjectType(objectType, indexType);
if (simplified) {
return type.simplified = simplified;
}
}
// So ultimately:
Expand Down Expand Up @@ -13833,10 +13840,17 @@ namespace ts {
// Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine
const simplified = getSimplifiedType(target);
if (simplified !== target) {
const key = source.id + "," + simplified.id;
if (!visited || !visited.get(key)) {
(visited || (visited = createMap<boolean>())).set(key, true);
inferFromTypes(source, simplified);
inferFromTypesOnce(source, simplified);
}
else if (target.flags & TypeFlags.IndexedAccess) {
Comment thread
weswigham marked this conversation as resolved.
Outdated
const indexType = getSimplifiedType((target as IndexedAccessType).indexType);
// Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider
// that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can.
if (indexType.flags & TypeFlags.Instantiable) {
const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType), indexType);
if (simplified && simplified !== target) {
inferFromTypesOnce(source, simplified);
}
}
}
}
Expand Down Expand Up @@ -13959,6 +13973,14 @@ namespace ts {
}
}
}

function inferFromTypesOnce(source: Type, target: Type) {
const key = source.id + "," + target.id;
if (!visited || !visited.get(key)) {
(visited || (visited = createMap<boolean>())).set(key, true);
inferFromTypes(source, target);
}
}
}

function inferFromContravariantTypes(source: Type, target: Type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class C<T> extends Component<{ x?: boolean; } & T> {}
>x : boolean | undefined

const y = new C({foobar: "example"});
>y : C<{ foobar: {}; }>
>new C({foobar: "example"}) : C<{ foobar: {}; }>
>y : C<{ foobar: string; }>
>new C({foobar: "example"}) : C<{ foobar: string; }>
>C : typeof C
>{foobar: "example"} : { foobar: string; }
>foobar : string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//// [tsxReactPropsInferenceSucceedsOnIntersections.tsx]
/// <reference path="/.lib/react16.d.ts" />

import React from "react";

export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
outline?: boolean;
} & T;

declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }

interface CustomButtonProps extends ButtonProps {
customProp: string;
}

const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;


//// [tsxReactPropsInferenceSucceedsOnIntersections.js]
"use strict";
/// <reference path="react16.d.ts" />
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var react_1 = __importDefault(require("react"));
var CustomButton = function (props) { return react_1["default"].createElement(Button, __assign({}, props)); };
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
=== tests/cases/compiler/tsxReactPropsInferenceSucceedsOnIntersections.tsx ===
/// <reference path="react16.d.ts" />

import React from "react";
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))

export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 24))
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
>ButtonHTMLAttributes : Symbol(React.ButtonHTMLAttributes, Decl(react16.d.ts, 1437, 9))
>HTMLButtonElement : Symbol(HTMLButtonElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))

outline?: boolean;
>outline : Symbol(outline, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 83))

} & T;
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 4, 24))

declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
>Button : Symbol(Button, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 6, 6))
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 21))
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))
>T : Symbol(T, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 21))

interface CustomButtonProps extends ButtonProps {
>CustomButtonProps : Symbol(CustomButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 72))
>ButtonProps : Symbol(ButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 26))

customProp: string;
>customProp : Symbol(CustomButtonProps.customProp, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 10, 49))
}

const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;
>CustomButton : Symbol(CustomButton, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 5))
>React : Symbol(React, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 2, 6))
>SFC : Symbol(React.SFC, Decl(react16.d.ts, 400, 9))
>CustomButtonProps : Symbol(CustomButtonProps, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 8, 72))
>props : Symbol(props, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 50))
>Button : Symbol(Button, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 6, 6))
>props : Symbol(props, Decl(tsxReactPropsInferenceSucceedsOnIntersections.tsx, 14, 50))

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=== tests/cases/compiler/tsxReactPropsInferenceSucceedsOnIntersections.tsx ===
/// <reference path="react16.d.ts" />

import React from "react";
>React : typeof React

export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
>ButtonProps : ButtonProps<T>
>React : any

outline?: boolean;
>outline : boolean | undefined

} & T;

declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }
>Button : Button<T>
>React.Component : React.Component<ButtonProps<T>, {}, any>
>React : typeof React
>Component : typeof React.Component

interface CustomButtonProps extends ButtonProps {
customProp: string;
>customProp : string
}

const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;
>CustomButton : React.StatelessComponent<CustomButtonProps>
>React : any
>props => <Button {...props} /> : (props: CustomButtonProps & { children?: React.ReactNode; }) => JSX.Element
>props : CustomButtonProps & { children?: React.ReactNode; }
><Button {...props} /> : JSX.Element
>Button : typeof Button
>props : CustomButtonProps & { children?: React.ReactNode; }

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @jsx: react
// @esModuleInterop: true
// @strictNullChecks: true
/// <reference path="/.lib/react16.d.ts" />

import React from "react";

export type ButtonProps<T = {}> = React.ButtonHTMLAttributes<HTMLButtonElement> & {
outline?: boolean;
} & T;

declare class Button<T = {}> extends React.Component<ButtonProps<T>> { }

interface CustomButtonProps extends ButtonProps {
customProp: string;
}

const CustomButton: React.SFC<CustomButtonProps> = props => <Button {...props} />;