What I want to achieve can be best explained in code:
Given shoe and dress class:
class Shoe { constructor(public size: number){} } class Dress { constructor(public style: string){} } Have a generic box that can only contain Shoe or Dress. Can't contain both:
class Box <T extends Shoe | Dress > { } Then have a utility class that takes care of moving shoes:
class ShoeMover { constructor(public size: number[]){} } Also, a utility class for moving Dresses:
class DressPacker { constructor(public style: string[]){} } Then have a generic mover, that if instantiated with Box<Shoe> or Box<Dress> has a mover method that makes use of either the ShoeMover or the DressPacker:
class Move<B extends Box<Shoe> | Box<Dress>> { private box: B; constructor(toMove: B) { this.box = toMove; } public mover(tool: ShoeMover | DressPacker) { } } Then the compile time guarantee should be that, if Move is instantiated with Box<Shoe>, then the mover method should only accept ShoeMover. If instantiated with Box<Dress>. the mover method should only accept DressPacker. That is:
let shoemover = new Move(new Box<Shoe>()); // compile shoemover.mover(new ShoeMover([21])) // should not compile. But currently does shoemover.mover(new DressPacker(["1"])) I tried using conditional types, but I guess the fact that generics is involved makes the intended solution not to work. Basically this is what I have tried:
type MoverFromEitherShoeOrDressA<T> = T extends Box<infer U> ? U extends Shoe ? ShoeMover : U extends Dress ? DressPacker : never: never; and type MoverFromEitherShoeOrDressB<T> = T extends Box<Shoe> ? ShoeMover: T extends Box<Dress> ? DressPacker: never; Then changing the definition of mover from:
public mover(tool: ShoeMover | DressPacker) { } to
public mover(tool: MoverFromEitherShoeOrDressB) { } or public mover(tool: MoverFromEitherShoeOrDressA) { } ..but these did not give the compile time guarantees that I sought.
Anyone knows how to achieve this?
Edit.
The accepted answer works for the scenario above. But there is a slightly different scenario which does not work. Instead of creating another question, I decided to update. The scenario is when the constructor of the Move is changed to take in union type.
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>>()); // compile shoemover.mover(new ShoeMover([21])) // should not compile. But currently does shoemover.mover(new DressPacker(["1"]))