[ty] Support renaming import aliases (#21792)

This commit is contained in:
Micha Reiser 2025-12-05 19:12:13 +01:00 committed by GitHub
parent b2fb421ddd
commit 9714c589e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 253 additions and 111 deletions

View File

@ -73,19 +73,29 @@ pub(crate) enum GotoTarget<'a> {
/// ``` /// ```
ImportModuleAlias { ImportModuleAlias {
alias: &'a ast::Alias, alias: &'a ast::Alias,
asname: &'a ast::Identifier,
},
/// In an import statement, the named under which the symbol is exported
/// in the imported file.
///
/// ```py
/// from foo import bar as baz
/// ^^^
/// ```
ImportExportedName {
alias: &'a ast::Alias,
import_from: &'a ast::StmtImportFrom,
}, },
/// Import alias in from import statement /// Import alias in from import statement
/// ```py /// ```py
/// from foo import bar as baz /// from foo import bar as baz
/// ^^^
/// from foo import bar as baz
/// ^^^ /// ^^^
/// ``` /// ```
ImportSymbolAlias { ImportSymbolAlias {
alias: &'a ast::Alias, alias: &'a ast::Alias,
range: TextRange, asname: &'a ast::Identifier,
import_from: &'a ast::StmtImportFrom,
}, },
/// Go to on the exception handler variable /// Go to on the exception handler variable
@ -290,8 +300,9 @@ impl GotoTarget<'_> {
GotoTarget::FunctionDef(function) => function.inferred_type(model), GotoTarget::FunctionDef(function) => function.inferred_type(model),
GotoTarget::ClassDef(class) => class.inferred_type(model), GotoTarget::ClassDef(class) => class.inferred_type(model),
GotoTarget::Parameter(parameter) => parameter.inferred_type(model), GotoTarget::Parameter(parameter) => parameter.inferred_type(model),
GotoTarget::ImportSymbolAlias { alias, .. } => alias.inferred_type(model), GotoTarget::ImportSymbolAlias { alias, .. }
GotoTarget::ImportModuleAlias { alias } => alias.inferred_type(model), | GotoTarget::ImportModuleAlias { alias, .. }
| GotoTarget::ImportExportedName { alias, .. } => alias.inferred_type(model),
GotoTarget::ExceptVariable(except) => except.inferred_type(model), GotoTarget::ExceptVariable(except) => except.inferred_type(model),
GotoTarget::KeywordArgument { keyword, .. } => keyword.value.inferred_type(model), GotoTarget::KeywordArgument { keyword, .. } => keyword.value.inferred_type(model),
// When asking the type of a callable, usually you want the callable itself? // When asking the type of a callable, usually you want the callable itself?
@ -378,7 +389,9 @@ impl GotoTarget<'_> {
alias_resolution: ImportAliasResolution, alias_resolution: ImportAliasResolution,
) -> Option<Definitions<'db>> { ) -> Option<Definitions<'db>> {
let definitions = match self { let definitions = match self {
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression), GotoTarget::Expression(expression) => {
definitions_for_expression(model, *expression, alias_resolution)
}
// For already-defined symbols, they are their own definitions // For already-defined symbols, they are their own definitions
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition( GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
function.definition(model), function.definition(model),
@ -393,22 +406,21 @@ impl GotoTarget<'_> {
)]), )]),
// For import aliases (offset within 'y' or 'z' in "from x import y as z") // For import aliases (offset within 'y' or 'z' in "from x import y as z")
GotoTarget::ImportSymbolAlias { GotoTarget::ImportSymbolAlias { asname, .. } => Some(definitions_for_name(
alias, import_from, .. model,
} => { asname.as_str(),
if let Some(asname) = alias.asname.as_ref() AnyNodeRef::from(*asname),
&& alias_resolution == ImportAliasResolution::PreserveAliases alias_resolution,
{ )),
Some(definitions_for_name(model, asname.as_str(), asname.into()))
} else { GotoTarget::ImportExportedName { alias, import_from } => {
let symbol_name = alias.name.as_str(); let symbol_name = alias.name.as_str();
Some(definitions_for_imported_symbol( Some(definitions_for_imported_symbol(
model, model,
import_from, import_from,
symbol_name, symbol_name,
alias_resolution, alias_resolution,
)) ))
}
} }
GotoTarget::ImportModuleComponent { GotoTarget::ImportModuleComponent {
@ -423,15 +435,12 @@ impl GotoTarget<'_> {
} }
// Handle import aliases (offset within 'z' in "import x.y as z") // Handle import aliases (offset within 'z' in "import x.y as z")
GotoTarget::ImportModuleAlias { alias } => { GotoTarget::ImportModuleAlias { asname, .. } => Some(definitions_for_name(
if let Some(asname) = alias.asname.as_ref() model,
&& alias_resolution == ImportAliasResolution::PreserveAliases asname.as_str(),
{ AnyNodeRef::from(*asname),
Some(definitions_for_name(model, asname.as_str(), asname.into())) alias_resolution,
} else { )),
definitions_for_module(model, Some(alias.name.as_str()), 0)
}
}
// Handle keyword arguments in call expressions // Handle keyword arguments in call expressions
GotoTarget::KeywordArgument { GotoTarget::KeywordArgument {
@ -454,12 +463,22 @@ impl GotoTarget<'_> {
// because they're not expressions // because they're not expressions
GotoTarget::PatternMatchRest(pattern_mapping) => { GotoTarget::PatternMatchRest(pattern_mapping) => {
pattern_mapping.rest.as_ref().map(|name| { pattern_mapping.rest.as_ref().map(|name| {
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name)) definitions_for_name(
model,
name.as_str(),
AnyNodeRef::Identifier(name),
alias_resolution,
)
}) })
} }
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| { GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name)) definitions_for_name(
model,
name.as_str(),
AnyNodeRef::Identifier(name),
alias_resolution,
)
}), }),
GotoTarget::PatternKeywordArgument(pattern_keyword) => { GotoTarget::PatternKeywordArgument(pattern_keyword) => {
@ -468,12 +487,18 @@ impl GotoTarget<'_> {
model, model,
name.as_str(), name.as_str(),
AnyNodeRef::Identifier(name), AnyNodeRef::Identifier(name),
alias_resolution,
)) ))
} }
GotoTarget::PatternMatchStarName(pattern_star) => { GotoTarget::PatternMatchStarName(pattern_star) => {
pattern_star.name.as_ref().map(|name| { pattern_star.name.as_ref().map(|name| {
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name)) definitions_for_name(
model,
name.as_str(),
AnyNodeRef::Identifier(name),
alias_resolution,
)
}) })
} }
@ -481,9 +506,18 @@ impl GotoTarget<'_> {
// //
// Prefer the function impl over the callable so that its docstrings win if defined. // Prefer the function impl over the callable so that its docstrings win if defined.
GotoTarget::Call { callable, call } => { GotoTarget::Call { callable, call } => {
let mut definitions = definitions_for_callable(model, call); let mut definitions = Vec::new();
// We prefer the specific overload for hover, go-to-def etc. However,
// `definitions_for_callable` always resolves import aliases. That's why we
// skip it in cases import alias resolution is turned of (rename, highlight references).
if alias_resolution == ImportAliasResolution::ResolveAliases {
definitions.extend(definitions_for_callable(model, call));
}
let expr_definitions = let expr_definitions =
definitions_for_expression(model, *callable).unwrap_or_default(); definitions_for_expression(model, *callable, alias_resolution)
.unwrap_or_default();
definitions.extend(expr_definitions); definitions.extend(expr_definitions);
if definitions.is_empty() { if definitions.is_empty() {
@ -517,7 +551,7 @@ impl GotoTarget<'_> {
let subexpr = covering_node(subast.syntax().into(), *subrange) let subexpr = covering_node(subast.syntax().into(), *subrange)
.node() .node()
.as_expr_ref()?; .as_expr_ref()?;
definitions_for_expression(&submodel, subexpr) definitions_for_expression(&submodel, subexpr, alias_resolution)
} }
// nonlocal and global are essentially loads, but again they're statements, // nonlocal and global are essentially loads, but again they're statements,
@ -527,6 +561,7 @@ impl GotoTarget<'_> {
model, model,
identifier.as_str(), identifier.as_str(),
AnyNodeRef::Identifier(identifier), AnyNodeRef::Identifier(identifier),
alias_resolution,
)) ))
} }
@ -537,6 +572,7 @@ impl GotoTarget<'_> {
model, model,
name.as_str(), name.as_str(),
AnyNodeRef::Identifier(name), AnyNodeRef::Identifier(name),
alias_resolution,
)) ))
} }
@ -546,6 +582,7 @@ impl GotoTarget<'_> {
model, model,
name.as_str(), name.as_str(),
AnyNodeRef::Identifier(name), AnyNodeRef::Identifier(name),
alias_resolution,
)) ))
} }
@ -555,6 +592,7 @@ impl GotoTarget<'_> {
model, model,
name.as_str(), name.as_str(),
AnyNodeRef::Identifier(name), AnyNodeRef::Identifier(name),
alias_resolution,
)) ))
} }
}; };
@ -580,12 +618,9 @@ impl GotoTarget<'_> {
GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())), GotoTarget::FunctionDef(function) => Some(Cow::Borrowed(function.name.as_str())),
GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())), GotoTarget::ClassDef(class) => Some(Cow::Borrowed(class.name.as_str())),
GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())), GotoTarget::Parameter(parameter) => Some(Cow::Borrowed(parameter.name.as_str())),
GotoTarget::ImportSymbolAlias { alias, .. } => { GotoTarget::ImportSymbolAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
if let Some(asname) = &alias.asname { GotoTarget::ImportExportedName { alias, .. } => {
Some(Cow::Borrowed(asname.as_str())) Some(Cow::Borrowed(alias.name.as_str()))
} else {
Some(Cow::Borrowed(alias.name.as_str()))
}
} }
GotoTarget::ImportModuleComponent { GotoTarget::ImportModuleComponent {
module_name, module_name,
@ -599,13 +634,7 @@ impl GotoTarget<'_> {
Some(Cow::Borrowed(module_name)) Some(Cow::Borrowed(module_name))
} }
} }
GotoTarget::ImportModuleAlias { alias } => { GotoTarget::ImportModuleAlias { asname, .. } => Some(Cow::Borrowed(asname.as_str())),
if let Some(asname) = &alias.asname {
Some(Cow::Borrowed(asname.as_str()))
} else {
Some(Cow::Borrowed(alias.name.as_str()))
}
}
GotoTarget::ExceptVariable(except) => { GotoTarget::ExceptVariable(except) => {
Some(Cow::Borrowed(except.name.as_ref()?.as_str())) Some(Cow::Borrowed(except.name.as_ref()?.as_str()))
} }
@ -667,7 +696,7 @@ impl GotoTarget<'_> {
// Is the offset within the alias name (asname) part? // Is the offset within the alias name (asname) part?
if let Some(asname) = &alias.asname { if let Some(asname) = &alias.asname {
if asname.range.contains_inclusive(offset) { if asname.range.contains_inclusive(offset) {
return Some(GotoTarget::ImportModuleAlias { alias }); return Some(GotoTarget::ImportModuleAlias { alias, asname });
} }
} }
@ -699,21 +728,13 @@ impl GotoTarget<'_> {
// Is the offset within the alias name (asname) part? // Is the offset within the alias name (asname) part?
if let Some(asname) = &alias.asname { if let Some(asname) = &alias.asname {
if asname.range.contains_inclusive(offset) { if asname.range.contains_inclusive(offset) {
return Some(GotoTarget::ImportSymbolAlias { return Some(GotoTarget::ImportSymbolAlias { alias, asname });
alias,
range: asname.range,
import_from,
});
} }
} }
// Is the offset in the original name part? // Is the offset in the original name part?
if alias.name.range.contains_inclusive(offset) { if alias.name.range.contains_inclusive(offset) {
return Some(GotoTarget::ImportSymbolAlias { return Some(GotoTarget::ImportExportedName { alias, import_from });
alias,
range: alias.name.range,
import_from,
});
} }
None None
@ -893,12 +914,13 @@ impl Ranged for GotoTarget<'_> {
GotoTarget::FunctionDef(function) => function.name.range, GotoTarget::FunctionDef(function) => function.name.range,
GotoTarget::ClassDef(class) => class.name.range, GotoTarget::ClassDef(class) => class.name.range,
GotoTarget::Parameter(parameter) => parameter.name.range, GotoTarget::Parameter(parameter) => parameter.name.range,
GotoTarget::ImportSymbolAlias { range, .. } => *range, GotoTarget::ImportSymbolAlias { asname, .. } => asname.range,
Self::ImportExportedName { alias, .. } => alias.name.range,
GotoTarget::ImportModuleComponent { GotoTarget::ImportModuleComponent {
component_range, .. component_range, ..
} => *component_range, } => *component_range,
GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange, GotoTarget::StringAnnotationSubexpr { subrange, .. } => *subrange,
GotoTarget::ImportModuleAlias { alias } => alias.asname.as_ref().unwrap().range, GotoTarget::ImportModuleAlias { asname, .. } => asname.range,
GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range, GotoTarget::ExceptVariable(except) => except.name.as_ref().unwrap().range,
GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range, GotoTarget::KeywordArgument { keyword, .. } => keyword.arg.as_ref().unwrap().range,
GotoTarget::PatternMatchRest(rest) => rest.rest.as_ref().unwrap().range, GotoTarget::PatternMatchRest(rest) => rest.rest.as_ref().unwrap().range,
@ -955,12 +977,14 @@ fn convert_resolved_definitions_to_targets<'db>(
fn definitions_for_expression<'db>( fn definitions_for_expression<'db>(
model: &SemanticModel<'db>, model: &SemanticModel<'db>,
expression: ruff_python_ast::ExprRef<'_>, expression: ruff_python_ast::ExprRef<'_>,
alias_resolution: ImportAliasResolution,
) -> Option<Vec<ResolvedDefinition<'db>>> { ) -> Option<Vec<ResolvedDefinition<'db>>> {
match expression { match expression {
ast::ExprRef::Name(name) => Some(definitions_for_name( ast::ExprRef::Name(name) => Some(definitions_for_name(
model, model,
name.id.as_str(), name.id.as_str(),
expression.into(), expression.into(),
alias_resolution,
)), )),
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute( ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
model, attribute, model, attribute,

View File

@ -37,6 +37,38 @@ pub enum ReferencesMode {
DocumentHighlights, DocumentHighlights,
} }
impl ReferencesMode {
pub(super) fn to_import_alias_resolution(self) -> ImportAliasResolution {
match self {
// Resolve import aliases for find references:
// ```py
// from warnings import deprecated as my_deprecated
//
// @my_deprecated
// def foo
// ```
//
// When finding references on `my_deprecated`, we want to find all usages of `deprecated` across the entire
// project.
Self::References | Self::ReferencesSkipDeclaration => {
ImportAliasResolution::ResolveAliases
}
// For rename, don't resolve import aliases.
//
// ```py
// from warnings import deprecated as my_deprecated
//
// @my_deprecated
// def foo
// ```
// When renaming `my_deprecated`, only rename the alias, but not the original definition in `warnings`.
Self::Rename | Self::RenameMultiFile | Self::DocumentHighlights => {
ImportAliasResolution::PreserveAliases
}
}
}
}
/// Find all references to a symbol at the given position. /// Find all references to a symbol at the given position.
/// Search for references across all files in the project. /// Search for references across all files in the project.
pub(crate) fn references( pub(crate) fn references(
@ -45,12 +77,9 @@ pub(crate) fn references(
goto_target: &GotoTarget, goto_target: &GotoTarget,
mode: ReferencesMode, mode: ReferencesMode,
) -> Option<Vec<ReferenceTarget>> { ) -> Option<Vec<ReferenceTarget>> {
// Get the definitions for the symbol at the cursor position
// When finding references, do not resolve any local aliases.
let model = SemanticModel::new(db, file); let model = SemanticModel::new(db, file);
let target_definitions = goto_target let target_definitions = goto_target
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)? .get_definition_targets(&model, mode.to_import_alias_resolution())?
.declaration_targets(db)?; .declaration_targets(db)?;
// Extract the target text from the goto target for fast comparison // Extract the target text from the goto target for fast comparison
@ -318,7 +347,7 @@ impl LocalReferencesFinder<'_> {
{ {
// Get the definitions for this goto target // Get the definitions for this goto target
if let Some(current_definitions) = goto_target if let Some(current_definitions) = goto_target
.get_definition_targets(self.model, ImportAliasResolution::PreserveAliases) .get_definition_targets(self.model, self.mode.to_import_alias_resolution())
.and_then(|definitions| definitions.declaration_targets(self.model.db())) .and_then(|definitions| definitions.declaration_targets(self.model.db()))
{ {
// Check if any of the current definitions match our target definitions // Check if any of the current definitions match our target definitions

View File

@ -3,7 +3,7 @@ use crate::references::{ReferencesMode, references};
use crate::{Db, ReferenceTarget}; use crate::{Db, ReferenceTarget};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_text_size::{Ranged, TextSize}; use ruff_text_size::{Ranged, TextSize};
use ty_python_semantic::{ImportAliasResolution, SemanticModel}; use ty_python_semantic::SemanticModel;
/// Returns the range of the symbol if it can be renamed, None if not. /// Returns the range of the symbol if it can be renamed, None if not.
pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> { pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> {
@ -24,26 +24,22 @@ pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text
let current_file_in_project = is_file_in_project(db, file); let current_file_in_project = is_file_in_project(db, file);
if let Some(definition_targets) = goto_target let definition_targets = goto_target
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases) .get_definition_targets(&model, ReferencesMode::Rename.to_import_alias_resolution())?
.and_then(|definitions| definitions.declaration_targets(db)) .declaration_targets(db)?;
{
for target in &definition_targets {
let target_file = target.file();
// If definition is outside the project, refuse rename for target in &definition_targets {
if !is_file_in_project(db, target_file) { let target_file = target.file();
return None;
}
// If current file is not in project and any definition is outside current file, refuse rename // If definition is outside the project, refuse rename
if !current_file_in_project && target_file != file { if !is_file_in_project(db, target_file) {
return None; return None;
} }
// If current file is not in project and any definition is outside current file, refuse rename
if !current_file_in_project && target_file != file {
return None;
} }
} else {
// No definition targets found. This happens for keywords, so refuse rename
return None;
} }
Some(goto_target.range()) Some(goto_target.range())
@ -1186,7 +1182,6 @@ result = func(10, y=20)
"); ");
} }
// TODO Should rename the alias
#[test] #[test]
fn import_alias() { fn import_alias() {
let test = CursorTest::builder() let test = CursorTest::builder()
@ -1202,10 +1197,80 @@ result = func(10, y=20)
) )
.build(); .build();
assert_snapshot!(test.rename("z"), @"Cannot rename"); assert_snapshot!(test.rename("z"), @r"
info[rename]: Rename symbol (found 2 locations)
--> main.py:3:20
|
2 | import warnings
3 | import warnings as abc
| ^^^
4 |
5 | x = abc
| ---
6 | y = warnings
|
");
}
#[test]
fn import_alias_to_first_party_definition() {
let test = CursorTest::builder()
.source("lib.py", "def deprecated(): pass")
.source(
"main.py",
r#"
import lib as lib2<CURSOR>
x = lib2
"#,
)
.build();
assert_snapshot!(test.rename("z"), @r"
info[rename]: Rename symbol (found 2 locations)
--> main.py:2:15
|
2 | import lib as lib2
| ^^^^
3 |
4 | x = lib2
| ----
|
");
}
#[test]
fn imported_first_party_definition() {
let test = CursorTest::builder()
.source("lib.py", "def deprecated(): pass")
.source(
"main.py",
r#"
from lib import deprecated<CURSOR>
x = deprecated
"#,
)
.build();
assert_snapshot!(test.rename("z"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:17
|
2 | from lib import deprecated
| ^^^^^^^^^^
3 |
4 | x = deprecated
| ----------
|
::: lib.py:1:5
|
1 | def deprecated(): pass
| ----------
|
");
} }
// TODO Should rename the alias
#[test] #[test]
fn import_alias_use() { fn import_alias_use() {
let test = CursorTest::builder() let test = CursorTest::builder()
@ -1221,7 +1286,19 @@ result = func(10, y=20)
) )
.build(); .build();
assert_snapshot!(test.rename("z"), @"Cannot rename"); assert_snapshot!(test.rename("z"), @r"
info[rename]: Rename symbol (found 2 locations)
--> main.py:3:20
|
2 | import warnings
3 | import warnings as abc
| ^^^
4 |
5 | x = abc
| ---
6 | y = warnings
|
");
} }
#[test] #[test]

View File

@ -259,7 +259,11 @@ impl<'db> SemanticTokenVisitor<'db> {
fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) { fn classify_name(&self, name: &ast::ExprName) -> (SemanticTokenType, SemanticTokenModifier) {
// First try to classify the token based on its definition kind. // First try to classify the token based on its definition kind.
let definition = definition_for_name(self.model, name); let definition = definition_for_name(
self.model,
name,
ty_python_semantic::ImportAliasResolution::ResolveAliases,
);
if let Some(definition) = definition { if let Some(definition) = definition {
let name_str = name.id.as_str(); let name_str = name.id.as_str();

View File

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::FxIndexSet;
use crate::place::builtins_module_scope; use crate::place::builtins_module_scope;
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::semantic_index::definition::DefinitionKind; use crate::semantic_index::definition::DefinitionKind;
@ -24,8 +25,9 @@ use resolve_definition::{find_symbol_in_scope, resolve_definition};
pub fn definition_for_name<'db>( pub fn definition_for_name<'db>(
model: &SemanticModel<'db>, model: &SemanticModel<'db>,
name: &ast::ExprName, name: &ast::ExprName,
alias_resolution: ImportAliasResolution,
) -> Option<Definition<'db>> { ) -> Option<Definition<'db>> {
let definitions = definitions_for_name(model, name.id.as_str(), name.into()); let definitions = definitions_for_name(model, name.id.as_str(), name.into(), alias_resolution);
// Find the first valid definition and return its kind // Find the first valid definition and return its kind
for declaration in definitions { for declaration in definitions {
@ -43,6 +45,7 @@ pub fn definitions_for_name<'db>(
model: &SemanticModel<'db>, model: &SemanticModel<'db>,
name_str: &str, name_str: &str,
node: AnyNodeRef<'_>, node: AnyNodeRef<'_>,
alias_resolution: ImportAliasResolution,
) -> Vec<ResolvedDefinition<'db>> { ) -> Vec<ResolvedDefinition<'db>> {
let db = model.db(); let db = model.db();
let file = model.file(); let file = model.file();
@ -53,7 +56,7 @@ pub fn definitions_for_name<'db>(
return vec![]; return vec![];
}; };
let mut all_definitions = Vec::new(); let mut all_definitions = FxIndexSet::default();
// Search through the scope hierarchy: start from the current scope and // Search through the scope hierarchy: start from the current scope and
// traverse up through parent scopes to find definitions // traverse up through parent scopes to find definitions
@ -89,13 +92,13 @@ pub fn definitions_for_name<'db>(
for binding in global_bindings { for binding in global_bindings {
if let Some(def) = binding.binding.definition() { if let Some(def) = binding.binding.definition() {
all_definitions.push(def); all_definitions.insert(def);
} }
} }
for declaration in global_declarations { for declaration in global_declarations {
if let Some(def) = declaration.declaration.definition() { if let Some(def) = declaration.declaration.definition() {
all_definitions.push(def); all_definitions.insert(def);
} }
} }
} }
@ -116,13 +119,13 @@ pub fn definitions_for_name<'db>(
for binding in bindings { for binding in bindings {
if let Some(def) = binding.binding.definition() { if let Some(def) = binding.binding.definition() {
all_definitions.push(def); all_definitions.insert(def);
} }
} }
for declaration in declarations { for declaration in declarations {
if let Some(def) = declaration.declaration.definition() { if let Some(def) = declaration.declaration.definition() {
all_definitions.push(def); all_definitions.insert(def);
} }
} }
@ -136,21 +139,14 @@ pub fn definitions_for_name<'db>(
let mut resolved_definitions = Vec::new(); let mut resolved_definitions = Vec::new();
for definition in &all_definitions { for definition in &all_definitions {
let resolved = resolve_definition( let resolved = resolve_definition(db, *definition, Some(name_str), alias_resolution);
db,
*definition,
Some(name_str),
ImportAliasResolution::ResolveAliases,
);
resolved_definitions.extend(resolved); resolved_definitions.extend(resolved);
} }
// If we didn't find any definitions in scopes, fallback to builtins // If we didn't find any definitions in scopes, fallback to builtins
if resolved_definitions.is_empty() { if resolved_definitions.is_empty()
let Some(builtins_scope) = builtins_module_scope(db) else { && let Some(builtins_scope) = builtins_module_scope(db)
return resolved_definitions; {
};
// Special cases for `float` and `complex` in type annotation positions. // Special cases for `float` and `complex` in type annotation positions.
// We don't know whether we're in a type annotation position, so we'll just ask `Name`'s type, // We don't know whether we're in a type annotation position, so we'll just ask `Name`'s type,
// which resolves to `int | float` or `int | float | complex` if `float` or `complex` is used in // which resolves to `int | float` or `int | float | complex` if `float` or `complex` is used in
@ -932,6 +928,12 @@ mod resolve_definition {
let module = parsed_module(db, file).load(db); let module = parsed_module(db, file).load(db);
let alias = import_def.alias(&module); let alias = import_def.alias(&module);
if alias.asname.is_some()
&& alias_resolution == ImportAliasResolution::PreserveAliases
{
return vec![ResolvedDefinition::Definition(definition)];
}
// Get the full module name being imported // Get the full module name being imported
let Some(module_name) = ModuleName::new(&alias.name) else { let Some(module_name) = ModuleName::new(&alias.name) else {
return Vec::new(); // Invalid module name, return empty list return Vec::new(); // Invalid module name, return empty list
@ -955,7 +957,13 @@ mod resolve_definition {
let file = definition.file(db); let file = definition.file(db);
let module = parsed_module(db, file).load(db); let module = parsed_module(db, file).load(db);
let import_node = import_from_def.import(&module); let import_node = import_from_def.import(&module);
let name = &import_from_def.alias(&module).name; let alias = import_from_def.alias(&module);
if alias.asname.is_some()
&& alias_resolution == ImportAliasResolution::PreserveAliases
{
return vec![ResolvedDefinition::Definition(definition)];
}
// For `ImportFrom`, we need to resolve the original imported symbol name // For `ImportFrom`, we need to resolve the original imported symbol name
// (alias.name), not the local alias (symbol_name) // (alias.name), not the local alias (symbol_name)
@ -963,7 +971,7 @@ mod resolve_definition {
db, db,
file, file,
import_node, import_node,
name, &alias.name,
visited, visited,
alias_resolution, alias_resolution,
) )