Skip to content

Infer to partially homomorphic types (such as Pick<T, K>) #29787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 11, 2019
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
24 changes: 17 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -14585,7 +14585,7 @@ namespace ts {
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
if (inference && !inference.isFixed) {
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, constraintType as IndexType);
const inferredType = inferTypeForHomomorphicMappedType(source, target, <IndexType>constraintType);
if (inferredType) {
const savePriority = priority;
priority |= InferencePriority.HomomorphicMappedType;
Expand All @@ -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<T, K>.
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.
Copy link
Member

Choose a reason for hiding this comment

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

We should probably consider handling unions of indexes here.

Copy link
Member

Choose a reason for hiding this comment

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

Specifically for along the lines of:

declare function f20<A, B, K extends keyof A | keyof B>(obj: Pick<A & B, K>): T;

which instantiable of the original f20 could easily produce.

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't do that for ordinary homomorphic types so I'm not sure why we'd do that here. Also, not clear how we would divide the inferences between A and B.

Copy link
Member

Choose a reason for hiding this comment

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

We do do that for ordinary homomorphic mapped types. It's why constraintType is passed into inferFromMappedTypeConstraint.

Copy link
Member

Choose a reason for hiding this comment

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

Like, in this inference function we break down keyof A | keyof B, and we break down (T extends keyof A) | (U extends keyof B), I don't see why we would not also break down T extends (keyof A | keyof B).

Copy link
Member

@weswigham weswigham Feb 7, 2019

Choose a reason for hiding this comment

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

-                     if (extendedConstraint && extendedConstraint.flags & TypeFlags.Index) {
-                         inferToHomomorphicMappedType(source, target, <IndexType>extendedConstraint);
-                         return true;
+                     if (extendedConstraint) {
+                         return inferFromMappedTypeConstraint(source, target, extendedConstraint);

maybe? Probably shouldn't return, since making inferences directly to the constrained type parameter as well as the things referenced in its constraint should both be possible.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see what you're getting at. We really need to repeat the whole process for the constraint.

const valueTypes = compact([
getIndexTypeOfType(source, IndexKind.String),
getIndexTypeOfType(source, IndexKind.Number),
...map(getPropertiesOfType(source), getTypeOfSymbol)
]);
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(<MappedType>target));
inferFromTypes(getUnionType(valueTypes), getTemplateTypeFromMappedType(target));
return true;
}
return false;
Expand All @@ -14622,7 +14632,7 @@ namespace ts {
}
if (getObjectFlags(target) & ObjectFlags.Mapped) {
const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
if (inferFromMappedTypeConstraint(source, target, constraintType)) {
if (inferToMappedType(source, <MappedType>target, constraintType)) {
return;
}
}
Expand Down
74 changes: 73 additions & 1 deletion tests/baselines/reference/isomorphicMappedTypeInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,35 @@ var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
const foo = <T>(object: T, partial: Partial<T>) => 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<T, K extends keyof T>(obj: Pick<T, K>): T;
declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): 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<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K> {
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) {
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -323,3 +363,35 @@ declare let o: {
a: number;
b: number;
};
declare function f20<T, K extends keyof T>(obj: Pick<T, K>): T;
declare function f21<T, K extends keyof T>(obj: Pick<T, K>): K;
declare function f22<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): T;
declare function f23<T, U extends keyof T, K extends U>(obj: Pick<T, K>): T;
declare function f24<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): 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<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K>;
declare const myAny: any;
declare const o1: Pick<any, "foo" | "bar">;
declare const o2: {
foo: any;
bar: any;
};
130 changes: 130 additions & 0 deletions tests/baselines/reference/isomorphicMappedTypeInference.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, K extends keyof T>(obj: Pick<T, K>): 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<T, K extends keyof T>(obj: Pick<T, K>): 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<T, K extends keyof T>(obj: Boxified<Pick<T, K>>): 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<T, U extends keyof T, K extends U>(obj: Pick<T, K>): 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<T, U, K extends keyof T | keyof U>(obj: Pick<T & U, K>): 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<T, K extends keyof T>(obj: T, list: K[]): Pick<T, K> {
>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))

Loading