[ty] Fix goto-declaration on the RHS of `from module import submodule` (#22042)

This commit is contained in:
Aria Desires 2025-12-18 02:28:05 -05:00 committed by GitHub
parent 06305f3c02
commit 7bb5dd87ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 27 deletions

View File

@ -386,6 +386,29 @@ FOO = 0
");
}
#[test]
fn goto_declaration_from_import_rhs_is_module() {
let test = CursorTest::builder()
.source("lib/__init__.py", r#""#)
.source("lib/module.py", r#""#)
.source("main.py", r#"from lib import module<CURSOR>"#)
.build();
// Should resolve to the actual function definition, not the import statement
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Go to declaration
--> main.py:1:17
|
1 | from lib import module
| ^^^^^^ Clicking here
|
info: Found 1 declaration
--> lib/module.py:1:1
|
|
");
}
#[test]
fn goto_declaration_from_import_as() {
let test = CursorTest::builder()

View File

@ -1041,21 +1041,26 @@ mod resolve_definition {
}
}
// Resolve the target module file
let module_file = {
// Resolve the module being imported from (handles both relative and absolute imports)
let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok()
else {
return Vec::new();
};
let Some(resolved_module) = resolve_module(db, file, &module_name) else {
return Vec::new();
};
resolved_module.file(db)
// Resolve the module being imported from (handles both relative and absolute imports)
let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok()
else {
return Vec::new();
};
let Some(resolved_module) = resolve_module(db, file, &module_name) else {
return Vec::new();
};
// Resolve the target module file
let module_file = resolved_module.file(db);
let Some(module_file) = module_file else {
return Vec::new(); // Module resolution failed
// No file means this is a namespace package, try to import the submodule
return Vec::from_iter(resolve_from_import_submodule_definitions(
db,
file,
symbol_name,
module_name,
));
};
// Find the definition of this symbol in the imported module's global scope
@ -1064,22 +1069,38 @@ mod resolve_definition {
// Recursively resolve any import definitions found in the target module
if definitions_in_module.is_empty() {
// If we can't find the specific symbol, return empty list
Vec::new()
} else {
let mut resolved_definitions = Vec::new();
for def in definitions_in_module {
let resolved = resolve_definition_recursive(
db,
def,
visited,
Some(symbol_name),
alias_resolution,
);
resolved_definitions.extend(resolved);
}
resolved_definitions
// This might be importing a submodule, try that
return Vec::from_iter(resolve_from_import_submodule_definitions(
db,
file,
symbol_name,
module_name,
));
}
let mut resolved_definitions = Vec::new();
for def in definitions_in_module {
let resolved =
resolve_definition_recursive(db, def, visited, Some(symbol_name), alias_resolution);
resolved_definitions.extend(resolved);
}
resolved_definitions
}
// Helper to resolve `from x.y import z` assuming `x.y.z` is a module.
fn resolve_from_import_submodule_definitions<'db>(
db: &'db dyn Db,
file: File,
symbol_name: &str,
module_name: ModuleName,
) -> Option<ResolvedDefinition<'db>> {
let submodule_name = ModuleName::new(symbol_name)?;
let mut full_submodule_name = module_name;
full_submodule_name.extend(&submodule_name);
let module = resolve_module(db, file, &full_submodule_name)?;
let file = module.file(db)?;
Some(ResolvedDefinition::Module(file))
}
/// Find definitions for a symbol name in a specific scope.