Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 19, 2025

Fixes incorrect unreachable code detection that persists after editing code in incremental compilation scenarios.

Problem

When code is edited to remove a statement that made subsequent code unreachable (e.g., a throw statement), the unreachable code error persists incorrectly. This reproduces the issue reported in microsoft/vscode#278270.

Root Cause

In incremental scenarios where ASTs are reused, the binder was not clearing the NodeFlags.Unreachable flag from previous binding passes. When re-binding a file after edits, statement nodes retained the Unreachable flag even though the code that made them unreachable had been removed.

Solution

Added code in src/compiler/binder.ts to clear the Unreachable flag in the bindChildren function before determining if the node is currently unreachable. This ensures that the flag is reset for each binding pass, preventing stale flags from persisting.

Why Labels Don't Need Special Handling

Through debugging, I discovered that label identifier nodes are recreated fresh by the parser in incremental scenarios (always have flags=0 before binding), whereas statement nodes like classes are reused with their existing flags. This is why the unused label test passes without needing additional handling - label nodes don't exhibit the same bug.

Changes

  • src/compiler/binder.ts: Added 5 lines to clear NodeFlags.Unreachable in bindChildren before reachability check
  • tests/cases/fourslash/unreachableCodeAfterEdit.ts: Test verifying unreachable code errors clear after removing throw statement
  • tests/cases/fourslash/unusedLabelAfterEdit.ts: Test verifying unused label detection works correctly after edits (validates labels don't have the same bug)

Testing

  • There is an associated issue in the Backlog milestone
  • Code is up-to-date with the main branch
  • Successfully ran hereby runtests-parallel locally (99,000 tests passing)
  • New tests validate the fix

Test Results

✅ Unreachable code test passes
✅ Unused label test passes
✅ Existing unreachable code test passes
✅ Label-related tests pass (60 tests)
✅ Lint passes
✅ Format passes

Fixes #62782

Original prompt

This section details on the original issue you should resolve

<issue_title>Incorrect unreachable code reported in latest nightly build</issue_title>
<issue_description>From microsoft/vscode#278270

🔎 Search Terms

  • Unreachable

🕗 Version & Regression Information

  • This changed between versions 6.0.0-dev.20251110 and 6.0.0-dev.20251117
  • Likely 09e31cf

Repo

  1. In the VS Code repo, open src/vs/code/browser/workbench/workbench.ts
  2. add throw new Error("foo"); to the top of import block
  3. Delete it again

Bug

Unreachable code still reported

Image

Here's the full server log: tsserver.log
</issue_description>

<agent_instructions>Create a fourslash test which replicates this issue. You will need to set the option to disallow unreachable code. The fourslash test should check for no errors, the make the edit, check for some errors again, then remove the throw and then assert no errors again. Ensure you have a FAILING test, then commit that, then attempt to fix the issue.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Nov 19, 2025
Copilot AI and others added 4 commits November 19, 2025 07:07
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
…nder

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unreachable code reported in latest nightly build Fix unreachable code detection persisting after incremental edits Nov 19, 2025
Copilot AI requested a review from jakebailey November 19, 2025 07:42
Copilot finished work on behalf of jakebailey November 19, 2025 07:42
@jakebailey jakebailey marked this pull request as ready for review November 19, 2025 07:44
Copilot AI review requested due to automatic review settings November 19, 2025 07:44
Copilot finished reviewing on behalf of jakebailey November 19, 2025 07:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug where unreachable code errors persist after removing the statement that caused unreachability in incremental compilation scenarios. When a throw statement is added and then deleted, subsequent code remains incorrectly flagged as unreachable despite being reachable again.

  • Clears stale NodeFlags.Unreachable flags during re-binding in incremental scenarios
  • Adds a fourslash test demonstrating the add/remove/verify workflow

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/cases/fourslash/unreachableCodeAfterEdit.ts New test that verifies unreachable code errors clear after removing throw statement
src/compiler/binder.ts Clears NodeFlags.Unreachable before reachability analysis to prevent stale state

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI requested a review from jakebailey November 19, 2025 07:53
Copilot finished work on behalf of jakebailey November 19, 2025 07:53
@jakebailey
Copy link
Member

@copilot also do this for labels

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot also do this for labels

Applied the same fix for labels in 6bb33c1. The Unreachable flag is now cleared before being set in bindLabeledStatement, and also cleared when a label becomes referenced.

Copilot finished work on behalf of jakebailey November 19, 2025 08:32
@jakebailey
Copy link
Member

@copilot There wont be an existing test. Undo your label fix, then make a new fourslash test which uses a label, then makes it unused and have an error, then restores the use and asserts no errors. You need to disallow unused labels. Make that failing test, commit it, then fix the bug. If it turns out it already works fine, commit the passing test anyway, then undo the main fix in the PR to make sure both tests fail

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot There wont be an existing test. Undo your label fix, then make a new fourslash test which uses a label, then makes it unused and have an error, then restores the use and asserts no errors. Yo...

Created the label test in ff3e6fd and investigated why it passes without needing a fix.

Debug output revealed that label identifier nodes always have flags=0 before binding (even after we set the Unreachable flag), while maintaining the same position (pos=0, end=7). This indicates the parser recreates label identifier nodes fresh in incremental scenarios rather than reusing them.

In contrast, statement nodes like class declarations ARE reused, which is why they need the flag cleared in bindChildren. The fix in bindChildren is sufficient for both cases - it handles reused statement nodes, and label nodes don't need special handling since they're recreated fresh.

Test committed in ff3e6fd, fix restored in f3981b2.

Copilot finished work on behalf of jakebailey November 19, 2025 08:51
@jakebailey
Copy link
Member

jakebailey commented Nov 19, 2025

The more reasonable answer for why this doesn't affect labels is that incremental parsing transfers statements, but if you have:

myLabel: while (true) {
    if (Math.random() > 0.5) {
        /*marker*/break myLabel;
    }
}

Even at the top level, modifying any part of this replaces the whole statement. And since label uses must necessarily be inside the labeled statement (no goto), any other situation is impossible.

But, for the throw new Error() case at the top level, that will reuse the other nodes in the source file, hence the bug.

@jakebailey jakebailey merged commit 486fce7 into main Nov 19, 2025
33 checks passed
@jakebailey jakebailey deleted the copilot/fix-unreachable-code-report branch November 19, 2025 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect unreachable code reported in latest nightly build

4 participants