Reputation: 32054
TypeScript uses "structural subtyping" to determine if two types are compatible, which allows me to do a surprising thing like:
abstract class Food {
}
class Vegetable extends Food {
}
class Fruit extends Food {
// doFruitThings() {}
}
class FruitBowl {
addFruit(fruit: Fruit) {}
}
new FruitBowl().addFruit(new Vegetable()); // Whaaaaaat?!
Note that the above snippet doesn't emit a compiler error until you uncomment the doFruitThings()
method and the types become structurally incompatible.
But - I would prefer that the compiler detects that my Vegetable
is not a Fruit
, even though they currently share the same properties/functions. I'm in the early stages of designing my API and am using types to flesh it out before providing implementation, and am constantly being tripped up by types that "suddenly" become incompatible even though, in my mind, they were not compatible to begin with.
Question: Is there any way to configure tsc
to emit a compiler error in the above playground?
Upvotes: 2
Views: 71
Reputation: 3501
Sure, you can use different techniques, even if there isn't an "official" way. For example, with class branding:
abstract class Food { }
class Vegetable extends Food {
_vegetableBrand!: string;
}
class Fruit extends Food {
_fruitBrand!: string;
// doFruitThings() {}
}
class FruitBowl {
addFruit(fruit: Fruit) {}
}
// error!
new FruitBowl().addFruit(new Vegetable());
This method is even used by TS team. It doesn't throw at runtime, but allows you to exclude structural compatibility. brand
suffix is from TS team guidelines.
There are other ways, you can read more here, however in this case interface branding seems the best hypothesis.
Upvotes: 1