Skip to content

Partially discriminated union with explicitly defined key loosens inferred typeΒ #55239

Closed as not planned

Description

@cwoolum

Bug Report

Please feel free to change the title to make it more accurate. I wasn't sure what to call this

πŸ”Ž Search Terms

union narrow

πŸ•— Version & Regression Information

5.1.6

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

export type DomNodeStyle = ElementStyle | ImageStyle;

export type ElementStyle = {
    tagName: string;
    style: { key: string; value: string }[];
    children: (ElementStyle | ContentStyle)[];
};

export type ContentStyle = { content: string };

export type ImageStyle = {
    tagName: 'img';
    imageSource: string | null;
    style: { key: string; value: string }[];
    children: (ElementStyle | ContentStyle)[];
};

const image = {
    tagName: 'img',
    imageSource: 'https://example.com/image.jpg', // This is correct. Compiler requires `imageSource` because tagName is `img`.
    style: [],
    children: []
};

function doSomeWork(node: DomNodeStyle) {
    if (image.tagName === "img") { // Narrowing is not occurring correctly
        console.log(image.imageSource) // Error message: Property 'imageSource' does not exist on type 'ElementStyle'
    }
}

πŸ™ Actual behavior

For a partially discriminated union, when trying to access a narrowed field value, the compiler does not properly narrow the type. This forces the user to use the in operator to determine if the field exists. When instantiating the object though, the compiler does correctly infer that the field from the narrowed type needs to exist.

πŸ™‚ Expected behavior

I would expect the compiler to properly narrow the type if the explicitly defined union is set. In this case, if tagName is img, the compiler should infer that imageSource is required and available. If tagName is any other string value, the compiler should fall back to ElementStyle for the type.

Activity

  1. fatcerberus commented on Aug 2, 2023

    @fatcerberus

    Pretty sure this is a duplicate of a few issues.

    This is not a bug; the underlying problem here is that image.tagName === "img" isn't actually sufficient to narrow the union to ImageStyle: image might still be an ElementStyle whose tagName just happens to be "img". You'd need a way to say string & not "img" for this to work correctly. See #4196 and #29317.

    You can actually see this in action by commenting out imageSource in the object literal - there is no compiler error and image is inferred to be a valid ElementStyle.

  2. cwoolum commented on Aug 2, 2023

    @cwoolum
    Author

    I guess I had assumed that the compiler would automatically pick the narrower type but being explicit with a negative type is probably the better approach.

    I'll close this as a dupe since it is the intended functionality.

  3. everett1992 commented on Aug 2, 2023

    @everett1992
    console.log(image);
    //          ^? const image: ElementStyle
    

    Woah, that's the first time I've seen the ^? comment on the playground, that's really useful!

  4. MartinJohns commented on Aug 2, 2023

    @MartinJohns
    Contributor

    guess I had assumed that the compiler would automatically pick the narrower type

    But that wouldn't be safe. Your type ElementStyle explicitly says that any string is a valid tagName, that includes "img".

  5. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions