I want to have an interface where I know the parameter is going to be 1 of a few different types (coming from a 3rd party library) and based on what type it is, the return type will change, with some types also being from a 3rd party library, or being a basic type like void or null.
Here is the simplest code to replicate my issue
type foo = {
statusCode: number;
body: string;
}
type fooReturn = string; // in reality, this could be a complex type
type bar = {
results: [];
}
type barReturn = number; // in reality, this could be a complex type
interface foobar {
myFunc<T extends (foo | bar)>(event: T): T extends foo ? fooReturn : barReturn;
}
class myClass implements foobar {
// the below code errors
myFunc(event: foo) { // we tell TS that the param is of type foo
return 'hello' // so the return must be a string
}
}
The error is
Property 'myFunc' in type 'myClass' is not assignable to the same property in base type 'foobar'.
Type '(event: foo) => string' is not assignable to type '(event: T) => T extends foo ? string : number'.
Types of parameters 'event' and 'event' are incompatible.
Type 'T' is not assignable to type 'foo'.
Type 'foo | bar' is not assignable to type 'foo'.
Type 'bar' is missing the following properties from type 'foo': statusCode, body
2
Answers
I think the correct way to handle this is by using Function Overloads. Then you can use a type guard to narrow the
event
parameter and return the correct type.TypeScript Playground
The problem here seems to be that you’ve incorrectly scoped the generic type parameter for your intent. The type
is a specific type with a generic method. Generic methods have the generic type parameter scoped to the call signature, meaning the caller decides what type argument goes in there. If I want to implement a
Foobar
, then itsmyFunc()
method has to allow the caller to call with whateverT
they want. It must support bothmyFunc(event: Foo): FooReturn
andmyFunc(event: Bar): BarReturn
. ButMyClass
does not implement that correctly; it only supportsFoo
and notBar
.Instead, you probably want
which is a generic type, with a specific method. Generic types have the generic type parameter scoped to the type itself, meaning that the implementer decides what type argument goes in there. With that definition, you need to decide whether you’re implementing
Foobar<Foo>
orFoobar<Bar>
. Once you make that decision, the type argument forT
is chosen and fixed. The call signature formyFunc()
isn’t generic. And yourMyClass
does properly implementFoobar<Foo>
:Playground link to code