Skip to content

Commit 69ab0cf

Browse files
authored
Merge pull request #18921 from Veykril/push-zwullmxomvsm
internal: Compute inlay hint text edits lazily
2 parents 1485a88 + 0f595b0 commit 69ab0cf

File tree

9 files changed

+150
-57
lines changed

9 files changed

+150
-57
lines changed

crates/ide/src/inlay_hints.rs

+49-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fmt::{self, Write},
3-
mem::take,
3+
mem::{self, take},
44
};
55

66
use either::Either;
@@ -297,6 +297,17 @@ pub struct InlayHintsConfig {
297297
pub closing_brace_hints_min_lines: Option<usize>,
298298
pub fields_to_resolve: InlayFieldsToResolve,
299299
}
300+
impl InlayHintsConfig {
301+
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> {
302+
if self.fields_to_resolve.resolve_text_edits {
303+
Lazy::Lazy
304+
} else {
305+
let edit = finish();
306+
never!(edit.is_empty(), "inlay hint produced an empty text edit");
307+
Lazy::Computed(edit)
308+
}
309+
}
310+
}
300311

301312
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
302313
pub struct InlayFieldsToResolve {
@@ -408,12 +419,32 @@ pub struct InlayHint {
408419
/// The actual label to show in the inlay hint.
409420
pub label: InlayHintLabel,
410421
/// Text edit to apply when "accepting" this inlay hint.
411-
pub text_edit: Option<TextEdit>,
422+
pub text_edit: Option<Lazy<TextEdit>>,
412423
/// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the
413424
/// hint does not support resolving.
414425
pub resolve_parent: Option<TextRange>,
415426
}
416427

428+
/// A type signaling that a value is either computed, or is available for computation.
429+
#[derive(Clone, Debug)]
430+
pub enum Lazy<T> {
431+
Computed(T),
432+
Lazy,
433+
}
434+
435+
impl<T> Lazy<T> {
436+
pub fn computed(self) -> Option<T> {
437+
match self {
438+
Lazy::Computed(it) => Some(it),
439+
_ => None,
440+
}
441+
}
442+
443+
pub fn is_lazy(&self) -> bool {
444+
matches!(self, Self::Lazy)
445+
}
446+
}
447+
417448
impl std::hash::Hash for InlayHint {
418449
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
419450
self.range.hash(state);
@@ -422,7 +453,7 @@ impl std::hash::Hash for InlayHint {
422453
self.pad_right.hash(state);
423454
self.kind.hash(state);
424455
self.label.hash(state);
425-
self.text_edit.is_some().hash(state);
456+
mem::discriminant(&self.text_edit).hash(state);
426457
}
427458
}
428459

@@ -439,10 +470,6 @@ impl InlayHint {
439470
resolve_parent: None,
440471
}
441472
}
442-
443-
pub fn needs_resolve(&self) -> Option<TextRange> {
444-
self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve())
445-
}
446473
}
447474

448475
#[derive(Debug, Hash)]
@@ -503,10 +530,6 @@ impl InlayHintLabel {
503530
}
504531
self.parts.push(part);
505532
}
506-
507-
pub fn needs_resolve(&self) -> bool {
508-
self.parts.iter().any(|part| part.linked_location.is_some() || part.tooltip.is_some())
509-
}
510533
}
511534

