Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement location link for type inlay hints #13699

Merged
merged 2 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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`.
Comment on lines +117 to +120
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in general we want to have proper visitors for various things at some point, since the HirWrite interface is tailored to IDE purposes right now and shouldn't really be part of hir-ty either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have something in hir-ty that basically turns a type into a tree that directly maps to surface syntax, but still uses IDs instead of names. That representation could then be consumed by the IDE crates

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