about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-04-30 20:35:15 +0000
committerbors <bors@rust-lang.org>2023-04-30 20:35:15 +0000
commit3a27518fee5a723005299cf49e2d58a842a261ca (patch)
tree563dbbec33945c6eff5b9849bdedbe0df711e347
parent7bcb4c2ef23e151a639ff918fbb8ab9d521eabb9 (diff)
parent5df545b3f06b40e0930bc26413125d084c7bc1dc (diff)
downloadrust-3a27518fee5a723005299cf49e2d58a842a261ca.tar.gz
rust-3a27518fee5a723005299cf49e2d58a842a261ca.zip
Auto merge of #14690 - HKalbasi:closure-hover, r=HKalbasi
Add hover for closure
-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 4affe7424e1..64e31583095 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 e7eb967c040..346c7f6df66 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 03536be8847..e5b356dde6c 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 a65cbf8ff14..f7a14bf36ad 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 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<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 7294b625539..24c39ab03d5 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#"