[ty] Move CompletionKind to ty_ide

I think this is a better home for it. This way, `ty_ide`
more clearly owns how the "kind" of a completion is computed.
In particular, it is computed differently for things where
we know its type versus unimported symbols.
This commit is contained in:
Andrew Gallant 2025-09-16 13:20:43 -04:00 committed by Andrew Gallant
parent 6c3c963f8a
commit 0a2325c5fe
6 changed files with 143 additions and 155 deletions

View file

@ -5,13 +5,121 @@ use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast as ast; use ruff_python_ast as ast;
use ruff_python_parser::{Token, TokenAt, TokenKind}; use ruff_python_parser::{Token, TokenAt, TokenKind};
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::{Completion, NameKind, SemanticModel}; use ty_python_semantic::{
Completion as SemanticCompletion, ModuleName, NameKind, SemanticModel,
types::{CycleDetector, Type},
};
use crate::docstring::Docstring; use crate::docstring::Docstring;
use crate::find_node::covering_node; use crate::find_node::covering_node;
use crate::goto::DefinitionsOrTargets; use crate::goto::DefinitionsOrTargets;
use crate::{Db, all_symbols}; use crate::{Db, all_symbols};
impl<'db> Completion<'db> {
/// Returns the "kind" of this completion.
///
/// This is meant to be a very general classification of this completion.
/// Typically, this is communicated from the LSP server to a client, and
/// the client uses this information to help improve the UX (perhaps by
/// assigning an icon of some kind to the completion).
pub fn kind(&self, db: &'db dyn Db) -> Option<CompletionKind> {
type CompletionKindVisitor<'db> =
CycleDetector<CompletionKind, Type<'db>, Option<CompletionKind>>;
fn imp<'db>(
db: &'db dyn Db,
ty: Type<'db>,
visitor: &CompletionKindVisitor<'db>,
) -> Option<CompletionKind> {
Some(match ty {
Type::FunctionLiteral(_)
| Type::DataclassDecorator(_)
| Type::WrapperDescriptor(_)
| Type::DataclassTransformer(_)
| Type::Callable(_) => CompletionKind::Function,
Type::BoundMethod(_) | Type::KnownBoundMethod(_) => CompletionKind::Method,
Type::ModuleLiteral(_) => CompletionKind::Module,
Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => {
CompletionKind::Class
}
// This is a little weird for "struct." I'm mostly interpreting
// "struct" here as a more general "object." ---AG
Type::NominalInstance(_)
| Type::PropertyInstance(_)
| Type::BoundSuper(_)
| Type::TypedDict(_) => CompletionKind::Struct,
Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::TypeIs(_)
| Type::StringLiteral(_)
| Type::LiteralString
| Type::BytesLiteral(_) => CompletionKind::Value,
Type::EnumLiteral(_) => CompletionKind::Enum,
Type::ProtocolInstance(_) => CompletionKind::Interface,
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
Type::Union(union) => union
.elements(db)
.iter()
.find_map(|&ty| imp(db, ty, visitor))?,
Type::Intersection(intersection) => intersection
.iter_positive(db)
.find_map(|ty| imp(db, ty, visitor))?,
Type::Dynamic(_)
| Type::Never
| Type::SpecialForm(_)
| Type::KnownInstance(_)
| Type::AlwaysTruthy
| Type::AlwaysFalsy => return None,
Type::TypeAlias(alias) => {
visitor.visit(ty, || imp(db, alias.value_type(db), visitor))?
}
})
}
self.kind.or_else(|| {
self.ty
.and_then(|ty| imp(db, ty, &CompletionKindVisitor::default()))
})
}
}
/// The "kind" of a completion.
///
/// This is taken directly from the LSP completion specification:
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind>
///
/// The idea here is that [`Completion::kind`] defines the mapping to this from
/// `Type` (and possibly other information), which might be interesting and
/// contentious. Then the outer edges map this to the LSP types, which is
/// expected to be mundane and boring.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompletionKind {
Text,
Method,
Function,
Constructor,
Field,
Variable,
Class,
Interface,
Module,
Property,
Unit,
Value,
Enum,
Keyword,
Snippet,
Color,
File,
Reference,
Folder,
EnumMember,
Constant,
Struct,
Event,
Operator,
TypeParameter,
}
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct CompletionSettings { pub struct CompletionSettings {
pub auto_import: bool, pub auto_import: bool,

View file

@ -26,7 +26,7 @@ mod symbols;
mod workspace_symbols; mod workspace_symbols;
pub use all_symbols::{AllSymbolInfo, all_symbols}; pub use all_symbols::{AllSymbolInfo, all_symbols};
pub use completion::{CompletionSettings, completion}; pub use completion::{Completion, CompletionKind, CompletionSettings, completion};
pub use doc_highlights::document_highlights; pub use doc_highlights::document_highlights;
pub use document_symbols::document_symbols; pub use document_symbols::document_symbols;
pub use goto::{goto_declaration, goto_definition, goto_type_definition}; pub use goto::{goto_declaration, goto_definition, goto_type_definition};

View file

@ -13,7 +13,8 @@ use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
use ruff_python_ast::{Expr, Stmt}; use ruff_python_ast::{Expr, Stmt};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use ty_project::Db; use ty_project::Db;
use ty_python_semantic::CompletionKind;
use crate::completion::CompletionKind;
/// A compiled query pattern used for searching symbols. /// A compiled query pattern used for searching symbols.
/// ///

View file

@ -19,7 +19,7 @@ pub use program::{
pub use python_platform::PythonPlatform; pub use python_platform::PythonPlatform;
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
pub use semantic_model::{ pub use semantic_model::{
Completion, CompletionKind, HasDefinition, HasType, NameKind, SemanticModel, Completion, HasDefinition, HasType, MemberDefinition, NameKind, SemanticModel,
}; };
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin}; pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
pub use types::DisplaySettings; pub use types::DisplaySettings;

View file

@ -11,7 +11,7 @@ use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::FileScopeId; use crate::semantic_index::scope::FileScopeId;
use crate::semantic_index::semantic_index; use crate::semantic_index::semantic_index;
use crate::types::ide_support::all_declarations_and_bindings; use crate::types::ide_support::all_declarations_and_bindings;
use crate::types::{CycleDetector, Type, binding_type, infer_scope_types}; use crate::types::{Type, binding_type, infer_scope_types};
pub struct SemanticModel<'db> { pub struct SemanticModel<'db> {
db: &'db dyn Db, db: &'db dyn Db,
@ -51,7 +51,6 @@ impl<'db> SemanticModel<'db> {
Completion { Completion {
name: Name::new(module.name(self.db).as_str()), name: Name::new(module.name(self.db).as_str()),
ty: Some(ty), ty: Some(ty),
kind: None,
builtin, builtin,
} }
}) })
@ -166,7 +165,6 @@ impl<'db> SemanticModel<'db> {
completions.push(Completion { completions.push(Completion {
name, name,
ty: Some(ty), ty: Some(ty),
kind: None,
builtin, builtin,
}); });
} }
@ -187,7 +185,6 @@ impl<'db> SemanticModel<'db> {
completions.push(Completion { completions.push(Completion {
name: Name::new(base), name: Name::new(base),
ty: Some(ty), ty: Some(ty),
kind: None,
builtin, builtin,
}); });
} }
@ -202,7 +199,6 @@ impl<'db> SemanticModel<'db> {
.map(|member| Completion { .map(|member| Completion {
name: member.name, name: member.name,
ty: Some(member.ty), ty: Some(member.ty),
kind: None,
builtin: false, builtin: false,
}) })
.collect() .collect()
@ -239,7 +235,6 @@ impl<'db> SemanticModel<'db> {
.map(|memberdef| Completion { .map(|memberdef| Completion {
name: memberdef.member.name, name: memberdef.member.name,
ty: Some(memberdef.member.ty), ty: Some(memberdef.member.ty),
kind: None,
builtin: false, builtin: false,
}), }),
); );
@ -296,15 +291,6 @@ pub struct Completion<'db> {
/// an unimported symbol. In that case, computing the /// an unimported symbol. In that case, computing the
/// type of all such symbols could be quite expensive. /// type of all such symbols could be quite expensive.
pub ty: Option<Type<'db>>, 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. /// 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
@ -314,110 +300,6 @@ pub struct Completion<'db> {
pub builtin: bool, pub builtin: bool,
} }
impl<'db> Completion<'db> {
/// Returns the "kind" of this completion.
///
/// This is meant to be a very general classification of this completion.
/// Typically, this is communicated from the LSP server to a client, and
/// the client uses this information to help improve the UX (perhaps by
/// assigning an icon of some kind to the completion).
pub fn kind(&self, db: &'db dyn Db) -> Option<CompletionKind> {
fn imp<'db>(
db: &'db dyn Db,
ty: Type<'db>,
visitor: &CompletionKindVisitor<'db>,
) -> Option<CompletionKind> {
Some(match ty {
Type::FunctionLiteral(_)
| Type::DataclassDecorator(_)
| Type::WrapperDescriptor(_)
| Type::DataclassTransformer(_)
| Type::Callable(_) => CompletionKind::Function,
Type::BoundMethod(_) | Type::KnownBoundMethod(_) => CompletionKind::Method,
Type::ModuleLiteral(_) => CompletionKind::Module,
Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => {
CompletionKind::Class
}
// This is a little weird for "struct." I'm mostly interpreting
// "struct" here as a more general "object." ---AG
Type::NominalInstance(_)
| Type::PropertyInstance(_)
| Type::BoundSuper(_)
| Type::TypedDict(_) => CompletionKind::Struct,
Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::TypeIs(_)
| Type::StringLiteral(_)
| Type::LiteralString
| Type::BytesLiteral(_) => CompletionKind::Value,
Type::EnumLiteral(_) => CompletionKind::Enum,
Type::ProtocolInstance(_) => CompletionKind::Interface,
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
Type::Union(union) => union
.elements(db)
.iter()
.find_map(|&ty| imp(db, ty, visitor))?,
Type::Intersection(intersection) => intersection
.iter_positive(db)
.find_map(|ty| imp(db, ty, visitor))?,
Type::Dynamic(_)
| Type::Never
| Type::SpecialForm(_)
| Type::KnownInstance(_)
| Type::AlwaysTruthy
| Type::AlwaysFalsy => return None,
Type::TypeAlias(alias) => {
visitor.visit(ty, || imp(db, alias.value_type(db), visitor))?
}
})
}
self.kind.or_else(|| {
self.ty
.and_then(|ty| imp(db, ty, &CompletionKindVisitor::default()))
})
}
}
type CompletionKindVisitor<'db> = CycleDetector<CompletionKind, Type<'db>, Option<CompletionKind>>;
/// The "kind" of a completion.
///
/// This is taken directly from the LSP completion specification:
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind>
///
/// The idea here is that `Completion::kind` defines the mapping to this from
/// `Type` (and possibly other information), which might be interesting and
/// contentious. Then the outer edges map this to the LSP types, which is
/// expected to be mundane and boring.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompletionKind {
Text,
Method,
Function,
Constructor,
Field,
Variable,
Class,
Interface,
Module,
Property,
Unit,
Value,
Enum,
Keyword,
Snippet,
Color,
File,
Reference,
Folder,
EnumMember,
Constant,
Struct,
Event,
Operator,
TypeParameter,
}
pub trait HasType { pub trait HasType {
/// Returns the inferred type of `self`. /// Returns the inferred type of `self`.
/// ///

View file

@ -425,14 +425,11 @@ 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.inner.name.into(), name: completion.name.into(),
documentation: completion documentation: completion
.documentation .documentation
.map(|documentation| documentation.render_plaintext()), .map(|documentation| documentation.render_plaintext()),
detail: completion detail: completion.ty.map(|ty| ty.display(&self.db).to_string()),
.inner
.ty
.map(|ty| ty.display(&self.db).to_string()),
}) })
.collect()) .collect())
} }
@ -956,34 +953,34 @@ pub enum CompletionKind {
TypeParameter, TypeParameter,
} }
impl From<ty_python_semantic::CompletionKind> for CompletionKind { impl From<ty_ide::CompletionKind> for CompletionKind {
fn from(value: ty_python_semantic::CompletionKind) -> Self { fn from(value: ty_ide::CompletionKind) -> Self {
match value { match value {
ty_python_semantic::CompletionKind::Text => Self::Text, ty_ide::CompletionKind::Text => Self::Text,
ty_python_semantic::CompletionKind::Method => Self::Method, ty_ide::CompletionKind::Method => Self::Method,
ty_python_semantic::CompletionKind::Function => Self::Function, ty_ide::CompletionKind::Function => Self::Function,
ty_python_semantic::CompletionKind::Constructor => Self::Constructor, ty_ide::CompletionKind::Constructor => Self::Constructor,
ty_python_semantic::CompletionKind::Field => Self::Field, ty_ide::CompletionKind::Field => Self::Field,
ty_python_semantic::CompletionKind::Variable => Self::Variable, ty_ide::CompletionKind::Variable => Self::Variable,
ty_python_semantic::CompletionKind::Class => Self::Class, ty_ide::CompletionKind::Class => Self::Class,
ty_python_semantic::CompletionKind::Interface => Self::Interface, ty_ide::CompletionKind::Interface => Self::Interface,
ty_python_semantic::CompletionKind::Module => Self::Module, ty_ide::CompletionKind::Module => Self::Module,
ty_python_semantic::CompletionKind::Property => Self::Property, ty_ide::CompletionKind::Property => Self::Property,
ty_python_semantic::CompletionKind::Unit => Self::Unit, ty_ide::CompletionKind::Unit => Self::Unit,
ty_python_semantic::CompletionKind::Value => Self::Value, ty_ide::CompletionKind::Value => Self::Value,
ty_python_semantic::CompletionKind::Enum => Self::Enum, ty_ide::CompletionKind::Enum => Self::Enum,
ty_python_semantic::CompletionKind::Keyword => Self::Keyword, ty_ide::CompletionKind::Keyword => Self::Keyword,
ty_python_semantic::CompletionKind::Snippet => Self::Snippet, ty_ide::CompletionKind::Snippet => Self::Snippet,
ty_python_semantic::CompletionKind::Color => Self::Color, ty_ide::CompletionKind::Color => Self::Color,
ty_python_semantic::CompletionKind::File => Self::File, ty_ide::CompletionKind::File => Self::File,
ty_python_semantic::CompletionKind::Reference => Self::Reference, ty_ide::CompletionKind::Reference => Self::Reference,
ty_python_semantic::CompletionKind::Folder => Self::Folder, ty_ide::CompletionKind::Folder => Self::Folder,
ty_python_semantic::CompletionKind::EnumMember => Self::EnumMember, ty_ide::CompletionKind::EnumMember => Self::EnumMember,
ty_python_semantic::CompletionKind::Constant => Self::Constant, ty_ide::CompletionKind::Constant => Self::Constant,
ty_python_semantic::CompletionKind::Struct => Self::Struct, ty_ide::CompletionKind::Struct => Self::Struct,
ty_python_semantic::CompletionKind::Event => Self::Event, ty_ide::CompletionKind::Event => Self::Event,
ty_python_semantic::CompletionKind::Operator => Self::Operator, ty_ide::CompletionKind::Operator => Self::Operator,
ty_python_semantic::CompletionKind::TypeParameter => Self::TypeParameter, ty_ide::CompletionKind::TypeParameter => Self::TypeParameter,
} }
} }
} }