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 fields_to_resolve: InlayFieldsToResolve,
}
impl InlayHintsConfig {
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> {
if self.fields_to_resolve.resolve_text_edits {
@ -329,6 +330,19 @@ impl InlayHintsConfig {
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)]
@ -509,7 +523,7 @@ impl InlayHintLabel {
pub fn simple(
s: impl Into<String>,
tooltip: Option<Lazy<InlayTooltip>>,
linked_location: Option<FileRange>,
linked_location: Option<Lazy<FileRange>>,
) -> InlayHintLabel {
InlayHintLabel {
parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
@ -593,7 +607,7 @@ pub struct InlayHintLabelPart {
/// refers to (not necessarily the location itself).
/// When setting this, no tooltip must be set on the containing hint, or VS Code will display
/// 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
/// hover requests to show.
pub tooltip: Option<Lazy<InlayTooltip>>,
@ -602,7 +616,7 @@ pub struct InlayHintLabelPart {
impl std::hash::Hash for InlayHintLabelPart {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.text.hash(state);
self.linked_location.hash(state);
self.linked_location.is_some().hash(state);
self.tooltip.is_some().hash(state);
}
}
@ -663,7 +677,7 @@ impl InlayHintLabelBuilder<'_> {
if !text.is_empty() {
self.result.parts.push(InlayHintLabelPart {
text,
linked_location: self.location.take(),
linked_location: self.location.take().map(Lazy::Computed),
tooltip: None,
});
}

View File

@ -22,11 +22,7 @@ pub(super) fn hints(
return None;
}
let linked_location =
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() }
});
let sized_trait = famous_defs.core_marker_Sized();
for param in params.type_or_const_params() {
match param {
@ -48,7 +44,17 @@ pub(super) fn hints(
}
hint.parts.push(InlayHintLabelPart {
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,
});
if has_bounds {
@ -134,12 +140,14 @@ fn foo<T>() {}
InlayHintLabelPart {
text: "Sized",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 135..140,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 135..140,
},
),
),
tooltip: "",
},

View File

@ -81,7 +81,10 @@ mod tests {
use crate::{
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,
};
@ -99,7 +102,7 @@ mod tests {
let (analysis, file_id) = fixture::file(ra_fixture);
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| {
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));
}
});
@ -134,12 +137,14 @@ fn main() {
InlayHintLabelPart {
text: "B",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 63..64,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 63..64,
},
),
),
tooltip: "",
},
@ -151,12 +156,14 @@ fn main() {
InlayHintLabelPart {
text: "A",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..8,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..8,
},
),
),
tooltip: "",
},
@ -213,12 +220,14 @@ fn main() {
InlayHintLabelPart {
text: "C",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 51..52,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 51..52,
},
),
),
tooltip: "",
},
@ -230,12 +239,14 @@ fn main() {
InlayHintLabelPart {
text: "B",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 29..30,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 29..30,
},
),
),
tooltip: "",
},
@ -276,12 +287,14 @@ fn main() {
InlayHintLabelPart {
text: "C",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 51..52,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 51..52,
},
),
),
tooltip: "",
},
@ -293,12 +306,14 @@ fn main() {
InlayHintLabelPart {
text: "B",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 29..30,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 29..30,
},
),
),
tooltip: "",
},
@ -340,12 +355,14 @@ fn main() {
InlayHintLabelPart {
text: "B",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 23..24,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 23..24,
},
),
),
tooltip: "",
},
@ -353,12 +370,14 @@ fn main() {
InlayHintLabelPart {
text: "X",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 55..56,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 55..56,
},
),
),
tooltip: "",
},
@ -371,12 +390,14 @@ fn main() {
InlayHintLabelPart {
text: "A",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..8,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..8,
},
),
),
tooltip: "",
},
@ -384,12 +405,14 @@ fn main() {
InlayHintLabelPart {
text: "X",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 55..56,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 55..56,
},
),
),
tooltip: "",
},
@ -435,12 +458,14 @@ fn main() {
InlayHintLabelPart {
text: "Iterator",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -448,12 +473,14 @@ fn main() {
InlayHintLabelPart {
text: "Item",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -467,12 +494,14 @@ fn main() {
InlayHintLabelPart {
text: "Iterator",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -480,12 +509,14 @@ fn main() {
InlayHintLabelPart {
text: "Item",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -499,12 +530,14 @@ fn main() {
InlayHintLabelPart {
text: "Iterator",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -512,12 +545,14 @@ fn main() {
InlayHintLabelPart {
text: "Item",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
1,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -531,12 +566,14 @@ fn main() {
InlayHintLabelPart {
text: "MyIter",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 0..0,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 0..0,
},
),
),
tooltip: "",
},
@ -577,12 +614,14 @@ fn main() {
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
),
),
tooltip: "",
},
@ -594,12 +633,14 @@ fn main() {
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
),
),
tooltip: "",
},
@ -611,12 +652,14 @@ fn main() {
InlayHintLabelPart {
text: "Struct",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 7..13,
},
),
),
tooltip: "",
},
@ -628,12 +671,14 @@ fn main() {
InlayHintLabelPart {
text: "self",
linked_location: Some(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 42..46,
},
Computed(
FileRangeWrapper {
file_id: FileId(
0,
),
range: 42..46,
},
),
),
tooltip: "",
},

View File

@ -11,7 +11,9 @@ use syntax::{
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(
acc: &mut Vec<InlayHint>,
@ -141,7 +143,7 @@ pub(super) fn hints(
acc.push(InlayHint {
range: closing_token.text_range(),
kind: InlayKind::ClosingBrace,
label: InlayHintLabel::simple(label, None, linked_location),
label: InlayHintLabel::simple(label, None, linked_location.map(Lazy::Computed)),
text_edit: None,
position: InlayHintPosition::After,
pad_left: true,

View File

@ -53,10 +53,6 @@ pub(super) fn hints(
let last = captures.len() - 1;
for (idx, capture) in captures.into_iter().enumerate() {
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!(
"{}{}",
@ -73,8 +69,17 @@ pub(super) fn hints(
}
hint.label.append_part(InlayHintLabelPart {
text: label,
linked_location: source.name().and_then(|name| {
name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(Into::into)
linked_location: config.lazy_location_opt(|| {
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,
});

View File

@ -47,6 +47,18 @@ pub(crate) fn hints(
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 should_hide = {
@ -60,34 +72,27 @@ pub(crate) fn hints(
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 label = InlayHintLabel::simple(
format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
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 {

View File

@ -49,7 +49,7 @@ pub(super) fn hints(
if mir.locals[place.local].ty.adt_id(ChalkTyInterner).is_none() {
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
};
let range = match terminator.span {
@ -91,25 +91,26 @@ pub(super) fn hints(
},
MirSpan::Unknown => continue,
};
let binding_source = source_map
.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 binding = &hir.bindings[binding_idx];
let name = binding.name.display_no_db(file_id.edition()).to_smolstr();
if name.starts_with("<ra@") {
continue; // Ignore desugared variables
}
let mut label = InlayHintLabel::simple(
name,
Some(config.lazy_tooltip(|| crate::InlayTooltip::String("moz".into()))),
binding_source,
None,
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.append_str(")");

View File

@ -43,23 +43,21 @@ pub(super) fn hints(
!should_hide_param_name_hint(sema, &callable, param_name.as_str(), arg)
})
.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 label = InlayHintLabel::simple(
format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
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 {
range,

View File

@ -684,7 +684,10 @@ fn inlay_hint_label(
*something_to_resolve |= part.linked_location.is_some();
None
} 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 {
value: part.text,