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

Type guards not working for indexed types with generics #22214

Open
TPReal opened this issue Feb 27, 2018 · 6 comments
Open

Type guards not working for indexed types with generics #22214

TPReal opened this issue Feb 27, 2018 · 6 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@TPReal
Copy link

TPReal commented Feb 27, 2018

TypeScript Version: 2.7

Search Terms: indexed type guard

Code

function f<T extends string>(q: {[k:string]: number | string}, t: T) {
    const v= q[t];
    if (typeof(v)!=="string") {
        const vv: number = v;
    }
}

Expected behavior:
Type of v is string|number, and in the if it is just number. Compiles.

Actual behavior:
Assignment to vv doesn't compile: string not assignable to number. When hovering, type of v is { [k: string]: string | number; }[T], so eliminating string from it does not change the type, it is still the same, and only when assigning to vv it gets resolved to string|number which then causes compilation error.

Changing type of t to string fixes the problem. The problem also appears if q has type keyed by [k in T].

Playground Link: link

Related Issues:

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Feb 27, 2018
@RyanCavanaugh
Copy link
Member

Note that if (typeof v === "number") { works, and should probably be preferred since you really can't be sure undefined isn't in there.

@TPReal
Copy link
Author

TPReal commented Feb 28, 2018

This code is just for illustration of the compiler problem, and not any good otherwise of course. A snippet where the problem is even more obvious, without such an obvious fix:

function f<T extends string>(q: {[k:string]: number | null}, t: T) {
    const v= q[t];
    if (v) {
        const vv: number = v;
    }
}

Note that this fails only with strict null checks enabled.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

Narrowing with primitives really does not work with generics.. here is a simpler example:

function f<T extends string | number>(t: T) {
    if (typeof t !== "string") {
        const v: number = t; // Error
    }
}

The issue here is that T has to stay generic. any narrowing we do has to be on the type of the constraint, so in if block t should be T extends number instead of T extends number | string.

@mhegazy mhegazy added Suggestion An idea for TypeScript In Discussion Not yet reached consensus and removed Bug A bug in TypeScript labels Mar 2, 2018
@weswigham
Copy link
Member

weswigham commented Mar 2, 2018

However we could totally improve our behavior and narrow to Exclude<T, string> (and Extract<T, string> in the other branch) now that we have it, which will handle the constraint behaviors while retaining the T relationships 😄

@TPReal
Copy link
Author

TPReal commented Mar 2, 2018

The problem is that this is a regression - my original example, or something equivalent to it, used to work in some earlier version (I'm not sure which exactly) because q[t] was immediately treated as string | number, at least in the case when the key was declared as [k in T]. So I'm quite sure this is a bug, not just a suggestion.

I think that this is why @mhegazy's example is different than mine, where the type does not really depend on T.

@mhegazy mhegazy added Bug A bug in TypeScript and removed In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Mar 2, 2018
@mhegazy mhegazy added this to the TypeScript 2.8 milestone Mar 2, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Mar 2, 2018

u are correct. q[t] is not generic really, since q has an index signature, so regardless of what value t will have the type of q[t] is always 0-order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants