TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Narrowring a readonly class with instanceof of does not work on the else branch

Open terenc3 opened this issue 1 year ago • 1 comments

🔎 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

terenc3 avatar May 15 '24 09:05 terenc3

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.

whzx5byb avatar May 15 '24 11:05 whzx5byb

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.

RyanCavanaugh avatar Jun 06 '24 18:06 RyanCavanaugh

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 👍

terenc3 avatar Jun 07 '24 07:06 terenc3

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

RyanCavanaugh avatar Jun 07 '24 20:06 RyanCavanaugh