Constrain one Typescript generic parameter based on properties of another?

后端 未结 2 328
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-22 08:34

I\'m trying to write a function that takes an object and a (string) key, then operates on a property of the object. This is easy:

function f

        
相关标签:
2条回答
  • 2021-01-22 08:53

    Just use a type assertion, your concern should be the call site, that is typed correctly and gives you errors where it should. The implementation can't really be typed correctly if you want to assign a specific value inside the function.

    You can make the result of T[K] extend number, for example but adding a constraint to T of Record<K, number>, but we still not be able to assign concrete values to obj[key]

    type KeyOfType<T, ValueType> = 
      { [Key in keyof T]-?: T[Key] extends ValueType | undefined ? Key : never }[keyof T]
    
    function f<T extends Record<K, number>, K extends KeyOfType<T, number>>(obj: T, key: K, value: T[K]) {
        let r = obj[key]; 
        r.toExponential(); // seems numberish, but it `T[K]` which does extend number, but might not be number
        obj[key] = obj[key] // T[K] is assignable to T[K]
        obj[key] = value; // even if it is a parameter 
        obj[key] = 5; // still an error
    }
    
    declare const foo: Foo;
    f(foo, "a", 1); // Allowed, good
    f(foo, "b", 2); // Error, good
    
    const other: { 
      a: 1
    } = {
      a: 1
    }
    f(other, "a", 1) // this will break the type of other because of obj[key] = 5
    

    Playground Link

    The reason this is so, is the last example f(other, "a", 1). Here a in other has type 1, which does extend number, so f(other, "a", 1) is a valid call to f, but inside we want to assign other[key] = 5. This would break the type of other. The problem here is that there is no way so specify that T[K] has an upper constraint of number just a lower constraint.

    0 讨论(0)
  • 2021-01-22 09:11

    You should extends from keys that result in a value of type number.

    export type PickByValue<T, ValueType> = Pick<
      T,
      { [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T]
    >;
    
    function f<T extends object, K extends keyof PickByValue<T, number>>(obj: T, key: K) : T[K] {
      return obj[key]
    }
    

    Edit: What you're trying to do is not possible in TS AFAIK, and sometimes there's a good reason for that. let's suppose you have below code:

    function f<T extends object, K extends keyof PickByValue<T, number>>(obj: T, key: K) {
        obj[key] = 5; // Type 'number' is not assignable to type 'T[K]'.
    } 
    
    const obj = {a: 9} as const;
    
    f(obj, "a")
    

    For example in the above scenario, value of property a is a number however it's not of type number but of type 9. there's no way for typescript to know this before hand. in other scenarios, the only thing that comes to my mind is using Type Assertions.

    0 讨论(0)
提交回复
热议问题