π Search Terms
type guard, narrowing, control flow analysis, property, variable index, bracket notation
β
Viability Checklist
β Suggestion
This is a re-opening or re-focusing of #10530. The original motivating example (narrowing obj via checking obj["key"] the same way as obj.key when "key" is the discriminant property) was fixed a long time ago, but the issue stayed open and started collecting requests for a more general feature of narrowing object properties by bracket access. Other issues about the general feature were closed as duplicates. But now #10530 is closed, and any unmet needs from there should be moved to a new issue. Like this one, maybe:
Please enable narrowing of object properties accessed via bracket notation based on the identity of the key, not just its type. If guard() is a type guard, then if guard(obj[key]) { β― obj[key] β― } should serve to narrow obj[key] inside the block, no matter what the type of key is.
π Motivating Example
Currently narrowing obj[key] only works if key is actually a string literal or a const of a single string literal type:
namespace Good {
declare const obj: { [key: string]: string | undefined };
if (obj["a"]) { obj["a"].toUpperCase() }; // okay
declare const key: "a";
if (obj[key]) { obj[key].toUpperCase() }; // okay
}
But superficially similar constructions do not work, where key is not const, and where the type is a wide type like string, or a union type like "a" | "b", or a generic type like K extends string:
namespace Problem1 {
declare const obj: { [key: string]: string | undefined };
declare let key: "a";
if (obj[key]) { obj[key].toUpperCase() } // error! possibly undefined
}
namespace Problem2 {
declare const obj: { [key: string]: string | undefined };
declare const key: string;
if (obj[key]) { obj[key].toUpperCase() } // error! possibly undefined
}
namespace Problem3 {
declare const obj: { a?: string, b?: string };
declare const key: "a" | "b";
if (obj[key]) { obj[key].toUpperCase() } // error! possibly undefined
}
namespace Problem4 {
function f<K extends string>(obj: { [P in K]?: string }, k: K) {
const key: K = k;
if (obj[key]) { obj[key].toUpperCase() } // error! possibly undefined
}
}
It would be great if all of those would "just work" (although I can understand why it's not trivial to do so, or that one can imagine some cases where it would be a bad idea to allow it).
π» Use Cases
See #10530 and various issues closed as duplicates for more use cases.
The standard workaround is to assign the property to a new const and do the guarding on that:
namespace Workaround {
declare const obj: { [key: string]: string | undefined };
declare let key: "a";
const ok = obj[key];
if (ok) { ok.toUpperCase() } // okay
}
This works well, although people on Stack Overflow often express dissatisfaction with that suggestion (e.g., "why should I have to create a new variable just to appease TypeScript?").
Unfortunately some folks also expect to be able to assign something to the narrowed property, and there's just no great way to do that without type assertions:
namespace Ugh {
function f(
obj: { a: string, b: number, c: string, d: number, e: string, f: number },
key: keyof typeof obj
) {
if (typeof obj[key] === "string") {
obj[key] = obj[key].toUpperCase() // error!
} else {
obj[key] = obj[key] + 1; // error!
}
}
}
π Search Terms
type guard, narrowing, control flow analysis, property, variable index, bracket notation
β Viability Checklist
β Suggestion
This is a re-opening or re-focusing of #10530. The original motivating example (narrowing
objvia checkingobj["key"]the same way asobj.keywhen"key"is the discriminant property) was fixed a long time ago, but the issue stayed open and started collecting requests for a more general feature of narrowing object properties by bracket access. Other issues about the general feature were closed as duplicates. But now #10530 is closed, and any unmet needs from there should be moved to a new issue. Like this one, maybe:Please enable narrowing of object properties accessed via bracket notation based on the identity of the key, not just its type. If
guard()is a type guard, thenif guard(obj[key]) { β― obj[key] β― }should serve to narrowobj[key]inside the block, no matter what the type ofkeyis.π Motivating Example
Currently narrowing
obj[key]only works ifkeyis actually a string literal or aconstof a single string literal type:But superficially similar constructions do not work, where
keyis notconst, and where the type is a wide type likestring, or a union type like"a" | "b", or a generic type likeK extends string:It would be great if all of those would "just work" (although I can understand why it's not trivial to do so, or that one can imagine some cases where it would be a bad idea to allow it).
π» Use Cases
See #10530 and various issues closed as duplicates for more use cases.
The standard workaround is to assign the property to a new
constand do the guarding on that:This works well, although people on Stack Overflow often express dissatisfaction with that suggestion (e.g., "why should I have to create a new variable just to appease TypeScript?").
Unfortunately some folks also expect to be able to assign something to the narrowed property, and there's just no great way to do that without type assertions: