Reputation: 1668
I get this error despite the validation working. All I need is a function that generates another function using an object to filter arguments of a fixed type
Type 'keyof T' does not satisfy the constraint '"a" | "b" | "c"'.
Type 'string' is not assignable to type '"a" | "b" | "c"'.
Type 'keyof T' is not assignable to type '"c"'.
Type 'string' is not assignable to type '"c"'
type GM = {
a: number;
b: string;
c: string
}
type VMeth<T, C > = (old: T, n: C) => any;
const getVF = <T extends { [key in keyof Partial<GM>]: 1 }>(part: T): VMeth<GM, Pick<GM, keyof T>> => {
return function(){ } as any
}
const fd = getVF({ a: 1 });
fd({ a: 1, b: "", c: "s"}, { a: 1 });
Upvotes: 7
Views: 33072
Reputation: 327624
The constraint
T extends { [key in keyof Partial<GM>]: 1 }
means that T
must be assignable to {a?:1, b?:1, c?:1}
. This includes types you are trying to support, like {a: 1}
. But it also includes types you are apparently not trying to support. Object types in TypeScript are extendable or open (as opposed to exact or closed). You are allowed to extend an object type by adding properties to it. So the type {a?: 1, b?:1, c?:1, oops: string}
is also supported:
const oopsie = getVF({ a: 1, oops: "oops" }) // no error!
// const oopsie: VMeth<GM, Pick<GM, "a" | "oops">>
Because T
may in fact have more keys than GM
, the compiler rightfully complains that
// Type 'keyof T' does not satisfy the constraint 'keyof GM'.
If you really want to limit the keys of part
to those of GM
(or at least only pay attention to those keys, since object types are open no matter what you do), you can make your function generic in those keys K
instead:
const getVF = <K extends keyof GM>(part: Record<K, 1>):
VMeth<GM, Pick<GM, K>> => {
return function () { } as any
}
Now K
must be some subset of the union of "a" | "b" | "c"
and cannot be "oops"
or anything else. And so Pick<GM, K>
will always work. Your desired use case still functions the same:
fd({ a: 1, b: "", c: "s" }, { a: 1 });
// const fd: (old: GM, n: Pick<GM, "a">) => any
And now we get compiler warnings if we obviously add an unexpected property:
getVF({ a: 1, oops: "oops" }); // error, excess property!
You can still manage to get such an excess property in there if you are sneaky:
const existingObj = {a: 1, oops: "oops"} as const;
const aliased: {a: 1} = existingObj;
const okay = getVF(aliased); // no error
// const okay: VMeth<GM, Pick<GM, "a">>
But at least the value that comes out is still Pick<GM, "a">
and not something invalid like Pick<GM, "a" | "oops">
.
Upvotes: 6