Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't TypeScript correctly infer `this` type?

Tags:

typescript

Take the following function:

function test<T>(this: { value: T }) {
  return this.value;
}

If you use call, apply or bind and provide the this context, TypeScript won't infer the provided type and it will return unknown in this case.

const a = test.apply({ value: 1 }); // a: unknown
const b = test.call({ value: 'b' }); // b: unknown
const fn = test.bind({ value: true }); // fn: () => unknown

Ok, it could be because apply, for example, is declared as apply<T, R>(this: (this: T) => R, thisArg: T): R, so it will require you to inform the R return type. But if you only explicitly declare the type of the test function, you will get the correct type:

const typed = (test<number>).apply({ value: 1 }); // typed: number

Why does it happen? Why it didn't infer R but then when I provided the type it did?

like image 347
Eduardo Rosostolato Avatar asked May 28 '26 20:05

Eduardo Rosostolato


1 Answers

The bind/call/supply support introduced in TypeScript 3.2, as implemented in microsoft/TypeScript#27028, is not able to really deal with overloaded or generic functions properly. According to the documentation, "when using these methods on a generic function, type parameters will be substituted with [the unknown type]" (it says the empty object type {} but it's been unknown since TypeScript 3.5, as implemented in microsoft/TypeScript#30637). That explains the behavior you're seeing. There's an open feature request at microsoft/TypeScript#54707 for improved support here, but for now it's not part of the language.

As for why it works with (test<number>), that's an instantiation expression, where the specified type argument gets substituted into the generic function type parameter without calling the function. So test<number> is no longer a generic function:

function test<T>(this: { value: T }) {
    return this.value;
}
type Test = typeof test // <T>(this: {value: T}) => T

const tn = test<number>;
type TestNumber = typeof tn // (this: { value: number; }) => number

It's just the specific function type (this: { value: number }) => number, for which bind/call/apply should work as desired.

Playground link to code

like image 173
jcalz Avatar answered May 31 '26 23:05

jcalz