From 79fe538458e9aebef3ac63d877b98f70ceebfc81 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 8 Jul 2025 13:25:59 -0400 Subject: [PATCH] [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` instead of `Vec`, where a `Member` contains a `Name`. This gives us an expansion point to include other data (such as the type of the `Name`). --- crates/ty_ide/src/completion.rs | 2 +- .../ty_python_semantic/src/semantic_model.rs | 28 +++++++---- .../ty_python_semantic/src/types/call/bind.rs | 2 +- .../src/types/ide_support.rs | 47 ++++++++++++++----- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 9a6190cb52..15caef7d46 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -10,7 +10,7 @@ use ty_python_semantic::{Completion, NameKind, SemanticModel}; use crate::Db; use crate::find_node::covering_node; -pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec { +pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec> { let parsed = parsed_module(db, file).load(db); let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else { diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 283549c821..8a794f2e6d 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -45,7 +45,7 @@ impl<'db> SemanticModel<'db> { &self, import: &ast::StmtImportFrom, _name: Option, - ) -> Vec { + ) -> Vec> { let module_name = match ModuleName::from_import_statement(self.db, self.file, import) { Ok(module_name) => module_name, Err(err) => { @@ -62,7 +62,7 @@ impl<'db> SemanticModel<'db> { /// Returns completions for symbols available in the given module as if /// it were imported by this model's `File`. - fn module_completions(&self, module_name: &ModuleName) -> Vec { + fn module_completions(&self, module_name: &ModuleName) -> Vec> { let Some(module) = resolve_module(self.db, module_name) else { tracing::debug!("Could not resolve module from `{module_name:?}`"); return vec![]; @@ -71,17 +71,22 @@ impl<'db> SemanticModel<'db> { let builtin = module.is_known(KnownModule::Builtins); crate::types::all_members(self.db, ty) .into_iter() - .map(|name| Completion { name, builtin }) + .map(|member| Completion { + name: member.name, + ty: None, + builtin, + }) .collect() } /// Returns completions for symbols available in a `object.` context. - pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec { + pub fn attribute_completions(&self, node: &ast::ExprAttribute) -> Vec> { let ty = node.value.inferred_type(self); crate::types::all_members(self.db, ty) .into_iter() - .map(|name| Completion { - name, + .map(|member| Completion { + name: member.name, + ty: None, builtin: false, }) .collect() @@ -92,7 +97,7 @@ impl<'db> SemanticModel<'db> { /// /// If a scope could not be determined, then completions for the global /// scope of this model's `File` are returned. - pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec { + pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec> { let index = semantic_index(self.db, self.file); // 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) { completions.extend( all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file)) - .map(|name| Completion { - name, + .map(|member| Completion { + name: member.name, + ty: None, builtin: false, }), ); @@ -163,9 +169,11 @@ impl NameKind { /// A suggestion for code completion. #[derive(Clone, Debug)] -pub struct Completion { +pub struct Completion<'db> { /// The label shown to the user for this suggestion. pub name: Name, + /// The type of this completion, if available. + pub ty: Option>, /// Whether this suggestion came from builtins or not. /// /// At time of writing (2025-06-26), this information diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index b180fdc007..54833cbcf3 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -668,7 +668,7 @@ impl<'db> Bindings<'db> { ide_support::all_members(db, *ty) .into_iter() .sorted() - .map(|member| Type::string_literal(db, &member)), + .map(|member| Type::string_literal(db, &member.name)), )); } } diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 087b2a9f56..0efe7881ad 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -14,7 +14,7 @@ use rustc_hash::FxHashSet; pub(crate) fn all_declarations_and_bindings<'db>( db: &'db dyn Db, scope_id: ScopeId<'db>, -) -> impl Iterator + 'db { +) -> impl Iterator + 'db { let use_def_map = use_def_map(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) .ok() .and_then(|result| { - result - .place - .ignore_possibly_unbound() - .and_then(|_| table.place_expr(symbol_id).as_name().cloned()) + result.place.ignore_possibly_unbound().and_then(|_| { + table + .place_expr(symbol_id) + .as_name() + .cloned() + .map(|name| Member { name }) + }) }) }) .chain( @@ -36,13 +39,19 @@ pub(crate) fn all_declarations_and_bindings<'db>( .filter_map(move |(symbol_id, bindings)| { place_from_bindings(db, bindings) .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 { - members: FxHashSet, + members: FxHashSet, } impl AllMembers { @@ -180,8 +189,9 @@ impl AllMembers { } } - self.members - .insert(place_table.place_expr(symbol_id).expect_name().clone()); + self.members.insert(Member { + name: place_table.place_expr(symbol_id).expect_name().clone(), + }); } let module_name = module.name(); @@ -190,7 +200,9 @@ impl AllMembers { .iter() .filter_map(|submodule_name| submodule_name.relative_to(module_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); for function_scope_id in attribute_scopes(db, class_body_scope) { let place_table = index.place_table(function_scope_id); - self.members - .extend(place_table.instance_attributes().cloned()); + self.members.extend( + 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 /// as an attribute on an object of the given type. -pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet { +pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet { AllMembers::of(db, ty).members }