Skip to content

Commit 7dad0a2

Browse files
committed
Auto merge of rust-lang#17063 - Veykril:inlay-hints-fix, r=Veykril
fix: Fix inlay hint resolution being broken So, things broke because we now store a hash (u64) in the resolution payload, but javascript and hence JSON only support integers of up to 53 bits (anything beyond gets truncated in various ways) which caused almost all hashes to always differ when resolving them. This masks the hash to 53 bits to work around that. Fixes rust-lang/rust-analyzer#16962
2 parents beb205f + 2c5c12a commit 7dad0a2

File tree

8 files changed

+89
-37
lines changed

8 files changed

+89
-37
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{
22
fmt::{self, Write},
3-
hash::{BuildHasher, BuildHasherDefault},
43
mem::take,
54
};
65

@@ -9,7 +8,7 @@ use hir::{
98
known, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
109
ModuleDefId, Semantics,
1110
};
12-
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, FxHasher, RootDatabase};
11+
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
1312
use itertools::Itertools;
1413
use smallvec::{smallvec, SmallVec};
1514
use stdx::never;
@@ -495,6 +494,7 @@ pub(crate) fn inlay_hints_resolve(
495494
position: TextSize,
496495
hash: u64,
497496
config: &InlayHintsConfig,
497+
hasher: impl Fn(&InlayHint) -> u64,
498498
) -> Option<InlayHint> {
499499
let _p = tracing::span!(tracing::Level::INFO, "inlay_hints").entered();
500500
let sema = Semantics::new(db);
@@ -506,20 +506,16 @@ pub(crate) fn inlay_hints_resolve(
506506
let mut acc = Vec::new();
507507

508508
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
509-
match file.token_at_offset(position).left_biased() {
510-
Some(token) => {
511-
if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
512-
parent_block.syntax().descendants().for_each(hints)
513-
} else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
514-
parent_item.syntax().descendants().for_each(hints)
515-
} else {
516-
return None;
517-
}
518-
}
519-
None => return None,
509+
let token = file.token_at_offset(position).left_biased()?;
510+
if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
511+
parent_block.syntax().descendants().for_each(hints)
512+
} else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
513+
parent_item.syntax().descendants().for_each(hints)
514+
} else {
515+
return None;
520516
}
521517

522-
acc.into_iter().find(|hint| BuildHasherDefault::<FxHasher>::default().hash_one(hint) == hash)
518+
acc.into_iter().find(|hint| hasher(hint) == hash)
523519
}
524520

525521
fn hints(

crates/ide/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ mod view_item_tree;
5858
mod view_memory_layout;
5959
mod view_mir;
6060

61+
use std::panic::UnwindSafe;
62+
6163
use cfg::CfgOptions;
6264
use fetch_crates::CrateInfo;
6365
use hir::ChangeWithProcMacros;
@@ -428,8 +430,11 @@ impl Analysis {
428430
file_id: FileId,
429431
position: TextSize,
430432
hash: u64,
433+
hasher: impl Fn(&InlayHint) -> u64 + Send + UnwindSafe,
431434
) -> Cancellable<Option<InlayHint>> {
432-
self.with_db(|db| inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config))
435+
self.with_db(|db| {
436+
inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config, hasher)
437+
})
433438
}
434439

435440
/// Returns the set of folding ranges.

crates/rust-analyzer/src/handlers/request.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,11 +1522,18 @@ pub(crate) fn handle_inlay_hints_resolve(
15221522
file_id,
15231523
hint_position,
15241524
resolve_data.hash,
1525+
|hint| {
1526+
std::hash::BuildHasher::hash_one(
1527+
&std::hash::BuildHasherDefault::<ide_db::FxHasher>::default(),
1528+
hint,
1529+
)
1530+
// json only supports numbers up to 2^53 - 1 as integers, so mask the rest
1531+
& ((1 << 53) - 1)
1532+
},
15251533
)?;
15261534

