Skip to content

Commit 5c8297d

Browse files
committed
[ty] Add ty.inlayHints.variablesTypes server option
1 parent b005cdb commit 5c8297d

File tree

8 files changed

+182
-19
lines changed

8 files changed

+182
-19
lines changed

crates/ty_ide/src/inlay_hints.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ impl fmt::Display for DisplayInlayHint<'_, '_> {
5151
}
5252
}
5353

54-
pub fn inlay_hints(db: &dyn Db, file: File, range: TextRange) -> Vec<InlayHint<'_>> {
55-
let mut visitor = InlayHintVisitor::new(db, file, range);
54+
pub fn inlay_hints<'db>(
55+
db: &'db dyn Db,
56+
file: File,
57+
range: TextRange,
58+
settings: &InlayHintSettings,
59+
) -> Vec<InlayHint<'db>> {
60+
let mut visitor = InlayHintVisitor::new(db, file, range, settings);
5661

5762
let ast = parsed_module(db, file).load(db);
5863

@@ -61,20 +66,34 @@ pub fn inlay_hints(db: &dyn Db, file: File, range: TextRange) -> Vec<InlayHint<'
6166
visitor.hints
6267
}
6368

64-
struct InlayHintVisitor<'db> {
69+
/// Settings to control the behavior of inlay hints.
70+
#[derive(Clone, Default, Debug)]
71+
pub struct InlayHintSettings {
72+
/// Whether to show variable type hints.
73+
///
74+
/// For example, this would enable / disable hints like the ones quoted below:
75+
/// ```python
76+
/// x": Literal[1]" = 1
77+
/// ```
78+
pub variable_types: bool,
79+
}
80+
81+
struct InlayHintVisitor<'a, 'db> {
6582
model: SemanticModel<'db>,
6683
hints: Vec<InlayHint<'db>>,
6784
in_assignment: bool,
6885
range: TextRange,
86+
settings: &'a InlayHintSettings,
6987
}
7088

