Reputation: 6884
I have something like the following:
declare class Base {
constructor(a: string)
}
declare class A extends Base {
constructor(a: number)
}
declare class B extends Base {
constructor(a: object)
}
declare class C extends Base {
constructor(a: boolean)
}
type ResponseMapper<T extends Base> = T extends A
? number
: T extends B
? object
: T extends C
? boolean
: never;
declare function getValue<T extends Base>(input: T): ResponseMapper<T>
let x: number = getValue(new A(1))
let y: object = getValue(new B({}))
let z: boolean = getValue(new C(true))
This doesn't quite work because A, B, and C are all structurally equivalent. So in ResponseMapper
, if I pass an instance of B
or C
in, it still evaluates to number
. So the variable declarations for y
and z
end up being type errors.
I figured out a workaround is to add a fake property to each class that makes the types no longer be structurally equivalent. So this works:
declare class Base {
constructor(a: string)
}
declare class A extends Base {
_name?: "A"
constructor(a: number)
}
declare class B extends Base {
_name?: "B"
constructor(a: object)
}
declare class C extends Base {
_name?: "C"
constructor(a: boolean)
}
type ResponseMapper<T extends Base> = T extends A
? number
: T extends B
? object
: T extends C
? boolean
: never;
declare function getValue<T extends Base>(input: T): ResponseMapper<T>
let x: number = getValue(new A(1))
let y: object = getValue(new B({}))
let z: boolean = getValue(new C(true))
Is there a better way to get this to work that doesn't require adding a fake field?
Upvotes: 1
Views: 56
Reputation: 15705
Here, try using this type:
type ResponseMapper<T, U> = T extends new (a: infer R) => U ? R : never;
declare function getValue<T extends new (a: V) => U, U, V = ResponseMapper<T, U>>(input: U): V;
Upvotes: 1