Unnamed repository; edit this file 'description' to name the repository.
Add hover for closure
hkalbasi 2023-04-30
parent 370b72c · commit 5df545b
-rw-r--r--crates/hir-ty/src/infer.rs4
-rw-r--r--crates/hir-ty/src/infer/closure.rs72
-rw-r--r--crates/hir-ty/src/lib.rs4
-rw-r--r--crates/hir/src/lib.rs51
-rw-r--r--crates/ide/src/hover.rs14
-rw-r--r--crates/ide/src/hover/render.rs32
-rw-r--r--crates/ide/src/hover/tests.rs79
7 files changed, 249 insertions, 7 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 4affe7424e..64e3158309 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -62,7 +62,7 @@ mod path;
mod expr;
mod pat;
mod coerce;
-mod closure;
+pub(crate) mod closure;
mod mutability;
/// The entry point of type inference.
@@ -426,7 +426,7 @@ impl InferenceResult {
_ => None,
})
}
- pub(crate) fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
+ pub fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, 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 e7eb967c04..346c7f6df6 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -4,6 +4,7 @@ use std::{cmp, collections::HashMap, convert::Infallible, mem};
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 smallvec::SmallVec;
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 03536be884..e5b356dde6 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -60,8 +60,8 @@ pub use autoderef::autoderef;
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 a65cbf8ff1..f7a14bf36a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -3174,6 +3174,46 @@ impl TraitRef {
}
}
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub struct Closure {
+ id: ClosureId,
+ subst: Substitution,
+}
+
+impl From<Closure> 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<hir_ty::CapturedItem> {
+ 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<TraitEnvironment>,
@@ -3463,6 +3503,13 @@ impl Type {
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
}
+ pub fn as_closure(&self) -> Option<Closure> {
+ 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 @@ impl Type {
.map(|id| TypeOrConstParam { id }.split(db).either_into())
.collect()
}
+
+ pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
+ 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 64b2221bde..bbbf39ca15 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 fb7b15e05d..e5019b7159 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<HoverResult> {
+ 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 7294b62553..24c39ab03d 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -199,6 +199,85 @@ fn main() {
}
#[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(
r#"