71-
impl<'db> InlayHintVisitor<'db> {
72-
fn new(db: &'db dyn Db, file: File, range: TextRange) -> Self {
89+
impl<'a, 'db> InlayHintVisitor<'a, 'db> {
90+
fn new(db: &'db dyn Db, file: File, range: TextRange, settings: &'a InlayHintSettings) -> Self {
7391
Self {
7492
model: SemanticModel::new(db, file),
7593
hints: Vec::new(),
7694
in_assignment: false,
7795
range,
96+
settings,
7897
}
7998
}
8099

@@ -86,7 +105,7 @@ impl<'db> InlayHintVisitor<'db> {
86105
}
87106
}
88107

89-
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_> {
108+
impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
90109
fn enter_node(&mut self, node: AnyNodeRef<'_>) -> TraversalSignal {
91110
if self.range.intersect(node.range()).is_some() {
92111
TraversalSignal::Traverse
@@ -104,6 +123,10 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_> {
104123

105124
match stmt {
106125
Stmt::Assign(assign) => {
126+
if !self.settings.variable_types {
127+
return;
128+
}
129+
107130
self.in_assignment = true;
108131
for target in &assign.targets {
109132
self.visit_expr(target);
@@ -213,8 +236,21 @@ mod tests {
213236
}
214237

215238
impl InlayHintTest {
239+
/// Returns the inlay hints for the given test case.
240+
///
241+
/// All inlay hints are generated using the applicable settings. Use
242+
/// [`inlay_hints_with_settings`] to generate hints with custom settings.
243+
///
244+
/// [`inlay_hints_with_settings`]: Self::inlay_hints_with_settings
216245
fn inlay_hints(&self) -> String {
217-
let hints = inlay_hints(&self.db, self.file, self.range);
246+
self.inlay_hints_with_settings(&InlayHintSettings {
247+
variable_types: true,
248+
})
249+
}
250+
251+
/// Returns the inlay hints for the given test case with custom settings.
252+
fn inlay_hints_with_settings(&self, settings: &InlayHintSettings) -> String {
253+
let hints = inlay_hints(&self.db, self.file, self.range, settings);
218254

219255
let mut buf = source_text(&self.db, self.file).as_str().to_string();
220256

@@ -276,4 +312,18 @@ mod tests {
276312
y = 2
277313
");
278314
}
315+
316+
#[test]
317+
fn disabled_variable_types() {
318+
let test = inlay_hint_test("x = 1");
319+
320+
assert_snapshot!(
321+
test.inlay_hints_with_settings(&InlayHintSettings {
322+
variable_types: false,
323+
}),
324+
@r"
325+
x = 1
326+
"
327+
);
328+
}
279329
}

crates/ty_ide/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub use document_symbols::{document_symbols, document_symbols_with_options};
2727
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
2828
pub use goto_references::goto_references;
2929
pub use hover::hover;
30-
pub use inlay_hints::inlay_hints;
30+
pub use inlay_hints::{InlayHintSettings, inlay_hints};
3131
pub use markup::MarkupKind;
3232
pub use references::ReferencesMode;
3333
pub use rename::{can_rename, rename};

crates/ty_server/src/server/api/requests/inlay_hints.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
4747
.range
4848
.to_text_range(&source, &index, snapshot.encoding());
4949

50-
let inlay_hints = inlay_hints(db, file, range);
50+
let inlay_hints = inlay_hints(db, file, range, snapshot.workspace_settings().inlay_hints());
51+
if inlay_hints.is_empty() {
52+
return Ok(None);
53+
}
5154

5255
let inlay_hints = inlay_hints
5356
.into_iter()

crates/ty_server/src/session/options.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
88
use serde_json::Value;
99

1010
use ty_combine::Combine;
11+
use ty_ide::InlayHintSettings;
1112
use ty_project::metadata::Options as TyOptions;
1213
use ty_project::metadata::options::ProjectOptionsOverrides;
1314
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
@@ -106,6 +107,15 @@ impl ClientOptions {
106107
self
107108
}
108109

110+
#[must_use]
111+
pub fn with_variable_types_inlay_hints(mut self, variable_types: bool) -> Self {
112+
self.workspace
113+
.inlay_hints
114+
.get_or_insert_default()
115+
.variable_types = Some(variable_types);
116+
self
117+
}
118+
109119
#[must_use]
110120
pub fn with_unknown(mut self, unknown: HashMap<String, Value>) -> Self {
111121
self.unknown = unknown;
@@ -141,6 +151,9 @@ pub(crate) struct WorkspaceOptions {
141151
/// Whether to disable language services like code completions, hover, etc.
142152
disable_language_services: Option<bool>,
143153

154+
/// Options to configure inlay hints.
155+
inlay_hints: Option<InlayHintOptions>,
156+
144157
/// Information about the currently active Python environment in the VS Code Python extension.
145158
///
146159
/// This is relevant only for VS Code and is populated by the ty VS Code extension.
@@ -194,11 +207,29 @@ impl WorkspaceOptions {
194207

195208
WorkspaceSettings {
196209
disable_language_services: self.disable_language_services.unwrap_or_default(),
210+
inlay_hints: self
211+
.inlay_hints
212+
.map(InlayHintOptions::into_settings)
213+
.unwrap_or_default(),
197214
overrides,
198215
}
199216
}
200217
}
201218

219+
#[derive(Clone, Combine, Debug, Serialize, Deserialize, Default)]
220+
#[serde(rename_all = "camelCase")]
221+
struct InlayHintOptions {
222+
variable_types: Option<bool>,
223+
}
224+
225+
impl InlayHintOptions {
226+
fn into_settings(self) -> InlayHintSettings {
227+
InlayHintSettings {
228+
variable_types: self.variable_types.unwrap_or_default(),
229+
}
230+
}
231+
}
232+
202233
/// Diagnostic mode for the language server.
203234
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
204235
#[serde(rename_all = "camelCase")]

crates/ty_server/src/session/settings.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::options::DiagnosticMode;
22

3+
use ty_ide::InlayHintSettings;
34
use ty_project::metadata::options::ProjectOptionsOverrides;
45

56
/// Resolved client settings that are shared across all workspaces.
@@ -21,6 +22,7 @@ impl GlobalSettings {
2122
#[derive(Clone, Default, Debug)]
2223
pub(crate) struct WorkspaceSettings {
2324
pub(super) disable_language_services: bool,
25+
pub(super) inlay_hints: InlayHintSettings,
2426
pub(super) overrides: Option<ProjectOptionsOverrides>,
2527
}
2628

@@ -32,4 +34,8 @@ impl WorkspaceSettings {
3234
pub(crate) fn project_options_overrides(&self) -> Option<&ProjectOptionsOverrides> {
3335
self.overrides.as_ref()
3436
}
37+
38+
pub(crate) fn inlay_hints(&self) -> &InlayHintSettings {
39+
&self.inlay_hints
40+
}
3541
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use anyhow::Result;
2+
use lsp_types::{Position, Range, notification::PublishDiagnostics};
3+
use ruff_db::system::SystemPath;
4+
use ty_server::ClientOptions;
5+
6+
use crate::TestServerBuilder;
7+
8+
/// Tests that disabling variable types inlay hints works correctly.
9+
#[test]
10+
fn variable_inlay_hints_disabled() -> Result<()> {
11+
let workspace_root = SystemPath::new("src");
12+
let foo = SystemPath::new("src/foo.py");
13+
let foo_content = "x = 1";
14+
15+
let mut server = TestServerBuilder::new()?
16+
.with_initialization_options(
17+
ClientOptions::default().with_variable_types_inlay_hints(false),
18+
)
19+
.with_workspace(workspace_root, None)?
20+
.with_file(foo, foo_content)?
21+
.enable_inlay_hints(true)
22+
.build()?
23+
.wait_until_workspaces_are_initialized()?;
24+
25+
server.open_text_document(foo, &foo_content, 1);
26+
let _ = server.await_notification::<PublishDiagnostics>()?;
27+
28+
let hints =
29+
server.inlay_hints_request(foo, Range::new(Position::new(0, 0), Position::new(0, 5)))?;
30+
assert!(
31+
hints.is_none(),
32+
"Expected no inlay hints, but found: {:?}",
33+
hints
34+
);
35+
36+
Ok(())
37+
}

crates/ty_server/tests/e2e/main.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
//! [`await_notification`]: TestServer::await_notification
2929
3030
mod initialize;
31+
mod inlay_hints;
3132
mod publish_diagnostics;
3233
mod pull_diagnostics;
3334

@@ -48,20 +49,21 @@ use lsp_types::notification::{
4849
Initialized, Notification,
4950
};
5051
use lsp_types::request::{
51-
DocumentDiagnosticRequest, HoverRequest, Initialize, Request, Shutdown, WorkspaceConfiguration,
52-
WorkspaceDiagnosticRequest,
52+
DocumentDiagnosticRequest, HoverRequest, Initialize, InlayHintRequest, Request, Shutdown,
53+
WorkspaceConfiguration, WorkspaceDiagnosticRequest,
5354
};
5455
use lsp_types::{
5556
ClientCapabilities, ConfigurationParams, DiagnosticClientCapabilities,
5657
DidChangeTextDocumentParams, DidChangeWatchedFilesClientCapabilities,
5758
DidChangeWatchedFilesParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
5859
DocumentDiagnosticParams, DocumentDiagnosticReportResult, FileEvent, Hover, HoverParams,
59-
InitializeParams, InitializeResult, InitializedParams, NumberOrString, PartialResultParams,
60-
Position, PreviousResultId, PublishDiagnosticsClientCapabilities,
61-
TextDocumentClientCapabilities, TextDocumentContentChangeEvent, TextDocumentIdentifier,
62-
TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier,
63-
WorkDoneProgressParams, WorkspaceClientCapabilities, WorkspaceDiagnosticParams,
64-
WorkspaceDiagnosticReportResult, WorkspaceFolder,
60+
InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintClientCapabilities,
61+
InlayHintParams, NumberOrString, PartialResultParams, Position, PreviousResultId,
62+
PublishDiagnosticsClientCapabilities, Range, TextDocumentClientCapabilities,
63+
TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
64+
TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkDoneProgressParams,
65+
WorkspaceClientCapabilities, WorkspaceDiagnosticParams, WorkspaceDiagnosticReportResult,
66+
WorkspaceFolder,
6567
};
6668
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf, TestSystem};
6769
use rustc_hash::FxHashMap;
@@ -725,6 +727,23 @@ impl TestServer {
725727
let id = self.send_request::<HoverRequest>(params);
726728
self.await_response::<HoverRequest>(&id)
727729
}
730+
731+
/// Sends a `textDocument/inlayHint` request for the document at the given path and range.
732+
pub(crate) fn inlay_hints_request(
733+
&mut self,
734+
path: impl AsRef<SystemPath>,
735+
range: Range,
736+
) -> Result<Option<Vec<InlayHint>>> {
737+
let params = InlayHintParams {
738+
text_document: TextDocumentIdentifier {
739+
uri: self.file_uri(path),
740+
},
741+
range,
742+
work_done_progress_params: WorkDoneProgressParams::default(),
743+
};
744+
let id = self.send_request::<InlayHintRequest>(params);
745+
self.await_response::<InlayHintRequest>(&id)
746+
}
728747
}
729748

730749
impl fmt::Debug for TestServer {
@@ -897,6 +916,19 @@ impl TestServerBuilder {
897916
self
898917
}
899918

919+
/// Enable or disable inlay hints capability
920+
pub(crate) fn enable_inlay_hints(mut self, enabled: bool) -> Self {
921+
self.client_capabilities
922+
.text_document
923+
.get_or_insert_default()
924+
.inlay_hint = if enabled {
925+
Some(InlayHintClientCapabilities::default())
926+
} else {
927+
None
928+
};
929+
self
930+
}
931+
900932
/// Enable or disable file watching capability
901933
#[expect(dead_code)]
902934
pub(crate) fn enable_did_change_watched_files(mut self, enabled: bool) -> Self {

crates/ty_wasm/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use ruff_python_formatter::formatted_file;
1616
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
1717
use ruff_text_size::{Ranged, TextSize};
1818
use ty_ide::{
19-
MarkupKind, RangedValue, document_highlights, goto_declaration, goto_definition,
20-
goto_references, goto_type_definition, hover, inlay_hints,
19+
InlayHintSettings, MarkupKind, RangedValue, document_highlights, goto_declaration,
20+
goto_definition, goto_references, goto_type_definition, hover, inlay_hints,
2121
};
2222
use ty_ide::{NavigationTargets, signature_help};
2323
use ty_project::metadata::options::Options;
@@ -435,6 +435,10 @@ impl Workspace {
435435
&self.db,
436436
file_id.file,
437437
range.to_text_range(&index, &source, self.position_encoding)?,
438+
// TODO: Provide a way to configure this
439+
&InlayHintSettings {
440+
variable_types: true,
441+
},
438442
);
439443

440444
Ok(result

0 commit comments

Comments
 (0)