about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-08-30 15:45:12 +0200
committerLukas Wirth <lukastw97@gmail.com>2024-08-30 15:57:52 +0200
commitbe33a2e1bd21dbb7d2a75ef949137e48d93e985e (patch)
treeeaaba3cb3041bfeefb32af8ead7fc987ee6b016f /src
parente77de3841575f0febfc3f774071214da2e676460 (diff)
downloadrust-be33a2e1bd21dbb7d2a75ef949137e48d93e985e.tar.gz
rust-be33a2e1bd21dbb7d2a75ef949137e48d93e985e.zip
Improve inlay hint resolution reliability
Diffstat (limited to 'src')
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs398
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs20
-rw-r--r--src/tools/rust-analyzer/docs/dev/lsp-extensions.md2
20 files changed, 282 insertions, 216 deletions
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
index 6a5d5e26a4f..93dd56a450d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
@@ -15,7 +15,7 @@ use span::{Edition, EditionedFileId};
 use stdx::never;
 use syntax::{
     ast::{self, AstNode},
-    match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
+    match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize, WalkEvent,
 };
 use text_edit::TextEdit;
 
@@ -36,6 +36,192 @@ mod implicit_static;
 mod param_name;
 mod range_exclusive;
 
+// Feature: Inlay Hints
+//
+// rust-analyzer shows additional information inline with the source code.
+// Editors usually render this using read-only virtual text snippets interspersed with code.
+//
+// rust-analyzer by default shows hints for
+//
+// * types of local variables
+// * names of function arguments
+// * names of const generic parameters
+// * types of chained expressions
+//
+// Optionally, one can enable additional hints for
+//
+// * return types of closure expressions
+// * elided lifetimes
+// * compiler inserted reborrows
+// * names of generic type and lifetime parameters
+//
+// Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
+// any of the
+// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L92-L99[following criteria]
+// are met:
+//
+// * the parameter name is a suffix of the function's name
+// * the argument is a qualified constructing or call expression where the qualifier is an ADT
+// * exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
+//   of argument with _ splitting it off
+// * the parameter name starts with `ra_fixture`
+// * the parameter name is a
+// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L200[well known name]
+// in a unary function
+// * the parameter name is a
+// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L201[single character]
+// in a unary function
+//
+// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
+pub(crate) fn inlay_hints(
+    db: &RootDatabase,
+    file_id: FileId,
+    range_limit: Option<TextRange>,
+    config: &InlayHintsConfig,
+) -> Vec<InlayHint> {
+    let _p = tracing::info_span!("inlay_hints").entered();
+    let sema = Semantics::new(db);
+    let file_id = sema
+        .attach_first_edition(file_id)
+        .unwrap_or_else(|| EditionedFileId::current_edition(file_id));
+    let file = sema.parse(file_id);
+    let file = file.syntax();
+
+    let mut acc = Vec::new();
+
+    let Some(scope) = sema.scope(file) else {
+        return acc;
+    };
+    let famous_defs = FamousDefs(&sema, scope.krate());
+
+    let parent_impl = &mut None;
+    let hints = |node| hints(&mut acc, parent_impl, &famous_defs, config, file_id, node);
+    match range_limit {
+        // FIXME: This can miss some hints that require the parent of the range to calculate
+        Some(range) => match file.covering_element(range) {
+            NodeOrToken::Token(_) => return acc,
+            NodeOrToken::Node(n) => n
+                .preorder()
+                .filter(|event| matches!(event, WalkEvent::Enter(node) if range.intersect(node.text_range()).is_some()))
+                .for_each(hints),
+        },
+        None => file.preorder().for_each(hints),
+    };
+
+    acc
+}
+
+pub(crate) fn inlay_hints_resolve(
+    db: &RootDatabase,
+    file_id: FileId,
+    resolve_range: TextRange,
+    hash: u64,
+    config: &InlayHintsConfig,
+    hasher: impl Fn(&InlayHint) -> u64,
+) -> Option<InlayHint> {
+    let _p = tracing::info_span!("inlay_hints_resolve").entered();
+    let sema = Semantics::new(db);
+    let file_id = sema
+        .attach_first_edition(file_id)
+        .unwrap_or_else(|| EditionedFileId::current_edition(file_id));
+    let file = sema.parse(file_id);
+    let file = file.syntax();
+
+    let scope = sema.scope(file)?;
+    let famous_defs = FamousDefs(&sema, scope.krate());
+    let mut acc = Vec::new();
+
+    let parent_impl = &mut None;
+    let hints = |node| hints(&mut acc, parent_impl, &famous_defs, config, file_id, node);
+
+    let mut res = file.clone();
+    let res = loop {
+        res = match res.child_or_token_at_range(resolve_range) {
+            Some(NodeOrToken::Node(n)) if n.text_range() == resolve_range => break n,
+            Some(NodeOrToken::Node(n)) => n,
+            _ => break res,
+        };
+    };
+    res.preorder().for_each(hints);
+    acc.into_iter().find(|hint| hasher(hint) == hash)
+}
+
+fn hints(
+    hints: &mut Vec<InlayHint>,
+    parent_impl: &mut Option<ast::Impl>,
+    famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+    config: &InlayHintsConfig,
+    file_id: EditionedFileId,
+    node: WalkEvent<SyntaxNode>,
+) {
+    let node = match node {
+        WalkEvent::Enter(node) => node,
+        WalkEvent::Leave(n) => {
+            if ast::Impl::can_cast(n.kind()) {
+                parent_impl.take();
+            }
+            return;
+        }
+    };
+    closing_brace::hints(hints, sema, config, file_id, node.clone());
+    if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
+        generic_param::hints(hints, sema, config, any_has_generic_args);
+    }
+
+    match_ast! {
+        match node {
+            ast::Expr(expr) => {
+                chaining::hints(hints, famous_defs, config, file_id, &expr);
+                adjustment::hints(hints, famous_defs, config, file_id, &expr);
+                match expr {
+                    ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
+                    ast::Expr::MethodCallExpr(it) => {
+                        param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
+                    }
+                    ast::Expr::ClosureExpr(it) => {
+                        closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
+                        closure_ret::hints(hints, famous_defs, config, file_id, it)
+                    },
+                    ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, file_id,  it),
+                    _ => None,
+                }
+            },
+            ast::Pat(it) => {
+                binding_mode::hints(hints, famous_defs, config, file_id,  &it);
+                match it {
+                    ast::Pat::IdentPat(it) => {
+                        bind_pat::hints(hints, famous_defs, config, file_id, &it);
+                    }
+                    ast::Pat::RangePat(it) => {
+                        range_exclusive::hints(hints, famous_defs, config, file_id, it);
+                    }
+                    _ => {}
+                }
+                Some(())
+            },
+            ast::Item(it) => match it {
+                // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
+                ast::Item::Impl(impl_) => {
+                    *parent_impl = Some(impl_);
+                    None
+                },
+                ast::Item::Fn(it) => {
+                    implicit_drop::hints(hints, famous_defs, config, file_id, &it);
+                    fn_lifetime_fn::hints(hints, famous_defs, config, file_id, it)
+                },
+                // static type elisions
+                ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)),
+                ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)),
+                ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it),
+                _ => None,
+            },
+            // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
+            ast::Type(_) => None,
+            _ => None,
+        }
+    };
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct InlayHintsConfig {
     pub render_colons: bool,
@@ -162,6 +348,9 @@ pub struct InlayHint {
     pub label: InlayHintLabel,
     /// Text edit to apply when "accepting" this inlay hint.
     pub text_edit: Option<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>,
 }
 
 impl std::hash::Hash for InlayHint {
@@ -186,6 +375,7 @@ impl InlayHint {
             position: InlayHintPosition::After,
             pad_left: false,
             pad_right: false,
+            resolve_parent: None,
         }
     }
 
@@ -198,11 +388,12 @@ impl InlayHint {
             position: InlayHintPosition::Before,
             pad_left: false,
             pad_right: false,
+            resolve_parent: None,
         }
     }
 
-    pub fn needs_resolve(&self) -> bool {
-        self.text_edit.is_some() || self.label.needs_resolve()
+    pub fn needs_resolve(&self) -> Option<TextRange> {
+        self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve())
     }
 }
 
@@ -434,190 +625,6 @@ fn label_of_ty(
     Some(r)
 }
 
-fn ty_to_text_edit(
-    sema: &Semantics<'_, RootDatabase>,
-    node_for_hint: &SyntaxNode,
-    ty: &hir::Type,
-    offset_to_insert: TextSize,
-    prefix: String,
-) -> Option<TextEdit> {
-    let scope = sema.scope(node_for_hint)?;
-    // 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())
-}
-
-// Feature: Inlay Hints
-//
-// rust-analyzer shows additional information inline with the source code.
-// Editors usually render this using read-only virtual text snippets interspersed with code.
-//
-// rust-analyzer by default shows hints for
-//
-// * types of local variables
-// * names of function arguments
-// * names of const generic parameters
-// * types of chained expressions
-//
-// Optionally, one can enable additional hints for
-//
-// * return types of closure expressions
-// * elided lifetimes
-// * compiler inserted reborrows
-// * names of generic type and lifetime parameters
-//
-// Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
-// any of the
-// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L92-L99[following criteria]
-// are met:
-//
-// * the parameter name is a suffix of the function's name
-// * the argument is a qualified constructing or call expression where the qualifier is an ADT
-// * exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
-//   of argument with _ splitting it off
-// * the parameter name starts with `ra_fixture`
-// * the parameter name is a
-// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L200[well known name]
-// in a unary function
-// * the parameter name is a
-// link:https://github.com/rust-lang/rust-analyzer/blob/6b8b8ff4c56118ddee6c531cde06add1aad4a6af/crates/ide/src/inlay_hints/param_name.rs#L201[single character]
-// in a unary function
-//
-// image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
-pub(crate) fn inlay_hints(
-    db: &RootDatabase,
-    file_id: FileId,
-    range_limit: Option<TextRange>,
-    config: &InlayHintsConfig,
-) -> Vec<InlayHint> {
-    let _p = tracing::info_span!("inlay_hints").entered();
-    let sema = Semantics::new(db);
-    let file_id = sema
-        .attach_first_edition(file_id)
-        .unwrap_or_else(|| EditionedFileId::current_edition(file_id));
-    let file = sema.parse(file_id);
-    let file = file.syntax();
-
-    let mut acc = Vec::new();
-
-    if let Some(scope) = sema.scope(file) {
-        let famous_defs = FamousDefs(&sema, scope.krate());
-
-        let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
-        match range_limit {
-            Some(range) => match file.covering_element(range) {
-                NodeOrToken::Token(_) => return acc,
-                NodeOrToken::Node(n) => n
-                    .descendants()
-                    .filter(|descendant| range.intersect(descendant.text_range()).is_some())
-                    .for_each(hints),
-            },
-            None => file.descendants().for_each(hints),
-        };
-    }
-
-    acc
-}
-
-pub(crate) fn inlay_hints_resolve(
-    db: &RootDatabase,
-    file_id: FileId,
-    position: TextSize,
-    hash: u64,
-    config: &InlayHintsConfig,
-    hasher: impl Fn(&InlayHint) -> u64,
-) -> Option<InlayHint> {
-    let _p = tracing::info_span!("inlay_hints_resolve").entered();
-    let sema = Semantics::new(db);
-    let file_id = sema
-        .attach_first_edition(file_id)
-        .unwrap_or_else(|| EditionedFileId::current_edition(file_id));
-    let file = sema.parse(file_id);
-    let file = file.syntax();
-
-    let scope = sema.scope(file)?;
-    let famous_defs = FamousDefs(&sema, scope.krate());
-    let mut acc = Vec::new();
-
-    let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
-    let token = file.token_at_offset(position).left_biased()?;
-    if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
-        parent_block.syntax().descendants().for_each(hints)
-    } else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
-        parent_item.syntax().descendants().for_each(hints)
-    } else {
-        return None;
-    }
-
-    acc.into_iter().find(|hint| hasher(hint) == hash)
-}
-
-fn hints(
-    hints: &mut Vec<InlayHint>,
-    famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
-    config: &InlayHintsConfig,
-    file_id: EditionedFileId,
-    node: SyntaxNode,
-) {
-    closing_brace::hints(hints, sema, config, file_id, node.clone());
-    if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
-        generic_param::hints(hints, sema, config, any_has_generic_args);
-    }
-    match_ast! {
-        match node {
-            ast::Expr(expr) => {
-                chaining::hints(hints, famous_defs, config, file_id, &expr);
-                adjustment::hints(hints, sema, config, file_id, &expr);
-                match expr {
-                    ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
-                    ast::Expr::MethodCallExpr(it) => {
-                        param_name::hints(hints, sema, config, ast::Expr::from(it))
-                    }
-                    ast::Expr::ClosureExpr(it) => {
-                        closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
-                        closure_ret::hints(hints, famous_defs, config, file_id, it)
-                    },
-                    ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, config, it),
-                    _ => None,
-                }
-            },
-            ast::Pat(it) => {
-                binding_mode::hints(hints, sema, config, &it);
-                match it {
-                    ast::Pat::IdentPat(it) => {
-                        bind_pat::hints(hints, famous_defs, config, file_id, &it);
-                    }
-                    ast::Pat::RangePat(it) => {
-                        range_exclusive::hints(hints, config, it);
-                    }
-                    _ => {}
-                }
-                Some(())
-            },
-            ast::Item(it) => match it {
-                // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
-                ast::Item::Impl(_) => None,
-                ast::Item::Fn(it) => {
-                    implicit_drop::hints(hints, sema, config, file_id, &it);
-                    fn_lifetime_fn::hints(hints, config, it)
-                },
-                // static type elisions
-                ast::Item::Static(it) => implicit_static::hints(hints, config, Either::Left(it)),
-                ast::Item::Const(it) => implicit_static::hints(hints, config, Either::Right(it)),
-                ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it),
-                _ => None,
-            },
-            // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
-            ast::Type(_) => None,
-            _ => None,
-        }
-    };
-}
-
 /// Checks if the type is an Iterator from std::iter and returns the iterator trait and the item type of the concrete iterator.
 fn hint_iterator(
     sema: &Semantics<'_, RootDatabase>,
@@ -653,6 +660,23 @@ fn hint_iterator(
     None
 }
 
+fn ty_to_text_edit(
+    sema: &Semantics<'_, RootDatabase>,
+    node_for_hint: &SyntaxNode,
+    ty: &hir::Type,
+    offset_to_insert: TextSize,
+    prefix: String,
+) -> Option<TextEdit> {
+    let scope = sema.scope(node_for_hint)?;
+    // 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())
+}
+
 fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
     matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
index 31c1a991d53..dc390f8f67a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
@@ -6,9 +6,8 @@
 use either::Either;
 use hir::{
     Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, OverloadedDeref, PointerCast, Safety,
-    Semantics,
 };
-use ide_db::RootDatabase;
+use ide_db::famous_defs::FamousDefs;
 
 use span::EditionedFileId;
 use stdx::never;
@@ -24,7 +23,7 @@ use crate::{
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
-    sema: &Semantics<'_, RootDatabase>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
     file_id: EditionedFileId,
     expr: &ast::Expr,
@@ -156,6 +155,7 @@ pub(super) fn hints(
             kind: InlayKind::Adjustment,
             label,
             text_edit: None,
+            resolve_parent: Some(expr.syntax().text_range()),
         });
     }
     if !postfix && needs_inner_parens {
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
index 82b0a6ffcf1..7a808fb4a92 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
@@ -110,6 +110,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: !render_colons,
         pad_right: false,
+        resolve_parent: Some(pat.syntax().text_range()),
     });
 
     Some(())
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs
index f27390ee898..d1c0677863d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs
@@ -2,17 +2,19 @@
 //! ```no_run
 //! let /* & */ (/* ref */ x,) = &(0,);
 //! ```
-use hir::{Mutability, Semantics};
-use ide_db::RootDatabase;
+use hir::Mutability;
+use ide_db::famous_defs::FamousDefs;
 
+use span::EditionedFileId;
 use syntax::ast::{self, AstNode};
 
 use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind};
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
-    sema: &Semantics<'_, RootDatabase>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
+    _file_id: EditionedFileId,
     pat: &ast::Pat,
 ) -> Option<()> {
     if !config.binding_mode_hints {
@@ -57,6 +59,7 @@ pub(super) fn hints(
             position: InlayHintPosition::Before,
             pad_left: false,
             pad_right: mut_reference,
+            resolve_parent: Some(pat.syntax().text_range()),
         });
     });
     match pat {
@@ -75,6 +78,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::Before,
                 pad_left: false,
                 pad_right: true,
+                resolve_parent: Some(pat.syntax().text_range()),
             });
         }
         ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
index 35f4d46e187..df34e4aa245 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
@@ -67,6 +67,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::After,
                 pad_left: true,
                 pad_right: false,
+                resolve_parent: Some(expr.syntax().text_range()),
             });
         }
     }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs
index d78fd64bdf4..8af5bd5661a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs
@@ -18,12 +18,13 @@ pub(super) fn hints(
     sema: &Semantics<'_, RootDatabase>,
     config: &InlayHintsConfig,
     file_id: EditionedFileId,
-    mut node: SyntaxNode,
+    original_node: SyntaxNode,
 ) -> Option<()> {
     let min_lines = config.closing_brace_hints_min_lines?;
 
     let name = |it: ast::Name| it.syntax().text_range();
 
+    let mut node = original_node.clone();
     let mut closing_token;
     let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
         closing_token = item_list.r_curly_token()?;
@@ -145,6 +146,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: true,
         pad_right: false,
+        resolve_parent: Some(original_node.text_range()),
     });
 
     None
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs
index e87e10d8504..adf7cbc3656 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs
@@ -40,6 +40,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::After,
                 pad_left: false,
                 pad_right: false,
