[ty] add docstrings to completions based on type (#20008)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This is a fairly simple but effective way to add docstrings to like 95%
of completions from initial experimentation.

Fixes https://github.com/astral-sh/ty/issues/1036

Although ironically this approach *does not* work specifically for
`print` and I haven't looked into why.
This commit is contained in:
Aria Desires 2025-08-20 17:00:09 -04:00 committed by GitHub
parent 7b75aee21d
commit 859475f017
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 64 additions and 9 deletions

View file

@ -8,9 +8,11 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::{Completion, NameKind, SemanticModel}; use ty_python_semantic::{Completion, NameKind, SemanticModel};
use crate::Db; use crate::Db;
use crate::docstring::Docstring;
use crate::find_node::covering_node; use crate::find_node::covering_node;
use crate::goto::DefinitionsOrTargets;
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'_>> { pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedCompletion<'_>> {
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 {
@ -40,6 +42,27 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'
completions.sort_by(compare_suggestions); completions.sort_by(compare_suggestions);
completions.dedup_by(|c1, c2| c1.name == c2.name); completions.dedup_by(|c1, c2| c1.name == c2.name);
completions completions
.into_iter()
.map(|completion| {
let definition = DefinitionsOrTargets::from_ty(db, completion.ty);
let documentation = definition.and_then(|def| def.docstring(db));
DetailedCompletion {
inner: completion,
documentation,
}
})
.collect()
}
pub struct DetailedCompletion<'db> {
pub inner: Completion<'db>,
pub documentation: Option<Docstring>,
}
impl<'db> std::ops::Deref for DetailedCompletion<'db> {
type Target = Completion<'db>;
fn deref(&self) -> &Self::Target {
&self.inner
}
} }
/// The kind of tokens identified under the cursor. /// The kind of tokens identified under the cursor.
@ -478,9 +501,8 @@ fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
mod tests { mod tests {
use insta::assert_snapshot; use insta::assert_snapshot;
use ruff_python_parser::{Mode, ParseOptions, TokenKind, Tokens}; use ruff_python_parser::{Mode, ParseOptions, TokenKind, Tokens};
use ty_python_semantic::Completion;
use crate::completion; use crate::completion::{DetailedCompletion, completion};
use crate::tests::{CursorTest, cursor_test}; use crate::tests::{CursorTest, cursor_test};
use super::token_suffix_by_kinds; use super::token_suffix_by_kinds;
@ -3022,14 +3044,14 @@ from os.<CURSOR>
) )
} }
fn completions_if(&self, predicate: impl Fn(&Completion) -> bool) -> String { fn completions_if(&self, predicate: impl Fn(&DetailedCompletion) -> bool) -> String {
self.completions_if_snapshot(predicate, |c| c.name.as_str().to_string()) self.completions_if_snapshot(predicate, |c| c.name.as_str().to_string())
} }
fn completions_if_snapshot( fn completions_if_snapshot(
&self, &self,
predicate: impl Fn(&Completion) -> bool, predicate: impl Fn(&DetailedCompletion) -> bool,
snapshot: impl Fn(&Completion) -> String, snapshot: impl Fn(&DetailedCompletion) -> String,
) -> String { ) -> String {
let completions = completion(&self.db, self.cursor.file, self.cursor.offset); let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
if completions.is_empty() { if completions.is_empty() {

View file

@ -159,6 +159,28 @@ pub(crate) enum DefinitionsOrTargets<'db> {
} }
impl<'db> DefinitionsOrTargets<'db> { impl<'db> DefinitionsOrTargets<'db> {
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
let ty_def = ty.definition(db)?;
let resolved = match ty_def {
ty_python_semantic::types::TypeDefinition::Module(module) => {
ResolvedDefinition::Module(module.file(db)?)
}
ty_python_semantic::types::TypeDefinition::Class(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::Function(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::TypeVar(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::TypeAlias(definition) => {
ResolvedDefinition::Definition(definition)
}
};
Some(DefinitionsOrTargets::Definitions(vec![resolved]))
}
/// Get the "goto-declaration" interpretation of this definition /// Get the "goto-declaration" interpretation of this definition
/// ///
/// In this case it basically returns exactly what was found. /// In this case it basically returns exactly what was found.

View file

@ -2,7 +2,9 @@ use std::borrow::Cow;
use std::time::Instant; use std::time::Instant;
use lsp_types::request::Completion; use lsp_types::request::Completion;
use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Url}; use lsp_types::{
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Documentation, Url,
};
use ruff_db::source::{line_index, source_text}; use ruff_db::source::{line_index, source_text};
use ty_ide::completion; use ty_ide::completion;
use ty_project::ProjectDatabase; use ty_project::ProjectDatabase;
@ -64,9 +66,12 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
.map(|(i, comp)| { .map(|(i, comp)| {
let kind = comp.kind(db).map(ty_kind_to_lsp_kind); let kind = comp.kind(db).map(ty_kind_to_lsp_kind);
CompletionItem { CompletionItem {
label: comp.name.into(), label: comp.inner.name.into(),
kind, kind,
sort_text: Some(format!("{i:-max_index_len$}")), sort_text: Some(format!("{i:-max_index_len$}")),
documentation: comp
.documentation
.map(|docstring| Documentation::String(docstring.render_plaintext())),
..Default::default() ..Default::default()
} }
}) })

View file

@ -421,7 +421,10 @@ impl Workspace {
.into_iter() .into_iter()
.map(|completion| Completion { .map(|completion| Completion {
kind: completion.kind(&self.db).map(CompletionKind::from), kind: completion.kind(&self.db).map(CompletionKind::from),
name: completion.name.into(), name: completion.inner.name.into(),
documentation: completion
.documentation
.map(|documentation| documentation.render_plaintext()),
}) })
.collect()) .collect())
} }
@ -908,6 +911,8 @@ pub struct Completion {
#[wasm_bindgen(getter_with_clone)] #[wasm_bindgen(getter_with_clone)]
pub name: String, pub name: String,
pub kind: Option<CompletionKind>, pub kind: Option<CompletionKind>,
#[wasm_bindgen(getter_with_clone)]
pub documentation: Option<String>,
} }
#[wasm_bindgen] #[wasm_bindgen]

View file

@ -319,6 +319,7 @@ class PlaygroundServer
? CompletionItemKind.Variable ? CompletionItemKind.Variable
: mapCompletionKind(completion.kind), : mapCompletionKind(completion.kind),
insertText: completion.name, insertText: completion.name,
documentation: completion.documentation,
// TODO(micha): It's unclear why this field is required for monaco but not VS Code. // TODO(micha): It's unclear why this field is required for monaco but not VS Code.
// and omitting it works just fine? The LSP doesn't expose this information right now // and omitting it works just fine? The LSP doesn't expose this information right now
// which is why we go with undefined for now. // which is why we go with undefined for now.