microsoft / microsoft/TypeScript

Assignment narrowing failing to reset in a loop with optional chaining

Open
#57,816 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Domain: check: Control Flow Help Wanted
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔎 Search Terms

assignment narrowing, optional chaining, narrowing to never,

🕗 Version & Regression Information
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about assignment narrowing and optional chaining.
⏯ 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).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked TypeScript Playground reproduction and inspect the assignment-narrowing and optional-chaining behavior shown in the loop. Trace why prevPrevFoo is treated as null after the loop assignment. Done means the reproduction compiles without the never-property error while preserving the expected Foo | null type inside the loop.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.