Typescript generic inference from interface implementation

Your problem is that ICommand<T> is not structurally dependent on T (as mentioned in @CRice's comment).

This is not recommended. (⬅ link to a TypeScript FAQ entry detailing a case that is almost exactly the same as this one, so that's as close to an official word as we're likely to get here)

TypeScript's type system is (mostly) structural, not nominal: two types are the same if and only if they are the same shape (e.g., have the same properties) and it has nothing to do with whether they have the same name. If ICommand<T> isn't structurally dependent on T, and none of its properties have anything to do with T, then ICommand<string> is the same type as ICommand<number>, which is the same type as ICommand<ICommand<boolean>>, which is the same type as ICommand<{}>. Yes, those are all different names, but the type system isn't nominal, so that doesn't count for much.

You can't rely on type inference to work in such cases. When you call execute() the compiler tries to infer a type for T in ICommand<T>, but there's nothing from which it can infer. So it ends up defaulting to the empty type {}.

The fix for this is to make ICommand<T> structurally dependent in some way on T, and to make sure any type that implements ICommand<Something> does so correctly. One way to do this given your example code is this:

interface ICommand<T> { 
  id: T;
}

So an ICommand<T> must have an id property of type T. Luckily the GetSomethingByIdCommand actually does have an id property of type string, as required by implements ICommand<string>, so that compiles fine.

And, importantly, the inference you want does indeed happen:

// goodResult is inferred as string even without manually specifying T
let goodResult = bus.execute(new GetSomethingByIdCommand('1'))

Okay, hope that helps; good luck!


Typescript seems to be able to correctly infer the type if the concrete type is coerced into its generic equivalent before it is passed to ICommandBus.execute():

let command: ICommand<string> = new GetSomethingByIdCommand('1')
let badResult = bus.execute(command)

Or:

let badResult = bus.execute(new GetSomethingByIdCommand('1') as ICommand<string>)

This isn't exactly an elegant solution, but it works. Clearly typescript generics are not very feature complete.