From 5df545b3f06b40e0930bc26413125d084c7bc1dc Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Sun, 30 Apr 2023 14:31:43 +0330 Subject: [PATCH] Add hover for closure --- crates/hir-ty/src/infer.rs | 4 +- crates/hir-ty/src/infer/closure.rs | 72 ++++++++++++++++++++++++++- crates/hir-ty/src/lib.rs | 4 +- crates/hir/src/lib.rs | 51 +++++++++++++++++++ crates/ide/src/hover.rs | 14 +++++- crates/ide/src/hover/render.rs | 32 ++++++++++++ crates/ide/src/hover/tests.rs | 79 ++++++++++++++++++++++++++++++ 7 files changed, 249 insertions(+), 7 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 4affe7424e1..64e31583095 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -62,7 +62,7 @@ mod expr; mod pat; mod coerce; -mod closure; +pub(crate) mod closure; mod mutability; /// The entry point of type inference. @@ -426,7 +426,7 @@ pub fn expr_type_mismatches(&self) -> impl Iterator None, }) } - pub(crate) fn closure_info(&self, closure: &ClosureId) -> &(Vec, FnTrait) { + pub fn closure_info(&self, closure: &ClosureId) -> &(Vec, FnTrait) { self.closure_info.get(closure).unwrap() } } diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index e7eb967c040..346c7f6df66 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -4,6 +4,7 @@ use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, Mutability, TyKind, WhereClause}; use hir_def::{ + data::adt::VariantData, hir::{ Array, BinaryOp, BindingAnnotation, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement, UnaryOp, @@ -18,6 +19,7 @@ use stdx::never; use crate::{ + db::HirDatabase, mir::{BorrowKind, MirSpan, ProjectionElem}, static_lifetime, to_chalk_trait_id, traits::FnTrait, @@ -146,13 +148,81 @@ pub(crate) enum CaptureKind { } #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct CapturedItem { +pub struct CapturedItem { pub(crate) place: HirPlace, pub(crate) kind: CaptureKind, pub(crate) span: MirSpan, pub(crate) ty: Ty, } +impl CapturedItem { + pub fn display_kind(&self) -> &'static str { + match self.kind { + CaptureKind::ByRef(k) => match k { + BorrowKind::Shared => "immutable borrow", + BorrowKind::Shallow => { + never!("shallow borrow should not happen in closure captures"); + "shallow borrow" + }, + BorrowKind::Unique => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))", + BorrowKind::Mut { .. } => "mutable borrow", + }, + CaptureKind::ByValue => "move", + } + } + + pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String { + let owner = db.lookup_intern_closure(owner.into()).0; + let body = db.body(owner); + let mut result = body[self.place.local].name.to_string(); + let mut field_need_paren = false; + for proj in &self.place.projections { + match proj { + ProjectionElem::Deref => { + result = format!("*{result}"); + field_need_paren = true; + } + ProjectionElem::Field(f) => { + if field_need_paren { + result = format!("({result})"); + } + let variant_data = f.parent.variant_data(db.upcast()); + let field = match &*variant_data { + VariantData::Record(fields) => fields[f.local_id] + .name + .as_str() + .unwrap_or("[missing field]") + .to_string(), + VariantData::Tuple(fields) => fields + .iter() + .position(|x| x.0 == f.local_id) + .unwrap_or_default() + .to_string(), + VariantData::Unit => "[missing field]".to_string(), + }; + result = format!("{result}.{field}"); + field_need_paren = false; + } + &ProjectionElem::TupleOrClosureField(field) => { + if field_need_paren { + result = format!("({result})"); + } + result = format!("{result}.{field}"); + field_need_paren = false; + } + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::OpaqueCast(_) => { + never!("Not happen in closure capture"); + continue; + } + } + } + result + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CapturedItemWithoutTy { pub(crate) place: HirPlace, diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 03536be8847..e5b356dde6c 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -60,8 +60,8 @@ macro_rules! eprintln { pub use builder::{ParamKind, TyBuilder}; pub use chalk_ext::*; pub use infer::{ - could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic, - InferenceResult, OverloadedDeref, PointerCast, + closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, + InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, }; pub use interner::Interner; pub use lower::{ diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index a65cbf8ff14..f7a14bf36ad 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3174,6 +3174,46 @@ pub fn get_type_argument(&self, idx: usize) -> Option { } } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Closure { + id: ClosureId, + subst: Substitution, +} + +impl From for ClosureId { + fn from(value: Closure) -> Self { + value.id + } +} + +impl Closure { + fn as_ty(self) -> Ty { + TyKind::Closure(self.id, self.subst).intern(Interner) + } + + pub fn display_with_id(&self, db: &dyn HirDatabase) -> String { + self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string() + } + + pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String { + self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string() + } + + pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let info = infer.closure_info(&self.id); + info.0.clone() + } + + pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait { + let owner = db.lookup_intern_closure((self.id).into()).0; + let infer = &db.infer(owner); + let info = infer.closure_info(&self.id); + info.1 + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct Type { env: Arc, @@ -3463,6 +3503,13 @@ pub fn is_closure(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::Closure { .. }) } + pub fn as_closure(&self) -> Option { + match self.ty.kind(Interner) { + TyKind::Closure(id, subst) => Some(Closure { id: *id, subst: subst.clone() }), + _ => None, + } + } + pub fn is_fn(&self) -> bool { matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. }) } @@ -4016,6 +4063,10 @@ pub fn generic_params(&self, db: &dyn HirDatabase) -> FxHashSet { .map(|id| TypeOrConstParam { id }.split(db).either_into()) .collect() } + + pub fn layout(&self, db: &dyn HirDatabase) -> Result { + layout_of_ty(db, &self.ty, self.env.krate) + } } // FIXME: Document this diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 64b2221bdea..bbbf39ca15b 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -119,8 +119,8 @@ fn hover_simple( | T![crate] | T![Self] | T![_] => 4, - // index and prefix ops - T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, + // index and prefix ops and closure pipe + T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3, kind if kind.is_keyword() => 2, T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, @@ -219,6 +219,16 @@ fn hover_simple( }; render::type_info_of(sema, config, &Either::Left(call_expr)) }) + }) + // try closure + .or_else(|| { + descended().find_map(|token| { + if token.kind() != T![|] { + return None; + } + let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; + render::closure_expr(sema, c) + }) }); result.map(|mut res: HoverResult| { diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index fb7b15e05d8..e5019b71595 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -42,6 +42,38 @@ pub(super) fn type_info_of( type_info(sema, _config, original, adjusted) } +pub(super) fn closure_expr( + sema: &Semantics<'_, RootDatabase>, + c: ast::ClosureExpr, +) -> Option { + let ty = &sema.type_of_expr(&c.into())?.original; + let layout = ty + .layout(sema.db) + .map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes())) + .unwrap_or_default(); + let c = ty.as_closure()?; + let mut captures = c + .captured_items(sema.db) + .into_iter() + .map(|x| { + format!("* `{}` by {}", x.display_place(c.clone().into(), sema.db), x.display_kind()) + }) + .join("\n"); + if captures.trim().is_empty() { + captures = "This closure captures nothing".to_string(); + } + let mut res = HoverResult::default(); + res.markup = format!( + "```rust\n{}{}\n{}\n```\n\n## Captures\n{}", + c.display_with_id(sema.db), + layout, + c.display_with_impl(sema.db), + captures, + ) + .into(); + Some(res) +} + pub(super) fn try_expr( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 7294b625539..24c39ab03d5 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -198,6 +198,85 @@ pub fn foo() -> u32 ); } +#[test] +fn hover_closure() { + check( + r#" +//- minicore: copy +fn main() { + let x = 2; + let y = $0|z| x + z; +} +"#, + expect![[r#" + *|* + ```rust + {closure#0} // size = 8, align = 8 + impl Fn(i32) -> i32 + ``` + + ## Captures + * `x` by immutable borrow + "#]], + ); + + check( + r#" +//- minicore: copy +fn foo(x: impl Fn(i32) -> i32) { + +} +fn main() { + foo($0|x: i32| x) +} +"#, + expect![[r#" + *|* + ```rust + {closure#0} // size = 0, align = 1 + impl Fn(i32) -> i32 + ``` + + ## Captures + This closure captures nothing + "#]], + ); + + check( + r#" +//- minicore: copy + +struct Z { f: i32 } + +struct Y(&'static mut Z) + +struct X { + f1: Y, + f2: (Y, Y), +} + +fn main() { + let x: X; + let y = $0|| { + x.f1; + &mut x.f2.0 .0.f; + }; +} +"#, + expect![[r#" + *|* + ```rust + {closure#0} // size = 16, align = 8 + impl FnOnce() + ``` + + ## Captures + * `x.f1` by move + * `(*x.f2.0.0).f` by mutable borrow + "#]], + ); +} + #[test] fn hover_shows_long_type_of_an_expression() { check(