You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Supported beast featuresinterfaceBeast{wings?: boolean;legs?: number}interfaceLegged{legs: number;}interfaceWinged{wings: boolean;}// Beast feature detection via user-defined type guardsfunctionhasLegs(x: Beast): x is Legged{returnx&&typeofx.legs==='number';}functionhasWings(x: Beast): x is Winged{returnx&&!!x.wings;}// Function to identify a given beast by detecting its featuresfunctionidentifyBeast(beast: Beast){// All beasts with legsif(hasLegs(beast)){// All winged beasts with legsif(hasWings(beast)){if(beast.legs===4){// ERROR TS2339: Property 'legs' does not exist on type 'Winged'.console.log(`pegasus - 4 legs, wings`);}elseif(beast.legs===2){// ERROR TS2339...console.log(`bird - 2 legs, wings`);}else{console.log(`unknown - ${beast.legs} legs, wings`);// ERROR TS2339...}}// All non-winged beasts with legselse{console.log(`manbearpig - ${beast.legs} legs, no wings`);}}// All beasts without legs else{if(hasWings(beast)){console.log(`quetzalcoatl - no legs, wings`)}else{console.log(`snake - no legs, no wings`)}}}// Runtime resultsidentifyBeast({wings: true});// quetzalcoatl - no legs, wingsidentifyBeast({wings: false});// snake - no legs, no wingsidentifyBeast({legs: 2});// manbearpig - 2 legs, no wingsidentifyBeast({legs: 4});// manbearpig - 4 legs, no wingsidentifyBeast({wings: true,legs: 2});// bird - 2 legs, wingsidentifyBeast({wings: true,legs: 4});// pegasus - 4 legs, wingsidentifyBeast({wings: true,legs: 6});// unknown - 6 legs, wingsidentifyBeast({wings: false,legs: 6});// manbearpig - 6 legs, no wings
The code above produces compiler errors with tsc@next, but it does represent valid JavaScript that runs correctly and outputs the expected results as shown at the bottom of the code.
I believe tsc is working as intended in this example. So this issue is a suggestion for improving type guards to support this coding pattern (sometimes called duck typing 🐥). The rationale is that it is a useful and common practice in JavaScript to identify and refine objects through successive feature detection.
The limitation with type guards in their current form is that if we have already narrowed to Legged, and then we further narrow to Winged using a nested type guard, then tsc 'forgets' that we still have a Legged. Imagine a blindfolded person feeling the legs of a beast. If further investigation reveals that the beast has wings, the person would probably assume the legs are still there. After all, legs and wings are not mutually exclusive features, and a beast can have both.
So the ideal behaviour in this case, would be that if we narrow to Legged and then further narrow to Winged, then its safe to say we must now have a Legged & Winged, ie both features simultaneously. (Strong duck typing? 💪 🐥)
A similar issue can be seen in the following snippet:
functionbeastFoo(beast: Beast){if(hasWings(beast)&&hasLegs(beast)){beast// beast is Legged// ideally, beast would be Winged && Legged here...}if(hasLegs(beast)&&hasWings(beast)){beast// beast is Winged// ideally, beast would be Legged && Winged here...}}
In summary, in a block of code that is only reachable by passing multiple type guards (eg isFoo(x) && isBar(x) && isBaz(x)), the narrowed type inside that block would be the intersection of the narrowed types from the type guards (eg x is Foo&Bar&Baz).
The text was updated successfully, but these errors were encountered:
yortus
changed the title
Suggestion: type guards for feature detection
Suggestion: successive narrowing with nested type guards
Jun 8, 2016
The code above produces compiler errors with tsc@next, but it does represent valid JavaScript that runs correctly and outputs the expected results as shown at the bottom of the code.
I believe tsc is working as intended in this example. So this issue is a suggestion for improving type guards to support this coding pattern (sometimes called duck typing 🐥). The rationale is that it is a useful and common practice in JavaScript to identify and refine objects through successive feature detection.
The limitation with type guards in their current form is that if we have already narrowed to
Legged
, and then we further narrow toWinged
using a nested type guard, then tsc 'forgets' that we still have aLegged
. Imagine a blindfolded person feeling the legs of a beast. If further investigation reveals that the beast has wings, the person would probably assume the legs are still there. After all, legs and wings are not mutually exclusive features, and a beast can have both.So the ideal behaviour in this case, would be that if we narrow to
Legged
and then further narrow toWinged
, then its safe to say we must now have aLegged & Winged
, ie both features simultaneously. (Strong duck typing? 💪 🐥)A similar issue can be seen in the following snippet:
In summary, in a block of code that is only reachable by passing multiple type guards (eg
isFoo(x) && isBar(x) && isBaz(x)
), the narrowed type inside that block would be the intersection of the narrowed types from the type guards (egx is Foo&Bar&Baz
).The text was updated successfully, but these errors were encountered: