`in` Operator Narrowing is Too Limited
DanielRosenwasser opened this issue · 3 comments
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.