Narrowring a readonly class with instanceof of does not work on the else branch
🔎 Search Terms
instanceof narrowing readonly generic
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about Readonly and instanceof Narrowing
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.5.0-dev.20240515#code/MYGwhgzhAECC0G8CwAoa7oAsCmIQHsAuaCAFwCcBLAOwHNoBeaAIhz32dQF9VUbTs5AGZhg2aACFEqDNADu+ciAAmxMlTrdeKIQFdqwUpXzVo1MOXL45ACnwAjAFbEAStjDKTIAJ4AeWAB80AA+kgCUahQ09MhoGJRC0HZO0DRkYAbY+ImwYdJxsujk2KS65KYOjgB0bAQA3DIYPAXQxaXl0JVVCkrKDShcQA
💻 Code
class A {
hello: string = "hello"
}
interface B {
world: string
}
function narrow(obj: Readonly<A> | B): string {
if (obj instanceof A) {
return obj.hello;
}
return obj.world;
}
🙁 Actual behavior
TypeScript is not narrowing the type of obj to B and therefore reports a property does not exist
🙂 Expected behavior
The same narrowring which will happen without the Readonly utitlity type
Additional information about the issue
No response
The same narrowing which will happen without the Readonly utitlity type
Please note that this behavior is unsound. In theory, "not instanceof" should never narrow the type, since ts uses structural typing, and something which is not an instance of A can still be of type A in structure. Here's an example and the explanation by a team member.
This analysis is done structurually and A is a subtype of Readonly<A>, so knowing that obj isn't an A doesn't tell you that it's a B.
By analogy, if have something that is an Animal (Readonly<A>) or House (B), knowing that it's not a Cat (A) is not enough information to determine that it's a House.
Interesting in my head it was the other way around that Readonly<A> would be subtype of A and then i thought that it would be possible for TS to figure that out. Thanks for the clarification 👍
Interesting in my head it was the other way around that Readonly would be subtype of A
This is definitely one of those things that feels true but isn't