microsoft/TypeScript

`in` Operator Narrowing is Too Limited

DanielRosenwasser opened this issue · 3 comments

Playground Link

interface RGB {
    red: number;
    green: number;
    blue: number;
}

function setColor(value: unknown) {
    const isRGB =
        // is an object
        value && typeof value === "object" &&
        // 'red' exists and is a 'number'
        "red" in value && typeof value.red === "number" &&
        // 'green' exists and is a 'number'
        "green" in value && typeof value.green === "number" &&
        // 'blue' exists and is a 'number'
        "blue" in value && typeof value.blue === "number";

    if (isRGB) {
        const rgb: RGB = value;
    }
}

Expected: No errors
Actual Errors.

Type 'object & Record<"red", unknown> & Record<"green", unknown> & Record<"blue", unknown>' is not assignable to type 'RGB'.
  Types of property 'red' are incompatible.
    Type 'unknown' is not assignable to type 'number'.

This is basically a duplicate of #42384 (which in turn is a duplicate of a much older issue for the same scenario, but I can't locate that right now). You'd see the same problem if you started out with a { red: unknown, green: unknown, blue: unknown } object and applied just the typeof checks.

EDIT: The oldest related issue I can locate right now is #29827, but no doubt there are more.

It's interesting, though...

We could consider having a typeof value.red === "number" narrow the type of value by intersecting with Record<"red", number>, and we could do similar narrowing for other types of tests:

type Point = { x: number, y: number };

declare function doWithPoint(p: Point): void;

function test(p: Partial<Point>) {
    if (p.x && p.y) {
        doWithPoint(p); // Narrowed to Partial<Point> & Record<"x", number> & Record<"y", number>
    }
}

One concern would be the noise created by all the intersections, but it does seem like a workable idea.

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.