about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-12-21 21:43:38 +0000
committerbors <bors@rust-lang.org>2022-12-21 21:43:38 +0000
commit271f7b44d3d71db2ec2d0cd1a8a59aa16f902d49 (patch)
treeb948a9ec5c18b72a00811a081a5bd7c9190e7a3e
parent113f17be6ee55c749ce1bba3d268099be4749e46 (diff)
parente1aa73ef40b8902b2cbdd8272978fcc1c47cf3c7 (diff)
downloadrust-271f7b44d3d71db2ec2d0cd1a8a59aa16f902d49.tar.gz
rust-271f7b44d3d71db2ec2d0cd1a8a59aa16f902d49.zip
Auto merge of #13699 - HKalbasi:inlaylink, r=Veykril
Implement location link for type inlay hints

fix #11701

This actually doesn't work due a problem in vscode: https://github.com/microsoft/vscode/issues/167564
-rw-r--r--crates/hir-ty/src/display.rs54
-rw-r--r--crates/hir/src/lib.rs10
-rw-r--r--crates/ide/src/inlay_hints.rs142
-rw-r--r--crates/ide/src/inlay_hints/bind_pat.rs63
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs255
-rw-r--r--crates/ide/src/inlay_hints/closing_brace.rs5
-rw-r--r--crates/ide/src/inlay_hints/closure_ret.rs15
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/bin/main.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs18
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
12 files changed, 483 insertions, 92 deletions
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 9d453eef716..57a15d114f0 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -16,7 +16,7 @@ use hir_def::{
     path::{Path, PathKind},
     type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef},
     visibility::Visibility,
-    HasModule, ItemContainerId, Lookup, ModuleId, TraitId,
+    HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId,
 };
 use hir_expand::{hygiene::Hygiene, name::Name};
 use itertools::Itertools;
@@ -35,9 +35,27 @@ use crate::{
     TraitRefExt, Ty, TyExt, TyKind, WhereClause,
 };
 
+pub trait HirWrite: fmt::Write {
+    fn start_location_link(&mut self, location: ModuleDefId);
+    fn end_location_link(&mut self);
+}
+
+// String will ignore link metadata
+impl HirWrite for String {
+    fn start_location_link(&mut self, _: ModuleDefId) {}
+
+    fn end_location_link(&mut self) {}
+}
+
+// `core::Formatter` will ignore metadata
+impl HirWrite for fmt::Formatter<'_> {
+    fn start_location_link(&mut self, _: ModuleDefId) {}
+    fn end_location_link(&mut self) {}
+}
+
 pub struct HirFormatter<'a> {
     pub db: &'a dyn HirDatabase,
-    fmt: &'a mut dyn fmt::Write,
+    fmt: &'a mut dyn HirWrite,
     buf: String,
     curr_size: usize,
     pub(crate) max_size: Option<usize>,
@@ -45,6 +63,16 @@ pub struct HirFormatter<'a> {
     display_target: DisplayTarget,
 }
 
+impl HirFormatter<'_> {
+    fn start_location_link(&mut self, location: ModuleDefId) {
+        self.fmt.start_location_link(location);
+    }
+
+    fn end_location_link(&mut self) {
+        self.fmt.end_location_link();
+    }
+}
+
 pub trait HirDisplay {
     fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError>;
 
@@ -245,12 +273,9 @@ pub struct HirDisplayWrapper<'a, T> {
     display_target: DisplayTarget,
 }
 
-impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
-where
-    T: HirDisplay,
-{
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self.t.hir_fmt(&mut HirFormatter {
+impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
+    pub fn write_to<F: HirWrite>(&self, f: &mut F) -> Result<(), HirDisplayError> {
+        self.t.hir_fmt(&mut HirFormatter {
             db: self.db,
             fmt: f,
             buf: String::with_capacity(20),
@@ -258,7 +283,16 @@ where
             max_size: self.max_size,
             omit_verbose_types: self.omit_verbose_types,
             display_target: self.display_target,
-        }) {
+        })
+    }
+}
+
+impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
+where
+    T: HirDisplay,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self.write_to(f) {
             Ok(()) => Ok(()),
             Err(HirDisplayError::FmtError) => Err(fmt::Error),
             Err(HirDisplayError::DisplaySourceCodeError(_)) => {
@@ -530,6 +564,7 @@ impl HirDisplay for Ty {
                 }
             }
             TyKind::Adt(AdtId(def_id), parameters) => {
+                f.start_location_link((*def_id).into());
                 match f.display_target {
                     DisplayTarget::Diagnostics | DisplayTarget::Test => {
                         let name = match *def_id {
@@ -554,6 +589,7 @@ impl HirDisplay for Ty {
                         }
                     }
                 }
+                f.end_location_link();
 
                 if parameters.len(Interner) > 0 {
                     let parameters_to_write = if f.display_target.is_source_code()
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index cc983ee0142..7e0e0245261 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -114,12 +114,20 @@ pub use {
         path::{ModPath, PathKind},
         type_ref::{Mutability, TypeRef},
         visibility::Visibility,
+        // FIXME: This is here since it is input of a method in `HirWrite`
+        // and things outside of hir need to implement that trait. We probably
+        // should move whole `hir_ty::display` to this crate so we will become
+        // able to use `ModuleDef` or `Definition` instead of `ModuleDefId`.
+        ModuleDefId,
     },
     hir_expand::{
         name::{known, Name},
         ExpandResult, HirFileId, InFile, MacroFile, Origin,
     },
-    hir_ty::{display::HirDisplay, PointerCast, Safety},
+    hir_ty::{
+        display::{HirDisplay, HirWrite},
+        PointerCast, Safety,
+    },
 };
 
 // These are negative re-exports: pub using these names is forbidden, they
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 152f31b3a57..9aef78143d6 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1,15 +1,19 @@
-use std::fmt;
+use std::{
+    fmt::{self, Write},
+    mem::take,
+};
 
 use either::Either;
-use hir::{known, HasVisibility, HirDisplay, Semantics};
+use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics};
 use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
 use itertools::Itertools;
+use stdx::never;
 use syntax::{
     ast::{self, AstNode},
     match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
 };
 
-use crate::FileId;
+use crate::{navigation_target::TryToNav, FileId};
 
 mod closing_brace;
 mod implicit_static;
@@ -23,6 +27,7 @@ mod bind_pat;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct InlayHintsConfig {
+    pub location_links: bool,
     pub render_colons: bool,
     pub type_hints: bool,
     pub parameter_hints: bool,
@@ -89,6 +94,7 @@ pub enum InlayTooltip {
     HoverOffset(FileId, TextSize),
 }
 
+#[derive(Default)]
 pub struct InlayHintLabel {
     pub parts: Vec<InlayHintLabelPart>,
 }
@@ -172,6 +178,104 @@ impl fmt::Debug for InlayHintLabelPart {
     }
 }
 
+#[derive(Debug)]
+struct InlayHintLabelBuilder<'a> {
+    db: &'a RootDatabase,
+    result: InlayHintLabel,
+    last_part: String,
+    location_link_enabled: bool,
+    location: Option<FileRange>,
+}
+
+impl fmt::Write for InlayHintLabelBuilder<'_> {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.last_part.write_str(s)
+    }
+}
+
+impl HirWrite for InlayHintLabelBuilder<'_> {
+    fn start_location_link(&mut self, def: ModuleDefId) {
+        if !self.location_link_enabled {
+            return;
+        }
+        if self.location.is_some() {
+            never!("location link is already started");
+        }
+        self.make_new_part();
+        let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
+        let location =
+            FileRange { file_id: location.file_id, range: location.focus_or_full_range() };
+        self.location = Some(location);
+    }
+
+    fn end_location_link(&mut self) {
+        if !self.location_link_enabled {
+            return;
+        }
+        self.make_new_part();
+    }
+}
+
+impl InlayHintLabelBuilder<'_> {
+    fn make_new_part(&mut self) {
+        self.result.parts.push(InlayHintLabelPart {
+            text: take(&mut self.last_part),
+            linked_location: self.location.take(),
+        });
+    }
+
+    fn finish(mut self) -> InlayHintLabel {
+        self.make_new_part();
+        self.result
+    }
+}
+
+fn label_of_ty(
+    sema: &Semantics<'_, RootDatabase>,
+    desc_pat: &impl AstNode,
+    config: &InlayHintsConfig,
+    ty: hir::Type,
+) -> Option<InlayHintLabel> {
+    fn rec(
+        sema: &Semantics<'_, RootDatabase>,
+        famous_defs: &FamousDefs<'_, '_>,
+        mut max_length: Option<usize>,
+        ty: hir::Type,
+        label_builder: &mut InlayHintLabelBuilder<'_>,
+    ) {
+        let iter_item_type = hint_iterator(sema, &famous_defs, &ty);
+        match iter_item_type {
+            Some(ty) => {
+                const LABEL_START: &str = "impl Iterator<Item = ";
+                const LABEL_END: &str = ">";
+
+                max_length =
+                    max_length.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len()));
+
+                label_builder.write_str(LABEL_START).unwrap();
+                rec(sema, famous_defs, max_length, ty, label_builder);
+                label_builder.write_str(LABEL_END).unwrap();
+            }
+            None => {
+                let _ = ty.display_truncated(sema.db, max_length).write_to(label_builder);
+            }
+        };
+    }
+
+    let krate = sema.scope(desc_pat.syntax())?.krate();
+    let famous_defs = FamousDefs(sema, krate);
+    let mut label_builder = InlayHintLabelBuilder {
+        db: sema.db,
+        last_part: String::new(),
+        location: None,
+        location_link_enabled: config.location_links,
+        result: InlayHintLabel::default(),
+    };
+    rec(sema, &famous_defs, config.max_length, ty, &mut label_builder);
+    let r = label_builder.finish();
+    Some(r)
+}
+
 // Feature: Inlay Hints
 //
 // rust-analyzer shows additional information inline with the source code.
