Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/inlay_hints.rs')
| -rw-r--r-- | crates/ide/src/inlay_hints.rs | 159 |
1 files changed, 128 insertions, 31 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index faa65019ee..6d83a747d7 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -1,6 +1,6 @@ use std::{ fmt::{self, Write}, - mem::take, + mem::{self, take}, }; use either::Either; @@ -24,6 +24,7 @@ use crate::{navigation_target::TryToNav, FileId}; mod adjustment; mod bind_pat; mod binding_mode; +mod bounds; mod chaining; mod closing_brace; mod closure_captures; @@ -111,6 +112,9 @@ pub(crate) fn inlay_hints( } hints(event); } + if let Some(range_limit) = range_limit { + acc.retain(|hint| range_limit.contains_range(hint.range)); + } acc } @@ -264,6 +268,7 @@ fn hints( ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path), _ => Some(()), }, + ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, file_id, it), _ => Some(()), } }; @@ -273,6 +278,7 @@ fn hints( pub struct InlayHintsConfig { pub render_colons: bool, pub type_hints: bool, + pub sized_bound: bool, pub discriminant_hints: DiscriminantHints, pub parameter_hints: bool, pub generic_parameter_hints: GenericParameterHints, @@ -294,6 +300,36 @@ 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 { + Lazy::Lazy + } else { + let edit = finish(); + never!(edit.is_empty(), "inlay hint produced an empty text edit"); + Lazy::Computed(edit) + } + } + + fn lazy_tooltip(&self, finish: impl FnOnce() -> InlayTooltip) -> Lazy<InlayTooltip> { + if self.fields_to_resolve.resolve_hint_tooltip + && self.fields_to_resolve.resolve_label_tooltip + { + Lazy::Lazy + } else { + let tooltip = finish(); + never!( + match &tooltip { + InlayTooltip::String(s) => s, + InlayTooltip::Markdown(s) => s, + } + .is_empty(), + "inlay hint produced an empty tooltip" + ); + Lazy::Computed(tooltip) + } + } +} #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct InlayFieldsToResolve { @@ -405,12 +441,32 @@ pub struct InlayHint { /// The actual label to show in the inlay hint. pub label: InlayHintLabel, /// Text edit to apply when "accepting" this inlay hint. - pub text_edit: Option<TextEdit>, + pub text_edit: Option<Lazy<TextEdit>>, /// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the /// hint does not support resolving. pub resolve_parent: Option<TextRange>, } +/// A type signaling that a value is either computed, or is available for computation. +#[derive(Clone, Debug)] +pub enum Lazy<T> { + Computed(T), + Lazy, +} + +impl<T> Lazy<T> { + pub fn computed(self) -> Option<T> { + match self { + Lazy::Computed(it) => Some(it), + _ => None, + } + } + + pub fn is_lazy(&self) -> bool { + matches!(self, Self::Lazy) + } +} + impl std::hash::Hash for InlayHint { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.range.hash(state); @@ -419,7 +475,7 @@ impl std::hash::Hash for InlayHint { self.pad_right.hash(state); self.kind.hash(state); self.label.hash(state); - self.text_edit.is_some().hash(state); + mem::discriminant(&self.text_edit).hash(state); } } @@ -436,10 +492,6 @@ impl InlayHint { resolve_parent: None, } } - - pub fn needs_resolve(&self) -> Option<TextRange> { - self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve()) - } } #[derive(Debug, Hash)] @@ -456,7 +508,7 @@ pub struct InlayHintLabel { impl InlayHintLabel { pub fn simple( s: impl Into<String>, - tooltip: Option<InlayTooltip>, + tooltip: Option<Lazy<InlayTooltip>>, linked_location: Option<FileRange>, ) -> InlayHintLabel { InlayHintLabel { @@ -500,10 +552,6 @@ impl InlayHintLabel { } self.parts.push(part); } - - pub fn needs_resolve(&self) -> bool { - self.parts.iter().any(|part| part.linked_location.is_some() || part.tooltip.is_some()) - } } impl From<String> for InlayHintLabel { @@ -538,7 +586,6 @@ impl fmt::Debug for InlayHintLabel { } } -#[derive(Hash)] pub struct InlayHintLabelPart { pub text: String, /// Source location represented by this label part. The client will use this to fetch the part's @@ -549,13 +596,21 @@ pub struct InlayHintLabelPart { pub linked_location: Option<FileRange>, /// The tooltip to show when hovering over the inlay hint, this may invoke other actions like /// hover requests to show. - pub tooltip: Option<InlayTooltip>, + pub tooltip: Option<Lazy<InlayTooltip>>, +} + +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.tooltip.is_some().hash(state); + } } impl fmt::Debug for InlayHintLabelPart { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self { text, linked_location: None, tooltip: None } => text.fmt(f), + Self { text, linked_location: None, tooltip: None | Some(Lazy::Lazy) } => text.fmt(f), Self { text, linked_location, tooltip } => f .debug_struct("InlayHintLabelPart") .field("text", text) @@ -563,7 +618,8 @@ impl fmt::Debug for InlayHintLabelPart { .field( "tooltip", &tooltip.as_ref().map_or("", |it| match it { - InlayTooltip::String(it) | InlayTooltip::Markdown(it) => it, + Lazy::Computed(InlayTooltip::String(it) | InlayTooltip::Markdown(it)) => it, + Lazy::Lazy => "", }), ) .finish(), @@ -722,19 +778,22 @@ fn hint_iterator( fn ty_to_text_edit( sema: &Semantics<'_, RootDatabase>, + config: &InlayHintsConfig, node_for_hint: &SyntaxNode, ty: &hir::Type, offset_to_insert: TextSize, - prefix: String, -) -> Option<TextEdit> { - let scope = sema.scope(node_for_hint)?; + prefix: impl Into<String>, +) -> Option<Lazy<TextEdit>> { // FIXME: Limit the length and bail out on excess somehow? - let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?; - - let mut builder = TextEdit::builder(); - builder.insert(offset_to_insert, prefix); - builder.insert(offset_to_insert, rendered); - Some(builder.finish()) + let rendered = sema + .scope(node_for_hint) + .and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?; + Some(config.lazy_text_edit(|| { + let mut builder = TextEdit::builder(); + builder.insert(offset_to_insert, prefix.into()); + builder.insert(offset_to_insert, rendered); + builder.finish() + })) } fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool { @@ -760,6 +819,7 @@ mod tests { render_colons: false, type_hints: false, parameter_hints: false, + sized_bound: false, generic_parameter_hints: GenericParameterHints { type_hints: false, lifetime_hints: false, @@ -794,12 +854,15 @@ mod tests { }; #[track_caller] - pub(super) fn check(ra_fixture: &str) { + pub(super) fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { check_with_config(TEST_CONFIG, ra_fixture); } #[track_caller] - pub(super) fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { + pub(super) fn check_with_config( + config: InlayHintsConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + ) { let (analysis, file_id) = fixture::file(ra_fixture); let mut expected = extract_annotations(&analysis.file_text(file_id).unwrap()); let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); @@ -814,16 +877,33 @@ mod tests { assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}"); } + #[track_caller] + pub(super) fn check_expect( + config: InlayHintsConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + expect: Expect, + ) { + let (analysis, file_id) = fixture::file(ra_fixture); + let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); + let filtered = + inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>(); + expect.assert_debug_eq(&filtered) + } + /// Computes inlay hints for the fixture, applies all the provided text edits and then runs /// expect test. #[track_caller] - pub(super) fn check_edit(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { + pub(super) fn check_edit( + config: InlayHintsConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + expect: Expect, + ) { let (analysis, file_id) = fixture::file(ra_fixture); let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); let edits = inlay_hints .into_iter() - .filter_map(|hint| hint.text_edit) + .filter_map(|hint| hint.text_edit?.computed()) .reduce(|mut acc, next| { acc.union(next).expect("merging text edits failed"); acc @@ -836,11 +916,15 @@ mod tests { } #[track_caller] - pub(super) fn check_no_edit(config: InlayHintsConfig, ra_fixture: &str) { + pub(super) fn check_no_edit( + config: InlayHintsConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + ) { let (analysis, file_id) = fixture::file(ra_fixture); let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); - let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect(); + let edits: Vec<_> = + inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect(); assert!(edits.is_empty(), "unexpected edits: {edits:?}"); } @@ -870,4 +954,17 @@ fn foo() { "#, ); } + + #[test] + fn regression_18898() { + check( + r#" +//- proc_macros: issue_18898 +#[proc_macros::issue_18898] +fn foo() { + let +} +"#, + ); + } } |