Gernot Raudner
Gernot Raudner

Reputation: 824

Type narrowing breaks when accessing an object property with bracket notation

Why do tests 1 and 2 work here, but test 3 shows a compiler error at foo[barConst]++: 'Object is possibly "undefined".'? I often need to access properties via bracket notation and thus like to have constants for these properties, but TypeScript doesn't allow this. It also doesn't work with const enums. Is it a bug or is there a good reason for the error?

const barConst = 'bar';

interface Foo {
    [barConst]?: number;
}

function test1(foo?: Foo) {
    if (foo && foo.bar) {
        foo.bar++;
    }
}

function test2(foo?: Foo) {
    if (foo && foo['bar']) {
        foo['bar']++;
    }
}

function test3(foo?: Foo) {
    if (foo && foo[barConst]) {
        foo[barConst]++; // compiler error: 'Object is possibly "undefined".'
    }
}

Playground

Upvotes: 6

Views: 2016

Answers (3)

0Valt
0Valt

Reputation: 10345

With the release of TypeScript 4.7 due to its improvements to the control flow analysis for bracketed element access, the original code no longer results in a compiler error because the foo && foo[barConst] guard now correctly narrows the foo[barConst] access to be of type number.

See PR #40617 for more details on the CFA update overall.

4.7 Playground

Upvotes: 3

ford04
ford04

Reputation: 74500

Narrowing the property access via computed propertyNames/literal expressions seems to be not possible currently. Have a look at this issue and its PR, also that issue.

You can narrow property access in bracket notation with string literals like for example foo["bar"]. Dynamic expressions like foo[barConst] don't work. Assigning foo[barConst] to a variable and working/narrowing down this variable instead is an alternative, but costs an additional declaration.

In your case the simplest solution would be to just cast the expression with non-null assertion operator !. As you do a pre-check for a falsy value, you are safe here:

function test3(foo?: Foo) {
    if (foo && foo[barConst]) {
        foo[barConst]!++; 
    }
}

Upvotes: 2

m.akbari
m.akbari

Reputation: 592

Continue of comment discussions. (I still do not know what are you looking for) you can try something like this:

type barConst = 'bar';

type Foo<K extends string> = Record<K, number>;

function test3(foo?: Foo<barConst>) {
  if (typeof foo !== 'undefined') {
    foo['bar']++;
  }
}
let x: Foo<barConst> = { bar: 2 };

test3(x);
console.log(x);

You can have autocomplete and no compiler error. Still, please Update your question.

Upvotes: 0

Related Questions