[ty] Use bitflags for resolved client capabilities (#19393)
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 / 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 / mkdocs (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

## Summary

This PR updates the `ResolvedClientCapabilities` to be represented as
`bitflags`. This allows us to remove the `Arc` as the type becomes copy.

Additionally, this PR also fixed the goto definition and declaration
code to use the `textDocument.definition.linkSupport` and
`textDocument.declaration.linkSupport` client capability.

This PR also removes the unused client capabilities which are
`code_action_deferred_edit_resolution`, `apply_edit`, and
`document_changes` which are all related to auto-fix ability.
This commit is contained in:
Dhruv Manilawala 2025-07-17 15:31:47 +05:30 committed by GitHub
parent fae0b5c89e
commit c2a05b4825
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 156 additions and 124 deletions

1
Cargo.lock generated
View file

@ -4300,6 +4300,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"crossbeam",
"jod-thread",
"libc",

View file

@ -23,6 +23,7 @@ ty_python_semantic = { workspace = true }
ty_vendored = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
crossbeam = { workspace = true }
jod-thread = { workspace = true }
lsp-server = { workspace = true }

View file

@ -64,7 +64,7 @@ pub(super) fn clear_diagnostics(key: &DocumentKey, client: &Client) {
///
/// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
pub(super) fn publish_diagnostics(session: &Session, key: &DocumentKey, client: &Client) {
if session.client_capabilities().pull_diagnostics {
if session.client_capabilities().supports_pull_diagnostics() {
return;
}

View file

@ -96,7 +96,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
let client_capabilities = session.client_capabilities();
if project_changed {
if client_capabilities.diagnostics_refresh {
if client_capabilities.supports_workspace_diagnostic_refresh() {
client.send_request::<types::request::WorkspaceDiagnosticRefresh>(
session,
(),
@ -111,7 +111,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
// TODO: always publish diagnostics for notebook files (since they don't use pull diagnostics)
}
if client_capabilities.inlay_refresh {
if client_capabilities.supports_inlay_hint_refresh() {
client.send_request::<types::request::InlayHintRefreshRequest>(session, (), |_, ()| {});
}

View file

@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoDeclarationRequestHandler {
if snapshot
.resolved_client_capabilities()
.type_definition_link_support
.supports_declaration_link()
{
let src = Some(ranged.range);
let links: Vec<_> = ranged

View file

@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoDefinitionRequestHandler {
if snapshot
.resolved_client_capabilities()
.type_definition_link_support
.supports_definition_link()
{
let src = Some(ranged.range);
let links: Vec<_> = ranged

View file

@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler {
if snapshot
.resolved_client_capabilities()
.type_definition_link_support
.supports_type_definition_link()
{
let src = Some(ranged.range);
let links: Vec<_> = ranged

View file

@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler {
let (markup_kind, lsp_markup_kind) = if snapshot
.resolved_client_capabilities()
.hover_prefer_markdown
.prefers_markdown_in_hover()
{
(MarkupKind::Markdown, lsp_types::MarkupKind::Markdown)
} else {

View file

@ -41,7 +41,7 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRequestHandler {
snapshot.encoding(),
snapshot
.resolved_client_capabilities()
.semantic_tokens_multiline_support,
.supports_multiline_semantic_tokens(),
);
Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {

View file

@ -51,7 +51,7 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRangeRequestHandler {
snapshot.encoding(),
snapshot
.resolved_client_capabilities()
.semantic_tokens_multiline_support,
.supports_multiline_semantic_tokens(),
);
Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {

View file

@ -71,7 +71,7 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler {
.parameters
.into_iter()
.map(|param| {
let label = if resolved_capabilities.signature_label_offset_support {
let label = if resolved_capabilities.supports_signature_label_offset() {
// Find the parameter's offset in the signature label
if let Some(start) = sig.label.find(&param.label) {
let encoding = snapshot.encoding();
@ -114,11 +114,12 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler {
})
.collect();
let active_parameter = if resolved_capabilities.signature_active_parameter_support {
sig.active_parameter.and_then(|p| u32::try_from(p).ok())
} else {
None
};
let active_parameter =
if resolved_capabilities.supports_signature_active_parameter() {
sig.active_parameter.and_then(|p| u32::try_from(p).ok())
} else {
None
};
SignatureInformation {
label: sig.label,

View file

@ -62,7 +62,7 @@ pub(crate) struct Session {
position_encoding: PositionEncoding,
/// Tracks what LSP features the client supports and doesn't support.
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
resolved_client_capabilities: ResolvedClientCapabilities,
/// Tracks the pending requests between client and server.
request_queue: RequestQueue,
@ -94,9 +94,7 @@ impl Session {
index: Some(index),
default_project: DefaultProject::new(),
projects: BTreeMap::new(),
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
client_capabilities,
)),
resolved_client_capabilities: ResolvedClientCapabilities::new(client_capabilities),
request_queue: RequestQueue::new(),
shutdown_requested: false,
})
@ -332,7 +330,7 @@ impl Session {
pub(crate) fn take_document_snapshot(&self, url: Url) -> DocumentSnapshot {
let index = self.index();
DocumentSnapshot {
resolved_client_capabilities: self.resolved_client_capabilities.clone(),
resolved_client_capabilities: self.resolved_client_capabilities,
client_settings: index.global_settings(),
position_encoding: self.position_encoding,
document_query_result: self
@ -432,8 +430,8 @@ impl Session {
}
}
pub(crate) fn client_capabilities(&self) -> &ResolvedClientCapabilities {
&self.resolved_client_capabilities
pub(crate) fn client_capabilities(&self) -> ResolvedClientCapabilities {
self.resolved_client_capabilities
}
pub(crate) fn global_settings(&self) -> Arc<ClientSettings> {
@ -483,7 +481,7 @@ impl Drop for MutIndexGuard<'_> {
/// An immutable snapshot of [`Session`] that references a specific document.
#[derive(Debug)]
pub(crate) struct DocumentSnapshot {
resolved_client_capabilities: Arc<ResolvedClientCapabilities>,
resolved_client_capabilities: ResolvedClientCapabilities,
client_settings: Arc<ClientSettings>,
position_encoding: PositionEncoding,
document_query_result: Result<DocumentQuery, DocumentQueryError>,
@ -491,8 +489,8 @@ pub(crate) struct DocumentSnapshot {
impl DocumentSnapshot {
/// Returns the resolved client capabilities that were captured during initialization.
pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities {
&self.resolved_client_capabilities
pub(crate) fn resolved_client_capabilities(&self) -> ResolvedClientCapabilities {
self.resolved_client_capabilities
}
/// Returns the position encoding that was negotiated during initialization.

View file

@ -1,87 +1,121 @@
use lsp_types::{ClientCapabilities, MarkupKind};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[expect(clippy::struct_excessive_bools)]
pub(crate) struct ResolvedClientCapabilities {
pub(crate) code_action_deferred_edit_resolution: bool,
pub(crate) apply_edit: bool,
pub(crate) document_changes: bool,
pub(crate) diagnostics_refresh: bool,
pub(crate) inlay_refresh: bool,
/// Whether [pull diagnostics] is supported.
bitflags::bitflags! {
/// Represents the resolved client capabilities for the language server.
///
/// [pull diagnostics]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics
pub(crate) pull_diagnostics: bool,
/// Whether `textDocument.typeDefinition.linkSupport` is `true`
pub(crate) type_definition_link_support: bool,
/// `true`, if the first markup kind in `textDocument.hover.contentFormat` is `Markdown`
pub(crate) hover_prefer_markdown: bool,
/// Whether the client supports multiline semantic tokens
pub(crate) semantic_tokens_multiline_support: bool,
/// Whether the client supports signature label offsets in signature help
pub(crate) signature_label_offset_support: bool,
/// Whether the client supports per-signature active parameter in signature help
pub(crate) signature_active_parameter_support: bool,
/// This tracks various capabilities that the client supports.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) struct ResolvedClientCapabilities: u32 {
const WORKSPACE_DIAGNOSTIC_REFRESH = 1 << 0;
const INLAY_HINT_REFRESH = 1 << 1;
const PULL_DIAGNOSTICS = 1 << 2;
const TYPE_DEFINITION_LINK_SUPPORT = 1 << 3;
const DEFINITION_LINK_SUPPORT = 1 << 4;
const DECLARATION_LINK_SUPPORT = 1 << 5;
const PREFER_MARKDOWN_IN_HOVER = 1 << 6;
const MULTILINE_SEMANTIC_TOKENS = 1 << 7;
const SIGNATURE_LABEL_OFFSET_SUPPORT = 1 << 8;
const SIGNATURE_ACTIVE_PARAMETER_SUPPORT = 1 << 9;
}
}
impl ResolvedClientCapabilities {
/// Returns `true` if the client supports workspace diagnostic refresh.
pub(crate) const fn supports_workspace_diagnostic_refresh(self) -> bool {
self.contains(Self::WORKSPACE_DIAGNOSTIC_REFRESH)
}
/// Returns `true` if the client supports inlay hint refresh.
pub(crate) const fn supports_inlay_hint_refresh(self) -> bool {
self.contains(Self::INLAY_HINT_REFRESH)
}
/// Returns `true` if the client supports pull diagnostics.
pub(crate) const fn supports_pull_diagnostics(self) -> bool {
self.contains(Self::PULL_DIAGNOSTICS)
}
/// Returns `true` if the client supports definition links in goto type definition.
pub(crate) const fn supports_type_definition_link(self) -> bool {
self.contains(Self::TYPE_DEFINITION_LINK_SUPPORT)
}
/// Returns `true` if the client supports definition links in goto definition.
pub(crate) const fn supports_definition_link(self) -> bool {
self.contains(Self::DEFINITION_LINK_SUPPORT)
}
/// Returns `true` if the client supports definition links in goto declaration.
pub(crate) const fn supports_declaration_link(self) -> bool {
self.contains(Self::DECLARATION_LINK_SUPPORT)
}
/// Returns `true` if the client prefers markdown in hover responses.
pub(crate) const fn prefers_markdown_in_hover(self) -> bool {
self.contains(Self::PREFER_MARKDOWN_IN_HOVER)
}
/// Returns `true` if the client supports multiline semantic tokens.
pub(crate) const fn supports_multiline_semantic_tokens(self) -> bool {
self.contains(Self::MULTILINE_SEMANTIC_TOKENS)
}
/// Returns `true` if the client supports signature label offsets in signature help.
pub(crate) const fn supports_signature_label_offset(self) -> bool {
self.contains(Self::SIGNATURE_LABEL_OFFSET_SUPPORT)
}
/// Returns `true` if the client supports per-signature active parameter in signature help.
pub(crate) const fn supports_signature_active_parameter(self) -> bool {
self.contains(Self::SIGNATURE_ACTIVE_PARAMETER_SUPPORT)
}
pub(super) fn new(client_capabilities: &ClientCapabilities) -> Self {
let code_action_settings = client_capabilities
.text_document
.as_ref()
.and_then(|doc_settings| doc_settings.code_action.as_ref());
let code_action_data_support = code_action_settings
.and_then(|code_action_settings| code_action_settings.data_support)
.unwrap_or_default();
let code_action_edit_resolution = code_action_settings
.and_then(|code_action_settings| code_action_settings.resolve_support.as_ref())
.is_some_and(|resolve_support| resolve_support.properties.contains(&"edit".into()));
let mut flags = Self::empty();
let apply_edit = client_capabilities
.workspace
.as_ref()
.and_then(|workspace| workspace.apply_edit)
.unwrap_or_default();
let workspace = client_capabilities.workspace.as_ref();
let text_document = client_capabilities.text_document.as_ref();
let document_changes = client_capabilities
.workspace
.as_ref()
.and_then(|workspace| workspace.workspace_edit.as_ref()?.document_changes)
.unwrap_or_default();
let declaration_link_support = client_capabilities
.text_document
.as_ref()
.and_then(|document| document.type_definition?.link_support)
.unwrap_or_default();
let diagnostics_refresh = client_capabilities
.workspace
.as_ref()
if workspace
.and_then(|workspace| workspace.diagnostics.as_ref()?.refresh_support)
.unwrap_or_default();
.unwrap_or_default()
{
flags |= Self::WORKSPACE_DIAGNOSTIC_REFRESH;
}
let inlay_refresh = client_capabilities
.workspace
.as_ref()
if workspace
.and_then(|workspace| workspace.inlay_hint.as_ref()?.refresh_support)
.unwrap_or_default();
.unwrap_or_default()
{
flags |= Self::INLAY_HINT_REFRESH;
}
let pull_diagnostics = client_capabilities
.text_document
.as_ref()
.and_then(|text_document| text_document.diagnostic.as_ref())
.is_some();
if text_document.is_some_and(|text_document| text_document.diagnostic.is_some()) {
flags |= Self::PULL_DIAGNOSTICS;
}
let hover_prefer_markdown = client_capabilities
.text_document
.as_ref()
if text_document
.and_then(|text_document| text_document.type_definition?.link_support)
.unwrap_or_default()
{
flags |= Self::TYPE_DEFINITION_LINK_SUPPORT;
}
if text_document
.and_then(|text_document| text_document.definition?.link_support)
.unwrap_or_default()
{
flags |= Self::DEFINITION_LINK_SUPPORT;
}
if text_document
.and_then(|text_document| text_document.declaration?.link_support)
.unwrap_or_default()
{
flags |= Self::DECLARATION_LINK_SUPPORT;
}
if text_document
.and_then(|text_document| {
Some(
text_document
@ -92,18 +126,24 @@ impl ResolvedClientCapabilities {
.contains(&MarkupKind::Markdown),
)
})
.unwrap_or_default();
.unwrap_or_default()
{
flags |= Self::PREFER_MARKDOWN_IN_HOVER;
}
let semantic_tokens_multiline_support = client_capabilities
.text_document
.as_ref()
.and_then(|doc| doc.semantic_tokens.as_ref())
.and_then(|semantic_tokens| semantic_tokens.multiline_token_support)
.unwrap_or(false);
if text_document
.and_then(|text_document| {
text_document
.semantic_tokens
.as_ref()?
.multiline_token_support
})
.unwrap_or_default()
{
flags |= Self::MULTILINE_SEMANTIC_TOKENS;
}
let signature_label_offset_support = client_capabilities
.text_document
.as_ref()
if text_document
.and_then(|text_document| {
text_document
.signature_help
@ -114,11 +154,12 @@ impl ResolvedClientCapabilities {
.as_ref()?
.label_offset_support
})
.unwrap_or_default();
.unwrap_or_default()
{
flags |= Self::SIGNATURE_LABEL_OFFSET_SUPPORT;
}
let signature_active_parameter_support = client_capabilities
.text_document
.as_ref()
if text_document
.and_then(|text_document| {
text_document
.signature_help
@ -127,21 +168,11 @@ impl ResolvedClientCapabilities {
.as_ref()?
.active_parameter_support
})
.unwrap_or_default();
Self {
code_action_deferred_edit_resolution: code_action_data_support
&& code_action_edit_resolution,
apply_edit,
document_changes,
diagnostics_refresh,
inlay_refresh,
pull_diagnostics,
type_definition_link_support: declaration_link_support,
hover_prefer_markdown,
semantic_tokens_multiline_support,
signature_label_offset_support,
signature_active_parameter_support,
.unwrap_or_default()
{
flags |= Self::SIGNATURE_ACTIVE_PARAMETER_SUPPORT;
}
flags
}
}