3 ms·
So - I know this in theory, but avoided mentioning it because I couldn’t immediately think of any persuasive examples (whereas subtype polymorphism is a core, w
by wk_end 2y ago
So - I know this in theory, but avoided mentioning it because I couldn’t immediately think of any persuasive examples (whereas subtype polymorphism is a core, widely used, wholly unrestricted property of the language) that didn’t involve casts or any/unknown or other things that people might make excuses for.
Do you have any examples off the top of your head?
- neongreen 2y agohttps://counterexamples.org/ https://counterexamples.org/ is a good collection of unsoundness examples in various languages. For TypeScript, they list an example with `instanceof`: https://counterexamples.org/polymorphic-union-refinement.html https://counterexamples.org/polymorphic-union-refinement.htm... In the playground: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABAdwE4EMAOAeAKgPgAoAPALkV0QB9EBBVDATz3wEpz6mXEBvAKESIYwRCSFgAzlHSQApnBGd0jVolSyoIVEmIBuAWo1akAbWIBdfQF8+fUJFgIhAExYlyudhV4GICKYgSHibmiAC8KBiYJKz6guqa2oEmAAyWfDZ8fpJQiM4wAOYwUEGIYCAAtgBGsqgh4S6EJgDMADSIAIztACzmsXwA9AOI4BJw4M7kAAb5RSVTQhKI6NAg6AA264yIU81TfLPFEgB0MJDrIM6yEoTdsYJDFIyYsgCiDHCo5Iclp+eX10WZTguXQiHs0HgYD4QA https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABA...
- sixbrx 2y agoHere's an example I constructed after reading the TS docs [1] about flow-based type inference and thinking "that can't be right...". 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.html#the-in-operator-narrowing https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
- oxidant 2y agoThis narrowing is probably not the best. I'm not sure why the TS docs suggest this approach. You should really check the type of the key to be safer, though it's still not perfect. if (typeof animal.swim === 'function') {....}
- saghm 2y agoCompilers don't really have the option of just avoiding non-idiomatic code, though. If the goal is to compile TypeScript ahead of time, the only options are to allow it or to break compatibility, and breaking compatibility makes using ahead-of-time TypeScript instead of some native language that already exists much less compelling.
- deleted 2y ago[deleted]
- noway421 2y agoHere's a simpler repro: 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.
- kcrwfrd_ 2y agoconst foo: string[] = ['foo'] // inferred as string // though it is undefined const bar = foo[1]