schimmch
schimmch

Reputation: 13

Why does TypeScript not infer function parameters types when those parameters are passed to a typed function?

In the following examples:

function functionA(x: string, y: number, z: SpecialType): void { }
const functionWrapper: (x, y, z) => functionA(x, y, z);

The parameters to functionWrapper are typed as any. Is there some way to encourage tsc to deduce their types from their usage? What limitation of the TypeScript type system prevents this from being resolved?

Upvotes: 0

Views: 180

Answers (2)

Maciej Sikora
Maciej Sikora

Reputation: 20132

TypeScript type analysis work in top->down way. It means that it is equal to the program data flow where the data also is passed top->down. TypeScript is analyzing control-flow, but it is using for it informations given from the top, not from the bottom.

Consider such example of function:

function functionMaker(x: string, y: string) { 
    return () => x + y; 
}

Above function return another function. There is none explicit typing in the definition of the anonymous function returned, but TS is able to analyse that functionMaker always returns () => string type of function.

Doing that another way around is not possible, as TS cannot predict what exactly you will do with the arguments. Consider below:

function functionA(x: string, y: number, z: SpecialType): void { }
function functionB(x: number): void { }
const functionWrapper: (x, y, z) => { 
  functionA(x, y, z);  // x should be string
  functionB(x); // x should be number
}

Now TS has two functions which are using one argument, but both of them have different requirement of the type. Any solving of this puzzle is wrong, as one or another function will fail.

In summary - type analysis is done top-down. But we can solve your issue by creating generic function which will wrap another.

function functionA(x: string, y: number, z: SpecialType): void { }

const wrap = <X, Y, Z, R>(f: (x: X, y: Y, z: Z) => R) => (x: X, y: Y, z: Z): R => f(x,y,z);
const functionWrapper = wrap(functionA);

Our wrap is clearly defined as a wrapper, it means that its purpose is to infer the types from the given function and create another function which has the same arguments and the same return.

Upvotes: 1

Jeff Bowman
Jeff Bowman

Reputation: 95614

One reason: With your proposal, if Typescript could narrow the signature automatically, it would implicitly propagate across all call sites and cause those to fail.

function yourFunction(x) {
  doSomethingThatTakesANumber(x);
}
// elsewhere
yourFunction(1);
// still elsewhere
yourFunction(-1);

Once you change yourFunction to doSomethingThatTakesABoolean, Typescript would infer that x must be a boolean, and then the two unrelated calls would start failing. This would be particularly difficult to debug.

For the case of writing a wrapper function that takes the same signature as the function it wraps, you can use typeof functionA to wrap the function without repeating yourself. Typescript can infer the types of the parameters from the type of the function, but not the code expressed in its body.

const functionWrapper2: typeof functionA = (x, y, z) => functionA(x, y, z);

typescript playground

Upvotes: 1

Related Questions