It yields no warnings or errors at compile stage but gives runtime error based on a wrong flow-based type inference. The crux of it is that something can be a Bird (with "fly" function) but can also have any other members, like "swim" because of structural typing (flying is the minimum expected of a Bird). The presence of a spurious "swim" member in the bird causes tsc to infer in a conditional that checks for a "swim" member that the animal must be a Fish or Human, when it is not (it's just a Bird with an unrelated, non-function "swim" member).
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void; fly?: () => void };
function move(animal: Fish | Bird | Human) {
if ("swim" in animal) {
// TSC infers wrongly here the presence of "swim" implies animal must be a Fish or Human
onlyForFishAndHumans(animal);
} else {
animal;
}
}
function onlyForFishAndHumans(animal: Fish | Human) {
if (animal.swim) {
animal.swim(); // Error: attempt to call "not-callable".
}
// (receives bird which is not a Fish or Human)
}
const someObj = { fly: () => {}, swim: "not-callable" };
const bird: Bird = someObj;
move(bird);
// runtime error: [ERR]: animal.swim is not a function
[1] https://www.typescriptlang.org/docs/handbook/2/narrowing.htm... if (typeof animal.swim === 'function') {....} type Bird = { id: number };
type Human = { swim?: () => void; id: number };
function move(animal: Bird | Human) {
onlyForHumans(animal);
}
function onlyForHumans(swimmer: Human) {
if (swimmer.swim) {
swimmer.swim();
}
}
const someObj = { id: 1, swim: "not-callable" };
move(someObj);
`someObj` gets casted as `Bird`, then `animal` gets casted as `Human`. Unfortunately unions here are indeed unsound.As as workaround you could add a property `type` (either `"bird"` or `"human"`) to both `Bird` and `Human`, then TypeScript would be able to catch it.
For TypeScript, they list an example with `instanceof`:
https://counterexamples.org/polymorphic-union-refinement.htm...
In the playground:
https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABA...
Do you have any examples off the top of your head?