TypeScript: instanceof typeguard fails if classes have similar structure
TypeScript Version:
1.8.x and nightly
Code
class C1 { item: string }
class C2 { item: string[] }
class C3 { item: string }
function Foo(x: C1 | C2 | C3): string {
if (x instanceof C1)
return x.item;
else if (x instanceof C2)
return x.item[0];
else if (x instanceof C3)
return x.item; // Error property item does not exist on C2
}
Expected behavior: Code should compile
Actual behavior: Code has an error as shown
More
The following works i.e. if C1 and C3 differ in structural compatibility:
class C1 { item: string }
class C2 { item: string[] }
class C3 { items: string }
function Foo(x: C1 | C2 | C3): string {
if (x instanceof C1)
return x.item;
else if (x instanceof C2)
return x.item[0];
else if (x instanceof C3)
return x.items; // Ok
}
🌹
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 1
- Comments: 24 (16 by maintainers)
Commits related to this issue
- Update the TypeScript latest to get `instanceof` checks to discriminate similar classes refs https://github.com/Microsoft/TypeScript/issues/7271 — committed to alm-tools/alm by deleted user 8 years ago
- refactor: convert all remaining patchers to TypeScript This also required adding a "brand" to the type of ExpansionPatcher, since TypeScript uses structural rather than nominal typing, even for class... — committed to alangpierce/decaffeinate by alangpierce 7 years ago
- refactor: convert all remaining patchers to TypeScript (#1152) This also required adding a "brand" to the type of ExpansionPatcher, since TypeScript uses structural rather than nominal typing, even ... — committed to decaffeinate/decaffeinate by alangpierce 7 years ago
As noted in #8503, classes should be treated definitely for instanceof checks.
note this applies to instanceof type guards only, and not for user defined type guards; the later stays structural as we have no guarantees on how they will be implemented, where as instanceof is known to always be nominal.
Is this a problem with structural classes or with wrong if-else instanceof?
I’ve also run into this problem, and wish that the compiler behaviour could be brought more into line with what happens at runtime. It’s not specific to
instanceofor classes, it’s really just about the structural similarity of the types in the union. E.g., this works fine at runtime, but fails at compile time:I think the problem is similar to @basarat’s, because from a structural typing viewpoint, a
UnaryFunctionis just a subtype of aBinaryFunction, triggering different narrowing behaviour than if the types were structurally independent. However there is no subtype relationship according to the runtime checks inisUnaryFunctionandisBinaryFunction.Here is another case that brings up this type guard behaviour because the compiler sees the types as structurally related. Consider a function that takes either (a) an options object, where all options are optional, or (b) a function that returns an options object:
This also works at runtime but fails at compile time. The compiler sees
OptionsFunctionas just a special case ofOptionsObject, because structurally it is. But it is not a subtype according to the runtime check in the type guard.I have learned how to spot and work around these cases now. But that involves taking valid runtime code, and rearranging it just right so the compiler won’t complain. It’s a (rare) case where the tool is fighting me rather than helping me. Probably also quite unintuitive for beginners.