Skip to content

Commit d66000b

Browse files
authored
Merge pull request #29787 from Microsoft/inferToPartiallyHomomorphic
Infer to partially homomorphic types (such as Pick<T, K>)
2 parents 32b44ac + 0404012 commit d66000b

File tree

5 files changed

+361
-9
lines changed

5 files changed

+361
-9
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14580,11 +14580,11 @@ namespace ts {
1458014580
return undefined;
1458114581
}
1458214582

14583-
function inferFromMappedTypeConstraint(source: Type, target: Type, constraintType: Type): boolean {
14583+
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
1458414584
if (constraintType.flags & TypeFlags.Union) {
1458514585
let result = false;
1458614586
for (const type of (constraintType as UnionType).types) {
14587-
result = inferFromMappedTypeConstraint(source, target, type) || result;
14587+
result = inferToMappedType(source, target, type) || result;
1458814588
}
1458914589
return result;
1459014590
}
@@ -14595,7 +14595,7 @@ namespace ts {
1459514595
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
1459614596
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
1459714597
if (inference && !inference.isFixed) {
14598-
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, constraintType as IndexType);
14598+
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType);
1459914599
if (inferredType) {
1460014600
const savePriority = priority;
1460114601
priority |= InferencePriority.HomomorphicMappedType;
@@ -14606,18 +14606,28 @@ namespace ts {
1460614606
return true;
1460714607
}
1460814608
if (constraintType.flags & TypeFlags.TypeParameter) {
14609-
// We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type
14610-
// parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X.
14609+
// We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type
14610+
// parameter. First infer from 'keyof S' to K.
1461114611
const savePriority = priority;
1461214612
priority |= InferencePriority.MappedTypeConstraint;
1461314613
inferFromTypes(getIndexType(source), constraintType);
1461414614
priority = savePriority;
14615+
// If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X },
14616+
// where K extends keyof T, we make the same inferences as for a homomorphic mapped type
14617+
// { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a
14618+
// Pick<T, K>.
14619+
const extendedConstraint = getConstraintOfType(constraintType);
14620+
if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) {
14621+
return true;
14622+
}
14623+
// If no inferences can be made to K's constraint, infer from a union of the property types
14624+
// in the source to the template type X.
1461514625
const valueTypes = compact([
1461614626
getIndexTypeOfType(source, IndexKind.String),
1461714627
getIndexTypeOfType(source, IndexKind.Number),
1461814628
...map(getPropertiesOfType(source), getTypeOfSymbol)
1461914629
]);
14620-
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(<MappedType>target));
14630+
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target));
1462114631
return true;
1462214632
}
1462314633
return false;
@@ -14632,7 +14642,7 @@ namespace ts {
1463214642
}
1463314643
if (getObjectFlags(target) & ObjectFlags.Mapped) {
1463414644
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
14635-
if (inferFromMappedTypeConstraint(source, target, constraintType)) {
14645+
if (inferToMappedType(source, <MappedType>target, constraintType)) {
1463614646
return;
1463714647
}
1463814648
}

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,35 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
149149
const foo = <T>(object: T, partial: Partial<T>) => object;
150150
let o = {a: 5, b: 7};
151151
foo(o, {b: 9});
152-
o = foo(o, {b: 9});
152+
o = foo(o, {b: 9});
153+
154+
// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as
155+
// inferring to { [P in keyof T]: X }.
156+
157+
declare function f20<T, K extends keyof T>(obj: Pick<T, K>): T;
158+
declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
159+
declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
160+
declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
161+
declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): T & U;
162+
163+
let x0 = f20({ foo: 42, bar: "hello" });
164+
let x1 = f21({ foo: 42, bar: "hello" });
165+
let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } });
166+
let x3 = f23({ foo: 42, bar: "hello" });
167+
let x4 = f24({ foo: 42, bar: "hello" });
168+
169+
// Repro from #29765
170+
171+
function getProps<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K> {
172+
return {} as any;
173+
}
174+
175+
const myAny: any = {};
176+
177+
const o1 = getProps(myAny, ['foo', 'bar']);
178+
179+
const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']);
180+
153181

154182
//// [isomorphicMappedTypeInference.js]
155183
function box(x) {
@@ -255,6 +283,18 @@ var foo = function (object, partial) { return object; };
255283
var o = { a: 5, b: 7 };
256284
foo(o, { b: 9 });
257285
o = foo(o, { b: 9 });
286+
var x0 = f20({ foo: 42, bar: "hello" });
287+
var x1 = f21({ foo: 42, bar: "hello" });
288+
var x2 = f22({ foo: { value: 42 }, bar: { value: "hello" } });
289+
var x3 = f23({ foo: 42, bar: "hello" });
290+
var x4 = f24({ foo: 42, bar: "hello" });
291+
// Repro from #29765
292+
function getProps(obj, list) {
293+
return {};
294+
}
295+
var myAny = {};
296+
var o1 = getProps(myAny, ['foo', 'bar']);
297+
var o2 = getProps(myAny, ['foo', 'bar']);
258298

259299

260300
//// [isomorphicMappedTypeInference.d.ts]
@@ -323,3 +363,35 @@ declare let o: {
323363
a: number;
324364
b: number;
325365
};
366+
declare function f20<T, K extends keyof T>(obj: Pick<T, K>): T;
367+
declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
368+
declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
369+
declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
370+
declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): T & U;
371+
declare let x0: {
372+
foo: number;
373+
bar: string;
374+
};
375+
declare let x1: "foo" | "bar";
376+
declare let x2: {
377+
foo: number;
378+
bar: string;
379+
};
380+
declare let x3: {
381+
foo: number;
382+
bar: string;
383+
};
384+
declare let x4: {
385+
foo: number;
386+
bar: string;
387+
} & {
388+
foo: number;
389+
bar: string;
390+
};
391+
declare function getProps<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K>;
392+
declare const myAny: any;
393+
declare const o1: Pick<any, "foo" | "bar">;
394+
declare const o2: {
395+
foo: any;
396+
bar: any;
397+
};

tests/baselines/reference/isomorphicMappedTypeInference.symbols

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,133 @@ o = foo(o, {b: 9});
486486
>o : Symbol(o, Decl(isomorphicMappedTypeInference.ts, 148, 3))
487487
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 150, 12))
488488

489+
// Inferring to { [P in K]: X }, where K extends keyof T, produces same inferences as
490+
// inferring to { [P in keyof T]: X }.
491+
492+
declare function f20<T, K extends keyof T>(obj: Pick<T, K>): T;
493+
>f20 : Symbol(f20, Decl(isomorphicMappedTypeInference.ts, 150, 19))
494+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21))
495+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 155, 23))
496+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21))
497+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 155, 43))
498+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
499+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21))
500+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 155, 23))
501+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 155, 21))
502+
503+
declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
504+
>f21 : Symbol(f21, Decl(isomorphicMappedTypeInference.ts, 155, 63))
505+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21))
506+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23))
507+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21))
508+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 156, 43))
509+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
510+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 156, 21))
511+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23))
512+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 156, 23))
513+
514+
declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
515+
>f22 : Symbol(f22, Decl(isomorphicMappedTypeInference.ts, 156, 63))
516+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21))
517+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 157, 23))
518+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21))
519+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 157, 43))
520+
>Boxified : Symbol(Boxified, Decl(isomorphicMappedTypeInference.ts, 2, 1))
521+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
522+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21))
523+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 157, 23))
524+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 157, 21))
525+
526+
declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
527+
>f23 : Symbol(f23, Decl(isomorphicMappedTypeInference.ts, 157, 73))
528+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21))
529+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 158, 23))
530+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21))
531+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 158, 42))
532+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 158, 23))
533+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 158, 56))
534+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
535+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21))
536+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 158, 42))
537+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 158, 21))
538+
539+
declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): T & U;
540+
>f24 : Symbol(f24, Decl(isomorphicMappedTypeInference.ts, 158, 76))
541+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21))
542+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23))
543+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 159, 26))
544+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21))
545+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23))
546+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 159, 56))
547+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
548+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21))
549+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23))
550+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 159, 26))
551+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 159, 21))
552+
>U : Symbol(U, Decl(isomorphicMappedTypeInference.ts, 159, 23))
553+
554+
let x0 = f20({ foo: 42, bar: "hello" });
555+
>x0 : Symbol(x0, Decl(isomorphicMappedTypeInference.ts, 161, 3))
556+
>f20 : Symbol(f20, Decl(isomorphicMappedTypeInference.ts, 150, 19))
557+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 161, 14))
558+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 161, 23))
559+
560+
let x1 = f21({ foo: 42, bar: "hello" });
561+
>x1 : Symbol(x1, Decl(isomorphicMappedTypeInference.ts, 162, 3))
562+
>f21 : Symbol(f21, Decl(isomorphicMappedTypeInference.ts, 155, 63))
563+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 162, 14))
564+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 162, 23))
565+
566+
let x2 = f22({ foo: { value: 42} , bar: { value: "hello" } });
567+
>x2 : Symbol(x2, Decl(isomorphicMappedTypeInference.ts, 163, 3))
568+
>f22 : Symbol(f22, Decl(isomorphicMappedTypeInference.ts, 156, 63))
569+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 163, 14))
570+
>value : Symbol(value, Decl(isomorphicMappedTypeInference.ts, 163, 21))
571+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 163, 34))
572+
>value : Symbol(value, Decl(isomorphicMappedTypeInference.ts, 163, 41))
573+
574+
let x3 = f23({ foo: 42, bar: "hello" });
575+
>x3 : Symbol(x3, Decl(isomorphicMappedTypeInference.ts, 164, 3))
576+
>f23 : Symbol(f23, Decl(isomorphicMappedTypeInference.ts, 157, 73))
577+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 164, 14))
578+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 164, 23))
579+
580+
let x4 = f24({ foo: 42, bar: "hello" });
581+
>x4 : Symbol(x4, Decl(isomorphicMappedTypeInference.ts, 165, 3))
582+
>f24 : Symbol(f24, Decl(isomorphicMappedTypeInference.ts, 158, 76))
583+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 165, 14))
584+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 165, 23))
585+
586+
// Repro from #29765
587+
588+
function getProps<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K> {
589+
>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40))
590+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18))
591+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20))
592+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18))
593+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 169, 40))
594+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18))
595+
>list : Symbol(list, Decl(isomorphicMappedTypeInference.ts, 169, 47))
596+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20))
597+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
598+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 169, 18))
599+
>K : Symbol(K, Decl(isomorphicMappedTypeInference.ts, 169, 20))
600+
601+
return {} as any;
602+
}
603+
604+
const myAny: any = {};
605+
>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5))
606+
607+
const o1 = getProps(myAny, ['foo', 'bar']);
608+
>o1 : Symbol(o1, Decl(isomorphicMappedTypeInference.ts, 175, 5))
609+
>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40))
610+
>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5))
611+
612+
const o2: { foo: any; bar: any } = getProps(myAny, ['foo', 'bar']);
613+
>o2 : Symbol(o2, Decl(isomorphicMappedTypeInference.ts, 177, 5))
614+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 177, 11))
615+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 177, 21))
616+
>getProps : Symbol(getProps, Decl(isomorphicMappedTypeInference.ts, 165, 40))
617+
>myAny : Symbol(myAny, Decl(isomorphicMappedTypeInference.ts, 173, 5))
618+

0 commit comments

Comments
 (0)