1527-
let mut resolved_hints = resolve_hints
1528-
.into_iter()
1529-
.filter_map(|it| {
1535+
Ok(resolve_hints
1536+
.and_then(|it| {
15301537
to_proto::inlay_hint(
15311538
&snap,
15321539
&forced_resolve_inlay_hints_config.fields_to_resolve,
@@ -1537,13 +1544,8 @@ pub(crate) fn handle_inlay_hints_resolve(
15371544
.ok()
15381545
})
15391546
.filter(|hint| hint.position == original_hint.position)
1540-
.filter(|hint| hint.kind == original_hint.kind);
1541-
if let Some(resolved_hint) = resolved_hints.next() {
1542-
if resolved_hints.next().is_none() {
1543-
return Ok(resolved_hint);
1544-
}
1545-
}
1546-
Ok(original_hint)
1547+
.filter(|hint| hint.kind == original_hint.kind)
1548+
.unwrap_or(original_hint))
15471549
}
15481550

15491551
pub(crate) fn handle_call_hierarchy_prepare(

crates/rust-analyzer/src/lsp/ext.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -463,13 +463,6 @@ pub struct TestInfo {
463463
pub runnable: Runnable,
464464
}
465465

466-
#[derive(Serialize, Deserialize, Debug)]
467-
#[serde(rename_all = "camelCase")]
468-
pub struct InlayHintsParams {
469-
pub text_document: TextDocumentIdentifier,
470-
pub range: Option<lsp_types::Range>,
471-
}
472-
473466
pub enum Ssr {}
474467

475468
impl Request for Ssr {

crates/rust-analyzer/src/lsp/to_proto.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ pub(crate) fn inlay_hint(
453453
&std::hash::BuildHasherDefault::<FxHasher>::default(),
454454
&inlay_hint,
455455
)
456+
// json only supports numbers up to 2^53 - 1 as integers, so mask the rest
457+
& ((1 << 53) - 1)
456458
});
457459

458460
let mut something_to_resolve = false;

crates/rust-analyzer/tests/slow-tests/main.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ use lsp_types::{
2323
notification::DidOpenTextDocument,
2424
request::{
2525
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
26-
WillRenameFiles, WorkspaceSymbolRequest,
26+
InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
2727
},
2828
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
2929
DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
30-
PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
31-
TextDocumentPositionParams, WorkDoneProgressParams,
30+
InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
31+
RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
3232
};
3333
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
3434
use serde_json::json;
@@ -75,6 +75,48 @@ use std::collections::Spam;
7575
assert!(res.to_string().contains("HashMap"));
7676
}
7777

78+
#[test]
79+
fn resolves_inlay_hints() {
80+
if skip_slow_tests() {
81+
return;
82+
}
83+
84+
let server = Project::with_fixture(
85+
r#"
86+
//- /Cargo.toml
87+
[package]
88+
name = "foo"
89+
version = "0.0.0"
90+
91+
//- /src/lib.rs
92+
struct Foo;
93+
fn f() {
94+
let x = Foo;
95+
}
96+
"#,
97+
)
98+
.server()
99+
.wait_until_workspace_is_loaded();
100+
101+
let res = server.send_request::<InlayHintRequest>(InlayHintParams {
102+
range: Range::new(Position::new(0, 0), Position::new(3, 1)),
103+
text_document: server.doc_id("src/lib.rs"),
104+
work_done_progress_params: WorkDoneProgressParams::default(),
105+
});
106+
let mut hints = serde_json::from_value::<Option<Vec<InlayHint>>>(res).unwrap().unwrap();
107+
let hint = hints.pop().unwrap();
108+
assert!(hint.data.is_some());
109+
assert!(
110+
matches!(&hint.label, InlayHintLabel::LabelParts(parts) if parts[1].location.is_none())
111+
);
112+
let res = server.send_request::<InlayHintResolveRequest>(hint);
113+
let hint = serde_json::from_value::<InlayHint>(res).unwrap();
114+
assert!(hint.data.is_none());
115+
assert!(
116+
matches!(&hint.label, InlayHintLabel::LabelParts(parts) if parts[1].location.is_some())
117+
);
118+
}
119+
78120
#[test]
79121
fn test_runnables_project() {
80122
if skip_slow_tests() {

crates/rust-analyzer/tests/slow-tests/support.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ impl Project<'_> {
159159
content_format: Some(vec![lsp_types::MarkupKind::Markdown]),
160160
..Default::default()
161161
}),
162+
inlay_hint: Some(lsp_types::InlayHintClientCapabilities {
163+
resolve_support: Some(lsp_types::InlayHintResolveClientCapabilities {
164+
properties: vec![
165+
"textEdits".to_owned(),
166+
"tooltip".to_owned(),
167+
"label.tooltip".to_owned(),
168+
"label.location".to_owned(),
169+
"label.command".to_owned(),
170+
],
171+
}),
172+
..Default::default()
173+
}),
162174
..Default::default()
163175
}),
164176
window: Some(lsp_types::WindowClientCapabilities {

docs/dev/lsp-extensions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp/ext.rs hash: 223f48a89a5126a0
2+
lsp/ext.rs hash: 4aacf4cca1c9ff5e
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:
@@ -444,7 +444,7 @@ interface DiscoverTestResults {
444444
// For each file which its uri is in this list, the response
445445
// contains all tests that are located in this file, and
446446
// client should remove old tests not included in the response.
447-
scopeFile: lc.TextDocumentIdentifier[] | undefined;
447+
scopeFile: lc.TextDocumentIdentifier[] | undefined;
448448
}
449449
```
450450

0 commit comments

Comments
 (0)