Reputation: 3666
If I have the following union which wraps a function and its argument, how can I call it?
type Wrapper = {
fn: (a: string) => void
arg: string
} | {
fn: (a: number) => void
arg: number
}
let foo!: Wrapper
foo.fn(foo.arg) // Error Type 'string' is not assignable to type 'never'.
I can't figure out how to call it. Everything that I've tried essentially boils down to a cast, (e.g. casting to (a:any) => void
) which I can do if I have to, but I feel that I shouldn't have to do that.
Can this function be called without casting?
Edit: To clarify, I'm asking if there exist solutions which don't involve changing definition of Wrapper
.
Upvotes: 3
Views: 487
Reputation: 995
You can use a conditional type to disambiguate the types. Unfortunately it requires you basically duplicate your wrapper union types again and if you have more things you need to check, your conditional type will get pretty ugly. But it does let you avoid a cast (sort of).
type Wrapper = {
fn: (a: string) => void
arg: string
} | {
fn: (a: number) => void
arg: number
}
type Disambiguate<W extends Wrapper> = W['arg'] extends string
? { fn: (a : string) => void, arg: string }
: { fn: (a: number) => void, arg: number };
let foo!: Disambiguate<Wrapper>
// The compiler is satisfied. No error here.
// foo can only be one of the branches in the conditional type.
foo.fn(foo.arg);
Upvotes: 1
Reputation: 2527
Not exactly sure about your use case, but above can be achieved by using this:
type ArgType = number | string
type Wrapper<T extends ArgType> = {
fn: (a: T) => void
arg: T
}
let foo!: Wrapper<number>
foo.fn(foo.arg)
let foo1!: Wrapper<string>
foo1.fn(foo1.arg)
Upvotes: 0