mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 07:04:37 +00:00
[ty] Include definition site for "members of" API
In the course of writing the "add an import" implementation, I realized that we needed to know which symbols were in scope and how they were defined. This was necessary to be able to determine how to add a new import in a way that (minimally) does not conflict with existing symbols. I'm not sure that this is fully correct (especially for symbol bindings) and it's unclear to me in which cases a definition site will be missing. But this seems to work for some of the basic cases that I tried.
This commit is contained in:
parent
6ec52991cb
commit
6c3c963f8a
2 changed files with 48 additions and 15 deletions
|
@ -236,9 +236,9 @@ impl<'db> SemanticModel<'db> {
|
||||||
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
||||||
completions.extend(
|
completions.extend(
|
||||||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
||||||
.map(|member| Completion {
|
.map(|memberdef| Completion {
|
||||||
name: member.name,
|
name: memberdef.member.name,
|
||||||
ty: Some(member.ty),
|
ty: Some(memberdef.member.ty),
|
||||||
kind: None,
|
kind: None,
|
||||||
builtin: false,
|
builtin: false,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -30,35 +30,55 @@ use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
||||||
pub(crate) fn all_declarations_and_bindings<'db>(
|
pub(crate) fn all_declarations_and_bindings<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
scope_id: ScopeId<'db>,
|
scope_id: ScopeId<'db>,
|
||||||
) -> impl Iterator<Item = Member<'db>> + 'db {
|
) -> impl Iterator<Item = MemberWithDefinition<'db>> + 'db {
|
||||||
let use_def_map = use_def_map(db, scope_id);
|
let use_def_map = use_def_map(db, scope_id);
|
||||||
let table = place_table(db, scope_id);
|
let table = place_table(db, scope_id);
|
||||||
|
|
||||||
use_def_map
|
use_def_map
|
||||||
.all_end_of_scope_symbol_declarations()
|
.all_end_of_scope_symbol_declarations()
|
||||||
.filter_map(move |(symbol_id, declarations)| {
|
.filter_map(move |(symbol_id, declarations)| {
|
||||||
place_from_declarations(db, declarations)
|
let place_result = place_from_declarations(db, declarations);
|
||||||
|
let definition = place_result.single_declaration;
|
||||||
|
place_result
|
||||||
.ignore_conflicting_declarations()
|
.ignore_conflicting_declarations()
|
||||||
.place
|
.place
|
||||||
.ignore_possibly_unbound()
|
.ignore_possibly_unbound()
|
||||||
.map(|ty| {
|
.map(|ty| {
|
||||||
let symbol = table.symbol(symbol_id);
|
let symbol = table.symbol(symbol_id);
|
||||||
Member {
|
let member = Member {
|
||||||
name: symbol.name().clone(),
|
name: symbol.name().clone(),
|
||||||
ty,
|
ty,
|
||||||
}
|
};
|
||||||
|
MemberWithDefinition { member, definition }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|
.chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|
||||||
move |(symbol_id, bindings)| {
|
move |(symbol_id, bindings)| {
|
||||||
|
// It's not clear to AG how to using a bindings
|
||||||
|
// iterator here to get the correct definition for
|
||||||
|
// this binding. Below, we look through all bindings
|
||||||
|
// with a definition and only take one if there is
|
||||||
|
// exactly one. I don't think this can be wrong, but
|
||||||
|
// it's probably omitting definitions in some cases.
|
||||||
|
let mut definition = None;
|
||||||
|
for binding in bindings.clone() {
|
||||||
|
if let Some(def) = binding.binding.definition() {
|
||||||
|
if definition.is_some() {
|
||||||
|
definition = None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
definition = Some(def);
|
||||||
|
}
|
||||||
|
}
|
||||||
place_from_bindings(db, bindings)
|
place_from_bindings(db, bindings)
|
||||||
.ignore_possibly_unbound()
|
.ignore_possibly_unbound()
|
||||||
.map(|ty| {
|
.map(|ty| {
|
||||||
let symbol = table.symbol(symbol_id);
|
let symbol = table.symbol(symbol_id);
|
||||||
Member {
|
let member = Member {
|
||||||
name: symbol.name().clone(),
|
name: symbol.name().clone(),
|
||||||
ty,
|
ty,
|
||||||
}
|
};
|
||||||
|
MemberWithDefinition { member, definition }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -305,12 +325,15 @@ impl<'db> AllMembers<'db> {
|
||||||
.map(|class| class.class_literal(db).0)
|
.map(|class| class.class_literal(db).0)
|
||||||
{
|
{
|
||||||
let parent_scope = parent.body_scope(db);
|
let parent_scope = parent.body_scope(db);
|
||||||
for Member { name, .. } in all_declarations_and_bindings(db, parent_scope) {
|
for memberdef in all_declarations_and_bindings(db, parent_scope) {
|
||||||
let result = ty.member(db, name.as_str());
|
let result = ty.member(db, memberdef.member.name.as_str());
|
||||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
self.members.insert(Member { name, ty });
|
self.members.insert(Member {
|
||||||
|
name: memberdef.member.name,
|
||||||
|
ty,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,17 +373,27 @@ impl<'db> AllMembers<'db> {
|
||||||
// class member. This gets us the right type for each
|
// class member. This gets us the right type for each
|
||||||
// member, e.g., `SomeClass.__delattr__` is not a bound
|
// member, e.g., `SomeClass.__delattr__` is not a bound
|
||||||
// method, but `instance_of_SomeClass.__delattr__` is.
|
// method, but `instance_of_SomeClass.__delattr__` is.
|
||||||
for Member { name, .. } in all_declarations_and_bindings(db, class_body_scope) {
|
for memberdef in all_declarations_and_bindings(db, class_body_scope) {
|
||||||
let result = ty.member(db, name.as_str());
|
let result = ty.member(db, memberdef.member.name.as_str());
|
||||||
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
let Some(ty) = result.place.ignore_possibly_unbound() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
self.members.insert(Member { name, ty });
|
self.members.insert(Member {
|
||||||
|
name: memberdef.member.name,
|
||||||
|
ty,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A member of a type with an optional definition.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MemberWithDefinition<'db> {
|
||||||
|
pub member: Member<'db>,
|
||||||
|
pub definition: Option<Definition<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A member of a type.
|
/// A member of a type.
|
||||||
///
|
///
|
||||||
/// This represents a single item in (ideally) the list returned by
|
/// This represents a single item in (ideally) the list returned by
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue