TypeScript Version: 3.2.0-dev.20181023
Search Terms:
type guard square bracket notation narrowing
Code
interface ConfigType {
[key: string]: boolean | { prop: string };
}
const config: ConfigType = {
works: { prop: 'foo' },
'works-not': { prop: 'bar' },
bool: true,
};
if (typeof config.works !== 'boolean') {
config.works.prop = 'test';
}
if (typeof config['works-not'] !== 'boolean') {
config['works-not'].prop = 'test'; // [ts] Property 'prop' does not exist on type 'false'.
}
Expected behavior:
The type guard using square bracket notation narrows the type in the same way the type guard using dot notation does, resulting in no type errors in the above example.
Actual behavior:
The type guard specifically using square bracket notation does not narrow the type.
Playground Link:
Link
Related Issues:
https://github.com/Microsoft/TypeScript/issues/10530
Is this a duplicate of #10530? @andy-ms @weswigham
No, this is not a duplicate. It's a missed interaction between indexed access and index signatures, where property access + index signatures works.
Here's a smaller repro, without the dashed-name red herring:
interface ConfigType {
[key: string]: boolean | { prop: string };
}
const config: ConfigType = {
works: { prop: 'foo' }
};
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}
The behaviour is the same if the discriminator is typeof config['works'] !== 'boolean', so I think it's just a failure of control flow from indexed access in this case.
Also note that if we have interface ConfigType { works: boolean | { prop: string } } then both statements work.
Just a drive by comment. We are working in Google internal TS to forbid using the prop access on index signature objects. The main reason is that to Closure prop access means safe to rename and index access means don't touch. This optimization pass is done without global knowledge (other than whats' coming externally) so its up to the developer to decide whether it is really "safe" to rename.
The transformation from foo.bar to foo['bar'] is mostly noop TS-wise, except for this issue.
Turns out this was just missing. It is super simple to add since it's the same code as property access. I just need to make sure performance is OK.
Update: The PR is trivial and performance is, as expected, bad for probably-uncommon adversarial code. We need to discuss in the design meeting whether to take this change.
Most helpful comment
Just a drive by comment. We are working in Google internal TS to forbid using the prop access on index signature objects. The main reason is that to Closure prop access means safe to rename and index access means don't touch. This optimization pass is done without global knowledge (other than whats' coming externally) so its up to the developer to decide whether it is really "safe" to rename.
The transformation from
foo.bartofoo['bar']is mostly noop TS-wise, except for this issue.