Home > Blockchain >  Why does TS infer the type of an inline function but not that of an external function?
Why does TS infer the type of an inline function but not that of an external function?

Time:07-04

function hof(cb: (a: string) => string): void {}

// @ts-expect-error
function someFunc(a) {
  return a.toUpperCase();
}

// Here TS has inferred that the inline function is of type 
// (a: string) => string
hof((a) => a.toUpperCase());

// But here it doesn't
hof(someFunc);

In the above snippet TS can infer the type of the inline function (a) => a.toUpperCase() but it cannot infer the type of someFunc, why?

CodePudding user response:

TypeScript knows that the argument hof tags is (value: string) => string, so when you have an inline function it can infer the argument from the context.

When you separate the function and store it in a variable, it is no longer associated with hof, so it doesn't.

CodePudding user response:

The inline function definition benefits from contextual typing; the context in which the function is defined gives the compiler a hint that it should be of type (a: string) => string, and thus that the function's first parameter will be of type string.

On the other hand, your someFunc definition has no such associated context. It is a standalone function statement whose type needs to be determined by the compiler completely independently of whether or not you ever pass it to hof(). You could argue that the mere existence of hof(someFunc) somewhere in your program could be used as context for the definition of someFunc, but such analysis would be incredibly costly as well as surprising, since it would be highly nonlocal. Thus the someFunc definition has no context for the a parameter, and it is implicitly given the any type (which is an error with the --noImplicitAny compiler option from the --strict suite of compiler features).

CodePudding user response:

The inline function is meant to be used only by hof, so TS can infer it's type safely.

But when the function is defined elsewhere and it's reference is passed to hof, TS cannot assume that the function is only meant to be used by hof because it can be used in other places as well. The only thing TS wants now is that the function that is being passed to hof is type compatible with what hof expects, in other words the type of the function being passed should be compatible with type: (a: string) => string.

And this becomes more clear when you observe the complaint by TS, the complaint is not at all related to hof, even if you remove all the other code and just have the someFunc function definition, TS would still be complaining, so there's no association between someFunc and hof.

If you explicitly state that argument a could be of type any, then there would be no complaints because (a: any) ⇒ string is compatible with (a: string) => string.

Finally, let's consider the following example:

function hof1(cb: (a: string) => string): void {}
function hof2(cb: (a: number) => string): void {}

function someFunc(a: string | number): string {
  return `${a}`;
}

hof1((a) => a.toUpperCase());
hof2((a) => a.toFixed(2));

hof1(someFunc);
hof2(someFunc);

In the example someFunc is being used for both hof1 and hof2, so it makes it more clear why TS wasn't able to make any assumption, because someFunc is more generic as compared to what either hof1 or hof2 accepts. But with that inline function TS knew that it's exactly what hof accepts because that's the only place it can be used.

  • Related