Skip to content

Conversation

@asukaminato0721
Copy link
Contributor

fix #1254

Added a suppression stack so binding code can execute unreachable branches without emitting type errors and exposed a helper to gate this behavior to filesystem/memory modules only, skipping builtins/__builtins__.

Updated the if binding logic to analyze statically false branches only when the new gate allows it, then discard their flow so control-flow facts stay unchanged while IDE features (like go-to-def) gain the needed Keys.

@meta-cla meta-cla bot added the cla signed label Nov 7, 2025
Comment on lines 1421 to 1828
/// Synthesize a static definition entry for `name` in the current scope if it
/// is missing. This is used when we deliberately analyze unreachable code for
/// IDE metadata; those code paths may not have been included in the up-front
/// static scan, so we add a lightweight placeholder on demand.
pub fn add_synthetic_definition(&mut self, name: &Name, range: TextRange) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels a bit complicated to me. I wonder if a better alternative would be to just keep all defs in the very beginning when they are calculated, recording their unreachable status so downstream type checking logic would avoid emitting type errors for those instead.

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

fmt
fmt

clippy

fix
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 issue #1254 where go-to-definition failed in unreachable code branches (e.g., if False:). The solution introduces a Reachability tracking system and error suppression mechanism to allow binding analysis of unreachable branches for IDE features while preventing type errors from being emitted.

Key changes:

  • Added Reachability enum to track whether definitions are reachable based on static analysis
  • Introduced error suppression stack to prevent type errors in unreachable branches
  • Modified if-statement binding logic to analyze statically-false branches with error suppression
  • Gated unreachable branch analysis to filesystem/memory modules only, excluding builtins

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyrefly/lib/export/definitions.rs Added Reachability enum and integrated it into Definition struct; updated if-statement analysis to track reachability
pyrefly/lib/export/exports.rs Updated Definitions::new call to pass false for include_unreachable parameter
pyrefly/lib/binding/scope.rs Integrated Reachability into StaticInfo and NameWriteInfo; added add_synthetic_definition method; modified scope initialization to accept include_unreachable_defs parameter
pyrefly/lib/binding/bindings.rs Added error suppression mechanism via error_suppression_depth; added should_bind_unreachable_branches helper; modified bind_name to handle synthetic definitions for unreachable code
pyrefly/lib/binding/stmt.rs Updated if-statement handling to bind names in unreachable branches with error suppression while excluding them from flow merges
pyrefly/lib/test/lsp/hover_type.rs Updated test expectations to show that hover now works in unreachable branches
pyrefly/lib/test/lsp/definition.rs Updated test expectations to show that go-to-definition now works in unreachable branches

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Hashed::new(name.clone()),
range,
StaticStyle::SingleDef(None),
Reachability::Reachable,
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

Synthetic definitions created for unreachable branches should be marked with Reachability::Unreachable instead of Reachability::Reachable. This function is specifically called when analyzing unreachable code for IDE metadata (as documented in the comment above), so these definitions should reflect their unreachable status.

Suggested change
Reachability::Reachable,
Reachability::Unreachable,

Copilot uses AI. Check for mistakes.
Comment on lines +885 to +886
let defs = calculate_unranged_definitions_with_unreachable(contents);
assert!(defs.definitions.get(&Name::new("foo")).is_some());
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

This test should verify the reachability of foo is Unreachable to be consistent with the test above (test_unreachable_if_defs_respected), which checks both that the definition exists and validates its reachability status.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +54
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Reachability {
#[default]
Reachable,
Unreachable,
}
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The Reachability enum would benefit from a doc comment explaining what it represents and when code is considered unreachable (e.g., when it's in a statically-false branch like if False:).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Go-to-def fails on unreachable branches

3 participants