Skip to content

Commit 801a223

Browse files
committed
Implement location link for type inlay hints
1 parent 927d56a commit 801a223

File tree

6 files changed

+362
-82
lines changed

6 files changed

+362
-82
lines changed

crates/hir-ty/src/display.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use hir_def::{
1616
path::{Path, PathKind},
1717
type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef},
1818
visibility::Visibility,
19-
HasModule, ItemContainerId, Lookup, ModuleId, TraitId,
19+
HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId,
2020
};
2121
use hir_expand::{hygiene::Hygiene, name::Name};
2222
use itertools::Itertools;
@@ -35,16 +35,44 @@ use crate::{
3535
TraitRefExt, Ty, TyExt, TyKind, WhereClause,
3636
};
3737

38+
pub trait HirWrite: fmt::Write {
39+
fn start_location_link(&mut self, location: ModuleDefId);
40+
fn end_location_link(&mut self);
41+
}
42+
43+
// String will ignore link metadata
44+
impl HirWrite for String {
45+
fn start_location_link(&mut self, _: ModuleDefId) {}
46+
47+
fn end_location_link(&mut self) {}
48+
}
49+
50+
// `core::Formatter` will ignore metadata
51+
impl HirWrite for fmt::Formatter<'_> {
52+
fn start_location_link(&mut self, _: ModuleDefId) {}
53+
fn end_location_link(&mut self) {}
54+
}
55+
3856
pub struct HirFormatter<'a> {
3957
pub db: &'a dyn HirDatabase,
40-
fmt: &'a mut dyn fmt::Write,
58+
fmt: &'a mut dyn HirWrite,
4159
buf: String,
4260
curr_size: usize,
4361
pub(crate) max_size: Option<usize>,
4462
omit_verbose_types: bool,
4563
display_target: DisplayTarget,
4664
}
4765

66+
impl HirFormatter<'_> {
67+
fn start_location_link(&mut self, location: ModuleDefId) {
68+
self.fmt.start_location_link(location);
69+
}
70+
71+
fn end_location_link(&mut self) {
72+
self.fmt.end_location_link();
73+
}
74+
}
75+
4876
pub trait HirDisplay {
4977
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError>;
5078

@@ -245,20 +273,26 @@ pub struct HirDisplayWrapper<'a, T> {
245273
display_target: DisplayTarget,
246274
}
247275

248-
impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
249-
where
250-
T: HirDisplay,
251-
{
252-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253-
match self.t.hir_fmt(&mut HirFormatter {
276+
impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
277+
pub fn write_to<F: HirWrite>(&self, f: &mut F) -> Result<(), HirDisplayError> {
278+
self.t.hir_fmt(&mut HirFormatter {
254279
db: self.db,
255280
fmt: f,
256281
buf: String::with_capacity(20),
257282
curr_size: 0,
258283
max_size: self.max_size,
259284
omit_verbose_types: self.omit_verbose_types,
260285
display_target: self.display_target,
261-
}) {
286+
})
287+
}
288+
}
289+
290+
impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
291+
where
292+
T: HirDisplay,
293+
{
294+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295+
match self.write_to(f) {
262296
Ok(()) => Ok(()),
263297
Err(HirDisplayError::FmtError) => Err(fmt::Error),
264298
Err(HirDisplayError::DisplaySourceCodeError(_)) => {
@@ -530,6 +564,7 @@ impl HirDisplay for Ty {
530564
}
531565
}
532566
TyKind::Adt(AdtId(def_id), parameters) => {
567+
f.start_location_link((*def_id).into());
533568
match f.display_target {
534569
DisplayTarget::Diagnostics | DisplayTarget::Test => {
535570
let name = match *def_id {
@@ -554,6 +589,7 @@ impl HirDisplay for Ty {
554589
}
555590
}
556591
}
592+
f.end_location_link();
557593

558594
if parameters.len(Interner) > 0 {
559595
let parameters_to_write = if f.display_target.is_source_code()

crates/hir/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,20 @@ pub use {
114114
path::{ModPath, PathKind},
115115
type_ref::{Mutability, TypeRef},
116116
visibility::Visibility,
117+
// FIXME: This is here since it is input of a method in `HirWrite`
118+
// and things outside of hir need to implement that trait. We probably
119+
// should move whole `hir_ty::display` to this crate so we will become
120+
// able to use `ModuleDef` or `Definition` instead of `ModuleDefId`.
121+
ModuleDefId,
117122
},
118123
hir_expand::{
119124
name::{known, Name},
120125
ExpandResult, HirFileId, InFile, MacroFile, Origin,
121126
},
122-
hir_ty::{display::HirDisplay, PointerCast, Safety},
127+
hir_ty::{
128+
display::{HirDisplay, HirWrite},
129+
PointerCast, Safety,
130+
},
123131
};
124132

125133
// These are negative re-exports: pub using these names is forbidden, they

crates/ide/src/inlay_hints.rs

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
use std::fmt;
1+
use std::{
2+
fmt::{self, Write},
3+
mem::take,
4+
};
25

36
use either::Either;
4-
use hir::{known, HasVisibility, HirDisplay, Semantics};
7+
use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics};
58
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
69
use itertools::Itertools;
10+
use stdx::never;
711
use syntax::{
812
ast::{self, AstNode},
913
match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
1014
};
1115

12-
use crate::FileId;
16+
use crate::{navigation_target::TryToNav, FileId};
1317

1418
mod closing_brace;
1519
mod implicit_static;
@@ -89,6 +93,7 @@ pub enum InlayTooltip {
8993
HoverOffset(FileId, TextSize),
9094
}
9195

96+
#[derive(Default)]
9297
pub struct InlayHintLabel {
9398
pub parts: Vec<InlayHintLabelPart>,
9499
}
@@ -172,6 +177,96 @@ impl fmt::Debug for InlayHintLabelPart {
172177
}
173178
}
174179

180+
#[derive(Debug)]
181+
struct InlayHintLabelBuilder<'a> {
182+
db: &'a RootDatabase,
183+
result: InlayHintLabel,
184+
last_part: String,
185+
location: Option<FileRange>,
186+
}
187+
188+
impl fmt::Write for InlayHintLabelBuilder<'_> {
189+
fn write_str(&mut self, s: &str) -> fmt::Result {
190+
self.last_part.write_str(s)
191+
}
192+
}
193+
194+
impl HirWrite for InlayHintLabelBuilder<'_> {
195+
fn start_location_link(&mut self, def: ModuleDefId) {
196+
if self.location.is_some() {
197+
never!("location link is already started");
198+
}
199+
self.make_new_part();
200+
let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
201+
let location =
202+
FileRange { file_id: location.file_id, range: location.focus_or_full_range() };
203+
self.location = Some(location);
204+
}
205+
206+
fn end_location_link(&mut self) {
207+
self.make_new_part();
208+
}
209+
}
210+
211+
impl InlayHintLabelBuilder<'_> {
212+
fn make_new_part(&mut self) {
213+
self.result.parts.push(InlayHintLabelPart {
214+
text: take(&mut self.last_part),
215+
linked_location: self.location.take(),
216+
});
217+
}
218+
219+
fn finish(mut self) -> InlayHintLabel {
220+
self.make_new_part();
221+
self.result
222+
}
223+
}
224+
225+
fn label_of_ty(
226+
sema: &Semantics<'_, RootDatabase>,
227+
desc_pat: &impl AstNode,
228+
config: &InlayHintsConfig,
229+
ty: hir::Type,
230+
) -> Option<InlayHintLabel> {
231+
fn rec(
232+
sema: &Semantics<'_, RootDatabase>,
233+
famous_defs: &FamousDefs<'_, '_>,
234+
mut max_length: Option<usize>,
235+
ty: hir::Type,
236+
label_builder: &mut InlayHintLabelBuilder<'_>,
237+
) {
238+
let iter_item_type = hint_iterator(sema, &famous_defs, &ty);
239+
match iter_item_type {
240+
Some(ty) => {
241+
const LABEL_START: &str = "impl Iterator<Item = ";
242+
const LABEL_END: &str = ">";
243+
244+
max_length =
245+
max_length.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len()));
246+
247+
label_builder.write_str(LABEL_START).unwrap();
248+
rec(sema, famous_defs, max_length, ty, label_builder);
249+
label_builder.write_str(LABEL_END).unwrap();
250+
}
251+
None => {
252+
let _ = ty.display_truncated(sema.db, max_length).write_to(label_builder);
253+
}
254+
};
255+
}
256+
257+
let krate = sema.scope(desc_pat.syntax())?.krate();
258+
let famous_defs = FamousDefs(sema, krate);
259+
let mut label_builder = InlayHintLabelBuilder {
260+
db: sema.db,
261+
last_part: String::new(),
262+
location: None,
263+
result: InlayHintLabel::default(),
264+
};
265+
rec(sema, &famous_defs, config.max_length, ty, &mut label_builder);
266+
let r = label_builder.finish();
267+
Some(r)
268+
}
269+
175270
// Feature: Inlay Hints
176271
//
177272
// rust-analyzer shows additional information inline with the source code.
@@ -224,7 +319,7 @@ pub(crate) fn inlay_hints(
224319

225320
fn hints(
226321
hints: &mut Vec<InlayHint>,
227-
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
322+
FamousDefs(sema, _): &FamousDefs<'_, '_>,
228323
config: &InlayHintsConfig,
229324
file_id: FileId,
230325
node: SyntaxNode,
@@ -233,14 +328,14 @@ fn hints(
233328
match_ast! {
234329
match node {
235330
ast::Expr(expr) => {
236-
chaining::hints(hints, sema, &famous_defs, config, file_id, &expr);
331+
chaining::hints(hints, sema, config, file_id, &expr);
237332
adjustment::hints(hints, sema, config, &expr);
238333
match expr {
239334
ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
240335
ast::Expr::MethodCallExpr(it) => {
241336
param_name::hints(hints, sema, config, ast::Expr::from(it))
242337
}
243-
ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, sema, &famous_defs, config, file_id, it),
338+
ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, sema, config, file_id, it),
244339
// We could show reborrows for all expressions, but usually that is just noise to the user
245340
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
246341
// ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
@@ -270,13 +365,12 @@ fn hints(
270365
};
271366
}
272367

273-
/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
368+
/// Checks if the type is an Iterator from std::iter and returns its item type.
274369
fn hint_iterator(
275370
sema: &Semantics<'_, RootDatabase>,
276371
famous_defs: &FamousDefs<'_, '_>,
277-
config: &InlayHintsConfig,
278372
ty: &hir::Type,
279-
) -> Option<String> {
373+
) -> Option<hir::Type> {
280374
let db = sema.db;
281375
let strukt = ty.strip_references().as_adt()?;
282376
let krate = strukt.module(db).krate();
@@ -299,21 +393,7 @@ fn hint_iterator(
299393
_ => None,
300394
})?;
301395
if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
302-
const LABEL_START: &str = "impl Iterator<Item = ";
303-
const LABEL_END: &str = ">";
304-
305-
let ty_display = hint_iterator(sema, famous_defs, config, &ty)
306-
.map(|assoc_type_impl| assoc_type_impl.to_string())
307-
.unwrap_or_else(|| {
308-
ty.display_truncated(
309-
db,
310-
config
311-
.max_length
312-
.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
313-
)
314-
.to_string()
315-
});
316-
return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
396+
return Some(ty);
317397
}
318398
}
319399

0 commit comments

Comments
 (0)