I want to write a function that does three things:
I'm following this guide which hints at this possibility:
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
type T40 = FunctionPropertyNames<Part>; // "updatePart"
type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"
type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }
type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }
However, it seems that I can only accomplish my goal if my function operates on concrete types. To compute the boolean property names of T, I've modified the first line from the example like this:
type BoolPropNames<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T];
It works if I use a concrete type:
class Thing {
isGreat: boolean;
}
function assignToThing(thing: Thing, key: BoolPropNames<Thing>) {
thing[key] = false;
}
But if I try to operate on a generic, it doesn't:
function assign<T>(thing: T, key: BoolPropNames<T>) {
thing[key] = false;
}
When I try to do this, TypeScript gives me the following error:
(parameter) key: { [K in keyof T]: T[K] extends boolean ? K : never; }[keyof T]
Type 'false' is not assignable to type 'T[{ [K in keyof T]: T[K] extends boolean ? K : never; }[keyof T]]'.ts(2322)
Why doesn't TypeScript allow me to operate on generics this way, and what can I do to fix it?
TS is not able to compute the result of BoolPropNames<T> as it is computed when T is given, differently then in previous examples where you had no type variable, but some specific type. As for example BoolPropNames<Thing> is computed into isGreat and thing[key] is known as to be boolean.
BoolPropNames<T> cannot be computed before so it does not know that result will be a key which gives us boolean. And if it doesn't know that think[key] is boolean then you cannot assign to it boolean.
In order to fix this, we can for example apply argument which will be considered as T[K] type. Then TS doesn't need to compute the type, as it will always be value of type at the given key. Consider following code:
function assign<T, K extends BoolPropNames<T>>(thing: T, key: K, value: T[K]) {
thing[key] = value;
}
assign({ a: true }, 'a', false);
If third argument does not satisfy you, and you just want to set any value without putting it as argument we can also do some partial application in order to create such behavior. Consider:
// function which creates assign function
const createAssign = <T, K extends BoolPropNames<T> = BoolPropNames<T>>
(value: T[K]) => (thing: T, key: K) => {
thing[key] = value;
}
// below assign function is created for type `Example`
type Example = { a: boolean };
const assign = createAssign<Example>(false);
assign({a: true}, 'a');
By partial application we have created assign function which works in the same way your original one was working, so it is assigning false into only boolean fields. By this way you can make all such functions.
Of course the downside is that we need to generate such for every type by setting generic type variable, so it is not so polymorphic as solution with different order of arguments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With