diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 9d453eef71..57a15d114f 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, @@ -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 HirDisplayWrapper<'_, T> { + pub fn write_to(&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 316f3938c6..80b3a7b02b 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 152f31b3a5..8163697fbf 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; @@ -89,6 +93,7 @@ pub enum InlayTooltip { HoverOffset(FileId, TextSize), } +#[derive(Default)] pub struct InlayHintLabel { pub parts: Vec, } @@ -172,6 +177,96 @@ impl fmt::Debug for InlayHintLabelPart { } } +#[derive(Debug)] +struct InlayHintLabelBuilder<'a> { + db: &'a RootDatabase, + result: InlayHintLabel, + last_part: String, + location: Option, +} + +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.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) { + 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 { + fn rec( + sema: &Semantics<'_, RootDatabase>, + famous_defs: &FamousDefs<'_, '_>, + mut max_length: Option, + 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 { + 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, + 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 +319,7 @@ pub(crate) fn inlay_hints( fn hints( hints: &mut Vec, - famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, + FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, file_id: FileId, node: SyntaxNode, @@ -233,14 +328,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 +365,12 @@ fn hints( }; } -/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator`. +/// 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 { +) -> Option { let db = sema.db; let strukt = ty.strip_references().as_adt()?; let krate = strukt.module(db).krate(); @@ -299,21 +393,7 @@ fn hint_iterator( _ => None, })?; if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) { - const LABEL_START: &str = "impl Iterator 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, 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()) @@ -346,7 +338,31 @@ fn main(a: SliceIter<'_, Container>) { range: 484..485, kind: ChainingHint, label: [ - "SliceIter", + "", + 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 32421afd39..c9aabcbb04 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, 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())), }); } @@ -110,7 +107,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 +134,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( @@ -185,7 +206,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 +233,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( @@ -245,7 +290,31 @@ fn main() { range: 246..283, kind: ChainingHint, label: [ - "B>", + "", + 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, + }, + ), + }, + ">", ], tooltip: Some( HoverRanged( @@ -260,7 +329,31 @@ fn main() { range: 246..265, kind: ChainingHint, label: [ - "A>", + "", + 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, + }, + ), + }, + ">", ], tooltip: Some( HoverRanged( @@ -352,7 +445,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 +501,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 +528,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 +555,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/closure_ret.rs b/crates/ide/src/inlay_hints/closure_ret.rs index de04f3ac75..247a4abcc5 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, 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, ¶m_list, config, ty)?, tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())), }); Some(())