[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,21 +1041,26 @@ mod resolve_definition {
} }
} }
// Resolve the target module file // Resolve the module being imported from (handles both relative and absolute imports)
let module_file = { let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok()
// Resolve the module being imported from (handles both relative and absolute imports) else {
let Some(module_name) = ModuleName::from_import_statement(db, file, import_node).ok() return Vec::new();
else { };
return Vec::new(); let Some(resolved_module) = resolve_module(db, file, &module_name) else {
}; return Vec::new();
let Some(resolved_module) = resolve_module(db, file, &module_name) else {
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,
let mut resolved_definitions = Vec::new(); file,
for def in definitions_in_module { symbol_name,
let resolved = resolve_definition_recursive( module_name,
db, ));
def,
visited,
Some(symbol_name),
alias_resolution,
);
resolved_definitions.extend(resolved);
}
resolved_definitions
} }
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. /// Find definitions for a symbol name in a specific scope.