@@ -224,7 +328,7 @@ pub(crate) fn inlay_hints(
 
 fn hints(
     hints: &mut Vec<InlayHint>,
-    famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
     file_id: FileId,
     node: SyntaxNode,
@@ -233,14 +337,14 @@ fn hints(
     match_ast! {
         match node {
             ast::Expr(expr) => {
-                chaining::hints(hints, sema, &famous_defs, config, file_id, &expr);
+                chaining::hints(hints, sema, config, file_id, &expr);
                 adjustment::hints(hints, sema, config, &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_ret::hints(hints, sema, &famous_defs, config, file_id, it),
+                    ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, sema, config, file_id, it),
                     // We could show reborrows for all expressions, but usually that is just noise to the user
                     // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
                     // ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
@@ -270,13 +374,12 @@ fn hints(
     };
 }
 
-/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
+/// Checks if the type is an Iterator from std::iter and returns its item type.
 fn hint_iterator(
     sema: &Semantics<'_, RootDatabase>,
     famous_defs: &FamousDefs<'_, '_>,
-    config: &InlayHintsConfig,
     ty: &hir::Type,
-) -> Option<String> {
+) -> Option<hir::Type> {
     let db = sema.db;
     let strukt = ty.strip_references().as_adt()?;
     let krate = strukt.module(db).krate();
@@ -299,21 +402,7 @@ fn hint_iterator(
             _ => None,
         })?;
         if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
-            const LABEL_START: &str = "impl Iterator<Item = ";
-            const LABEL_END: &str = ">";
-
-            let ty_display = hint_iterator(sema, famous_defs, config, &ty)
-                .map(|assoc_type_impl| assoc_type_impl.to_string())
-                .unwrap_or_else(|| {
-                    ty.display_truncated(
-                        db,
-                        config
-                            .max_length
-                            .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
-                    )
-                    .to_string()
-                });
-            return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
+            return Some(ty);
         }
     }
 
@@ -336,6 +425,7 @@ mod tests {
     use super::ClosureReturnTypeHints;
 
     pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+        location_links: false,
         render_colons: false,
         type_hints: false,
         parameter_hints: false,
@@ -350,6 +440,8 @@ mod tests {
         max_length: None,
         closing_brace_hints_min_lines: None,
     };
+    pub(super) const DISABLED_CONFIG_WITH_LINKS: InlayHintsConfig =
+        InlayHintsConfig { location_links: true, ..DISABLED_CONFIG };
     pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
         type_hints: true,
         parameter_hints: true,
@@ -357,7 +449,7 @@ mod tests {
         closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
         binding_mode_hints: true,
         lifetime_elision_hints: LifetimeElisionHints::Always,
-        ..DISABLED_CONFIG
+        ..DISABLED_CONFIG_WITH_LINKS
     };
 
     #[track_caller]
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs
index 754df09df1d..7766d497918 100644
--- a/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/crates/ide/src/inlay_hints/bind_pat.rs
@@ -3,8 +3,8 @@
 //! fn f(a: i32, b: i32) -> i32 { a + b }
 //! let _x /* i32 */= f(4, 4);
 //! ```
-use hir::{HirDisplay, Semantics, TypeInfo};
-use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
+use hir::{Semantics, TypeInfo};
+use ide_db::{base_db::FileId, RootDatabase};
 
 use itertools::Itertools;
 use syntax::{
@@ -13,10 +13,11 @@ use syntax::{
 };
 
 use crate::{
-    inlay_hints::{closure_has_block_body, hint_iterator},
-    InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
+    inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
 };
 
+use super::label_of_ty;
+
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<'_, RootDatabase>,
@@ -36,22 +37,13 @@ pub(super) fn hints(
         return None;
     }
 
-    let krate = sema.scope(desc_pat.syntax())?.krate();
-    let famous_defs = FamousDefs(sema, krate);
-    let label = hint_iterator(sema, &famous_defs, config, &ty);
+    let label = label_of_ty(sema, desc_pat, config, ty)?;
 
-    let label = match label {
-        Some(label) => label,
-        None => {
-            let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
-            if config.hide_named_constructor_hints
-                && is_named_constructor(sema, pat, &ty_name).is_some()
-            {
-                return None;
-            }
-            ty_name
-        }
-    };
+    if config.hide_named_constructor_hints
+        && is_named_constructor(sema, pat, &label.to_string()).is_some()
+    {
+        return None;
+    }
 
     acc.push(InlayHint {
         range: match pat.name() {
@@ -59,7 +51,7 @@ pub(super) fn hints(
             None => pat.syntax().text_range(),
         },
         kind: InlayKind::TypeHint,
-        label: label.into(),
+        label,
         tooltip: pat
             .name()
             .map(|it| it.syntax().text_range())
@@ -202,7 +194,8 @@ mod tests {
     use crate::{fixture, inlay_hints::InlayHintsConfig};
 
     use crate::inlay_hints::tests::{
-        check, check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
+        check, check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS,
+        TEST_CONFIG,
     };
     use crate::ClosureReturnTypeHints;
 
@@ -298,7 +291,7 @@ fn main() {
     fn iterator_hint_regression_issue_12674() {
         // Ensure we don't crash while solving the projection type of iterators.
         check_expect(
-            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
             r#"
 //- minicore: iterators
 struct S<T>(T);
@@ -346,7 +339,31 @@ fn main(a: SliceIter<'_, Container>) {
                         range: 484..485,
                         kind: ChainingHint,
                         label: [
-                            "SliceIter<Container>",
+                            "",
+                            InlayHintLabelPart {
+                                text: "SliceIter",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 289..298,
+                                    },
+                                ),
+                            },
+                            "<",
+                            InlayHintLabelPart {
+                                text: "Container",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 238..247,
+                                    },
+                                ),
+                            },
+                            ">",
                         ],
                         tooltip: Some(
                             HoverRanged(
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 32421afd39f..efeb2b79255 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -1,19 +1,18 @@
 //! Implementation of "chaining" inlay hints.
-use hir::{HirDisplay, Semantics};
-use ide_db::{famous_defs::FamousDefs, RootDatabase};
+use hir::Semantics;
+use ide_db::RootDatabase;
 use syntax::{
     ast::{self, AstNode},
     Direction, NodeOrToken, SyntaxKind, T,
 };
 
-use crate::{
-    inlay_hints::hint_iterator, FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
-};
+use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+
+use super::label_of_ty;
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<'_, RootDatabase>,
-    famous_defs: &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
     file_id: FileId,
     expr: &ast::Expr,
@@ -62,9 +61,7 @@ pub(super) fn hints(
             acc.push(InlayHint {
                 range: expr.syntax().text_range(),
                 kind: InlayKind::ChainingHint,
-                label: hint_iterator(sema, &famous_defs, config, &ty)
-                    .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
-                    .into(),
+                label: label_of_ty(sema, desc_expr, config, ty)?,
                 tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
             });
         }
@@ -77,7 +74,10 @@ mod tests {
     use expect_test::expect;
 
     use crate::{
-        inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
+        inlay_hints::tests::{
+            check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS,
+            TEST_CONFIG,
+        },
         InlayHintsConfig,
     };
 
@@ -89,7 +89,11 @@ mod tests {
     #[test]
     fn chaining_hints_ignore_comments() {
         check_expect(
-            InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
+            InlayHintsConfig {
+                type_hints: false,
+                chaining_hints: true,
+                ..DISABLED_CONFIG_WITH_LINKS
+            },
             r#"
 struct A(B);
 impl A { fn into_b(self) -> B { self.0 } }
@@ -110,7 +114,19 @@ fn main() {
                         range: 147..172,
                         kind: ChainingHint,
                         label: [
-                            "B",
+                            "",
+                            InlayHintLabelPart {
+                                text: "B",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 63..64,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -125,7 +141,19 @@ fn main() {
                         range: 147..154,
                         kind: ChainingHint,
                         label: [
-                            "A",
+                            "",
+                            InlayHintLabelPart {
+                                text: "A",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 7..8,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -158,10 +186,69 @@ fn main() {
     }
 
     #[test]
-    fn struct_access_chaining_hints() {
+    fn disabled_location_links() {
         check_expect(
             InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
             r#"
+    struct A { pub b: B }
+    struct B { pub c: C }
+    struct C(pub bool);
+    struct D;
+
+    impl D {
+        fn foo(&self) -> i32 { 42 }
+    }
+
+    fn main() {
+        let x = A { b: B { c: C(true) } }
+            .b
+            .c
+            .0;
+        let x = D
+            .foo();
+    }"#,
+            expect![[r#"
+                [
+                    InlayHint {
+                        range: 143..190,
+                        kind: ChainingHint,
+                        label: [
+                            "C",
+                        ],
+                        tooltip: Some(
+                            HoverRanged(
+                                FileId(
+                                    0,
+                                ),
+                                143..190,
+                            ),
+                        ),
+                    },
+                    InlayHint {
+                        range: 143..179,
+                        kind: ChainingHint,
+                        label: [
+                            "B",
+                        ],
+                        tooltip: Some(
+                            HoverRanged(
+                                FileId(
+                                    0,
+                                ),
+                                143..179,
+                            ),
+                        ),
+                    },
+                ]
+            "#]],
+        );
+    }
+
+    #[test]
+    fn struct_access_chaining_hints() {
+        check_expect(
+            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+            r#"
 struct A { pub b: B }
 struct B { pub c: C }
 struct C(pub bool);
@@ -185,7 +272,19 @@ fn main() {
                         range: 143..190,
                         kind: ChainingHint,
                         label: [
-                            "C",
+                            "",
+                            InlayHintLabelPart {
+                                text: "C",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 51..52,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -200,7 +299,19 @@ fn main() {
                         range: 143..179,
                         kind: ChainingHint,
                         label: [
-                            "B",
+                            "",
+                            InlayHintLabelPart {
+                                text: "B",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 29..30,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -219,7 +330,7 @@ fn main() {
     #[test]
     fn generic_chaining_hints() {
         check_expect(
-            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
             r#"
 struct A<T>(T);
 struct B<T>(T);
@@ -245,7 +356,31 @@ fn main() {
                         range: 246..283,
                         kind: ChainingHint,
                         label: [
-                            "B<X<i32, bool>>",
+                            "",
+                            InlayHintLabelPart {
+                                text: "B",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 23..24,
+                                    },
+                                ),
+                            },
+                            "<",
+                            InlayHintLabelPart {
+                                text: "X",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 55..56,
+                                    },
+                                ),
+                            },
+                            "<i32, bool>>",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -260,7 +395,31 @@ fn main() {
                         range: 246..265,
                         kind: ChainingHint,
                         label: [
-                            "A<X<i32, bool>>",
+                            "",
+                            InlayHintLabelPart {
+                                text: "A",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 7..8,
+                                    },
+                                ),
+                            },
+                            "<",
+                            InlayHintLabelPart {
+                                text: "X",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 55..56,
+                                    },
+                                ),
+                            },
+                            "<i32, bool>>",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -279,7 +438,7 @@ fn main() {
     #[test]
     fn shorten_iterator_chaining_hints() {
         check_expect(
-            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
             r#"
 //- minicore: iterators
 use core::iter;
@@ -352,7 +511,19 @@ fn main() {
                         range: 174..189,
                         kind: ChainingHint,
                         label: [
-                            "&mut MyIter",
+                            "&mut ",
+                            InlayHintLabelPart {
+                                text: "MyIter",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 24..30,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -396,7 +567,19 @@ fn main() {
                         range: 124..130,
                         kind: TypeHint,
                         label: [
-                            "Struct",
+                            "",
+                            InlayHintLabelPart {
+                                text: "Struct",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 7..13,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -411,7 +594,19 @@ fn main() {
                         range: 145..185,
                         kind: ChainingHint,
                         label: [
-                            "Struct",
+                            "",
+                            InlayHintLabelPart {
+                                text: "Struct",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 7..13,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
@@ -426,7 +621,19 @@ fn main() {
                         range: 145..168,
                         kind: ChainingHint,
                         label: [
-                            "Struct",
+                            "",
+                            InlayHintLabelPart {
+                                text: "Struct",
+                                linked_location: Some(
+                                    FileRange {
+                                        file_id: FileId(
+                                            0,
+                                        ),
+                                        range: 7..13,
+                                    },
+                                ),
+                            },
+                            "",
                         ],
                         tooltip: Some(
                             HoverRanged(
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index 57605b392a8..e340c64c54b 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -109,7 +109,10 @@ pub(super) fn hints(
         return None;
     }
 
-    let linked_location = name_range.map(|range| FileRange { file_id, range });
+    let linked_location = config
+        .location_links
+        .then(|| name_range.map(|range| FileRange { file_id, range }))
+        .flatten();
     acc.push(InlayHint {
         range: closing_token.text_range(),
         kind: InlayKind::ClosingBraceHint,
diff --git a/crates/ide/src/inlay_hints/closure_ret.rs b/crates/ide/src/inlay_hints/closure_ret.rs
index de04f3ac751..247a4abcc56 100644
--- a/crates/ide/src/inlay_hints/closure_ret.rs
+++ b/crates/ide/src/inlay_hints/closure_ret.rs
@@ -1,17 +1,18 @@
 //! Implementation of "closure return type" inlay hints.
-use hir::{HirDisplay, Semantics};
-use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
+use hir::Semantics;
+use ide_db::{base_db::FileId, RootDatabase};
 use syntax::ast::{self, AstNode};
 
 use crate::{
-    inlay_hints::{closure_has_block_body, hint_iterator},
-    ClosureReturnTypeHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
+    inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig,
+    InlayKind, InlayTooltip,
 };
 
+use super::label_of_ty;
+
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<'_, RootDatabase>,
-    famous_defs: &FamousDefs<'_, '_>,
     config: &InlayHintsConfig,
     file_id: FileId,
     closure: ast::ClosureExpr,
@@ -42,9 +43,7 @@ pub(super) fn hints(
     acc.push(InlayHint {
         range: param_list.syntax().text_range(),
         kind: InlayKind::ClosureReturnTypeHint,
-        label: hint_iterator(sema, &famous_defs, config, &ty)
-            .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
-            .into(),
+        label: label_of_ty(sema, &param_list, config, ty)?,
         tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
     });
     Some(())
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 2380cf7381c..42b5951c842 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -106,6 +106,7 @@ impl StaticIndex<'_> {
             .analysis
             .inlay_hints(
                 &InlayHintsConfig {
+                    location_links: true,
                     render_colons: true,
                     type_hints: true,
                     parameter_hints: true,
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 7bf595d2a45..ec5053e991d 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -183,6 +183,8 @@ fn run_server() -> Result<()> {
         }
     }
 
+    config.client_specific_adjustments(&initialize_params.client_info);
+
     let server_capabilities = rust_analyzer::server_capabilities(&config);
 
     let initialize_result = lsp_types::InitializeResult {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 835b37c98e2..0bcc91eb411 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -20,7 +20,7 @@ use ide_db::{
     SnippetCap,
 };
 use itertools::Itertools;
-use lsp_types::{ClientCapabilities, MarkupKind};
+use lsp_types::{ClientCapabilities, ClientInfo, MarkupKind};
 use project_model::{
     CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource,
     UnsetTestCrates,
@@ -333,6 +333,8 @@ config_data! {
         inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
         /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
         inlayHints_lifetimeElisionHints_useParameterNames: bool    = "false",
+        /// Whether to use location links for parts of type mentioned in inlay hints.
+        inlayHints_locationLinks: bool                             = "true",
         /// Maximum length for inlay hints. Set to null to have an unlimited length.
         inlayHints_maxLength: Option<usize>                        = "25",
         /// Whether to show function parameter name inlay hints at the call
@@ -714,6 +716,19 @@ impl Config {
         }
     }
 
+    pub fn client_specific_adjustments(&mut self, client_info: &Option<ClientInfo>) {
+        // FIXME: remove this when we drop support for vscode 1.65 and below
+        if let Some(client) = client_info {
+            if client.name.contains("Code") || client.name.contains("Codium") {
+                if let Some(version) = &client.version {
+                    if version.as_str() < "1.76" {
+                        self.data.inlayHints_locationLinks = false;
+                    }
+                }
+            }
+        }
+    }
+
     pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
         tracing::info!("updating config from JSON: {:#}", json);
         if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
@@ -1196,6 +1211,7 @@ impl Config {
 
     pub fn inlay_hints(&self) -> InlayHintsConfig {
         InlayHintsConfig {
+            location_links: self.data.inlayHints_locationLinks,
             render_colons: self.data.inlayHints_renderColons,
             type_hints: self.data.inlayHints_typeHints_enable,
             parameter_hints: self.data.inlayHints_parameterHints_enable,
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index db41c7bf109..47511aad0fe 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -469,6 +469,11 @@ Whether to show inlay type hints for elided lifetimes in function signatures.
 --
 Whether to prefer using parameter names as the name for elided lifetime hints if possible.
 --
+[[rust-analyzer.inlayHints.locationLinks]]rust-analyzer.inlayHints.locationLinks (default: `true`)::
++
+--
+Whether to use location links for parts of type mentioned in inlay hints.
+--
 [[rust-analyzer.inlayHints.maxLength]]rust-analyzer.inlayHints.maxLength (default: `25`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index f9b0e28dadb..5b09ee6f7da 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -995,6 +995,11 @@
                     "default": false,
                     "type": "boolean"
                 },
+                "rust-analyzer.inlayHints.locationLinks": {
+                    "markdownDescription": "Whether to use location links for parts of type mentioned in inlay hints.",
+                    "default": true,
+                    "type": "boolean"
+                },
                 "rust-analyzer.inlayHints.maxLength": {
                     "markdownDescription": "Maximum length for inlay hints. Set to null to have an unlimited length.",
                     "default": 25,