[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] #[test]
fn goto_declaration_from_import_as() { fn goto_declaration_from_import_as() {
let test = CursorTest::builder() let test = CursorTest::builder()

View File

@ -1041,8 +1041,6 @@ mod resolve_definition {
} }
} }
// Resolve the target module file
let module_file = {
// Resolve the module being imported from (handles both relative and absolute imports) // 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() let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok()
else { else {
@ -1051,11 +1049,18 @@ mod resolve_definition {
let Some(resolved_module) = resolve_module(db, file, &module_name) else { let Some(resolved_module) = resolve_module(db, file, &module_name) else {
return Vec::new(); return Vec::new();
}; };
resolved_module.file(db)
}; // Resolve the target module file
let module_file = resolved_module.file(db);
let Some(module_file) = module_file else { 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 // 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 // Recursively resolve any import definitions found in the target module
if definitions_in_module.is_empty() { if definitions_in_module.is_empty() {
// If we can't find the specific symbol, return empty list // This might be importing a submodule, try that
Vec::new() return Vec::from_iter(resolve_from_import_submodule_definitions(
} else { db,
file,
symbol_name,
module_name,
));
}
let mut resolved_definitions = Vec::new(); let mut resolved_definitions = Vec::new();
for def in definitions_in_module { for def in definitions_in_module {
let resolved = resolve_definition_recursive( let resolved =
db, resolve_definition_recursive(db, def, visited, Some(symbol_name), alias_resolution);
def,
visited,
Some(symbol_name),
alias_resolution,
);
resolved_definitions.extend(resolved); resolved_definitions.extend(resolved);
} }
resolved_definitions 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. /// Find definitions for a symbol name in a specific scope.