Implement goto-definition and find-references for global/nonlocal statements (#21616)

## Summary

The implementation here is to just record the idents of these statements
in `scopes_by_expression` (which already supported idents but only ones
that happened to appear in expressions), so that `definitions_for_name`
Just Works.

goto-type (and therefore hover) notably does not work on these
statements because the typechecker does not record info for them. I am
tempted to just introduce `type_for_name` which runs
`definitions_for_name` to find other expressions and queries the
inferred type... but that's a bit whack because it won't be the computed
type at the right point in the code. It probably wouldn't be
particularly expensive to just compute/record the type at those nodes,
as if they were a load, because global/nonlocal is so scarce?

## Test Plan

Snapshot tests added/re-enabled.
This commit is contained in:
Aria Desires
2025-11-25 08:56:57 -05:00
committed by GitHub
parent 88bfc32dfc
commit 209ea06592
7 changed files with 342 additions and 34 deletions

View File

@@ -2255,6 +2255,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
names,
}) => {
for name in names {
self.scopes_by_expression
.record_expression(name, self.current_scope());
let symbol_id = self.add_symbol(name.id.clone());
let symbol = self.current_place_table().symbol(symbol_id);
// Check whether the variable has already been accessed in this scope.
@@ -2290,6 +2292,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
names,
}) => {
for name in names {
self.scopes_by_expression
.record_expression(name, self.current_scope());
let symbol_id = self.add_symbol(name.id.clone());
let symbol = self.current_place_table().symbol(symbol_id);
// Check whether the variable has already been accessed in this scope.

View File

@@ -22,7 +22,7 @@ use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
use ruff_db::files::FileRange;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast};
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
@@ -515,7 +515,7 @@ pub fn definition_for_name<'db>(
model: &SemanticModel<'db>,
name: &ast::ExprName,
) -> Option<Definition<'db>> {
let definitions = definitions_for_name(model, name);
let definitions = definitions_for_name(model, name.id.as_str(), name.into());
// Find the first valid definition and return its kind
for declaration in definitions {
@@ -531,15 +531,15 @@ pub fn definition_for_name<'db>(
/// are resolved (recursively) to the original definitions or module files.
pub fn definitions_for_name<'db>(
model: &SemanticModel<'db>,
name: &ast::ExprName,
name_str: &str,
node: AnyNodeRef<'_>,
) -> Vec<ResolvedDefinition<'db>> {
let db = model.db();
let file = model.file();
let index = semantic_index(db, file);
let name_str = name.id.as_str();
// Get the scope for this name expression
let Some(file_scope) = model.scope(name.into()) else {
let Some(file_scope) = model.scope(node) else {
return vec![];
};
@@ -648,7 +648,8 @@ pub fn definitions_for_name<'db>(
//
// https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
if matches!(name_str, "float" | "complex")
&& let Some(union) = name.inferred_type(&SemanticModel::new(db, file)).as_union()
&& let Some(expr) = node.expr_name()
&& let Some(union) = expr.inferred_type(&SemanticModel::new(db, file)).as_union()
&& is_float_or_complex_annotation(db, union, name_str)
{
return union