microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.08k stars 12.37k forks source link

Assignment narrowing failing to reset in a loop with optional chaining #57816

Open jcalz opened 5 months ago

jcalz commented 5 months ago

πŸ”Ž Search Terms

assignment narrowing, optional chaining, narrowing to never,

πŸ•— Version & Regression Information

⏯ Playground Link

Playground link

πŸ’» Code

interface Foo { bar: number; }
declare const foos: Foo[];
let prevPrevFoo: Foo | null = null;
let prevFoo: Foo | null = null;
for (const foo of foos) {
  while (
    foo.bar === prevPrevFoo?.bar && // error!
    // --------------------> ~~~
    // Property 'bar' does not exist on type 'never'.
    foo.bar === prevFoo?.bar
  ) {
    foo.bar++
  }
  prevPrevFoo = prevFoo;
  prevFoo = foo;
}

πŸ™ Actual behavior

prevPrevFoo is apparently narrowed to null via assignment narrowing, so prevPrevFoo?.bar gives an error that never doesn't have a bar property. But prevPrevFoo is assigned within the loop, so it seems strange that the narrowing persists in that scope.

πŸ™‚ Expected behavior

No error, prevPrevFoo should be considered Foo | null inside the loop.

Additional information about the issue

Comes from this SO question. Not sure what's going on here, but it seems quite dependent on the nesting and order of operations. Most things I did to try to reduce it further made the error disappear.

Note that this is not a case of #9998, there's no closure to speak of. And of course we can work around this by opting out of assignment narrowing via let prevPrevFoo = null as Foo | null;. I don't think this is a big deal, but it would be nice to know the cause of this and whether it's a bug, design limitation, or somehow working as intended (and if so, why).

Andarist commented 5 months ago

I think it is just a bug in the control flow analysis. The current graph for prevPrevFoo in prevPrevFoo?.bar can be seen here

control flow graph of prevPrevFoo at the faulty location
jcalz commented 5 months ago

@RyanCavanaugh so this is not considered a bug, but a missing feature, right?

RyanCavanaugh commented 5 months ago

Eh, bug is probably more accurate