typescriptinterfacenarrowing

How do I infer generics from an extended interface?


I have an interface with a generic field, sort of like

interface HasVal<T> {
    val: T
}

I want to create a function like get_val which accepts any HasVal implementer and returns it's val:

function get_val<T, Hv extends HasVal<T>>(hv: Hv): T {
    return hv.val
}

This doesn't work, because T doesn't get narrowed for some reason:

class MyVal {
    val = "hello world!"
}

let u = get_val(new MyVal());
// type of u is unknown :(

Is there a type signature for get_val that will return the proper type without requiring the caller to specify it?


Solution

  • The problem with

    declare function get_val<T, Hv extends HasVal<T>>(hv: Hv): T;
    

    is that generic constraints are not used as inference sites for other generic type arguments. (Such a feature was suggested at microsoft/TypeScript#7234 but eventually declined in favor of other techniques.) So while Hv can be inferred from the hv argument, T cannot be inferred from the Hv extends HasVal<T> constraint. There's pretty much nowhere from which T can be inferred, so it falls back to the implicit unknown constraint, and you get the undesirable behavior.


    Given this example, I'd be inclined to just remove the Hv type parameter entirely. If you want to infer T from an input of type HasVal<T>, tell the compiler that hv is of type HasVal<T> directly, not indirectly via a constraint:

    function get_val<T>(hv: HasVal<T>): T {
        return hv.val
    }
    

    And now things behave as desired:

    let u = get_val(new MyVal());
    // let u: string
    

    T is inferred as string because a MyVal instance is seen as a valid HasVal<string>.

    Playground link to code