Infer return type from generic types
I want to infer a type in a specific situation to either get a Box<T>
or a T
, based on an input parameter, but I end up with Box<unknown>
and unknown instead.
I think it is because of my usage of W extends Wrap<T>
, but I don't know how to express this better.
See this code example, hover over boxResult
and noBoxResult
to see the inferred types are not ideal for me:
// Type definitions
interface ComplicatedDataContainer<T> {
// contains many Ts in some complicated fashion
}
interface WrapA<T> {
v: ComplicatedDataContainer<T>,
p: "Box"
}
interface WrapB<T> {
v: ComplicatedDataContainer<T>,
p: "NoBox"
}
type Wrap<T> = WrapA<T> | WrapB<T>;
type Box<T> = {
t: T
}
type SelectThing = <T, W extends Wrap<T>>(id: string, w: W) => W extends {p: "Box"} ? Box<T> : T
// Usage example
const wBox: WrapA<string> = {
v: "",
p: "Box"
}
const wNoBox: WrapB<string> = {
v: "",
p: "NoBox"
}
const selector: SelectThing = (id: string, w: Wrap<any>) => {throw new Error()}; // dont care about runtime behavior here, real impls are way more complicated
// I want the type of this to be Box<string>, but it is Box<unknown>
const boxResult = selector("", wBox);
// I want the type of this to be string, but it is unknown
const noBoxResult = selector("", wNoBox);
You can run this example here.
The problem is that there's no inference site for T
so it defaults to unknown
. Actually you don't need a separate type parameter T
here, since it can be derived from the type parameter W
. The Unwrap
type below does this derivation.
type Unwrap<W extends Wrap<any>> = W extends Wrap<infer T> ? T : never
type SelectThing =
<W extends Wrap<any>>(id: string, w: W) => W extends {p: "Box"} ? Box<Unwrap<W>> : Unwrap<W>
Playground Link
Note that I had to fill in ComplicatedDataContainer
in the example so that it depends on T
in some way, to avoid unintended structural equivalence.