Typescript: type narrowing not working for `in` when key is stored in a variable

This is essentially the same issue as microsoft/TypeScript#10530; type narrowing from control flow analysis only happens for properties that are directly literals like "b" and not for arbitrary expressions whose types are literal types. Issue #10530 talks about narrowing via property access… like a.b or a["b"], which does cause a to be narrowed, vs a[key], which does not.

As you’ve noticed, this also happens with the in operator type guard (as implemented in microsoft/TypeScript#15256), where "b" in a narrows the type of a, but key in a does not. This is not explicitly mentioned in #10530 (which pre-dates the in type guard) but I don’t think there’s another existing issue specifically about this.

According to microsoft/TypeScript#10565, an initial attempt to address the issue in #10530, adding type guard functionality for arbitrary expressions of literal types significantly worsens the compiler performance. Maybe performing extra analysis for all x in y checks would be less expensive than performing extra analysis for all y[x] property accesses, but at least so far nobody has cared much.

You could always open your own issue about it in GitHub (many issues end up being duplicates, and I’m not 100% sure this wouldn’t just be considered a duplicate of #10530, or that there isn’t some other issue this duplicates), but practically speaking it’s probably not going to change anytime soon.


If you want a workaround for the case where you can’t just replace key with a string literal, you could write your own user-defined type guard function called hasProp(obj, prop). The implementation would just return prop in obj, but its type signature explicitly says that a true result should cause obj to be narrowed to just those union members with a key of type prop:

function hasProp<T extends object, K extends PropertyKey>(
    obj: T, prop: K
): obj is Extract<T, { [P in K]?: any }> {
    return prop in obj;
}

and then in your function, replace key in a with hasProp(a, key):

function f3(a: A) {
    const key = 'b';
    if (hasProp(a, key)) {
        return a[key];  // okay
    }
    return 42;
}

Playground link to code

Leave a Comment