+                resolve_parent: Some(closure.syntax().text_range()),
             });
             range
         }
@@ -52,6 +53,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: false,
         pad_right: false,
+        resolve_parent: None,
     });
     let last = captures.len() - 1;
     for (idx, capture) in captures.into_iter().enumerate() {
@@ -85,6 +87,7 @@ pub(super) fn hints(
             position: InlayHintPosition::After,
             pad_left: false,
             pad_right: false,
+            resolve_parent: Some(closure.syntax().text_range()),
         });
 
         if idx != last {
@@ -96,6 +99,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::After,
                 pad_left: false,
                 pad_right: false,
+                resolve_parent: None,
             });
         }
     }
@@ -107,6 +111,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: false,
         pad_right: true,
+        resolve_parent: None,
     });
 
     Some(())
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs
index 325c2040691..6827540fa82 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs
@@ -72,6 +72,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: false,
         pad_right: false,
+        resolve_parent: Some(closure.syntax().text_range()),
     });
     Some(())
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
index eca0ebe629f..35b62878329 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
@@ -35,7 +35,7 @@ pub(super) fn enum_hints(
         return None;
     }
     for variant in enum_.variant_list()?.variants() {
-        variant_hints(acc, sema, &variant);
+        variant_hints(acc, sema, &enum_, &variant);
     }
     Some(())
 }
