Skip to content

Type guard not applied for type which is a union with an indexed type #21045

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

Closed
sylvanaar opened this issue Jan 6, 2018 · 5 comments
Closed
Assignees
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@sylvanaar
Copy link

sylvanaar commented Jan 6, 2018

TypeScript Version: 2.7.0-dev.20180105

Code

type union = { foo: { bar: true }, baz: true } | { [key: string]: {} };

const test = (value: union) => {
    if ("foo" in value) {
        if ("bar" in value.foo) { // incorrect error: Property 'foo' does not exist on type '{ [key: string]: {}; }'.
            return value.foo.bar; // incorrect erro:r Property 'foo' does not exist on type '{ [key: string]: {}; }'.
        }
        return value.baz;         // correct error:  Property 'baz' does not exist on type '{ [key: string]: {}; }'
    } else {
        return value.baz;  // knows its {}
    }
};

Expected behavior:
after performing in tests, access to the property tested for should not be an error, we know it is there.

Actual behavior:
error when trying to use the property guarded by the in test.
Property 'foo' does not exist on type '{ [key: string]: {}; }'.

@Jessidhia
Copy link

The error seems confusing, but I'm not sure the in check should cause a refinement there.

The property 'foo' most certainly should exist in { [key: string]: {} } because any string key exists in an indexed type... 😕

@mhegazy mhegazy added the Bug A bug in TypeScript label Jan 9, 2018
@sandersn
Copy link
Member

The true branch behaves correctly, as @Kovensky points out. Both types could have a member named "foo", and the second error follows on from the first.

I thought the false branch should actually be never, but after looking at the existing test cases, I see that it shouldn't; "prop" in stringIndexed ? stringIndexed["prop"] : stringIndexed["other"] asserts that "prop" exists in the true branch, but doesn't exclude the possibility that it might have some "other" property in the false case.

@sandersn sandersn added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Bug A bug in TypeScript labels Jan 10, 2018
@Jessidhia
Copy link

@sandersn I still think the error message itself is incorrect. { [key: string]: {} } is an indexed type so any string key should be treated as existing in it; the "bar" in value.foo should error saying that bar doesn't exist in {} (depending on how the types are merged).

value.baz also should not be an error; it should be true | {}.

@sandersn
Copy link
Member

@Kovensky that's a separate issue, although one that doesn't seem to be reported yet. You don't need to narrow to encounter it:

type U = { foo: { bar: true }, baz: true } | { [key: string]: {} };
declare var u: U;
u.foo
u.baz

Both foo and baz should be present, but neither are. Probably we should have another issue for this suggestion, though.

@sandersn
Copy link
Member

@Kovensky I opened #21141 to track your issue.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants