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
When a class B inherits from A but doesn't actually have any fields of its own it is treated as identical to A for the purposes of refuting a type guard. I ran into this in the typings for the axios project. I'll give an example below.
classA{message: string;constructor(message: string){this.message=message;}}classBextendsA{// declare message: "I am B"; // ← workaroundconstructor(){super("I am B");}}functionisB(obj: any): obj is B{return"message"inobj&&obj.message==="I am B";}functionsucceeds(test: A){if(isB(test)){console.log(test.message);}else{// test is `never`console.log(test.message);}}
🙁 Actual behavior
In the else branch above test has type never, and therefore test.message is inaccessible.
🙂 Expected behavior
Refutation of B should not refute A. (Though I'm aware that these two types might just be treated as the same type by TS and that's not really a bug, but a feature.)
🦌 The Bug in the Wild
There's a workaround (indicated by the comment above, you have to include something that differentiates the type). I ran into this in the typings for axios where there's a class CanceledError extends AxiosError that only differs from AxiosError by a special value for one of its fields. In essence, that's a reification of the type similar to a tagged union. Since axios is written in JS, the types for it didn't really reflect that.
This is effectively an error in the implementation of isB, it can only write obj is B if it returns results which are true for all structuralB, which includes A.
@adimit Also watch out for cases where two classes are structurally identical but otherwise unrelated. TS will treat them as the same type. (the workaround being to add private properties)
Thanks for the hint, @fatcerberus. It all made sense once I started thinking about classes as sugar for interfaces. I guess class just evokes Java in me and I got blindsided, but there's nothing special to a class in TS.
Bug Report
When a class
B
inherits fromA
but doesn't actually have any fields of its own it is treated as identical toA
for the purposes of refuting a type guard. I ran into this in the typings for theaxios
project. I'll give an example below.🔎 Search Terms
type guard, inheritance, subclass
🕗 Version & Regression Information
This problem exists for all 4.x versions.
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
In the
else
branch abovetest
has typenever
, and thereforetest.message
is inaccessible.🙂 Expected behavior
Refutation of
B
should not refuteA
. (Though I'm aware that these two types might just be treated as the same type by TS and that's not really a bug, but a feature.)🦌 The Bug in the Wild
There's a workaround (indicated by the comment above, you have to include something that differentiates the type). I ran into this in the typings for
axios
where there's a classCanceledError extends AxiosError
that only differs fromAxiosError
by a special value for one of its fields. In essence, that's a reification of the type similar to a tagged union. Sinceaxios
is written in JS, the types for it didn't really reflect that.Here's the original issue we ran into and Here's the offending subclass, with its type here.
The text was updated successfully, but these errors were encountered: