diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce06e84b2fd26..b550710861c72 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14570,11 +14570,11 @@ namespace ts { return undefined; } - function inferFromMappedTypeConstraint(source: Type, target: Type, constraintType: Type): boolean { + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; for (const type of (constraintType as UnionType).types) { - result = inferFromMappedTypeConstraint(source, target, type) || result; + result = inferToMappedType(source, target, type) || result; } return result; } @@ -14585,7 +14585,7 @@ namespace ts { // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); if (inference && !inference.isFixed) { - const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); + const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { const savePriority = priority; priority |= InferencePriority.HomomorphicMappedType; @@ -14596,18 +14596,28 @@ namespace ts { return true; } if (constraintType.flags & TypeFlags.TypeParameter) { - // We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type - // parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X. + // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type + // parameter. First infer from 'keyof S' to K. const savePriority = priority; priority |= InferencePriority.MappedTypeConstraint; inferFromTypes(getIndexType(source), constraintType); priority = savePriority; + // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, + // where K extends keyof T, we make the same inferences as for a homomorphic mapped type + // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a + // Pick. + const extendedConstraint = getConstraintOfType(constraintType); + if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { + return true; + } + // If no inferences can be made to K's constraint, infer from a union of the property types + // in the source to the template type X. const valueTypes = compact([ getIndexTypeOfType(source, IndexKind.String), getIndexTypeOfType(source, IndexKind.Number), ...map(getPropertiesOfType(source), getTypeOfSymbol) ]); - inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target)); + inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target)); return true; } return false; @@ -14622,7 +14632,7 @@ namespace ts { } if (getObjectFlags(target) & ObjectFlags.Mapped) { const constraintType = getConstraintTypeFromMappedType(target); - if (inferFromMappedTypeConstraint(source, target, constraintType)) { + if (inferToMappedType(source, target, constraintType)) { return; } } diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.js b/tests/baselines/reference/isomorphicMappedTypeInference.js index 589122444323a..aa904e9afa173 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.js +++ b/tests/baselines/reference/isomorphicMappedTypeInference.js @@ -149,7 +149,35 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); const foo = (object: T, partial: Partial) => object; let o = {a: 5, b: 7}; foo(o, {b: 9}); -o = foo(o, {b: 9}); +o = foo(o, {b: 9}); + +// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as +// inferring to { [P in keyof T]: X }. + +declare function f20(obj: Pick): T; +declare function f21(obj: Pick): K; +declare function f22(obj: Boxified>): T; +declare function f23(obj: Pick): T; +declare function f24(obj: Pick): T & U; + +let x0 = f20({ foo: 42, bar: "hello" }); +let x1 = f21({ foo: 42, bar: "hello" }); +let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } }); +let x3 = f23({ foo: 42, bar: "hello" }); +let x4 = f24({ foo: 42, bar: "hello" }); + +// Repro from #29765 + +function getProps(obj: T, list: K[]): Pick { + return {} as any; +} + +const myAny: any = {}; + +const o1 = getProps(myAny, ['foo', 'bar']); + +const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']); + //// [isomorphicMappedTypeInference.js] function box(x) { @@ -255,6 +283,18 @@ var foo = function (object, partial) { return object; }; var o = { a: 5, b: 7 }; foo(o, { b: 9 }); o = foo(o, { b: 9 }); +var x0 = f20({ foo: 42, bar: "hello" }); +var x1 = f21({ foo: 42, bar: "hello" }); +var x2 = f22({ foo: { value: 42 }, bar: { value: "hello" } }); +var x3 = f23({ foo: 42, bar: "hello" }); +var x4 = f24({ foo: 42, bar: "hello" }); +// Repro from #29765 +function getProps(obj, list) { + return {}; +} +var myAny = {}; +var o1 = getProps(myAny, ['foo', 'bar']); +var o2 = getProps(myAny, ['foo', 'bar']); //// [isomorphicMappedTypeInference.d.ts] @@ -323,3 +363,35 @@ declare let o: { a: number; b: number; }; +declare function f20(obj: Pick): T; +declare function f21(obj: Pick): K; +declare function f22(obj: Boxified>): T; +declare function f23(obj: Pick): T; +declare function f24(obj: Pick): T & U; +declare let x0: { + foo: number; + bar: string; +}; +declare let x1: "foo" | "bar"; +declare let x2: { + foo: number; + bar: string; +}; +declare let x3: { + foo: number; + bar: string; +}; +declare let x4: { + foo: number; + bar: string; +} & { + foo: number; + bar: string; +}; +declare function getProps(obj: T, list: K[]): Pick; +declare const myAny: any; +declare const o1: Pick; +declare const o2: { + foo: any; + bar: any; +}; diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.symbols b/tests/baselines/reference/isomorphicMappedTypeInference.symbols index d36ebc7a4a517..3192be13ff634 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.symbols +++ b/tests/baselines/reference/isomorphicMappedTypeInference.symbols @@ -486,3 +486,133 @@ o = foo(o, {b: 9}); >o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 148, 3)) >b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 150, 12)) +// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as +// inferring to { [P in keyof T]: X }. + +declare function f20(obj: Pick): T; +>f20 : Symbol(f20, Decl(isomorphicMappedTypeInference.ts, 150, 19)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 155, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 155, 43)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 155, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21)) + +declare function f21(obj: Pick): K; +>f21 : Symbol(f21, Decl(isomorphicMappedTypeInference.ts, 155, 63)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 156, 43)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23)) + +declare function f22(obj: Boxified>): T; +>f22 : Symbol(f22, Decl(isomorphicMappedTypeInference.ts, 156, 63)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 157, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 157, 43)) +>Boxified : Symbol(Boxified, Decl(isomorphicMappedTypeInference.ts, 2, 1)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 157, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21)) + +declare function f23(obj: Pick): T; +>f23 : Symbol(f23, Decl(isomorphicMappedTypeInference.ts, 157, 73)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 158, 23)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 158, 42)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 158, 23)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 158, 56)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 158, 42)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21)) + +declare function f24(obj: Pick): T & U; +>f24 : Symbol(f24, Decl(isomorphicMappedTypeInference.ts, 158, 76)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 159, 26)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 159, 56)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 159, 26)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21)) +>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23)) + +let x0 = f20({ foo: 42, bar: "hello" }); +>x0 : Symbol(x0, Decl(isomorphicMappedTypeInference.ts, 161, 3)) +>f20 : Symbol(f20, Decl(isomorphicMappedTypeInference.ts, 150, 19)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 161, 14)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 161, 23)) + +let x1 = f21({ foo: 42, bar: "hello" }); +>x1 : Symbol(x1, Decl(isomorphicMappedTypeInference.ts, 162, 3)) +>f21 : Symbol(f21, Decl(isomorphicMappedTypeInference.ts, 155, 63)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 162, 14)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 162, 23)) + +let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } }); +>x2 : Symbol(x2, Decl(isomorphicMappedTypeInference.ts, 163, 3)) +>f22 : Symbol(f22, Decl(isomorphicMappedTypeInference.ts, 156, 63)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 163, 14)) +>value : Symbol(value, Decl(isomorphicMappedTypeInference.ts, 163, 21)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 163, 34)) +>value : Symbol(value, Decl(isomorphicMappedTypeInference.ts, 163, 41)) + +let x3 = f23({ foo: 42, bar: "hello" }); +>x3 : Symbol(x3, Decl(isomorphicMappedTypeInference.ts, 164, 3)) +>f23 : Symbol(f23, Decl(isomorphicMappedTypeInference.ts, 157, 73)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 164, 14)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 164, 23)) + +let x4 = f24({ foo: 42, bar: "hello" }); +>x4 : Symbol(x4, Decl(isomorphicMappedTypeInference.ts, 165, 3)) +>f24 : Symbol(f24, Decl(isomorphicMappedTypeInference.ts, 158, 76)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 165, 14)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 165, 23)) + +// Repro from #29765 + +function getProps(obj: T, list: K[]): Pick { +>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 169, 40)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18)) +>list : Symbol(list, Decl(isomorphicMappedTypeInference.ts, 169, 47)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18)) +>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20)) + + return {} as any; +} + +const myAny: any = {}; +>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5)) + +const o1 = getProps(myAny, ['foo', 'bar']); +>o1 : Symbol(o1, Decl(isomorphicMappedTypeInference.ts, 175, 5)) +>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40)) +>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5)) + +const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']); +>o2 : Symbol(o2, Decl(isomorphicMappedTypeInference.ts, 177, 5)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 177, 11)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 177, 21)) +>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40)) +>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5)) + diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.types b/tests/baselines/reference/isomorphicMappedTypeInference.types index 4488fa27daf6c..1f1fa0d200a0c 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.types +++ b/tests/baselines/reference/isomorphicMappedTypeInference.types @@ -507,3 +507,116 @@ o = foo(o, {b: 9}); >b : number >9 : 9 +// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as +// inferring to { [P in keyof T]: X }. + +declare function f20(obj: Pick): T; +>f20 : (obj: Pick) => T +>obj : Pick + +declare function f21(obj: Pick): K; +>f21 : (obj: Pick) => K +>obj : Pick + +declare function f22(obj: Boxified>): T; +>f22 : (obj: Boxified>) => T +>obj : Boxified> + +declare function f23(obj: Pick): T; +>f23 : (obj: Pick) => T +>obj : Pick + +declare function f24(obj: Pick): T & U; +>f24 : (obj: Pick) => T & U +>obj : Pick + +let x0 = f20({ foo: 42, bar: "hello" }); +>x0 : { foo: number; bar: string; } +>f20({ foo: 42, bar: "hello" }) : { foo: number; bar: string; } +>f20 : (obj: Pick) => T +>{ foo: 42, bar: "hello" } : { foo: number; bar: string; } +>foo : number +>42 : 42 +>bar : string +>"hello" : "hello" + +let x1 = f21({ foo: 42, bar: "hello" }); +>x1 : "foo" | "bar" +>f21({ foo: 42, bar: "hello" }) : "foo" | "bar" +>f21 : (obj: Pick) => K +>{ foo: 42, bar: "hello" } : { foo: number; bar: string; } +>foo : number +>42 : 42 +>bar : string +>"hello" : "hello" + +let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } }); +>x2 : { foo: number; bar: string; } +>f22({ foo: { value: 42} , bar: { value: "hello" } }) : { foo: number; bar: string; } +>f22 : (obj: Boxified>) => T +>{ foo: { value: 42} , bar: { value: "hello" } } : { foo: { value: number; }; bar: { value: string; }; } +>foo : { value: number; } +>{ value: 42} : { value: number; } +>value : number +>42 : 42 +>bar : { value: string; } +>{ value: "hello" } : { value: string; } +>value : string +>"hello" : "hello" + +let x3 = f23({ foo: 42, bar: "hello" }); +>x3 : { foo: number; bar: string; } +>f23({ foo: 42, bar: "hello" }) : { foo: number; bar: string; } +>f23 : (obj: Pick) => T +>{ foo: 42, bar: "hello" } : { foo: number; bar: string; } +>foo : number +>42 : 42 +>bar : string +>"hello" : "hello" + +let x4 = f24({ foo: 42, bar: "hello" }); +>x4 : { foo: number; bar: string; } & { foo: number; bar: string; } +>f24({ foo: 42, bar: "hello" }) : { foo: number; bar: string; } & { foo: number; bar: string; } +>f24 : (obj: Pick) => T & U +>{ foo: 42, bar: "hello" } : { foo: number; bar: string; } +>foo : number +>42 : 42 +>bar : string +>"hello" : "hello" + +// Repro from #29765 + +function getProps(obj: T, list: K[]): Pick { +>getProps : (obj: T, list: K[]) => Pick +>obj : T +>list : K[] + + return {} as any; +>{} as any : any +>{} : {} +} + +const myAny: any = {}; +>myAny : any +>{} : {} + +const o1 = getProps(myAny, ['foo', 'bar']); +>o1 : Pick +>getProps(myAny, ['foo', 'bar']) : Pick +>getProps : (obj: T, list: K[]) => Pick +>myAny : any +>['foo', 'bar'] : ("foo" | "bar")[] +>'foo' : "foo" +>'bar' : "bar" + +const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']); +>o2 : { foo: any; bar: any; } +>foo : any +>bar : any +>getProps(myAny, ['foo', 'bar']) : Pick +>getProps : (obj: T, list: K[]) => Pick +>myAny : any +>['foo', 'bar'] : ("foo" | "bar")[] +>'foo' : "foo" +>'bar' : "bar" + diff --git a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts index 14bb765a84011..031cf840d33f4 100644 --- a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts +++ b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts @@ -152,4 +152,31 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); const foo = (object: T, partial: Partial) => object; let o = {a: 5, b: 7}; foo(o, {b: 9}); -o = foo(o, {b: 9}); \ No newline at end of file +o = foo(o, {b: 9}); + +// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as +// inferring to { [P in keyof T]: X }. + +declare function f20(obj: Pick): T; +declare function f21(obj: Pick): K; +declare function f22(obj: Boxified>): T; +declare function f23(obj: Pick): T; +declare function f24(obj: Pick): T & U; + +let x0 = f20({ foo: 42, bar: "hello" }); +let x1 = f21({ foo: 42, bar: "hello" }); +let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } }); +let x3 = f23({ foo: 42, bar: "hello" }); +let x4 = f24({ foo: 42, bar: "hello" }); + +// Repro from #29765 + +function getProps(obj: T, list: K[]): Pick { + return {} as any; +} + +const myAny: any = {}; + +const o1 = getProps(myAny, ['foo', 'bar']); + +const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']);