Skip to content

Commit 7dad0a2

Browse files
committed
Auto merge of #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. Fixesrust-lang/rust-analyzer#16962
2 parents beb205f + 2c5c12a commit 7dad0a2

8 files changed

Lines changed: 89 additions & 37 deletions

File tree

‎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:implFn(&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
letmut 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-
ifletSome(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast){
512-
parent_block.syntax().descendants().for_each(hints)
513-
}elseifletSome(parent_item) = token.parent_ancestors().find_map(ast::Item::cast){
514-
parent_item.syntax().descendants().for_each(hints)
515-
}else{
516-
returnNone;
517-
}
518-
}
519-
None => returnNone,
509+
let token = file.token_at_offset(position).left_biased()?;
510+
ifletSome(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast){
511+
parent_block.syntax().descendants().for_each(hints)
512+
}elseifletSome(parent_item) = token.parent_ancestors().find_map(ast::Item::cast){
513+
parent_item.syntax().descendants().for_each(hints)
514+
}else{
515+
returnNone;
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
fnhints(

‎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:implFn(&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-
letmut 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-
ifletSome(resolved_hint) = resolved_hints.next(){
1542-
if resolved_hints.next().is_none(){
1543-
returnOk(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)fnhandle_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
pubrunnable:Runnable,
464464
}
465465

466-
#[derive(Serialize,Deserialize,Debug)]
467-
#[serde(rename_all = "camelCase")]
468-
pubstructInlayHintsParams{
469-
pubtext_document:TextDocumentIdentifier,
470-
pubrange:Option<lsp_types::Range>,
471-
}
472-
473466
pubenumSsr{}
474467

475468
implRequestforSsr{

‎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
letmut 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+
fnresolves_inlay_hints(){
80+
ifskip_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+
letmut 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
fntest_runnables_project(){
80122
ifskip_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)