Make InlayHint::linked_location computation lazy

This commit is contained in:
Lukas Wirth 2025-01-21 12:07:50 +01:00
parent 831e3535e6
commit 1977aa99b0
9 changed files with 285 additions and 204 deletions

View File

@ -300,6 +300,7 @@ pub struct InlayHintsConfig {
pub closing_brace_hints_min_lines: Option<usize>, pub closing_brace_hints_min_lines: Option<usize>,
pub fields_to_resolve: InlayFieldsToResolve, pub fields_to_resolve: InlayFieldsToResolve,
} }
impl InlayHintsConfig { impl InlayHintsConfig {
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> { fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> {
if self.fields_to_resolve.resolve_text_edits { if self.fields_to_resolve.resolve_text_edits {
@ -329,6 +330,19 @@ impl InlayHintsConfig {
Lazy::Computed(tooltip) Lazy::Computed(tooltip)
} }
} }
/// This always reports a resolvable location, so only use this when it is very likely for a
/// location link to actually resolve but where computing `finish` would be costly.
fn lazy_location_opt(
&self,
finish: impl FnOnce() -> Option<FileRange>,
) -> Option<Lazy<FileRange>> {
if self.fields_to_resolve.resolve_label_location {
Some(Lazy::Lazy)
} else {
finish().map(Lazy::Computed)
}
}
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -509,7 +523,7 @@ impl InlayHintLabel {
pub fn simple( pub fn simple(
s: impl Into<String>, s: impl Into<String>,
tooltip: Option<Lazy<InlayTooltip>>, tooltip: Option<Lazy<InlayTooltip>>,
linked_location: Option<FileRange>, linked_location: Option<Lazy<FileRange>>,
) -> InlayHintLabel { ) -> InlayHintLabel {
InlayHintLabel { InlayHintLabel {
parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }], parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
@ -593,7 +607,7 @@ pub struct InlayHintLabelPart {
/// refers to (not necessarily the location itself). /// refers to (not necessarily the location itself).
/// When setting this, no tooltip must be set on the containing hint, or VS Code will display /// When setting this, no tooltip must be set on the containing hint, or VS Code will display
/// them both. /// them both.
pub linked_location: Option<FileRange>, pub linked_location: Option<Lazy<FileRange>>,
/// The tooltip to show when hovering over the inlay hint, this may invoke other actions like /// The tooltip to show when hovering over the inlay hint, this may invoke other actions like
/// hover requests to show. /// hover requests to show.
pub tooltip: Option<Lazy<InlayTooltip>>, pub tooltip: Option<Lazy<InlayTooltip>>,
@ -602,7 +616,7 @@ pub struct InlayHintLabelPart {
impl std::hash::Hash for InlayHintLabelPart { impl std::hash::Hash for InlayHintLabelPart {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.text.hash(state); self.text.hash(state);
self.linked_location.hash(state); self.linked_location.is_some().hash(state);
self.tooltip.is_some().hash(state); self.tooltip.is_some().hash(state);
} }
} }
@ -663,7 +677,7 @@ impl InlayHintLabelBuilder<'_> {
if !text.is_empty() { if !text.is_empty() {
self.result.parts.push(InlayHintLabelPart { self.result.parts.push(InlayHintLabelPart {
text, text,
linked_location: self.location.take(), linked_location: self.location.take().map(Lazy::Computed),
tooltip: None, tooltip: None,
}); });
} }

View File

@ -22,11 +22,7 @@ pub(super) fn hints(
return None; return None;
} }
let linked_location = let sized_trait = famous_defs.core_marker_Sized();
famous_defs.core_marker_Sized().and_then(|it| it.try_to_nav(sema.db)).map(|it| {
let n = it.call_site();
FileRange { file_id: n.file_id, range: n.focus_or_full_range() }
});
for param in params.type_or_const_params() { for param in params.type_or_const_params() {
match param { match param {
@ -48,7 +44,17 @@ pub(super) fn hints(
} }
hint.parts.push(InlayHintLabelPart { hint.parts.push(InlayHintLabelPart {
text: "Sized".to_owned(), text: "Sized".to_owned(),
linked_location, linked_location: sized_trait.and_then(|it| {
config.lazy_location_opt(|| {
it.try_to_nav(sema.db).map(|it| {
let n = it.call_site();
FileRange {
file_id: n.file_id,
range: n.focus_or_full_range(),
}
})
})
}),
tooltip: None, tooltip: None,
}); });
if has_bounds { if has_bounds {
@ -134,12 +140,14 @@ fn foo<T>() {}
InlayHintLabelPart { InlayHintLabelPart {
text: "Sized", text: "Sized",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 135..140, ),
}, range: 135..140,
},
),
), ),
tooltip: "", tooltip: "",
}, },

View File

@ -81,7 +81,10 @@ mod tests {
use crate::{ use crate::{
fixture, fixture,
inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG}, inlay_hints::{
tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
Lazy,
},
InlayHintsConfig, InlayHintsConfig,
}; };
@ -99,7 +102,7 @@ mod tests {
let (analysis, file_id) = fixture::file(ra_fixture); let (analysis, file_id) = fixture::file(ra_fixture);
let mut inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); let mut inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
inlay_hints.iter_mut().flat_map(|hint| &mut hint.label.parts).for_each(|hint| { inlay_hints.iter_mut().flat_map(|hint| &mut hint.label.parts).for_each(|hint| {
if let Some(loc) = &mut hint.linked_location { if let Some(Lazy::Computed(loc)) = &mut hint.linked_location {
loc.range = TextRange::empty(TextSize::from(0)); loc.range = TextRange::empty(TextSize::from(0));
} }
}); });
@ -134,12 +137,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "B", text: "B",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 63..64, ),
}, range: 63..64,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -151,12 +156,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "A", text: "A",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 7..8, ),
}, range: 7..8,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -213,12 +220,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "C", text: "C",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 51..52, ),
}, range: 51..52,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -230,12 +239,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "B", text: "B",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 29..30, ),
}, range: 29..30,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -276,12 +287,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "C", text: "C",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 51..52, ),
}, range: 51..52,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -293,12 +306,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "B", text: "B",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 29..30, ),
}, range: 29..30,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -340,12 +355,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "B", text: "B",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 23..24, ),
}, range: 23..24,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -353,12 +370,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "X", text: "X",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 55..56, ),
}, range: 55..56,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -371,12 +390,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "A", text: "A",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 7..8, ),
}, range: 7..8,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -384,12 +405,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "X", text: "X",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 55..56, ),
}, range: 55..56,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -435,12 +458,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Iterator", text: "Iterator",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -448,12 +473,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Item", text: "Item",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -467,12 +494,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Iterator", text: "Iterator",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -480,12 +509,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Item", text: "Item",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -499,12 +530,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Iterator", text: "Iterator",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -512,12 +545,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Item", text: "Item",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
1, file_id: FileId(
), 1,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -531,12 +566,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "MyIter", text: "MyIter",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 0..0, ),
}, range: 0..0,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -577,12 +614,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Struct", text: "Struct",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 7..13, ),
}, range: 7..13,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -594,12 +633,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Struct", text: "Struct",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 7..13, ),
}, range: 7..13,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -611,12 +652,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "Struct", text: "Struct",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 7..13, ),
}, range: 7..13,
},
),
), ),
tooltip: "", tooltip: "",
}, },
@ -628,12 +671,14 @@ fn main() {
InlayHintLabelPart { InlayHintLabelPart {
text: "self", text: "self",
linked_location: Some( linked_location: Some(
FileRangeWrapper { Computed(
file_id: FileId( FileRangeWrapper {
0, file_id: FileId(
), 0,
range: 42..46, ),
}, range: 42..46,
},
),
), ),
tooltip: "", tooltip: "",
}, },

View File

@ -11,7 +11,9 @@ use syntax::{
match_ast, SyntaxKind, SyntaxNode, T, match_ast, SyntaxKind, SyntaxNode, T,
}; };
use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; use crate::{
inlay_hints::Lazy, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind,
};
pub(super) fn hints( pub(super) fn hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
@ -141,7 +143,7 @@ pub(super) fn hints(
acc.push(InlayHint { acc.push(InlayHint {
range: closing_token.text_range(), range: closing_token.text_range(),
kind: InlayKind::ClosingBrace, kind: InlayKind::ClosingBrace,
label: InlayHintLabel::simple(label, None, linked_location), label: InlayHintLabel::simple(label, None, linked_location.map(Lazy::Computed)),
text_edit: None, text_edit: None,
position: InlayHintPosition::After, position: InlayHintPosition::After,
pad_left: true, pad_left: true,

View File

@ -53,10 +53,6 @@ pub(super) fn hints(
let last = captures.len() - 1; let last = captures.len() - 1;
for (idx, capture) in captures.into_iter().enumerate() { for (idx, capture) in captures.into_iter().enumerate() {
let local = capture.local(); let local = capture.local();
let source = local.primary_source(sema.db);
// force cache the source file, otherwise sema lookup will potentially panic
_ = sema.parse_or_expand(source.file());
let label = format!( let label = format!(
"{}{}", "{}{}",
@ -73,8 +69,17 @@ pub(super) fn hints(
} }
hint.label.append_part(InlayHintLabelPart { hint.label.append_part(InlayHintLabelPart {
text: label, text: label,
linked_location: source.name().and_then(|name| { linked_location: config.lazy_location_opt(|| {
name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(Into::into) let source = local.primary_source(sema.db);
// force cache the source file, otherwise sema lookup will potentially panic
_ = sema.parse_or_expand(source.file());
source.name().and_then(|name| {
name.syntax()
.original_file_range_opt(sema.db)
.map(TupleExt::head)
.map(Into::into)
})
}), }),
tooltip: None, tooltip: None,
}); });

View File

@ -47,6 +47,18 @@ pub(crate) fn hints(
return None; return None;
} }
let allowed = match (param, &arg) {
(hir::GenericParam::TypeParam(_), ast::GenericArg::TypeArg(_)) => type_hints,
(hir::GenericParam::ConstParam(_), ast::GenericArg::ConstArg(_)) => const_hints,
(hir::GenericParam::LifetimeParam(_), ast::GenericArg::LifetimeArg(_)) => {
lifetime_hints
}
_ => false,
};
if !allowed {
return None;
}
let param_name = param.name(sema.db); let param_name = param.name(sema.db);
let should_hide = { let should_hide = {
@ -60,34 +72,27 @@ pub(crate) fn hints(
let range = sema.original_range_opt(arg.syntax())?.range; let range = sema.original_range_opt(arg.syntax())?.range;
let source_syntax = match param {
hir::GenericParam::TypeParam(it) => {
if !type_hints || !matches!(arg, ast::GenericArg::TypeArg(_)) {
return None;
}
sema.source(it.merge()).map(|it| it.value.syntax().clone())
}
hir::GenericParam::ConstParam(it) => {
if !const_hints || !matches!(arg, ast::GenericArg::ConstArg(_)) {
return None;
}
let syntax = sema.source(it.merge())?.value.syntax().clone();
let const_param = ast::ConstParam::cast(syntax)?;
const_param.name().map(|it| it.syntax().clone())
}
hir::GenericParam::LifetimeParam(it) => {
if !lifetime_hints || !matches!(arg, ast::GenericArg::LifetimeArg(_)) {
return None;
}
sema.source(it).map(|it| it.value.syntax().clone())
}
};
let linked_location = source_syntax.and_then(|it| sema.original_range_opt(&it));
let colon = if config.render_colons { ":" } else { "" }; let colon = if config.render_colons { ":" } else { "" };
let label = InlayHintLabel::simple( let label = InlayHintLabel::simple(
format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))), format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
None, None,
linked_location.map(Into::into), config.lazy_location_opt(|| {
let source_syntax = match param {
hir::GenericParam::TypeParam(it) => {
sema.source(it.merge()).map(|it| it.value.syntax().clone())
}
hir::GenericParam::ConstParam(it) => {
let syntax = sema.source(it.merge())?.value.syntax().clone();
let const_param = ast::ConstParam::cast(syntax)?;
const_param.name().map(|it| it.syntax().clone())
}
hir::GenericParam::LifetimeParam(it) => {
sema.source(it).map(|it| it.value.syntax().clone())
}
};
let linked_location = source_syntax.and_then(|it| sema.original_range_opt(&it));
linked_location.map(Into::into)
}),
); );
Some(InlayHint { Some(InlayHint {

View File

@ -49,7 +49,7 @@ pub(super) fn hints(
if mir.locals[place.local].ty.adt_id(ChalkTyInterner).is_none() { if mir.locals[place.local].ty.adt_id(ChalkTyInterner).is_none() {
continue; // Arguably only ADTs have significant drop impls continue; // Arguably only ADTs have significant drop impls
} }
let Some(binding) = local_to_binding.get(place.local) else { let Some(&binding_idx) = local_to_binding.get(place.local) else {
continue; // Ignore temporary values continue; // Ignore temporary values
}; };
let range = match terminator.span { let range = match terminator.span {
@ -91,25 +91,26 @@ pub(super) fn hints(
}, },
MirSpan::Unknown => continue, MirSpan::Unknown => continue,
}; };
let binding_source = source_map let binding = &hir.bindings[binding_idx];
.patterns_for_binding(*binding)
.first()
.and_then(|d| source_map.pat_syntax(*d).ok())
.and_then(|d| {
Some(FileRange {
file_id: d.file_id.file_id()?.into(),
range: d.value.text_range(),
})
});
let binding = &hir.bindings[*binding];
let name = binding.name.display_no_db(file_id.edition()).to_smolstr(); let name = binding.name.display_no_db(file_id.edition()).to_smolstr();
if name.starts_with("<ra@") { if name.starts_with("<ra@") {
continue; // Ignore desugared variables continue; // Ignore desugared variables
} }
let mut label = InlayHintLabel::simple( let mut label = InlayHintLabel::simple(
name, name,
Some(config.lazy_tooltip(|| crate::InlayTooltip::String("moz".into()))), None,
binding_source, config.lazy_location_opt(|| {
source_map
.patterns_for_binding(binding_idx)
.first()
.and_then(|d| source_map.pat_syntax(*d).ok())
.and_then(|d| {
Some(FileRange {
file_id: d.file_id.file_id()?.into(),
range: d.value.text_range(),
})
})
}),
); );
label.prepend_str("drop("); label.prepend_str("drop(");
label.append_str(")"); label.append_str(")");

View File

@ -43,23 +43,21 @@ pub(super) fn hints(
!should_hide_param_name_hint(sema, &callable, param_name.as_str(), arg) !should_hide_param_name_hint(sema, &callable, param_name.as_str(), arg)
}) })
.map(|(param, param_name, _, hir::FileRange { range, .. })| { .map(|(param, param_name, _, hir::FileRange { range, .. })| {
let linked_location = (|| {
let source = sema.source(param)?;
let name_syntax = match source.value.as_ref() {
Either::Left(pat) => pat.name(),
Either::Right(param) => match param.pat()? {
ast::Pat::IdentPat(it) => it.name(),
_ => None,
},
}?;
sema.original_range_opt(name_syntax.syntax())
})();
let colon = if config.render_colons { ":" } else { "" }; let colon = if config.render_colons { ":" } else { "" };
let label = InlayHintLabel::simple( let label = InlayHintLabel::simple(
format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))), format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
None, None,
linked_location.map(Into::into), config.lazy_location_opt(|| {
let source = sema.source(param)?;
let name_syntax = match source.value.as_ref() {
Either::Left(pat) => pat.name(),
Either::Right(param) => match param.pat()? {
ast::Pat::IdentPat(it) => it.name(),
_ => None,
},
}?;
sema.original_range_opt(name_syntax.syntax()).map(Into::into)
}),
); );
InlayHint { InlayHint {
range, range,

View File

@ -684,7 +684,10 @@ fn inlay_hint_label(
*something_to_resolve |= part.linked_location.is_some(); *something_to_resolve |= part.linked_location.is_some();
None None
} else { } else {
part.linked_location.map(|range| location(snap, range)).transpose()? part.linked_location
.and_then(|it| it.computed())
.map(|range| location(snap, range))
.transpose()?
}; };
Ok(lsp_types::InlayHintLabelPart { Ok(lsp_types::InlayHintLabelPart {
value: part.text, value: part.text,