@@ -43,6 +43,7 @@ pub(super) fn enum_hints(
 fn variant_hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<'_, RootDatabase>,
+    enum_: &ast::Enum,
     variant: &ast::Variant,
 ) -> Option<()> {
     if variant.expr().is_some() {
@@ -90,6 +91,7 @@ fn variant_hints(
         position: InlayHintPosition::After,
         pad_left: false,
         pad_right: false,
+        resolve_parent: Some(enum_.syntax().text_range()),
     });
 
     Some(())
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
index d3666754e2b..4d35e71a06f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
@@ -2,8 +2,9 @@
 //! ```no_run
 //! fn example/* <'0> */(a: &/* '0 */()) {}
 //! ```
-use ide_db::{syntax_helpers::node_ext::walk_ty, FxHashMap};
+use ide_db::{famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap};
 use itertools::Itertools;
+use span::EditionedFileId;
 use syntax::{
     ast::{self, AstNode, HasGenericParams, HasName},
     SyntaxToken,
@@ -14,7 +15,9 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
+    FamousDefs(_, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
+    _file_id: EditionedFileId,
     func: ast::Fn,
 ) -> Option<()> {
     if config.lifetime_elision_hints == LifetimeElisionHints::Never {
@@ -29,6 +32,7 @@ pub(super) fn hints(
         position: InlayHintPosition::After,
         pad_left: false,
         pad_right: true,
+        resolve_parent: None,
     };
 
     let param_list = func.param_list()?;
@@ -195,6 +199,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::After,
                 pad_left: false,
                 pad_right: true,
+                resolve_parent: None,
             });
         }
         (None, allocated_lifetimes) => acc.push(InlayHint {
@@ -205,6 +210,7 @@ pub(super) fn hints(
             position: InlayHintPosition::After,
             pad_left: false,
             pad_right: false,
+            resolve_parent: None,
         }),
     }
     Some(())
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs
index b60a80a8ac6..ed7ebc3b1e7 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs
@@ -92,6 +92,7 @@ pub(crate) fn hints(
             kind: InlayKind::GenericParameter,
             label,
             text_edit: None,
+            resolve_parent: Some(node.syntax().text_range()),
         })
     });
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs
index b4695a2b351..dd4b3efeecf 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs
@@ -8,9 +8,9 @@
 use hir::{
     db::{DefDatabase as _, HirDatabase as _},
     mir::{MirSpan, TerminatorKind},
-    ChalkTyInterner, DefWithBody, Semantics,
+    ChalkTyInterner, DefWithBody,
 };
