Skip to content

Commit 4a93368

Browse files
committed
Use a hash to find the correct inlay hint when resolving
1 parent 3115fd8 commit 4a93368

File tree

7 files changed

+109
-68
lines changed

7 files changed

+109
-68
lines changed

crates/ide/src/inlay_hints.rs

+55-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
fmt::{self, Write},
3+
hash::{BuildHasher, BuildHasherDefault},
34
mem::take,
45
};
56

@@ -8,7 +9,7 @@ use hir::{
89
known, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
910
ModuleDefId, Semantics,
1011
};
11-
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
12+
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, FxHasher, RootDatabase};
1213
use itertools::Itertools;
1314
use smallvec::{smallvec, SmallVec};
1415
use stdx::never;
@@ -116,7 +117,7 @@ pub enum AdjustmentHintsMode {
116117
PreferPostfix,
117118
}
118119

119-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
120121
pub enum InlayKind {
121122
Adjustment,
122123
BindingMode,
@@ -132,7 +133,7 @@ pub enum InlayKind {
132133
RangeExclusive,
133134
}
134135

135-
#[derive(Debug)]
136+
#[derive(Debug, Hash)]
136137
pub enum InlayHintPosition {
137138
Before,
138139
After,
@@ -153,6 +154,18 @@ pub struct InlayHint {
153154
pub text_edit: Option<TextEdit>,
154155
}
155156

157+
impl std::hash::Hash for InlayHint {
158+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
159+
self.range.hash(state);
160+
self.position.hash(state);
161+
self.pad_left.hash(state);
162+
self.pad_right.hash(state);
163+
self.kind.hash(state);
164+
self.label.hash(state);
165+
self.text_edit.is_some().hash(state);
166+
}
167+
}
168+
156169
impl InlayHint {
157170
fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
158171
InlayHint {
@@ -183,13 +196,13 @@ impl InlayHint {
183196
}
184197
}
185198

186-
#[derive(Debug)]
199+
#[derive(Debug, Hash)]
187200
pub enum InlayTooltip {
188201
String(String),
189202
Markdown(String),
190203
}
191204

192-
#[derive(Default)]
205+
#[derive(Default, Hash)]
193206
pub struct InlayHintLabel {
194207
pub parts: SmallVec<[InlayHintLabelPart; 1]>,
195208
}
@@ -267,6 +280,7 @@ impl fmt::Debug for InlayHintLabel {
267280
}
268281
}
269282

283+
#[derive(Hash)]
270284
pub struct InlayHintLabelPart {
271285
pub text: String,
272286
/// Source location represented by this label part. The client will use this to fetch the part's
@@ -315,9 +329,7 @@ impl fmt::Write for InlayHintLabelBuilder<'_> {
315329

316330
impl HirWrite for InlayHintLabelBuilder<'_> {
317331
fn start_location_link(&mut self, def: ModuleDefId) {
318-
if self.location.is_some() {
319-
never!("location link is already started");
320-
}
332+
never!(self.location.is_some(), "location link is already started");
321333
self.make_new_part();
322334
let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
323335
let location = location.call_site();
@@ -427,11 +439,6 @@ fn ty_to_text_edit(
427439
Some(builder.finish())
428440
}
429441

430-
pub enum RangeLimit {
431-
Fixed(TextRange),
432-
NearestParent(TextSize),
433-
}
434-
435442
// Feature: Inlay Hints
436443
//
437444
// rust-analyzer shows additional information inline with the source code.
@@ -453,7 +460,7 @@ pub enum RangeLimit {
453460
pub(crate) fn inlay_hints(
454461
db: &RootDatabase,
455462
file_id: FileId,
456-
range_limit: Option<RangeLimit>,
463+
range_limit: Option<TextRange>,
457464
config: &InlayHintsConfig,
458465
) -> Vec<InlayHint> {
459466
let _p = tracing::span!(tracing::Level::INFO, "inlay_hints").entered();
@@ -468,38 +475,53 @@ pub(crate) fn inlay_hints(
468475

469476
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
470477
match range_limit {
471-
Some(RangeLimit::Fixed(range)) => match file.covering_element(range) {
478+
Some(range) => match file.covering_element(range) {
472479
NodeOrToken::Token(_) => return acc,
473480
NodeOrToken::Node(n) => n
474481
.descendants()
475482
.filter(|descendant| range.intersect(descendant.text_range()).is_some())
476483
.for_each(hints),
477484
},
478-
Some(RangeLimit::NearestParent(position)) => {
479-
match file.token_at_offset(position).left_biased() {
480-
Some(token) => {
481-
if let Some(parent_block) =
482-
token.parent_ancestors().find_map(ast::BlockExpr::cast)
483-
{
484-
parent_block.syntax().descendants().for_each(hints)
485-
} else if let Some(parent_item) =
486-
token.parent_ancestors().find_map(ast::Item::cast)
487-
{
488-
parent_item.syntax().descendants().for_each(hints)
489-
} else {
490-
return acc;
491-
}
492-
}
493-
None => return acc,
494-
}
495-
}
496485
None => file.descendants().for_each(hints),
497486
};
498487
}
499488

500489
acc
501490
}
502491

492+
pub(crate) fn inlay_hints_resolve(
493+
db: &RootDatabase,
494+
file_id: FileId,
495+
position: TextSize,
496+
hash: u64,
497+
config: &InlayHintsConfig,
498+
) -> Option<InlayHint> {
499+
let _p = tracing::span!(tracing::Level::INFO, "inlay_hints").entered();
500+
let sema = Semantics::new(db);
501+
let file = sema.parse(file_id);
502+
let file = file.syntax();
503+
504+
let scope = sema.scope(file)?;
505+
let famous_defs = FamousDefs(&sema, scope.krate());
506+
let mut acc = Vec::new();
507+
508+
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,
520+
}
521+
522+
acc.into_iter().find(|hint| BuildHasherDefault::<FxHasher>::default().hash_one(hint) == hash)
523+
}
524+
503525
fn hints(
504526
hints: &mut Vec<InlayHint>,
505527
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,

crates/ide/src/inlay_hints/bind_pat.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,7 @@ mod tests {
176176
use syntax::{TextRange, TextSize};
177177
use test_utils::extract_annotations;
178178

179-
use crate::{
180-
fixture,
181-
inlay_hints::{InlayHintsConfig, RangeLimit},
182-
ClosureReturnTypeHints,
183-
};
179+
use crate::{fixture, inlay_hints::InlayHintsConfig, ClosureReturnTypeHints};
184180

185181
use crate::inlay_hints::tests::{
186182
check, check_edit, check_no_edit, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
@@ -403,7 +399,7 @@ fn main() {
403399
.inlay_hints(
404400
&InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
405401
file_id,
406-
Some(RangeLimit::Fixed(TextRange::new(TextSize::from(500), TextSize::from(600)))),
402+
Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
407403
)
408404
.unwrap();
409405
let actual =

crates/ide/src/lib.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub use crate::{
9090
inlay_hints::{
9191
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,
9292
InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition,
93-
InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, RangeLimit,
93+
InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
9494
},
9595
join_lines::JoinLinesConfig,
9696
markup::Markup,
@@ -415,10 +415,19 @@ impl Analysis {
415415
&self,
416416
config: &InlayHintsConfig,
417417
file_id: FileId,
418-
range: Option<RangeLimit>,
418+
range: Option<TextRange>,
419419
) -> Cancellable<Vec<InlayHint>> {
420420
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
421421
}
422+
pub fn inlay_hints_resolve(
423+
&self,
424+
config: &InlayHintsConfig,
425+
file_id: FileId,
426+
position: TextSize,
427+
hash: u64,
428+
) -> Cancellable<Option<InlayHint>> {
429+
self.with_db(|db| inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config))
430+
}
422431

423432
/// Returns the set of folding ranges.
424433
pub fn folding_ranges(&self, file_id: FileId) -> Cancellable<Vec<Fold>> {

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use anyhow::Context;
1212

1313
use ide::{
1414
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
15-
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, RangeLimit,
16-
ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
15+
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
16+
Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
1717
};
1818
use ide_db::SymbolKind;
1919
use itertools::Itertools;
@@ -1465,7 +1465,7 @@ pub(crate) fn handle_inlay_hints(
14651465
let inlay_hints_config = snap.config.inlay_hints();
14661466
Ok(Some(
14671467
snap.analysis
1468-
.inlay_hints(&inlay_hints_config, file_id, Some(RangeLimit::Fixed(range)))?
1468+
.inlay_hints(&inlay_hints_config, file_id, Some(range))?
14691469
.into_iter()
14701470
.map(|it| {
14711471
to_proto::inlay_hint(
@@ -1499,10 +1499,11 @@ pub(crate) fn handle_inlay_hints_resolve(
14991499
let hint_position = from_proto::offset(&line_index, original_hint.position)?;
15001500
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
15011501
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
1502-
let resolve_hints = snap.analysis.inlay_hints(
1502+
let resolve_hints = snap.analysis.inlay_hints_resolve(
15031503
&forced_resolve_inlay_hints_config,
15041504
file_id,
1505-
Some(RangeLimit::NearestParent(hint_position)),
1505+
hint_position,
1506+
resolve_data.hash,
15061507
)?;
15071508

15081509
let mut resolved_hints = resolve_hints

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

+1
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ pub struct CompletionResolveData {
800800
#[derive(Debug, Serialize, Deserialize)]
801801
pub struct InlayHintResolveData {
802802
pub file_id: u32,
803+
pub hash: u64,
803804
}
804805

805806
#[derive(Debug, Serialize, Deserialize)]

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

+30-18
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use ide::{
1313
NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
1414
SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
1515
};
16-
use ide_db::rust_doc::format_docs;
16+
use ide_db::{rust_doc::format_docs, FxHasher};
1717
use itertools::Itertools;
1818
use semver::VersionReq;
1919
use serde_json::to_value;
@@ -444,30 +444,42 @@ pub(crate) fn inlay_hint(
444444
fields_to_resolve: &InlayFieldsToResolve,
445445
line_index: &LineIndex,
446446
file_id: FileId,
447-
inlay_hint: InlayHint,
447+
mut inlay_hint: InlayHint,
448448
) -> Cancellable<lsp_types::InlayHint> {
449-
let needs_resolve = inlay_hint.needs_resolve();
450-
let (label, tooltip, mut something_to_resolve) =
451-
inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?;
449+
let resolve_hash = inlay_hint.needs_resolve().then(|| {
450+
std::hash::BuildHasher::hash_one(
451+
&std::hash::BuildHasherDefault::<FxHasher>::default(),
452+
&inlay_hint,
453+
)
454+
});
452455

456+
let mut something_to_resolve = false;
453457
let text_edits = if snap
454458
.config
455459
.visual_studio_code_version()
456460
// https://github.com/microsoft/vscode/issues/193124
457461
.map_or(true, |version| VersionReq::parse(">=1.86.0").unwrap().matches(version))
458-
&& needs_resolve
462+
&& resolve_hash.is_some()
459463
&& fields_to_resolve.resolve_text_edits
460464
{
461465
something_to_resolve |= inlay_hint.text_edit.is_some();
462466
None
463467
} else {
464-
inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
468+
inlay_hint.text_edit.take().map(|it| text_edit_vec(line_index, it))
465469
};
466-
467-
let data = if needs_resolve && something_to_resolve {
468-
Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.index() }).unwrap())
469-
} else {
470-
None
470+
let (label, tooltip) = inlay_hint_label(
471+
snap,
472+
fields_to_resolve,
473+
&mut something_to_resolve,
474+
resolve_hash.is_some(),
475+
inlay_hint.label,
476+
)?;
477+
478+
let data = match resolve_hash {
479+
Some(hash) if something_to_resolve => Some(
480+
to_value(lsp_ext::InlayHintResolveData { file_id: file_id.index(), hash }).unwrap(),
481+
),
482+
_ => None,
471483
};
472484

473485
Ok(lsp_types::InlayHint {
@@ -492,15 +504,15 @@ pub(crate) fn inlay_hint(
492504
fn inlay_hint_label(
493505
snap: &GlobalStateSnapshot,
494506
fields_to_resolve: &InlayFieldsToResolve,
507+
something_to_resolve: &mut bool,
495508
needs_resolve: bool,
496509
mut label: InlayHintLabel,
497-
) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>, bool)> {
498-
let mut something_to_resolve = false;
510+
) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
499511
let (label, tooltip) = match &*label.parts {
500512
[InlayHintLabelPart { linked_location: None, .. }] => {
501513
let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
502514
let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip {
503-
something_to_resolve |= tooltip.is_some();
515+
*something_to_resolve |= tooltip.is_some();
504516
None
505517
} else {
506518
match tooltip {
@@ -524,7 +536,7 @@ fn inlay_hint_label(
524536
.into_iter()
525537
.map(|part| {
526538
let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip {
527-
something_to_resolve |= part.tooltip.is_some();
539+
*something_to_resolve |= part.tooltip.is_some();
528540
None
529541
} else {
530542
match part.tooltip {
@@ -543,7 +555,7 @@ fn inlay_hint_label(
543555
}
544556
};
545557
let location = if needs_resolve && fields_to_resolve.resolve_label_location {
546-
something_to_resolve |= part.linked_location.is_some();
558+
*something_to_resolve |= part.linked_location.is_some();
547559
None
548560
} else {
549561
part.linked_location.map(|range| location(snap, range)).transpose()?
@@ -559,7 +571,7 @@ fn inlay_hint_label(
559571
(lsp_types::InlayHintLabel::LabelParts(parts), None)
560572
}
561573
};
562-
Ok((label, tooltip, something_to_resolve))
574+
Ok((label, tooltip))
563575
}
564576

565577
static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);

0 commit comments

Comments
 (0)