Skip to content

Commit 360eb70

Browse files
UnboundVariableUnboundVariable
andauthored
[ty] Added support for "go to definition" for attribute accesses and keyword arguments (astral-sh#19417)
This PR builds upon astral-sh#19371. It addresses a few additional code review suggestions and adds support for attribute accesses (expressions of the form `x.y`) and keyword arguments within call expressions. --------- Co-authored-by: UnboundVariable <[email protected]>
1 parent 630c7a3 commit 360eb70

File tree

5 files changed

+603
-64
lines changed

5 files changed

+603
-64
lines changed

crates/ty_ide/src/goto.rs

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ pub use crate::goto_type_definition::goto_type_definition;
44

55
use crate::find_node::covering_node;
66
use crate::stub_mapping::StubMapper;
7-
use ruff_db::parsed::ParsedModuleRef;
7+
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
88
use ruff_python_ast::{self as ast, AnyNodeRef};
99
use ruff_python_parser::TokenKind;
1010
use ruff_text_size::{Ranged, TextRange, TextSize};
1111
use ty_python_semantic::types::Type;
12-
use ty_python_semantic::{HasType, SemanticModel};
12+
use ty_python_semantic::types::definitions_for_keyword_argument;
13+
use ty_python_semantic::{HasType, SemanticModel, definitions_for_name};
1314

1415
#[derive(Clone, Copy, Debug)]
1516
pub(crate) enum GotoTarget<'a> {
@@ -150,15 +151,19 @@ impl GotoTarget<'_> {
150151
use ruff_python_ast as ast;
151152

152153
match self {
153-
// For names, find the definitions of the symbol
154-
GotoTarget::Expression(expression) => {
155-
if let ast::ExprRef::Name(name) = expression {
156-
Self::get_name_definition_targets(name, file, db, stub_mapper)
157-
} else {
158-
// For other expressions, we can't find definitions
159-
None
160-
}
161-
}
154+
GotoTarget::Expression(expression) => match expression {
155+
ast::ExprRef::Name(name) => definitions_to_navigation_targets(
156+
db,
157+
stub_mapper,
158+
definitions_for_name(db, file, name),
159+
),
160+
ast::ExprRef::Attribute(attribute) => definitions_to_navigation_targets(
161+
db,
162+
stub_mapper,
163+
ty_python_semantic::definitions_for_attribute(db, file, attribute),
164+
),
165+
_ => None,
166+
},
162167

163168
// For already-defined symbols, they are their own definitions
164169
GotoTarget::FunctionDef(function) => {
@@ -195,40 +200,30 @@ impl GotoTarget<'_> {
195200
None
196201
}
197202

198-
// TODO: Handle attribute and method accesses (y in `x.y` expressions)
199-
// TODO: Handle keyword arguments in call expression
200-
// TODO: Handle multi-part module names in import statements
201-
// TODO: Handle imported symbol in y in `from x import y as z` statement
202-
// TODO: Handle string literals that map to TypedDict fields
203-
_ => None,
204-
}
205-
}
203+
// Handle keyword arguments in call expressions
204+
GotoTarget::KeywordArgument(keyword) => {
205+
// Find the call expression that contains this keyword
206+
let module = parsed_module(db, file).load(db);
206207

207-
/// Get navigation targets for definitions associated with a name expression
208-
fn get_name_definition_targets(
209-
name: &ruff_python_ast::ExprName,
210-
file: ruff_db::files::File,
211-
db: &dyn crate::Db,
212-
stub_mapper: Option<&StubMapper>,
213-
) -> Option<crate::NavigationTargets> {
214-
use ty_python_semantic::definitions_for_name;
208+
// Use the keyword's range to find the containing call expression
209+
let covering_node = covering_node(module.syntax().into(), keyword.range())
210+
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
211+
.ok()?;
215212

216-
// Get all definitions for this name
217-
let mut definitions = definitions_for_name(db, file, name);
213+
if let AnyNodeRef::ExprCall(call_expr) = covering_node.node() {
214+
let definitions =
215+
definitions_for_keyword_argument(db, file, keyword, call_expr);
216+
return definitions_to_navigation_targets(db, stub_mapper, definitions);
217+
}
218218

219-
// Apply stub mapping if a mapper is provided
220-
if let Some(mapper) = stub_mapper {
221-
definitions = mapper.map_definitions(definitions);
222-
}
219+
None
220+
}
223221

224-
if definitions.is_empty() {
225-
return None;
222+
// TODO: Handle multi-part module names in import statements
223+
// TODO: Handle imported symbol in y in `from x import y as z` statement
224+
// TODO: Handle string literals that map to TypedDict fields
225+
_ => None,
226226
}
227-
228-
// Convert definitions to navigation targets
229-
let targets = convert_resolved_definitions_to_targets(db, definitions);
230-
231-
Some(crate::NavigationTargets::unique(targets))
232227
}
233228
}
234229

@@ -279,18 +274,35 @@ fn convert_resolved_definitions_to_targets(
279274
full_range: full_range.range(),
280275
}
281276
}
282-
ty_python_semantic::ResolvedDefinition::ModuleFile(module_file) => {
283-
// For module files, navigate to the beginning of the file
277+
ty_python_semantic::ResolvedDefinition::FileWithRange(file_range) => {
278+
// For file ranges, navigate to the specific range within the file
284279
crate::NavigationTarget {
285-
file: module_file,
286-
focus_range: ruff_text_size::TextRange::default(), // Start of file
287-
full_range: ruff_text_size::TextRange::default(), // Start of file
280+
file: file_range.file(),
281+
focus_range: file_range.range(),
282+
full_range: file_range.range(),
288283
}
289284
}
290285
})
291286
.collect()
292287
}
293288

289+
/// Shared helper to map and convert resolved definitions into navigation targets.
290+
fn definitions_to_navigation_targets<'db>(
291+
db: &dyn crate::Db,
292+
stub_mapper: Option<&StubMapper<'db>>,
293+
mut definitions: Vec<ty_python_semantic::ResolvedDefinition<'db>>,
294+
) -> Option<crate::NavigationTargets> {
295+
if let Some(mapper) = stub_mapper {
296+
definitions = mapper.map_definitions(definitions);
297+
}
298+
if definitions.is_empty() {
299+
None
300+
} else {
301+
let targets = convert_resolved_definitions_to_targets(db, definitions);
302+
Some(crate::NavigationTargets::unique(targets))
303+
}
304+
}
305+
294306
pub(crate) fn find_goto_target(
295307
parsed: &ParsedModuleRef,
296308
offset: TextSize,

0 commit comments

Comments
 (0)