Why doesn't TypeScript narrow based on checks using a variable key?

59 views Asked by At

If I have code roughly like this:

const obj: Record<string, SomeType>;
if ("key" in obj) {
  const val: SomeType = obj["key"];
  void val;
}

the TypeScript compiler doesn't give me any grief about the type of val.

Likewise if I write code like:

const val = obj["key"];
if (val) {
  const val2: SomeType = val;
  void val2;
}

the TypeScript compiler knows that val/val2 can't be | undefined inside the check.

But if I write code like:

let k = "key";
if (obj[k]) {
  const val: SomeType = obj[k];
  void val;
}

then TypeScript complains that val: Sometype could still be | undefined.

Even if I use a const in a situation somewhat similar to the last it stays unhappy:

(["a", "b", "c"]).forEach(_k => {
  const k = _k;
  if (obj[k]) {
    const val: SomeType = obj[k];
    //         ^^^ VSCode still thinks it could be `| undefined`!
  }
});

Why is this?

1

There are 1 answers

5
Linden Wells On

I'm not that good at reasoning with types, but in your third block, changing k to a const makes the types work:

const k = "key";
if (obj[k]) {
  const val: string = obj[k];
  void val;
}

And this doesn't fix your issue, but you can assert that an array is const inline. The fourth code block I can only make typecheck by adding an intermediate step:

(["a", "b", "c"] as const).forEach(k => {
    const val = obj[k] as const
    if (val) {
      const narrowed_val: string = val;
    }
  });

Hope this helps you get on with whatever you're doing, but hopefully someone smarter with typescript reasoning can explain exactly what's going on.

n.b. I changed your SomeType to string to make it easier to work with in vscode.