3

I have the following code:

function testFunc<T>(
    a: keyof T,
    b: keyof T
) {
    return function(object2: T): T {
        return object2;
    }
}

const obj2 = {
    a1: 1,
    b1: 2,
    c1: 3,
    e1: 4,
    d1: 5
}

const result = testFunc('a1', 'b1')(obj2);
const c1 = result.c1; // Error. Property 'c1' does not exist on type '{ a1: any; } & { b1: any; }'. It's wrong

// We can try this

const result2 = testFunc<typeof obj2>('a1', 'b1')(obj2);
const c12 = result2.c1; // Works great

How can a generic be used from a nested function without explicitly specifying the type in testFunc (testFunc<typeof obj2>) ? Thanks. You can see here: https://tsplay.dev/mbk7BW

10
  • Yes, but if I add a generic parameter, the code still doesn't work the way I want Commented Jun 28, 2021 at 17:11
  • I'm afraid it's not clear what you're asking. You seemed to be asking how the code you've shown works, but I think you must be asking something else. Commented Jun 28, 2021 at 17:11
  • Have you considered reversing the currying (so that you pass object first, then the fields you want to handle)? Commented Jun 28, 2021 at 17:12
  • @raina77ow I would like it with this syntax :) Commented Jun 28, 2021 at 17:27
  • @T.J.Crowder Updated the example, it may become clearer. I want to use generic type in first function from nested function Commented Jun 28, 2021 at 17:28

1 Answer 1

3

Hmm, type inference doesn't work that way in TypeScript. As soon as you call testFunc('a1', 'b1'), the compiler has already specified the generic type parameter T; there's no way to defer that specification. Put another way: the compiler cannot contextually type the function returned from testFunc('a1', 'b1') based on the fact that you go on to pass obj2 into it. It's as if you wrote the following code:

const returnedFunction = testFunc('a1', 'b1');
/* const returnedFunction: (object2: { a1: any;} & { b1: any;}) => 
   { a1: any; } & { b1: any; } */

const result = returnedFunction(obj2);
/* const result: { a1: any; } & { b1: any; } */

const c1 = result.c1; // oops

The type of returnedFunction is a function whose input and output is of type {a1: any} & {b1: any}... and therefore result is of type {a1: any} & {b1: any} and the compiler knows nothing about its c1 property.


In order to fix this, I'd recommend changing the type parameters so that returnedFunction still has enough information to do what you want:

function testFunc<K extends PropertyKey>(
    a: K,
    b: K
) {
    return function <T extends Record<K, any>>(object2: T): T {
        return object2;
    }
}

Here, testFunc is not generic in T, since a and b don't contain enough information to produce a very meaningful type for T ({a: any} & {b: any} is the best the compiler can do). Instead we make it generic in K, the union of the key types of a and b.

And then, the returned function is itself generic in T, which is constrained to an object type with at least the keys in K, but possibly more.

Now when you call testFunc('a1', 'b1'), the result is more strongly typed:

const returnedFunction = testFunc('a1', 'b1');
/* const returnedFunction: 
   <T extends Record<"a1" | "b1", any>>(object2: T) => T */

And thus it does what you want when you pass obj2 to it:

const result = returnedFunction(obj2);
/* const result: {
  a1: number;
  b1: number;
  c1: number;
  e1: number;
  d1: number;
} */

const c1 = result.c1; // okay, number

Now that it works when you save the intermediate result, you can stop doing that and it will still work:

const result = testFunc('a1', 'b1')(obj2);
const c1 = result.c1; // okay, number

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.