512535
impl From<String> for InlayHintLabel {
@@ -725,19 +748,22 @@ fn hint_iterator(
725748

726749
fn ty_to_text_edit(
727750
sema: &Semantics<'_, RootDatabase>,
751+
config: &InlayHintsConfig,
728752
node_for_hint: &SyntaxNode,
729753
ty: &hir::Type,
730754
offset_to_insert: TextSize,
731-
prefix: String,
732-
) -> Option<TextEdit> {
733-
let scope = sema.scope(node_for_hint)?;
755+
prefix: impl Into<String>,
756+
) -> Option<Lazy<TextEdit>> {
734757
// FIXME: Limit the length and bail out on excess somehow?
735-
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
736-
737-
let mut builder = TextEdit::builder();
738-
builder.insert(offset_to_insert, prefix);
739-
builder.insert(offset_to_insert, rendered);
740-
Some(builder.finish())
758+
let rendered = sema
759+
.scope(node_for_hint)
760+
.and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
761+
Some(config.lazy_text_edit(|| {
762+
let mut builder = TextEdit::builder();
763+
builder.insert(offset_to_insert, prefix.into());
764+
builder.insert(offset_to_insert, rendered);
765+
builder.finish()
766+
}))
741767
}
742768

743769
fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
@@ -847,7 +873,7 @@ mod tests {
847873

848874
let edits = inlay_hints
849875
.into_iter()
850-
.filter_map(|hint| hint.text_edit)
876+
.filter_map(|hint| hint.text_edit?.computed())
851877
.reduce(|mut acc, next| {
852878
acc.union(next).expect("merging text edits failed");
853879
acc
@@ -867,7 +893,8 @@ mod tests {
867893
let (analysis, file_id) = fixture::file(ra_fixture);
868894
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
869895

870-
let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect();
896+
let edits: Vec<_> =
897+
inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect();
871898

872899
assert!(edits.is_empty(), "unexpected edits: {edits:?}");
873900
}

crates/ide/src/inlay_hints/adjustment.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ pub(super) fn hints(
183183
return None;
184184
}
185185
if allow_edit {
186-
let edit = {
186+
let edit = Some(config.lazy_text_edit(|| {
187187
let mut b = TextEditBuilder::default();
188188
if let Some(pre) = &pre {
189189
b.insert(
@@ -198,14 +198,14 @@ pub(super) fn hints(
198198
);
199199
}
200200
b.finish()
201-
};
201+
}));
202202
match (&mut pre, &mut post) {
203203
(Some(pre), Some(post)) => {
204-
pre.text_edit = Some(edit.clone());
205-
post.text_edit = Some(edit);
204+
pre.text_edit = edit.clone();
205+
post.text_edit = edit;
206206
}
207-
(Some(pre), None) => pre.text_edit = Some(edit),
208-
(None, Some(post)) => post.text_edit = Some(edit),
207+
(Some(pre), None) => pre.text_edit = edit,
208+
(None, Some(post)) => post.text_edit = edit,
209209
(None, None) => (),
210210
}
211211
}

crates/ide/src/inlay_hints/bind_pat.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,14 @@ pub(super) fn hints(
7878
let text_edit = if let Some(colon_token) = &type_ascriptable {
7979
ty_to_text_edit(
8080
sema,
81+
config,
8182
desc_pat.syntax(),
8283
&ty,
8384
colon_token
8485
.as_ref()
8586
.map_or_else(|| pat.syntax().text_range(), |t| t.text_range())
8687
.end(),
87-
if colon_token.is_some() { String::new() } else { String::from(": ") },
88+
if colon_token.is_some() { "" } else { ": " },
8889
)
8990
} else {
9091
None

crates/ide/src/inlay_hints/binding_mode.rs

+18-11
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,24 @@ pub(super) fn hints(
9999
}
100100

101101
if let hints @ [_, ..] = &mut acc[acc_base..] {
102-
let mut edit = TextEditBuilder::default();
103-
for h in &mut *hints {
104-
edit.insert(
105-
match h.position {
106-
InlayHintPosition::Before => h.range.start(),
107-
InlayHintPosition::After => h.range.end(),
108-
},
109-
h.label.parts.iter().map(|p| &*p.text).chain(h.pad_right.then_some(" ")).collect(),
110-
);
111-
}
112-
let edit = edit.finish();
102+
let edit = config.lazy_text_edit(|| {
103+
let mut edit = TextEditBuilder::default();
104+
for h in &mut *hints {
105+
edit.insert(
106+
match h.position {
107+
InlayHintPosition::Before => h.range.start(),
108+
InlayHintPosition::After => h.range.end(),
109+
},
110+
h.label
111+
.parts
112+
.iter()
113+
.map(|p| &*p.text)
114+
.chain(h.pad_right.then_some(" "))
115+
.collect(),
116+
);
117+
}
118+
edit.finish()
119+
});
113120
hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone()));
114121
}
115122

crates/ide/src/inlay_hints/closure_ret.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@ pub(super) fn hints(
5252
let text_edit = if has_block_body {
5353
ty_to_text_edit(
5454
sema,
55+
config,
5556
closure.syntax(),
5657
&ty,
5758
arrow
5859
.as_ref()
5960
.map_or_else(|| param_list.syntax().text_range(), |t| t.text_range())
6061
.end(),
61-
if arrow.is_none() { String::from(" -> ") } else { String::new() },
62+
if arrow.is_none() { " -> " } else { "" },
6263
)
6364
} else {
6465
None

crates/ide/src/inlay_hints/discriminant.rs

+37-3
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ pub(super) fn enum_hints(
3636
return None;
3737
}
3838
for variant in enum_.variant_list()?.variants() {
39-
variant_hints(acc, sema, &enum_, &variant);
39+
variant_hints(acc, config, sema, &enum_, &variant);
4040
}
4141
Some(())
4242
}
4343

4444
fn variant_hints(
4545
acc: &mut Vec<InlayHint>,
46+
config: &InlayHintsConfig,
4647
sema: &Semantics<'_, RootDatabase>,
4748
enum_: &ast::Enum,
4849
variant: &ast::Variant,
@@ -88,7 +89,9 @@ fn variant_hints(
8889
},
8990
kind: InlayKind::Discriminant,
9091
label,
91-
text_edit: d.ok().map(|val| TextEdit::insert(range.start(), format!("{eq_} {val}"))),
92+
text_edit: d.ok().map(|val| {
93+
config.lazy_text_edit(|| TextEdit::insert(range.end(), format!("{eq_} {val}")))
94+
}),
9295
position: InlayHintPosition::After,
9396
pad_left: false,
9497
pad_right: false,
@@ -99,8 +102,10 @@ fn variant_hints(
99102
}
100103
#[cfg(test)]
101104
mod tests {
105+
use expect_test::expect;
106+
102107
use crate::inlay_hints::{
103-
tests::{check_with_config, DISABLED_CONFIG},
108+
tests::{check_edit, check_with_config, DISABLED_CONFIG},
104109
DiscriminantHints, InlayHintsConfig,
105110
};
106111

@@ -207,4 +212,33 @@ enum Enum {
207212
"#,
208213
);
209214
}
215+
216+
#[test]
217+
fn edit() {
218+
check_edit(
219+
InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
220+
r#"
221+
#[repr(u8)]
222+
enum Enum {
223+
Variant(),
224+
Variant1,
225+
Variant2 {},
226+
Variant3,
227+
Variant5,
228+
Variant6,
229+
}
230+
"#,
231+
expect![[r#"
232+
#[repr(u8)]
233+
enum Enum {
234+
Variant() = 0,
235+
Variant1 = 1,
236+
Variant2 {} = 2,
237+
Variant3 = 3,
238+
Variant5 = 4,
239+
Variant6 = 5,
240+
}
241+
"#]],
242+
);
243+
}
210244
}

crates/ide/src/inlay_hints/extern_block.rs

+16-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{InlayHint, InlayHintsConfig};
88
pub(super) fn extern_block_hints(
99
acc: &mut Vec<InlayHint>,
1010
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
11-
_config: &InlayHintsConfig,
11+
config: &InlayHintsConfig,
1212
_file_id: EditionedFileId,
1313
extern_block: ast::ExternBlock,
1414
) -> Option<()> {
@@ -23,7 +23,9 @@ pub(super) fn extern_block_hints(
2323
pad_right: true,
2424
kind: crate::InlayKind::ExternUnsafety,
2525
label: crate::InlayHintLabel::from("unsafe"),
26-
text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())),
26+
text_edit: Some(config.lazy_text_edit(|| {
27+
TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())
28+
})),
2729
resolve_parent: Some(extern_block.syntax().text_range()),
2830
});
2931
Some(())
@@ -32,7 +34,7 @@ pub(super) fn extern_block_hints(
3234
pub(super) fn fn_hints(
3335
acc: &mut Vec<InlayHint>,
3436
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
35-
_config: &InlayHintsConfig,
37+
config: &InlayHintsConfig,
3638
_file_id: EditionedFileId,
3739
fn_: &ast::Fn,
3840
extern_block: &ast::ExternBlock,
@@ -42,14 +44,14 @@ pub(super) fn fn_hints(
4244
return None;
4345
}
4446
let fn_ = fn_.fn_token()?;
45-
acc.push(item_hint(extern_block, fn_));
47+
acc.push(item_hint(config, extern_block, fn_));
4648
Some(())
4749
}
4850

4951
pub(super) fn static_hints(
5052
acc: &mut Vec<InlayHint>,
5153
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
52-
_config: &InlayHintsConfig,
54+
config: &InlayHintsConfig,
5355
_file_id: EditionedFileId,
5456
static_: &ast::Static,
5557
extern_block: &ast::ExternBlock,
@@ -59,28 +61,32 @@ pub(super) fn static_hints(
5961
return None;
6062
}
6163
let static_ = static_.static_token()?;
62-
acc.push(item_hint(extern_block, static_));
64+
acc.push(item_hint(config, extern_block, static_));
6365
Some(())
6466
}
6567

66-
fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint {
68+
fn item_hint(
69+
config: &InlayHintsConfig,
70+
extern_block: &ast::ExternBlock,
71+
token: SyntaxToken,
72+
) -> InlayHint {
6773
InlayHint {
6874
range: token.text_range(),
6975
position: crate::InlayHintPosition::Before,
7076
pad_left: false,
7177
pad_right: true,
7278
kind: crate::InlayKind::ExternUnsafety,
7379
label: crate::InlayHintLabel::from("unsafe"),
74-
text_edit: {
80+
text_edit: Some(config.lazy_text_edit(|| {
7581
let mut builder = TextEdit::builder();
7682
builder.insert(token.text_range().start(), "unsafe ".to_owned());
7783
if extern_block.unsafe_token().is_none() {
7884
if let Some(abi) = extern_block.abi() {
7985
builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned());
8086
}
8187
}
82-
Some(builder.finish())
83-
},
88+
builder.finish()
89+
})),
8490
resolve_parent: Some(extern_block.syntax().text_range()),
8591
}
8692
}

0 commit comments

Comments
 (0)