Search code examples
typescripttypechecking

Typescript is not performing typecheck


I'm trying to take advantage of the TypeScript typecheck, but I'm stuck with the following code:

abstract class Mammal {
  abstract breed(other: Mammal);
}

class Dog extends Mammal {
  breed(other: Dog) {}
}

class Cat extends Mammal {
  breed(other: Cat) {}
}

const toby = new Dog();
const lucy = new Dog();
const luna = new Cat();

toby.breed(lucy); // OK
toby.breed(luna); // Works, but it shouldn't since luna is a Cat!

It seems like TypeScript is performing some kind of duck typing here and is considering Dog == Cat. How can I get the typechecker to reject this code?

Sidenote: This is how I would do this in Rust:

trait Mammal {
    fn breed(&self, other: &Self);
}

struct Cat {}

impl Mammal for Cat {
    fn breed(&self, _other: &Self) {}
}

struct Dog {}

impl Mammal for Dog {
    fn breed(&self, _other: &Self) {}
}

fn main() {
    let toby = Dog{};
    let lucy = Dog{};
    let luna = Cat{};

    toby.breed(&lucy);
    toby.breed(&luna); // expected struct `Dog`, found struct `Cat`
}

Solution

  • TypeScript is structurally typed (thanks to @jcalz).

    The idea behind structural typing is that two types are compatible if their members are compatible.

    Not so elegant but working: You can add a dummy property to make some differences between the classes (Playground):

    abstract class Mammal {
      abstract breed(other: this): void; // Bonus: Here you can make use of Polymorphic this types
    }
    
    class Dog extends Mammal {
        private dummy1 = undefined;
        breed(other: Dog) {}
    }
    
    class Cat extends Mammal {
      private dummy2 = undefined;
      breed(other: Cat) {}
    }
    
    const toby = new Dog();
    const lucy = new Dog();
    const luna = new Cat();
    
    toby.breed(lucy); // OK
    toby.breed(luna); // Error
    

    Bonus: You can make use of Polymorphic this types (thanks to @jcalz) in your base class.

    abstract breed(other: this): void;