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