Expected 3 type arguments but got 1 but it should infer 2 types

As of TS3.4 there is no partial type parameter inference. Either you let the compiler try to infer all the type parameters, or you specify all the type parameters. (Well, there are default type parameters but that doesn’t give you what you want: you want to infer the type parameters you leave out, not assign a default type to them). There have been several proposals to address this, but so far none have met with full approval.

For now, therefore, there are only workarounds. The two that I can think of are to use a dummy function parameter or to use currying.

The dummy parameter version:

function pathBuilderDummy<
    T,
    K1 extends keyof T,
    K2 extends keyof NonNullable<T[K1]>>(dummy: T, p: K1, p2?: K2) {
    let res = String(p);
    if (p2) { res += "." + p2; }
    return res;
}

const pathDummyTest = pathBuilderDummy(null! as ISome, "b", "c");

Here we are doing what you said you didn’t want to do… pass in a parameter of type T. But since it’s just a dummy parameter and not used at runtime, it only matters what the type system thinks it is. The actual type of the value you pass in doesn’t matter. So you can just pass it null and use a type assertion to choose T.

The curried function solution:

const pathBuilderCurry =
    <T>() => <
        K1 extends keyof T,
        K2 extends keyof NonNullable<T[K1]>>(p: K1, p2?: K2) => {
        let res = String(p);
        if (p2) { res += "." + p2; }
        return res;
    }

const pathCurryTest = pathBuilderCurry<ISome>()("b", "c")

Here you are returning a function that returns another function. The first function takes no value parameters but it does take the one type parameter you want to specify. It then returns a function where T is specified but the other type parameters are inferred.

Neither solution is perfect, but they are the best we can do for now. Hope that helps; good luck!

Leave a Comment