user5480949
user5480949

Reputation: 1668

Type 'keyof T' does not satisfy the constraint

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 });

Playground link

Upvotes: 7

Views: 33072

Answers (1)

jcalz
jcalz

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">.


Playground link to code

Upvotes: 6

Related Questions