I have a base and a descendant type
interface IInferredTypeBase {
}
interface IInferredType extends IInferredTypeBase {
myProperty;
}
In reality, there are many descendants in that hierarchy.
Now, I also have a type which is generic for this hierarchy:
interface IGenericForInferredType<T extends IInferredTypeBase> {
}
What I want is to make TypeScript automatically infter the generic argument in a situation like this:
type ICallback<TInferredType extends IInferredTypeBase, TGenericForInferredType extends IGenericForInferredType<TInferredType>> = (instance: TInferredType) => any;
function register<
TInferredType extends IInferredTypeBase,
TGenericForInferredType extends IGenericForInferredType<TInferredType>
>(
param1: new () => TGenericForInferredType,
callback: ICallback<TInferredType, TGenericForInferredType>
) {
//...
}
So, there is the register
function taking 2 arguments: param1
is any kind of a descriptor of a type which is IGenericForInferredType<TInferredType>
, just for the other parameter callback
to have its argument as TInferredType
inferred. But it does not work.
So, let's take a concrete example on this.
There is this class for param1
:
class ClassImplementingGenericForInferredType implements IGenericForInferredType<IInferredType> {
}
And here is an exmaple call:
register(ClassImplementingGenericForInferredType, (instance) => {})
The type of instance
is however the base type IInferredTypeBase
and not IInferredType
.
How could I make it infer IInferredType
?
Meaning I don't want to write the word IInferredType
anywhere when I call register
, I want TypeScript to figure it out for me.
Motivation: ClassImplementingGenericForInferredType
, IInferredType
, IInferredTypeBase
--> These are generated from C#, their hierachical and generic relations are defined in C#, I don't want to write it 2x
Here is a TypeScript Playground link demonstrating the issue, and as a fallback, a github repo
TypeScript's type system is structural, not nominal. So a generic type like
interface Gen<T extends Base> {}
where the generic type parameter T
does not appear in the definition (it's just {}
) has no structural dependency on T
. And therefore T
cannot possibly be inferred from it. In the above, Gen<A>
and Gen<B>
are both just {}
, and looking at {}
cannot reliably give you any information about A
and B
. See the relevant TS FAQ entry.
You need some structural dependence, like
interface Gen<T extends Base> {
t: T;
}
Again
type ICallback<T extends Base, G extends Gen<T>> = (instance: T) => any;
has no structural dependence on G
, and therefore you cannot possibly infer G
from it. For this type, G
presumably serves no purpose whatsoever. We can remove G
from that definition, and indeed, remove ICallback
entirely because the indirection doesn't buy us anything; we can just use (t: T) => any
.
From there, you need to make sure that you have as few generic type parameters as necessary. The minimum code necessary to make your example work looks like
function register<T extends Base>(
param1: new () => Gen<T>,
callback: (t: T) => any
) { }
interface Ext extends Base {
myProperty: string;
}
class ClassImplementingGenericForInferredType implements Gen<Ext> {
declare t: Ext
}
register(ClassImplementingGenericForInferredType, (instance) => {
instance.myProperty = "...";
})
Here calling register()
with ClassImplementingGenericForInferredType
allows TypeScript to infer that T
is Ext
. Note that the implements
clause on the class declaration does not cause that inference to succeed. Such clauses have no effect whatsoever on inference, and are only used for type checking the body of the class after the fact. (You can remove the implements
clause and see that it doesn't change things.) It's the t
property of type Ext
that lets TypeScript infer T
. (If you try to remove the t
property you'll see how everything blows up.) Again, structural typing is fundamental to TypeScript; declaration sites don't count as much.
And once T
is inferred as Ext
, then the contextual type of the callback parameter instance
is Ext
, and hence instance.myProperty
exists.