-use ide_db::{FileRange, RootDatabase};
+use ide_db::{famous_defs::FamousDefs, FileRange};
 
 use span::EditionedFileId;
 use syntax::{
@@ -22,16 +22,16 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
-    sema: &Semantics<'_, RootDatabase>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
     file_id: EditionedFileId,
-    def: &ast::Fn,
+    node: &ast::Fn,
 ) -> Option<()> {
     if !config.implicit_drop_hints {
         return None;
     }
 
-    let def = sema.to_def(def)?;
+    let def = sema.to_def(node)?;
     let def: DefWithBody = def.into();
 
     let (hir, source_map) = sema.db.body_with_source_map(def.into());
@@ -121,6 +121,7 @@ pub(super) fn hints(
                 kind: InlayKind::Drop,
                 label,
                 text_edit: None,
+                resolve_parent: Some(node.syntax().text_range()),
             })
         }
     }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs
index 42223ddf580..8d422478cbf 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs
@@ -3,6 +3,8 @@
 //! static S: &/* 'static */str = "";
 //! ```
 use either::Either;
+use ide_db::famous_defs::FamousDefs;
+use span::EditionedFileId;
 use syntax::{
     ast::{self, AstNode},
     SyntaxKind,
@@ -12,7 +14,9 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
+    FamousDefs(_sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
+    _file_id: EditionedFileId,
     statik_or_const: Either<ast::Static, ast::Const>,
 ) -> Option<()> {
     if config.lifetime_elision_hints != LifetimeElisionHints::Always {
@@ -38,6 +42,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::After,
                 pad_left: false,
                 pad_right: true,
+                resolve_parent: None,
             });
         }
     }
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs
index 0f3142ef3f8..28b0fa6dd4d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs
@@ -7,8 +7,9 @@ use std::fmt::Display;
 
 use either::Either;
 use hir::{Callable, Semantics};
-use ide_db::RootDatabase;
+use ide_db::{famous_defs::FamousDefs, RootDatabase};
 
+use span::EditionedFileId;
 use stdx::to_lower_snake_case;
 use syntax::{
     ast::{self, AstNode, HasArgList, HasName, UnaryOp},
@@ -19,8 +20,9 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
-    sema: &Semantics<'_, RootDatabase>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
+    _file_id: EditionedFileId,
     expr: ast::Expr,
 ) -> Option<()> {
     if !config.parameter_hints {
@@ -60,6 +62,7 @@ pub(super) fn hints(
                 position: InlayHintPosition::Before,
                 pad_left: false,
                 pad_right: true,
+                resolve_parent: Some(expr.syntax().text_range()),
             }
         });
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs
index bfb92838857..de9b0e98a4b 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs
@@ -3,13 +3,17 @@
 //! for i in 0../* < */10 {}
 //! if let ../* < */100 = 50 {}
 //! ```
