[ty] IDE: only provide declarations and bindings as completions (#18456)

## Summary

Previously, all symbols where provided as possible completions. In an
example like the following, both `foo` and `f` were suggested as
completions, because `f` itself is a symbol.
```py
foo = 1

f<CURSOR>
```
Similarly, in the following example, `hidden_symbol` was suggested, even
though it is not statically visible:
```py
if 1 + 2 != 3:
    hidden_symbol = 1

hidden_<CURSOR>
```

With the change suggested here, we only use statically visible
declarations and bindings as a source for completions.


## Test Plan

- Updated snapshot tests
- New test for statically hidden definitions
- Added test for star import
This commit is contained in:
David Peter 2025-06-04 16:11:05 +02:00 committed by GitHub
parent 11db567b0b
commit f1883d71a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 86 deletions

View file

@ -10,6 +10,7 @@ use crate::module_resolver::{Module, resolve_module};
use crate::semantic_index::ast_ids::HasScopedExpressionId;
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::FileScopeId;
use crate::types::ide_support::all_declarations_and_bindings;
use crate::types::{Type, binding_type, infer_scope_types};
pub struct SemanticModel<'db> {
@ -66,9 +67,10 @@ impl<'db> SemanticModel<'db> {
};
let mut symbols = vec![];
for (file_scope, _) in index.ancestor_scopes(file_scope) {
for symbol in index.symbol_table(file_scope).symbols() {
symbols.push(symbol.name().clone());
}
symbols.extend(all_declarations_and_bindings(
self.db,
file_scope.to_scope_id(self.db, self.file),
));
}
symbols
}

View file

@ -65,7 +65,7 @@ mod diagnostic;
mod display;
mod function;
mod generics;
mod ide_support;
pub(crate) mod ide_support;
mod infer;
mod instance;
mod mro;

View file

@ -8,6 +8,37 @@ use crate::types::{ClassBase, ClassLiteral, KnownClass, Type};
use ruff_python_ast::name::Name;
use rustc_hash::FxHashSet;
pub(crate) fn all_declarations_and_bindings<'db>(
db: &'db dyn Db,
scope_id: ScopeId<'db>,
) -> impl Iterator<Item = Name> + 'db {
let use_def_map = use_def_map(db, scope_id);
let symbol_table = symbol_table(db, scope_id);
use_def_map
.all_public_declarations()
.filter_map(move |(symbol_id, declarations)| {
if symbol_from_declarations(db, declarations)
.is_ok_and(|result| !result.symbol.is_unbound())
{
Some(symbol_table.symbol(symbol_id).name().clone())
} else {
None
}
})
.chain(
use_def_map
.all_public_bindings()
.filter_map(move |(symbol_id, bindings)| {
if symbol_from_bindings(db, bindings).is_unbound() {
None
} else {
Some(symbol_table.symbol(symbol_id).name().clone())
}
}),
)
}
struct AllMembers {
members: FxHashSet<Name>,
}
@ -118,24 +149,8 @@ impl AllMembers {
}
fn extend_with_declarations_and_bindings(&mut self, db: &dyn Db, scope_id: ScopeId) {
let use_def_map = use_def_map(db, scope_id);
let symbol_table = symbol_table(db, scope_id);
for (symbol_id, declarations) in use_def_map.all_public_declarations() {
if symbol_from_declarations(db, declarations)
.is_ok_and(|result| !result.symbol.is_unbound())
{
self.members
.insert(symbol_table.symbol(symbol_id).name().clone());
}
}
for (symbol_id, bindings) in use_def_map.all_public_bindings() {
if !symbol_from_bindings(db, bindings).is_unbound() {
self.members
.insert(symbol_table.symbol(symbol_id).name().clone());
}
}
self.members
.extend(all_declarations_and_bindings(db, scope_id));
}
fn extend_with_class_members<'db>(