I want to have a function that takes two union types as parameters but force them to be the same subtype... without having to check the types myself before calling the function.
This is what I have tried:
The backstory
For simplicity I will define some types:
type Foo = number;
type Bar = [number, number?];
type FooBar = Foo | Bar;
I have a function:
function fn(x: FooBar, y: FooBar) {}
I want to force that x and y be of the same type. IE allow fn(Foo, Foo) and fn(Bar, Bar) but not allow fn(Foo, Bar) and fn(Bar, Foo). So I decided to overload the function.
function fn(x: Foo, y: Foo): void;
function fn(x: Bar, Bar): void;
function fn(x: FooBar, y: FooBar): void {}
I request data from an API. I get two objects from the API, I doubt it matters but they will both be of the same type, either both Foo or both Bar. It depends on what I am asking for.
The problem
When I call my function it complains (I'm using JSON.parse as a way of simulating the API call):
const x: FooBar = JSON.parse("1");
const y: FooBar = JSON.parse("2");
fn(x, y);
Argument of type 'FooBar' is not assignable to parameter of type '[number, number?]'. Type 'number' is not assignable to type '[number, number?]'.
If I check the types before hand it works:
(side note: I wish there was an easier way to check the type when using declared types)
if(typeof x === 'number' && typeof y === 'number') fn(x, y);
else if(Array.isArray(x) && Array.isArray(y)) fn(x, y);
but this seems silly, I don't want to have to check the types before I call the function, when in the end the call will be the exact same.
Somewhat surprisingly using or doesn't work (even though I still don't want to have to do this)
if((typeof x === 'number' && typeof y === 'number') ||
(Array.isArray(x) && Array.isArray(y))) {
fn(x, y); // same error as above
}
Is there anyway to do what I want? Maybe using a different pattern or approach.