To better illustrate my goal, I will use code:
Let's start with two classes: Shoe and Dress
class Shoe {
constructor(public size: number){}
}
class Dress {
constructor(public style: string){}
}
I need a generic box that can hold either a Shoe or a Dress (but not both):
class Box <T extends Shoe | Dress > {
}
Next, there are utility classes for handling the movement of shoes and dresses:
class ShoeMover {
constructor(public size: number[]){}
}
And another utility class for packing dresses:
class DressPacker {
constructor(public style: string[]){}
}
Now, we create a generic mover class that works with either Shoes or Dresses:
class Move<B extends Box<Shoe> | Box<Dress>> {
private box: B;
constructor(toMove: B) {
this.box = toMove;
}
public mover(tool: ShoeMover | DressPacker) {
}
}
The challenge is to ensure that if Move is instantiated with Box<Shoe>, only ShoeMover is accepted, and vice versa. This way, only compatible tools can be used:
let shoemover = new Move(new Box<Shoe>());
// This should compile
shoemover.mover(new ShoeMover([21]))
// This should not compile, but currently does
shoemover.mover(new DressPacker(["1"]))
Several attempts were made using conditional types, but none provided the desired compile-time guarantees. Any suggestions on how to achieve this?
Edit.
The solution mentioned above works in one scenario but fails in another when the constructor of Move takes a union type as input.
type Mover<T> =
T extends Shoe ? ShoeMover :
T extends Dress ? DressPacker :
never;
class Move<T extends Shoe | Dress> {
private box: Box<T>;
constructor(public toMove: Box<Shoe>[] | Box<Dress>[]) {
this.box = toMove;
}
public mover(tool: Mover<T>) {
}
}
let shoemover = new Move(new Array<Box<Shoe>>());
// This should compile
shoemover.mover(new ShoeMover([21]))
// This should not compile, but currently does
shoemover.mover(new DressPacker(["1"]))