+use ide_db::famous_defs::FamousDefs;
+use span::EditionedFileId;
 use syntax::{ast, SyntaxToken, T};
 
 use crate::{InlayHint, InlayHintsConfig};
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
+    FamousDefs(_sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
+    _file_id: EditionedFileId,
     range: impl ast::RangeItem,
 ) -> Option<()> {
     (config.range_exclusive_hints && range.end().is_some())
@@ -30,6 +34,7 @@ fn inlay_hint(token: SyntaxToken) -> InlayHint {
         kind: crate::InlayKind::RangeExclusive,
         label: crate::InlayHintLabel::from("<"),
         text_edit: None,
+        resolve_parent: None,
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index ba0aaae19c9..cdadfeea4b9 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -439,12 +439,12 @@ impl Analysis {
         &self,
         config: &InlayHintsConfig,
         file_id: FileId,
-        position: TextSize,
+        resolve_range: TextRange,
         hash: u64,
         hasher: impl Fn(&InlayHint) -> u64 + Send + UnwindSafe,
     ) -> Cancellable<Option<InlayHint>> {
         self.with_db(|db| {
-            inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config, hasher)
+            inlay_hints::inlay_hints_resolve(db, file_id, resolve_range, hash, config, hasher)
         })
     }
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index 1ad5ff0c8cd..50df9dc8760 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -1602,14 +1602,14 @@ pub(crate) fn handle_inlay_hints_resolve(
     anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
 
     let line_index = snap.file_line_index(file_id)?;
-    let hint_position = from_proto::offset(&line_index, original_hint.position)?;
+    let range = from_proto::text_range(&line_index, resolve_data.resolve_range)?;
 
     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_resolve(
         &forced_resolve_inlay_hints_config,
         file_id,
-        hint_position,
+        range,
         hash,
         |hint| {
             std::hash::BuildHasher::hash_one(
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
index 8d1a686dc4d..ed6f16a1730 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
@@ -819,6 +819,7 @@ pub struct InlayHintResolveData {
     pub file_id: u32,
     // This is a string instead of a u64 as javascript can't represent u64 fully
     pub hash: String,
+    pub resolve_range: lsp_types::Range,
     pub version: Option<i32>,
 }
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
index eb6bc2a9ce9..7eed9ce2f38 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -452,10 +452,13 @@ pub(crate) fn inlay_hint(
     file_id: FileId,
     mut inlay_hint: InlayHint,
 ) -> Cancellable<lsp_types::InlayHint> {
-    let resolve_hash = inlay_hint.needs_resolve().then(|| {
-        std::hash::BuildHasher::hash_one(
-            &std::hash::BuildHasherDefault::<FxHasher>::default(),
-            &inlay_hint,
+    let resolve_range_and_hash = inlay_hint.needs_resolve().map(|range| {
+        (
+            range,
+            std::hash::BuildHasher::hash_one(
+                &std::hash::BuildHasherDefault::<FxHasher>::default(),
+                &inlay_hint,
+            ),
         )
     });
 
@@ -465,7 +468,7 @@ pub(crate) fn inlay_hint(
         .visual_studio_code_version()
         // https://github.com/microsoft/vscode/issues/193124
         .map_or(true, |version| VersionReq::parse(">=1.86.0").unwrap().matches(version))
-        && resolve_hash.is_some()
+        && resolve_range_and_hash.is_some()
         && fields_to_resolve.resolve_text_edits
     {
         something_to_resolve |= inlay_hint.text_edit.is_some();
@@ -477,16 +480,17 @@ pub(crate) fn inlay_hint(
         snap,
         fields_to_resolve,
         &mut something_to_resolve,
-        resolve_hash.is_some(),
+        resolve_range_and_hash.is_some(),
         inlay_hint.label,
     )?;
 
-    let data = match resolve_hash {
-        Some(hash) if something_to_resolve => Some(
+    let data = match resolve_range_and_hash {
+        Some((resolve_range, hash)) if something_to_resolve => Some(
             to_value(lsp_ext::InlayHintResolveData {
                 file_id: file_id.index(),
                 hash: hash.to_string(),
                 version: snap.file_version(file_id),
+                resolve_range: range(line_index, resolve_range),
             })
             .unwrap(),
         ),
diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
index 4786bd54d59..68afacf2db5 100644
--- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
+++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 3429c08745984b3d
+lsp/ext.rs hash: c6e83d3d08d993de
 
 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: