diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 103200600b219..23a7cd2bde6c9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9526,6 +9526,17 @@ namespace ts { return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type) : type; } + function distributeIndexOverObjectType(objectType: Type, indexType: Type) { + // (T | U)[K] -> T[K] | U[K] + if (objectType.flags & TypeFlags.Union) { + 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 { @@ -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: @@ -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())).set(key, true); - inferFromTypes(source, simplified); + inferFromTypesOnce(source, simplified); + } + else if (target.flags & TypeFlags.IndexedAccess) { + 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); + } } } } @@ -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())).set(key, true); + inferFromTypes(source, target); + } + } } function inferFromContravariantTypes(source: Type, target: Type) { diff --git a/tests/baselines/reference/checkJsxIntersectionElementPropsType.types b/tests/baselines/reference/checkJsxIntersectionElementPropsType.types index 0e95dbed36166..187d0f274a409 100644 --- a/tests/baselines/reference/checkJsxIntersectionElementPropsType.types +++ b/tests/baselines/reference/checkJsxIntersectionElementPropsType.types @@ -20,8 +20,8 @@ class C 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 diff --git a/tests/baselines/reference/tsxReactPropsInferenceSucceedsOnIntersections.js b/tests/baselines/reference/tsxReactPropsInferenceSucceedsOnIntersections.js new file mode 100644 index 0000000000000..2b63a442da772 --- /dev/null +++ b/tests/baselines/reference/tsxReactPropsInferenceSucceedsOnIntersections.js @@ -0,0 +1,38 @@ +//// [tsxReactPropsInferenceSucceedsOnIntersections.tsx] +/// + +import React from "react"; + +export type ButtonProps = React.ButtonHTMLAttributes & { + outline?: boolean; +} & T; + +declare class Button extends React.Component> { } + +interface CustomButtonProps extends ButtonProps { + customProp: string; +} + +const CustomButton: React.SFC = props =>