[ty] Expand API of all_members to return a struct

This commit doesn't change any behavior, but makes it so `all_members`
returns a `Vec<Member>` instead of `Vec<Name>`, where a `Member`
contains a `Name`. This gives us an expansion point to include other
data (such as the type of the `Name`).
This commit is contained in:
Andrew Gallant 2025-07-08 13:25:59 -04:00 committed by Andrew Gallant
parent f7234cb474
commit 79fe538458
4 changed files with 54 additions and 25 deletions

View file

@ -10,7 +10,7 @@ use ty_python_semantic::{Completion, NameKind, SemanticModel};
use crate::Db; use crate::Db;
use crate::find_node::covering_node; use crate::find_node::covering_node;
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion> { pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'_>> {
let parsed = parsed_module(db, file).load(db); let parsed = parsed_module(db, file).load(db);
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else { let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {

View file

@ -45,7 +45,7 @@ impl<'db> SemanticModel<'db> {
&self, &self,
import: &ast::StmtImportFrom, import: &ast::StmtImportFrom,
_name: Option<usize>, _name: Option<usize>,
) -> Vec<Completion> { ) -> Vec<Completion<'db>> {
let module_name = match ModuleName::from_import_statement(self.db, self.file, import) { let module_name = match ModuleName::from_import_statement(self.db, self.file, import) {
Ok(module_name) => module_name, Ok(module_name) => module_name,
Err(err) => { Err(err) => {
@ -62,7 +62,7 @@ impl<'db> SemanticModel<'db> {
/// Returns completions for symbols available in the given module as if /// Returns completions for symbols available in the given module as if
/// it were imported by this model's `File`. /// it were imported by this model's `File`.
fn module_completions(&self, module_name: &ModuleName) -> Vec<Completion> { fn module_completions(&self, module_name: &ModuleName) -> Vec<Completion<'db>> {
let Some(module) = resolve_module(self.db, module_name) else { let Some(module) = resolve_module(self.db, module_name) else {
tracing::debug!("Could not resolve module from `{module_name:?}`"); tracing::debug!("Could not resolve module from `{module_name:?}`");
return vec![]; return vec![];
@ -71,17 +71,22 @@ impl<'db> SemanticModel<'db> {
let builtin = module.is_known(KnownModule::Builtins); let builtin = module.is_known(KnownModule::Builtins);
crate::types::all_members(self.db, ty) crate::types::all_members(self.db, ty)
.into_iter() .into_iter()
.map(|name| Completion { name, builtin }) .map(|member| Completion {
name: member.name,
ty: None,
builtin,
})
.collect() .collect()
} }
/// Returns completions for symbols available in a `object.<CURSOR>` context. /// Returns completions for symbols available in a `object.<CURSOR>` context.
pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec<Completion> { pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec<Completion<'db>> {
let ty = node.value.inferred_type(self); let ty = node.value.inferred_type(self);
crate::types::all_members(self.db, ty) crate::types::all_members(self.db, ty)
.into_iter() .into_iter()
.map(|name| Completion { .map(|member| Completion {
name, name: member.name,
ty: None,
builtin: false, builtin: false,
}) })
.collect() .collect()
@ -92,7 +97,7 @@ impl<'db> SemanticModel<'db> {
/// ///
/// If a scope could not be determined, then completions for the global /// If a scope could not be determined, then completions for the global
/// scope of this model's `File` are returned. /// scope of this model's `File` are returned.
pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec<Completion> { pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec<Completion<'db>> {
let index = semantic_index(self.db, self.file); let index = semantic_index(self.db, self.file);
// TODO: We currently use `try_expression_scope_id` here as a hotfix for [1]. // TODO: We currently use `try_expression_scope_id` here as a hotfix for [1].
@ -115,8 +120,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(|name| Completion { .map(|member| Completion {
name, name: member.name,
ty: None,
builtin: false, builtin: false,
}), }),
); );
@ -163,9 +169,11 @@ impl NameKind {
/// A suggestion for code completion. /// A suggestion for code completion.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Completion { pub struct Completion<'db> {
/// The label shown to the user for this suggestion. /// The label shown to the user for this suggestion.
pub name: Name, pub name: Name,
/// The type of this completion, if available.
pub ty: Option<Type<'db>>,
/// Whether this suggestion came from builtins or not. /// Whether this suggestion came from builtins or not.
/// ///
/// At time of writing (2025-06-26), this information /// At time of writing (2025-06-26), this information

View file

@ -668,7 +668,7 @@ impl<'db> Bindings<'db> {
ide_support::all_members(db, *ty) ide_support::all_members(db, *ty)
.into_iter() .into_iter()
.sorted() .sorted()
.map(|member| Type::string_literal(db, &member)), .map(|member| Type::string_literal(db, &member.name)),
)); ));
} }
} }

View file

@ -14,7 +14,7 @@ use rustc_hash::FxHashSet;
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 = Name> + 'db { ) -> impl Iterator<Item = Member> + '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);
@ -24,10 +24,13 @@ pub(crate) fn all_declarations_and_bindings<'db>(
place_from_declarations(db, declarations) place_from_declarations(db, declarations)
.ok() .ok()
.and_then(|result| { .and_then(|result| {
result result.place.ignore_possibly_unbound().and_then(|_| {
.place table
.ignore_possibly_unbound() .place_expr(symbol_id)
.and_then(|_| table.place_expr(symbol_id).as_name().cloned()) .as_name()
.cloned()
.map(|name| Member { name })
})
}) })
}) })
.chain( .chain(
@ -36,13 +39,19 @@ pub(crate) fn all_declarations_and_bindings<'db>(
.filter_map(move |(symbol_id, bindings)| { .filter_map(move |(symbol_id, bindings)| {
place_from_bindings(db, bindings) place_from_bindings(db, bindings)
.ignore_possibly_unbound() .ignore_possibly_unbound()
.and_then(|_| table.place_expr(symbol_id).as_name().cloned()) .and_then(|_| {
table
.place_expr(symbol_id)
.as_name()
.cloned()
.map(|name| Member { name })
})
}), }),
) )
} }
struct AllMembers { struct AllMembers {
members: FxHashSet<Name>, members: FxHashSet<Member>,
} }
impl AllMembers { impl AllMembers {
@ -180,8 +189,9 @@ impl AllMembers {
} }
} }
self.members self.members.insert(Member {
.insert(place_table.place_expr(symbol_id).expect_name().clone()); name: place_table.place_expr(symbol_id).expect_name().clone(),
});
} }
let module_name = module.name(); let module_name = module.name();
@ -190,7 +200,9 @@ impl AllMembers {
.iter() .iter()
.filter_map(|submodule_name| submodule_name.relative_to(module_name)) .filter_map(|submodule_name| submodule_name.relative_to(module_name))
.filter_map(|relative_submodule_name| { .filter_map(|relative_submodule_name| {
Some(Name::from(relative_submodule_name.components().next()?)) Some(Member {
name: Name::from(relative_submodule_name.components().next()?),
})
}), }),
); );
} }
@ -232,16 +244,25 @@ impl AllMembers {
let index = semantic_index(db, file); let index = semantic_index(db, file);
for function_scope_id in attribute_scopes(db, class_body_scope) { for function_scope_id in attribute_scopes(db, class_body_scope) {
let place_table = index.place_table(function_scope_id); let place_table = index.place_table(function_scope_id);
self.members self.members.extend(
.extend(place_table.instance_attributes().cloned()); place_table
.instance_attributes()
.cloned()
.map(|name| Member { name }),
);
} }
} }
} }
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Member {
pub name: Name,
}
/// List all members of a given type: anything that would be valid when accessed /// List all members of a given type: anything that would be valid when accessed
/// as an attribute on an object of the given type. /// as an attribute on an object of the given type.
pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Name> { pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Member> {
AllMembers::of(db, ty).members AllMembers::of(db, ty).members
} }