about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/inlay_hints.rs33
-rw-r--r--crates/ide/src/inlay_hints/adjustment.rs20
-rw-r--r--crates/ide/src/inlay_hints/bind_pat.rs1
-rw-r--r--crates/ide/src/inlay_hints/binding_mode.rs6
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs20
-rw-r--r--crates/ide/src/inlay_hints/closing_brace.rs1
-rw-r--r--crates/ide/src/inlay_hints/closure_captures.rs38
-rw-r--r--crates/ide/src/inlay_hints/closure_ret.rs1
-rw-r--r--crates/ide/src/inlay_hints/discriminant.rs1
-rw-r--r--crates/ide/src/inlay_hints/fn_lifetime_fn.rs3
-rw-r--r--crates/ide/src/inlay_hints/implicit_static.rs3
-rw-r--r--crates/ide/src/inlay_hints/param_name.rs1
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/static_index.rs2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs6
-rw-r--r--crates/rust-analyzer/src/config.rs24
-rw-r--r--crates/rust-analyzer/src/global_state.rs4
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs70
-rw-r--r--crates/rust-analyzer/src/lsp/ext.rs4
-rw-r--r--crates/rust-analyzer/src/lsp/to_proto.rs101
-rw-r--r--crates/vfs/src/lib.rs5
-rw-r--r--docs/dev/lsp-extensions.md6
22 files changed, 274 insertions, 82 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 29259167419..a5d070fe767 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -52,6 +52,28 @@ pub struct InlayHintsConfig {
     pub closure_style: ClosureStyle,
     pub max_length: Option<usize>,
     pub closing_brace_hints_min_lines: Option<usize>,
+    pub fields_to_resolve: InlayFieldsToResolve,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct InlayFieldsToResolve {
+    pub resolve_text_edits: bool,
+    pub resolve_hint_tooltip: bool,
+    pub resolve_label_tooltip: bool,
+    pub resolve_label_location: bool,
+    pub resolve_label_command: bool,
+}
+
+impl InlayFieldsToResolve {
+    pub const fn empty() -> Self {
+        Self {
+            resolve_text_edits: false,
+            resolve_hint_tooltip: false,
+            resolve_label_tooltip: false,
+            resolve_label_location: false,
+            resolve_label_command: false,
+        }
+    }
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -123,11 +145,13 @@ pub struct InlayHint {
     pub label: InlayHintLabel,
     /// Text edit to apply when "accepting" this inlay hint.
     pub text_edit: Option<TextEdit>,
+    pub needs_resolve: bool,
 }
 
 impl InlayHint {
     fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
         InlayHint {
+            needs_resolve: false,
             range,
             kind,
             label: InlayHintLabel::from(")"),
@@ -139,6 +163,7 @@ impl InlayHint {
     }
     fn opening_paren_before(kind: InlayKind, range: TextRange) -> InlayHint {
         InlayHint {
+            needs_resolve: false,
             range,
             kind,
             label: InlayHintLabel::from("("),
@@ -196,6 +221,10 @@ impl InlayHintLabel {
             }),
         }
     }
+
+    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 {
@@ -529,6 +558,7 @@ fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
 
 #[cfg(test)]
 mod tests {
+
     use expect_test::Expect;
     use hir::ClosureStyle;
     use itertools::Itertools;
@@ -538,7 +568,7 @@ mod tests {
     use crate::DiscriminantHints;
     use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
 
-    use super::ClosureReturnTypeHints;
+    use super::{ClosureReturnTypeHints, InlayFieldsToResolve};
 
     pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
         discriminant_hints: DiscriminantHints::Never,
@@ -559,6 +589,7 @@ mod tests {
         param_names_for_lifetime_elision_hints: false,
         max_length: None,
         closing_brace_hints_min_lines: None,
+        fields_to_resolve: InlayFieldsToResolve::empty(),
     };
     pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
         type_hints: true,
diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs
index 6d6bd315ebb..631807d99a7 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -137,21 +137,23 @@ pub(super) fn hints(
             }
             _ => continue,
         };
+        let label = InlayHintLabel::simple(
+            if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
+            Some(InlayTooltip::Markdown(format!(
+                "`{}` → `{}` ({coercion} coercion)",
+                source.display(sema.db),
+                target.display(sema.db),
+            ))),
+            None,
+        );
         acc.push(InlayHint {
+            needs_resolve: label.needs_resolve(),
             range: expr.syntax().text_range(),
             pad_left: false,
             pad_right: false,
             position: if postfix { InlayHintPosition::After } else { InlayHintPosition::Before },
             kind: InlayKind::Adjustment,
-            label: InlayHintLabel::simple(
-                if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
-                Some(InlayTooltip::Markdown(format!(
-                    "`{}` → `{}` ({coercion} coercion)",
-                    source.display(sema.db),
-                    target.display(sema.db),
-                ))),
-                None,
-            ),
+            label,
             text_edit: None,
         });
     }
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs
index 07b9f9cc1ff..680035c721b 100644
--- a/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/crates/ide/src/inlay_hints/bind_pat.rs
@@ -99,6 +99,7 @@ pub(super) fn hints(
         None => pat.syntax().text_range(),
     };
     acc.push(InlayHint {
+        needs_resolve: label.needs_resolve() || text_edit.is_some(),
         range: match type_ascriptable {
             Some(Some(t)) => text_range.cover(t.text_range()),
             _ => text_range,
diff --git a/crates/ide/src/inlay_hints/binding_mode.rs b/crates/ide/src/inlay_hints/binding_mode.rs
index 343cf17e50e..35504ffa785 100644
--- a/crates/ide/src/inlay_hints/binding_mode.rs
+++ b/crates/ide/src/inlay_hints/binding_mode.rs
@@ -50,9 +50,10 @@ pub(super) fn hints(
             _ => return,
         };
         acc.push(InlayHint {
+            needs_resolve: false,
             range,
             kind: InlayKind::BindingMode,
-            label: r.to_string().into(),
+            label: r.into(),
             text_edit: None,
             position: InlayHintPosition::Before,
             pad_left: false,
@@ -68,9 +69,10 @@ pub(super) fn hints(
                 hir::BindingMode::Ref(Mutability::Shared) => "ref",
             };
             acc.push(InlayHint {
+                needs_resolve: false,
                 range: pat.syntax().text_range(),
                 kind: InlayKind::BindingMode,
-                label: bm.to_string().into(),
+                label: bm.into(),
                 text_edit: None,
                 position: InlayHintPosition::Before,
                 pad_left: false,
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 8207bec7e89..12e46c0f883 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -57,10 +57,12 @@ pub(super) fn hints(
                     }
                 }
             }
+            let label = label_of_ty(famous_defs, config, &ty)?;
             acc.push(InlayHint {
+                needs_resolve: label.needs_resolve(),
                 range: expr.syntax().text_range(),
                 kind: InlayKind::Chaining,
-                label: label_of_ty(famous_defs, config, &ty)?,
+                label,
                 text_edit: None,
                 position: InlayHintPosition::After,
                 pad_left: true,
@@ -128,6 +130,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 147..154,
@@ -152,6 +155,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
@@ -221,6 +225,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 143..179,
@@ -245,6 +250,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
@@ -298,6 +304,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 143..179,
@@ -322,6 +329,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
@@ -389,6 +397,7 @@ fn main() {
                             "<i32, bool>>",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 246..265,
@@ -426,6 +435,7 @@ fn main() {
                             "<i32, bool>>",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
@@ -495,6 +505,7 @@ fn main() {
                             " = ()>",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 174..224,
@@ -532,6 +543,7 @@ fn main() {
                             " = ()>",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 174..206,
@@ -569,6 +581,7 @@ fn main() {
                             " = ()>",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 174..189,
@@ -593,6 +606,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
@@ -655,6 +669,7 @@ fn main() {
                                 ],
                             },
                         ),
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 145..185,
@@ -679,6 +694,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 145..168,
@@ -703,6 +719,7 @@ fn main() {
                             "",
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                     InlayHint {
                         range: 222..228,
@@ -725,6 +742,7 @@ fn main() {
                             },
                         ],
                         text_edit: None,
+                        needs_resolve: true,
                     },
                 ]
             "#]],
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index 2cefd5acdc2..2b68538c198 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -109,6 +109,7 @@ pub(super) fn hints(
 
     let linked_location = name_range.map(|range| FileRange { file_id, range });
     acc.push(InlayHint {
+        needs_resolve: linked_location.is_some(),
         range: closing_token.text_range(),
         kind: InlayKind::ClosingBrace,
         label: InlayHintLabel::simple(label, None, linked_location),
diff --git a/crates/ide/src/inlay_hints/closure_captures.rs b/crates/ide/src/inlay_hints/closure_captures.rs
index 9d5defcbb71..d691303c18b 100644
--- a/crates/ide/src/inlay_hints/closure_captures.rs
+++ b/crates/ide/src/inlay_hints/closure_captures.rs
@@ -31,9 +31,10 @@ pub(super) fn hints(
             let range = closure.syntax().first_token()?.prev_token()?.text_range();
             let range = TextRange::new(range.end() - TextSize::from(1), range.end());
             acc.push(InlayHint {
+                needs_resolve: false,
                 range,
                 kind: InlayKind::ClosureCapture,
-                label: InlayHintLabel::simple("move", None, None),
+                label: InlayHintLabel::from("move"),
                 text_edit: None,
                 position: InlayHintPosition::After,
                 pad_left: false,
@@ -43,6 +44,7 @@ pub(super) fn hints(
         }
     };
     acc.push(InlayHint {
+        needs_resolve: false,
         range: move_kw_range,
         kind: InlayKind::ClosureCapture,
         label: InlayHintLabel::from("("),
@@ -59,23 +61,25 @@ pub(super) fn hints(
         // force cache the source file, otherwise sema lookup will potentially panic
         _ = sema.parse_or_expand(source.file());
 
+        let label = InlayHintLabel::simple(
+            format!(
+                "{}{}",
+                match capture.kind() {
+                    hir::CaptureKind::SharedRef => "&",
+                    hir::CaptureKind::UniqueSharedRef => "&unique ",
+                    hir::CaptureKind::MutableRef => "&mut ",
+                    hir::CaptureKind::Move => "",
+                },
+                capture.display_place(sema.db)
+            ),
+            None,
+            source.name().and_then(|name| name.syntax().original_file_range_opt(sema.db)),
+        );
         acc.push(InlayHint {
+            needs_resolve: label.needs_resolve(),
             range: move_kw_range,
             kind: InlayKind::ClosureCapture,
-            label: InlayHintLabel::simple(
-                format!(
-                    "{}{}",
-                    match capture.kind() {
-                        hir::CaptureKind::SharedRef => "&",
-                        hir::CaptureKind::UniqueSharedRef => "&unique ",
-                        hir::CaptureKind::MutableRef => "&mut ",
-                        hir::CaptureKind::Move => "",
-                    },
-                    capture.display_place(sema.db)
-                ),
-                None,
-                source.name().and_then(|name| name.syntax().original_file_range_opt(sema.db)),
-            ),
+            label,
             text_edit: None,
             position: InlayHintPosition::After,
             pad_left: false,
@@ -84,9 +88,10 @@ pub(super) fn hints(
 
         if idx != last {
             acc.push(InlayHint {
+                needs_resolve: false,
                 range: move_kw_range,
                 kind: InlayKind::ClosureCapture,
-                label: InlayHintLabel::simple(", ", None, None),
+                label: InlayHintLabel::from(", "),
                 text_edit: None,
                 position: InlayHintPosition::After,
                 pad_left: false,
@@ -95,6 +100,7 @@ pub(super) fn hints(
         }
     }
     acc.push(InlayHint {
+        needs_resolve: false,
         range: move_kw_range,
         kind: InlayKind::ClosureCapture,
         label: InlayHintLabel::from(")"),
diff --git a/crates/ide/src/inlay_hints/closure_ret.rs b/crates/ide/src/inlay_hints/closure_ret.rs
index 3b41db0f13d..204967cd7ca 100644
--- a/crates/ide/src/inlay_hints/closure_ret.rs
+++ b/crates/ide/src/inlay_hints/closure_ret.rs
@@ -64,6 +64,7 @@ pub(super) fn hints(
     };
 
     acc.push(InlayHint {
+        needs_resolve: label.needs_resolve() || text_edit.is_some(),
         range: param_list.syntax().text_range(),
         kind: InlayKind::Type,
         label,
diff --git a/crates/ide/src/inlay_hints/discriminant.rs b/crates/ide/src/inlay_hints/discriminant.rs
index c4d2ac75cfa..26dc6fa8b9c 100644
--- a/crates/ide/src/inlay_hints/discriminant.rs
+++ b/crates/ide/src/inlay_hints/discriminant.rs
@@ -79,6 +79,7 @@ fn variant_hints(
         None,
     );
     acc.push(InlayHint {
+        needs_resolve: label.needs_resolve(),
         range: match eq_token {
             Some(t) => range.cover(t.text_range()),
             _ => range,
diff --git a/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
index 5fce11b785a..7b05e32ad86 100644
--- a/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
+++ b/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
@@ -22,6 +22,7 @@ pub(super) fn hints(
     }
 
     let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
+        needs_resolve: false,
         range: t.text_range(),
         kind: InlayKind::Lifetime,
         label: label.into(),
@@ -185,6 +186,7 @@ pub(super) fn hints(
             let angle_tok = gpl.l_angle_token()?;
             let is_empty = gpl.generic_params().next().is_none();
             acc.push(InlayHint {
+                needs_resolve: false,
                 range: angle_tok.text_range(),
                 kind: InlayKind::Lifetime,
                 label: format!(
@@ -200,6 +202,7 @@ pub(super) fn hints(
             });
         }
         (None, allocated_lifetimes) => acc.push(InlayHint {
+            needs_resolve: false,
             range: func.name()?.syntax().text_range(),
             kind: InlayKind::GenericParamList,
             label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
diff --git a/crates/ide/src/inlay_hints/implicit_static.rs b/crates/ide/src/inlay_hints/implicit_static.rs
index fc297a8d824..f18e6421cbc 100644
--- a/crates/ide/src/inlay_hints/implicit_static.rs
+++ b/crates/ide/src/inlay_hints/implicit_static.rs
@@ -31,9 +31,10 @@ pub(super) fn hints(
         if ty.lifetime().is_none() {
             let t = ty.amp_token()?;
             acc.push(InlayHint {
+                needs_resolve: false,
                 range: t.text_range(),
                 kind: InlayKind::Lifetime,
-                label: "'static".to_owned().into(),
+                label: "'static".into(),
                 text_edit: None,
                 position: InlayHintPosition::After,
                 pad_left: false,
diff --git a/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs
index c4f43f41175..b4260d82506 100644
--- a/crates/ide/src/inlay_hints/param_name.rs
+++ b/crates/ide/src/inlay_hints/param_name.rs
@@ -57,6 +57,7 @@ pub(super) fn hints(
             let label =
                 InlayHintLabel::simple(format!("{param_name}{colon}"), None, linked_location);
             InlayHint {
+                needs_resolve: label.needs_resolve(),
                 range,
                 kind: InlayKind::Parameter,
                 label,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index ff0048903db..aee03d218ad 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -91,9 +91,9 @@ pub use crate::{
         MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
     },
     inlay_hints::{
-        AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint,
-        InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
-        InlayTooltip, LifetimeElisionHints,
+        AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,
+        InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition,
+        InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
     },
     join_lines::JoinLinesConfig,
     markup::Markup,
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index d8696198d3b..aabd26da289 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -12,6 +12,7 @@ use ide_db::{
 };
 use syntax::{AstNode, SyntaxKind::*, TextRange, T};
 
+use crate::inlay_hints::InlayFieldsToResolve;
 use crate::{
     hover::hover_for_definition,
     inlay_hints::AdjustmentHintsMode,
@@ -125,6 +126,7 @@ impl StaticIndex<'_> {
                     max_length: Some(25),
                     closure_capture_hints: false,
                     closing_brace_hints_min_lines: Some(25),
+                    fields_to_resolve: InlayFieldsToResolve::empty(),
                 },
                 file_id,
                 None,
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 25ed57f18cc..dcb3ca6581c 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -15,7 +15,10 @@ use hir_def::{
     hir::{ExprId, PatId},
 };
 use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
-use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase};
+use ide::{
+    Analysis, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve, InlayHintsConfig, LineCol,
+    RootDatabase,
+};
 use ide_db::{
     base_db::{
         salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@@ -786,6 +789,7 @@ impl flags::AnalysisStats {
                     closure_style: hir::ClosureStyle::ImplFn,
                     max_length: Some(25),
                     closing_brace_hints_min_lines: Some(20),
+                    fields_to_resolve: InlayFieldsToResolve::empty(),
                 },
                 file_id,
                 None,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 40c50f6d176..ea3a21241cb 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -13,8 +13,9 @@ use cfg::{CfgAtom, CfgDiff};
 use flycheck::FlycheckConfig;
 use ide::{
     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
-    HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
-    JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope,
+    HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
+    InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
+    Snippet, SnippetScope,
 };
 use ide_db::{
     imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -1335,6 +1336,18 @@ impl Config {
     }
 
     pub fn inlay_hints(&self) -> InlayHintsConfig {
+        let client_capability_fields = self
+            .caps
+            .text_document
+            .as_ref()
+            .and_then(|text| text.inlay_hint.as_ref())
+            .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
+            .map(|inlay_resolve| inlay_resolve.properties.iter())
+            .into_iter()
+            .flatten()
+            .cloned()
+            .collect::<FxHashSet<_>>();
+
         InlayHintsConfig {
             render_colons: self.data.inlayHints_renderColons,
             type_hints: self.data.inlayHints_typeHints_enable,
@@ -1395,6 +1408,13 @@ impl Config {
             } else {
                 None
             },
+            fields_to_resolve: InlayFieldsToResolve {
+                resolve_text_edits: client_capability_fields.contains("textEdits"),
+                resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
+                resolve_label_tooltip: client_capability_fields.contains("label.tooltip"),
+                resolve_label_location: client_capability_fields.contains("label.location"),
+                resolve_label_command: client_capability_fields.contains("label.command"),
+            },
         }
     }
 
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index e5d8438d80d..c09f57252ce 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -498,6 +498,10 @@ impl GlobalStateSnapshot {
     pub(crate) fn vfs_memory_usage(&self) -> usize {
         self.vfs_read().memory_usage()
     }
+
+    pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
+        self.vfs.read().0.exists(file_id)
+    }
 }
 
 pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 425bcce7a22..b8a1a39be19 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -11,8 +11,8 @@ use anyhow::Context;
 
 use ide::{
     AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
-    HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind,
-    SingleResolve, SourceChange, TextEdit,
+    HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
+    Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
 };
 use ide_db::SymbolKind;
 use lsp_server::ErrorCode;
@@ -30,7 +30,7 @@ use serde_json::json;
 use stdx::{format_to, never};
 use syntax::{algo, ast, AstNode, TextRange, TextSize};
 use triomphe::Arc;
-use vfs::{AbsPath, AbsPathBuf, VfsPath};
+use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -1412,17 +1412,73 @@ pub(crate) fn handle_inlay_hints(
         snap.analysis
             .inlay_hints(&inlay_hints_config, file_id, Some(range))?
             .into_iter()
-            .map(|it| to_proto::inlay_hint(&snap, &line_index, it))
+            .map(|it| {
+                to_proto::inlay_hint(
+                    &snap,
+                    &inlay_hints_config.fields_to_resolve,
+                    &line_index,
+                    file_id,
+                    it,
+                )
+            })
             .collect::<Cancellable<Vec<_>>>()?,
     ))
 }
 
 pub(crate) fn handle_inlay_hints_resolve(
-    _snap: GlobalStateSnapshot,
-    hint: InlayHint,
+    snap: GlobalStateSnapshot,
+    mut original_hint: InlayHint,
 ) -> anyhow::Result<InlayHint> {
     let _p = profile::span("handle_inlay_hints_resolve");
-    Ok(hint)
+
+    let data = match original_hint.data.take() {
+        Some(it) => it,
+        None => return Ok(original_hint),
+    };
+
+    let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?;
+    let file_id = FileId(resolve_data.file_id);
+    anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
+
+    let line_index = snap.file_line_index(file_id)?;
+    let range = from_proto::text_range(
+        &line_index,
+        lsp_types::Range { start: original_hint.position, end: original_hint.position },
+    )?;
+    let range_start = range.start();
+    let range_end = range.end();
+    let large_range = TextRange::new(
+        range_start.checked_sub(1.into()).unwrap_or(range_start),
+        range_end.checked_add(1.into()).unwrap_or(range_end),
+    );
+    let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
+    forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
+    let resolve_hints = snap.analysis.inlay_hints(
+        &forced_resolve_inlay_hints_config,
+        file_id,
+        Some(large_range),
+    )?;
+
+    let mut resolved_hints = resolve_hints
+        .into_iter()
+        .filter_map(|it| {
+            to_proto::inlay_hint(
+                &snap,
+                &forced_resolve_inlay_hints_config.fields_to_resolve,
+                &line_index,
+                file_id,
+                it,
+            )
+            .ok()
+        })
+        .filter(|hint| hint.position == original_hint.position)
+        .filter(|hint| hint.kind == original_hint.kind);
+    if let Some(resolved_hint) = resolved_hints.next() {
+        if resolved_hints.next().is_none() {
+            return Ok(resolved_hint);
+        }
+    }
+    Ok(original_hint)
 }
 
 pub(crate) fn handle_call_hierarchy_prepare(
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index d0989b3230d..ad56899163d 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -682,7 +682,9 @@ pub struct CompletionResolveData {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-pub struct InlayHintResolveData {}
+pub struct InlayHintResolveData {
+    pub file_id: u32,
+}
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct CompletionImport {
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index 57f46022540..ce2f993ca15 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -8,10 +8,10 @@ use std::{
 use ide::{
     Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
     CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
-    Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
-    InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory,
-    RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind,
-    SymbolKind, TextEdit, TextRange, TextSize,
+    Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
+    InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
+    NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
+    SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
 };
 use ide_db::rust_doc::format_docs;
 use itertools::Itertools;
@@ -438,10 +438,25 @@ pub(crate) fn signature_help(
 
 pub(crate) fn inlay_hint(
     snap: &GlobalStateSnapshot,
+    fields_to_resolve: &InlayFieldsToResolve,
     line_index: &LineIndex,
+    file_id: FileId,
     inlay_hint: InlayHint,
 ) -> Cancellable<lsp_types::InlayHint> {
-    let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?;
+    let needs_resolve = inlay_hint.needs_resolve;
+    let (label, tooltip, mut something_to_resolve) =
+        inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?;
+    let text_edits = if needs_resolve && fields_to_resolve.resolve_text_edits {
+        something_to_resolve |= inlay_hint.text_edit.is_some();
+        None
+    } else {
+        inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
+    };
+    let data = if needs_resolve && something_to_resolve {
+        Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.0 }).unwrap())
+    } else {
+        None
+    };
 
     Ok(lsp_types::InlayHint {
         position: match inlay_hint.position {
@@ -455,8 +470,8 @@ pub(crate) fn inlay_hint(
             InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
             _ => None,
         },
-        text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)),
-        data: None,
+        text_edits,
+        data,
         tooltip,
         label,
     })
@@ -464,13 +479,18 @@ pub(crate) fn inlay_hint(
 
 fn inlay_hint_label(
     snap: &GlobalStateSnapshot,
+    fields_to_resolve: &InlayFieldsToResolve,
+    needs_resolve: bool,
     mut label: InlayHintLabel,
-) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
-    let res = match &*label.parts {
+) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>, bool)> {
+    let mut something_to_resolve = false;
+    let (label, tooltip) = match &*label.parts {
         [InlayHintLabelPart { linked_location: None, .. }] => {
             let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
-            (
-                lsp_types::InlayHintLabel::String(text),
+            let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip {
+                something_to_resolve |= tooltip.is_some();
+                None
+            } else {
                 match tooltip {
                     Some(ide::InlayTooltip::String(s)) => {
                         Some(lsp_types::InlayHintTooltip::String(s))
@@ -482,41 +502,52 @@ fn inlay_hint_label(
                         }))
                     }
                     None => None,
-                },
-            )
+                }
+            };
+            (lsp_types::InlayHintLabel::String(text), hint_tooltip)
         }
         _ => {
             let parts = label
                 .parts
                 .into_iter()
                 .map(|part| {
-                    part.linked_location.map(|range| location(snap, range)).transpose().map(
-                        |location| lsp_types::InlayHintLabelPart {
-                            value: part.text,
-                            tooltip: match part.tooltip {
-                                Some(ide::InlayTooltip::String(s)) => {
-                                    Some(lsp_types::InlayHintLabelPartTooltip::String(s))
-                                }
-                                Some(ide::InlayTooltip::Markdown(s)) => {
-                                    Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
-                                        lsp_types::MarkupContent {
-                                            kind: lsp_types::MarkupKind::Markdown,
-                                            value: s,
-                                        },
-                                    ))
-                                }
-                                None => None,
-                            },
-                            location,
-                            command: None,
-                        },
-                    )
+                    let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip {
+                        something_to_resolve |= part.tooltip.is_some();
+                        None
+                    } else {
+                        match part.tooltip {
+                            Some(ide::InlayTooltip::String(s)) => {
+                                Some(lsp_types::InlayHintLabelPartTooltip::String(s))
+                            }
+                            Some(ide::InlayTooltip::Markdown(s)) => {
+                                Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
+                                    lsp_types::MarkupContent {
+                                        kind: lsp_types::MarkupKind::Markdown,
+                                        value: s,
+                                    },
+                                ))
+                            }
+                            None => None,
+                        }
+                    };
+                    let location = if needs_resolve && fields_to_resolve.resolve_label_location {
+                        something_to_resolve |= part.linked_location.is_some();
+                        None
+                    } else {
+                        part.linked_location.map(|range| location(snap, range)).transpose()?
+                    };
+                    Ok(lsp_types::InlayHintLabelPart {
+                        value: part.text,
+                        tooltip,
+                        location,
+                        command: None,
+                    })
                 })
                 .collect::<Cancellable<_>>()?;
             (lsp_types::InlayHintLabel::LabelParts(parts), None)
         }
     };
-    Ok(res)
+    Ok((label, tooltip, something_to_resolve))
 }
 
 static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
index fe3dfe61968..06004adad34 100644
--- a/crates/vfs/src/lib.rs
+++ b/crates/vfs/src/lib.rs
@@ -184,6 +184,11 @@ impl Vfs {
         mem::take(&mut self.changes)
     }
 
+    /// Provides a panic-less way to verify file_id validity.
+    pub fn exists(&self, file_id: FileId) -> bool {
+        self.get(file_id).is_some()
+    }
+
     /// Returns the id associated with `path`
     ///
     /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 67d82a68548..0801e988f5c 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 149a5be3c5e469d1
+lsp/ext.rs hash: 121482ee911854da
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
@@ -322,7 +322,7 @@ Position[]
 
 ```rust
 fn main() {
-    let x: Vec<()>/*cursor here*/ = vec![]
+    let x: Vec<()>/*cursor here*/ = vec![];
 }
 ```
 
@@ -362,7 +362,7 @@ interface RunnablesParams {
 ```typescript
 interface Runnable {
     label: string;
-    /// If this Runnable is associated with a specific function/module, etc, the location of this item
+    /// If this Runnable is associated with a specific function/module, etc., the location of this item
     location?: LocationLink;
     /// Running things is necessary technology specific, `kind` needs to be advertised via server capabilities,
     // the type of `args` is specific to `kind`. The actual running is handled by the client.