See the TypeScript FAQ entry on unused type parameters for a canonical answer.
TypeScript's type system is largely structural and not nominal. That means if you are comparing type A
and B
, they are considered to be the same type if and only if they have the same structure (e.g., both are object types with members of the same keys and same value types). It does not matter what types A
and B
are named (e.g., the fact that I use names like "A
" and "B
" isn't relevant) or where they are declared (e.g., having separate declarations like interface A { a: string }
and interface B { a: string }
doesn't make them separate types).
In particular, given the declaration
declare abstract class Part<T> { }
You can see that no matter what type you specify for T
, the resulting type is an empty object type with no members, and therefore is the same type as just {}
. The compiler really doesn't see any difference between Part<swine>
and Part<string>
or anything else.
To see this, note that TypeScript will not let you redeclare a var
with a type annotation of a different type from the original declaration:
var bad: Swine;
var bad: string; // error!
// ~~~
// Subsequent variable declarations must have the same type.
But none of the following yields any compiler error:
var okay: Part<Swine>;
var okay: {}; // no error
var okay: Part<string>; // no error
var okay: Part<unknown>; // no error
So they really are the same type in TypeScript. And that means when you hand the compiler a value of type Part<Swine>
, there's nothing Swine
-like about it. The name Part<Swine>
is just a name, and should no bearing on how the compiler processes the type. Trying to infer T
from a value of type Part<T>
is therefore impossible in principle; the inference fails, and you get unknown
.
Adding a property of type T
to Part<T>
changes everything. Now there is a
structural difference between Part<Swine>
and Part<string>
or Part<unknown>
or {}
, and the compiler has a handle on which to determine T
from Part<T>
.
If we imagine taking this down from the type level into the value level by looking at functions that take string representations of types and turns them into other string representations of types, the analog of what you were doing is something like this:
function part(T: string): string {
return "{}";
}
const hoof = part("Swine");
function getOwnerType(partT: string): string {
// how would you implement this ???
return "unknown";
}
console.log(getOwnerType(hoof)); // unknown
The translation of declare abstract class Part<T> {}
is just (T: string) => "{}"
). Since part("Swine")
produces the same output as part("string"}
or anything else, namely the string "{}"
, there's no way to write the reverse function getOwnerType()
. If, on the other hand, your part()
function has an output that actually depends on its input, things become more reasonable:
function part(T: string): string {
return "{animal: " + T + "}";
}
const hoof = part("Swine");
function getOwnerType(partT: string): string {
return (partT.match(/^{animal: (.*)}$/) ?? ["", "unknown"])[1];
}
console.log(getOwnerType(hoof)); // Swine
Playground link to code