Skip to content

Commit

Permalink
Auto merge of #13699 - HKalbasi:inlaylink, r=Veykril
Browse files Browse the repository at this point in the history
Implement location link for type inlay hints

fix #11701

This actually doesn't work due a problem in vscode: microsoft/vscode#167564
  • Loading branch information
bors committed Dec 21, 2022
2 parents 113f17b + e1aa73e commit 271f7b4
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 92 deletions.
54 changes: 45 additions & 9 deletions crates/hir-ty/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,16 +35,44 @@ 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>,
omit_verbose_types: bool,
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>;

Expand Down Expand Up @@ -245,20 +273,26 @@ 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),
curr_size: 0,
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(_)) => {
Expand Down Expand Up @@ -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 {
Expand All @@ -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()
Expand Down
10 changes: 9 additions & 1 deletion crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
142 changes: 117 additions & 25 deletions crates/ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -89,6 +94,7 @@ pub enum InlayTooltip {
HoverOffset(FileId, TextSize),
}

#[derive(Default)]
pub struct InlayHintLabel {
pub parts: Vec<InlayHintLabelPart>,
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
}

Expand All @@ -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,
Expand All @@ -350,14 +440,16 @@ 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,
chaining_hints: true,
closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
..DISABLED_CONFIG
..DISABLED_CONFIG_WITH_LINKS
};

#[track_caller]
Expand Down

0 comments on commit 271f7b4

Please sign in to comment.