This behavior is completely normal and expected. For more information, you can refer to the TypeScript FAQ section titled: "Why doesn't type inference work on this interface: interface Foo<T> { }
?".
It's important to understand that TypeScript's type system operates on a structural rather than nominal basis. This means that types like Foo
and Bar
are considered the same if they have identical structures (including key names and value types), regardless of their declared names. In the case of your interface:
interface I<T> {};
The type parameter T
is not utilized in any way within the structure. Consequently, no matter what value you assign to T
, the resulting type I<T>
will be equivalent to an empty interface {}
. Therefore, both I<string>
and I<number>
are indistinguishable from each other and from {}
. Essentially, I<T>
discards all information related to T
, making it impossible for the compiler to infer string
based solely on I<string>
.
To rectify this issue, you must actively incorporate T
into the structure as shown below:
interface I<T> {
someProperty: T // <-- Now T is integral to the structure
};
class C implements I<string> {
someProperty = "hello"
};
With these modifications, an object of type I<string>
will feature a property named someProperty
with a type of string
, while one of type I<number>
will have someProperty
defined with a type of number
. This differentiation allows for accurate type inference as desired:
function test<T>(b: I<T>): T {
return b.someProperty;
}
let x = test(new C); // string
console.log(x.toUpperCase()) // HELLO
Here's a link to try out the code in the TypeScript Playground.