mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Add naive implementation of completions for unimported symbols
This re-works the `all_symbols` based added previously to work across all modules available, and not just what is directly in the workspace. Note that we always pass an empty string as a query, which makes the results always empty. We'll fix this in a subsequent commit.
This commit is contained in:
parent
78db56e362
commit
8e52027a88
9 changed files with 121 additions and 34 deletions
|
@ -1,6 +1,8 @@
|
|||
use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only};
|
||||
use ruff_db::files::File;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::all_modules;
|
||||
|
||||
use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only};
|
||||
|
||||
/// Get all symbols matching the query string.
|
||||
///
|
||||
|
@ -12,29 +14,29 @@ pub fn all_symbols(db: &dyn Db, query: &str) -> Vec<AllSymbolInfo> {
|
|||
return Vec::new();
|
||||
}
|
||||
|
||||
let project = db.project();
|
||||
|
||||
let query = QueryPattern::new(query);
|
||||
let files = project.files(db);
|
||||
let results = std::sync::Mutex::new(Vec::new());
|
||||
{
|
||||
let modules = all_modules(db);
|
||||
let db = db.dyn_clone();
|
||||
let files = &files;
|
||||
let results = &results;
|
||||
let query = &query;
|
||||
|
||||
rayon::scope(move |s| {
|
||||
// For each file, extract symbols and add them to results
|
||||
for file in files.iter() {
|
||||
for module in modules {
|
||||
let db = db.dyn_clone();
|
||||
let Some(file) = module.file(&*db) else {
|
||||
continue;
|
||||
};
|
||||
s.spawn(move |_| {
|
||||
for (_, symbol) in symbols_for_file_global_only(&*db, *file).search(query) {
|
||||
for (_, symbol) in symbols_for_file_global_only(&*db, file).search(query) {
|
||||
// It seems like we could do better here than
|
||||
// locking `results` for every single symbol,
|
||||
// but this works pretty well as it is.
|
||||
results.lock().unwrap().push(AllSymbolInfo {
|
||||
symbol: symbol.to_owned(),
|
||||
file: *file,
|
||||
file,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -42,7 +44,13 @@ pub fn all_symbols(db: &dyn Db, query: &str) -> Vec<AllSymbolInfo> {
|
|||
});
|
||||
}
|
||||
|
||||
results.into_inner().unwrap()
|
||||
let mut results = results.into_inner().unwrap();
|
||||
results.sort_by(|s1, s2| {
|
||||
let key1 = (&s1.symbol.name, s1.file.path(db).as_str());
|
||||
let key2 = (&s2.symbol.name, s2.file.path(db).as_str());
|
||||
key1.cmp(&key2)
|
||||
});
|
||||
results
|
||||
}
|
||||
|
||||
/// A symbol found in the workspace and dependencies, including the
|
||||
|
@ -97,17 +105,15 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
|||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.all_symbols("ufunc"), @r"
|
||||
assert_snapshot!(test.all_symbols("acegikmo"), @r"
|
||||
info[all-symbols]: AllSymbolInfo
|
||||
--> utils.py:2:5
|
||||
--> constants.py:2:1
|
||||
|
|
||||
2 | ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Function utility_function
|
||||
");
|
||||
info: Constant ABCDEFGHIJKLMNOP
|
||||
|
||||
assert_snapshot!(test.all_symbols("data"), @r"
|
||||
info[all-symbols]: AllSymbolInfo
|
||||
--> models.py:2:7
|
||||
|
|
||||
|
@ -116,11 +122,10 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
|||
3 | '''A data model class'''
|
||||
4 | def __init__(self):
|
||||
|
|
||||
info: Class DataModel
|
||||
");
|
||||
info: Class Abcdefghijklmnop
|
||||
|
||||
info[all-symbols]: AllSymbolInfo
|
||||
--> constants.py:2:1
|
||||
--> utils.py:2:5
|
||||
|
|
||||
2 | def abcdefghijklmnop():
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -7,10 +7,10 @@ use ruff_python_parser::{Token, TokenAt, TokenKind};
|
|||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::{Completion, NameKind, SemanticModel};
|
||||
|
||||
use crate::Db;
|
||||
use crate::docstring::Docstring;
|
||||
use crate::find_node::covering_node;
|
||||
use crate::goto::DefinitionsOrTargets;
|
||||
use crate::{Db, all_symbols};
|
||||
|
||||
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedCompletion<'_>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
|
@ -37,14 +37,27 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedComp
|
|||
CompletionTargetAst::Import { .. } | CompletionTargetAst::ImportViaFrom { .. } => {
|
||||
model.import_completions()
|
||||
}
|
||||
CompletionTargetAst::Scoped { node } => model.scoped_completions(node),
|
||||
CompletionTargetAst::Scoped { node } => {
|
||||
let mut completions = model.scoped_completions(node);
|
||||
for symbol in all_symbols(db, "") {
|
||||
completions.push(Completion {
|
||||
name: ast::name::Name::new(&symbol.symbol.name),
|
||||
ty: None,
|
||||
kind: symbol.symbol.kind.to_completion_kind(),
|
||||
builtin: false,
|
||||
});
|
||||
}
|
||||
completions
|
||||
}
|
||||
};
|
||||
completions.sort_by(compare_suggestions);
|
||||
completions.dedup_by(|c1, c2| c1.name == c2.name);
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|completion| {
|
||||
let definition = DefinitionsOrTargets::from_ty(db, completion.ty);
|
||||
let definition = completion
|
||||
.ty
|
||||
.and_then(|ty| DefinitionsOrTargets::from_ty(db, ty));
|
||||
let documentation = definition.and_then(|def| def.docstring(db));
|
||||
DetailedCompletion {
|
||||
inner: completion,
|
||||
|
@ -3040,7 +3053,14 @@ from os.<CURSOR>
|
|||
fn completions_without_builtins_with_types(&self) -> String {
|
||||
self.completions_if_snapshot(
|
||||
|c| !c.builtin,
|
||||
|c| format!("{} :: {}", c.name, c.ty.display(&self.db)),
|
||||
|c| {
|
||||
format!(
|
||||
"{} :: {}",
|
||||
c.name,
|
||||
c.ty.map(|ty| ty.display(&self.db).to_string())
|
||||
.unwrap_or_else(|| "Unavailable".to_string())
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
|
|||
use ruff_python_ast::{Expr, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::CompletionKind;
|
||||
|
||||
/// A compiled query pattern used for searching symbols.
|
||||
///
|
||||
|
@ -282,6 +283,27 @@ impl SymbolKind {
|
|||
SymbolKind::Import => "Import",
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps this to a "completion" kind if a sensible mapping exists.
|
||||
pub fn to_completion_kind(self) -> Option<CompletionKind> {
|
||||
Some(match self {
|
||||
SymbolKind::Module => CompletionKind::Module,
|
||||
SymbolKind::Class => CompletionKind::Class,
|
||||
SymbolKind::Method => CompletionKind::Method,
|
||||
SymbolKind::Function => CompletionKind::Function,
|
||||
SymbolKind::Variable => CompletionKind::Variable,
|
||||
SymbolKind::Constant => CompletionKind::Constant,
|
||||
SymbolKind::Property => CompletionKind::Property,
|
||||
SymbolKind::Field => CompletionKind::Field,
|
||||
SymbolKind::Constructor => CompletionKind::Constructor,
|
||||
SymbolKind::Parameter => CompletionKind::Variable,
|
||||
SymbolKind::TypeParameter => CompletionKind::TypeParameter,
|
||||
// Not quite sure what to do with this one. I guess
|
||||
// in theory the import should be "resolved" to its
|
||||
// underlying kind, but that seems expensive.
|
||||
SymbolKind::Import => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a flat list of symbols in the file given.
|
||||
|
|
|
@ -11,8 +11,8 @@ use crate::suppression::{INVALID_IGNORE_COMMENT, UNKNOWN_RULE, UNUSED_IGNORE_COM
|
|||
pub use db::Db;
|
||||
pub use module_name::ModuleName;
|
||||
pub use module_resolver::{
|
||||
Module, SearchPath, SearchPathValidationError, SearchPaths, list_modules, resolve_module,
|
||||
resolve_real_module, system_module_search_paths,
|
||||
Module, SearchPath, SearchPathValidationError, SearchPaths, all_modules, list_modules,
|
||||
resolve_module, resolve_real_module, system_module_search_paths,
|
||||
};
|
||||
pub use program::{
|
||||
Program, ProgramSettings, PythonVersionFileSource, PythonVersionSource,
|
||||
|
|
|
@ -12,6 +12,20 @@ use super::resolver::{
|
|||
ModuleResolveMode, ResolverContext, is_non_shadowable, resolve_file_module, search_paths,
|
||||
};
|
||||
|
||||
/// List all available modules, including all sub-modules, sorted in lexicographic order.
|
||||
pub fn all_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
||||
let mut modules = list_modules(db);
|
||||
let mut stack = modules.clone();
|
||||
while let Some(module) = stack.pop() {
|
||||
for &submodule in module.all_submodules(db) {
|
||||
modules.push(submodule);
|
||||
stack.push(submodule);
|
||||
}
|
||||
}
|
||||
modules.sort_by_key(|module| module.name(db));
|
||||
modules
|
||||
}
|
||||
|
||||
/// List all available top-level modules.
|
||||
#[salsa::tracked]
|
||||
pub fn list_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::iter::FusedIterator;
|
||||
|
||||
pub use list::list_modules;
|
||||
pub use list::{all_modules, list_modules};
|
||||
pub(crate) use module::KnownModule;
|
||||
pub use module::Module;
|
||||
pub use path::{SearchPath, SearchPathValidationError};
|
||||
|
|
|
@ -50,7 +50,8 @@ impl<'db> SemanticModel<'db> {
|
|||
let ty = Type::module_literal(self.db, self.file, module);
|
||||
Completion {
|
||||
name: Name::new(module.name(self.db).as_str()),
|
||||
ty,
|
||||
ty: Some(ty),
|
||||
kind: None,
|
||||
builtin,
|
||||
}
|
||||
})
|
||||
|
@ -162,7 +163,12 @@ impl<'db> SemanticModel<'db> {
|
|||
|
||||
let mut completions = vec![];
|
||||
for crate::types::Member { name, ty } in crate::types::all_members(self.db, ty) {
|
||||
completions.push(Completion { name, ty, builtin });
|
||||
completions.push(Completion {
|
||||
name,
|
||||
ty: Some(ty),
|
||||
kind: None,
|
||||
builtin,
|
||||
});
|
||||
}
|
||||
completions.extend(self.submodule_completions(&module));
|
||||
completions
|
||||
|
@ -177,7 +183,8 @@ impl<'db> SemanticModel<'db> {
|
|||
let ty = Type::module_literal(self.db, self.file, *submodule);
|
||||
completions.push(Completion {
|
||||
name: Name::new(submodule.name(self.db).as_str()),
|
||||
ty,
|
||||
ty: Some(ty),
|
||||
kind: None,
|
||||
builtin,
|
||||
});
|
||||
}
|
||||
|
@ -191,7 +198,8 @@ impl<'db> SemanticModel<'db> {
|
|||
.into_iter()
|
||||
.map(|member| Completion {
|
||||
name: member.name,
|
||||
ty: member.ty,
|
||||
ty: Some(member.ty),
|
||||
kind: None,
|
||||
builtin: false,
|
||||
})
|
||||
.collect()
|
||||
|
@ -227,7 +235,8 @@ impl<'db> SemanticModel<'db> {
|
|||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
||||
.map(|member| Completion {
|
||||
name: member.name,
|
||||
ty: member.ty,
|
||||
ty: Some(member.ty),
|
||||
kind: None,
|
||||
builtin: false,
|
||||
}),
|
||||
);
|
||||
|
@ -277,8 +286,22 @@ impl NameKind {
|
|||
pub struct Completion<'db> {
|
||||
/// The label shown to the user for this suggestion.
|
||||
pub name: Name,
|
||||
/// The type of this completion.
|
||||
pub ty: Type<'db>,
|
||||
/// The type of this completion, if available.
|
||||
///
|
||||
/// Generally speaking, this is always available
|
||||
/// *unless* this was a completion corresponding to
|
||||
/// an unimported symbol. In that case, computing the
|
||||
/// type of all such symbols could be quite expensive.
|
||||
pub ty: Option<Type<'db>>,
|
||||
/// The "kind" of this completion.
|
||||
///
|
||||
/// When this is set, it takes priority over any kind
|
||||
/// inferred from `ty`.
|
||||
///
|
||||
/// Usually this is set when `ty` is `None`, since it
|
||||
/// may be cheaper to compute at scale. (e.g., For
|
||||
/// unimported symbol completions.)
|
||||
pub kind: Option<CompletionKind>,
|
||||
/// Whether this suggestion came from builtins or not.
|
||||
///
|
||||
/// At time of writing (2025-06-26), this information
|
||||
|
@ -336,7 +359,7 @@ impl<'db> Completion<'db> {
|
|||
Type::TypeAlias(alias) => imp(db, alias.value_type(db))?,
|
||||
})
|
||||
}
|
||||
imp(db, self.ty)
|
||||
self.kind.or_else(|| self.ty.and_then(|ty| imp(db, ty)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
|||
label: comp.inner.name.into(),
|
||||
kind,
|
||||
sort_text: Some(format!("{i:-max_index_len$}")),
|
||||
detail: comp.inner.ty.display(db).to_string().into(),
|
||||
detail: comp.inner.ty.map(|ty| ty.display(db).to_string()),
|
||||
documentation: comp
|
||||
.documentation
|
||||
.map(|docstring| Documentation::String(docstring.render_plaintext())),
|
||||
|
|
|
@ -425,7 +425,10 @@ impl Workspace {
|
|||
documentation: completion
|
||||
.documentation
|
||||
.map(|documentation| documentation.render_plaintext()),
|
||||
detail: completion.inner.ty.display(&self.db).to_string().into(),
|
||||
detail: completion
|
||||
.inner
|
||||
.ty
|
||||
.map(|ty| ty.display(&self.db).to_string()),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue