Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
cwoolum opened this issue Aug 2, 2023 · 4 comments
Closed

Comments

@cwoolum
Copy link

cwoolum commented Aug 2, 2023

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.

@fatcerberus
Copy link

fatcerberus commented Aug 2, 2023

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.

@cwoolum
Copy link
Author

cwoolum commented Aug 2, 2023

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.

@cwoolum cwoolum closed this as not planned Won't fix, can't repro, duplicate, stale Aug 2, 2023
@everett1992
Copy link

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

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

@MartinJohns
Copy link
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".

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants