Skip to content

Check for missing property on union type causes failure in subsequent property checks #58448

Open
@molisani

Description

@molisani

🔎 Search Terms

"missing property", "property check", "type union", "control flow analysis"

🕗 Version & Regression Information

  • This is the behavior in every version I tried*, and I reviewed the FAQ for entries about property checks

*There was a minor change between 4.8 and 4.9 that changed the types but did not change the behavior: inference improved from never to incorrectUnionElement & Record<"key", unknown>.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.4.5#code/FASwdgLgpgTgZgQwMZQARIPYFsAOBGVAb2FVNRigQBMMwAbAT1QQC5UwBXLAI1gG4SZCtVqNU3Npx79gAX2ChIsRCnTYcAJiKDSwmvSat2XXjAHzgEBjjSZcqALw7UAHzW48zt3c0K4HMCQIEFpUGgB9AGdsKAgAC3AAcwAKAEptMjUwSIh3HEciWWZIvIFnEDhUZIAiBGrUcDz04kzMnwA6BHaIDABlCBgktIFWsgB6MdQAPQB+ZwtMiqrq7nrGn2bnNvV27m6+gaHUkdHUCem5zIWyJZqkNbAmjNOOpH3+wbAU463xydn5gpFpUanUGo8Ns9Rh0uj0PkcTqNzgCrkCbiCVg8ni0Xjs9nDDl9hr9SMjLmR5LIgA

💻 Code

interface comp1 {
    readonly a: number;
    readonly b: number;
}

interface comp2 {
    readonly a: number;
}

type comp =
    | comp1
    | comp2

function do_something() {
    const comp = {} as comp;

    if ("a" in comp) {
        comp.a.toString();
        // ^? comp
    }

    if ("b" in comp) {
        comp.b.toString();
        // ^? comp1
    }

    if ("c" in comp) {
        comp.c.toString();
        // ^? comp & Record<"c", unknown>
    }

    if ("a" in comp) {
        comp.a.toString();
        // ^? comp2
    }

    if ("b" in comp) {
        comp.b.toString();
        // ^? comp2 & Record<"b", unknown>
    }
}

🙁 Actual behavior

Every property check after the ("c" in comp) one fails to narrow correctly if the key is one that is only defined for some of the element types of the type union. Attempting to access the property after one of these checks results in unknown. As well, the narrowed variable in subsequent conditionals is an arbitrary(?) union element.

🙂 Expected behavior

The conditionals that come after the ("c" in comp) conditional should have the same behavior as the ones before it, as they are the exact same code.

Additional information about the issue

This was initially noticed in a more complicated context with assertions, that I can include here as a secondary example. This one is even more strange, as it is a check for a property that is only defined on some of the elements of the union and it blocks its own duplicate check later.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Help WantedYou can do thisPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions