about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-05-08 09:52:29 +0000
committerbors <bors@rust-lang.org>2023-05-08 09:52:29 +0000
commitd3ce333ec858e33e7c881386784d7871c58db2d8 (patch)
treed4b93f00579543d24a3c31939ad09217da003fb3
parentff8b9699518d4fe92f08629d91b3983e00f75282 (diff)
parent4c5fd19ee5734992344827b11c0d0e864b020b25 (diff)
Auto merge of #14742 - Veykril:closure-capture-inlays, r=Veykril
feat: Closure capture inlay hints

I opted for a fictional `move(foo, &bar, &mut qux)` syntax here, disabled by default as these are not correct rust syntax and hence could cause confusion.
![image](https://user-images.githubusercontent.com/3757771/236447484-649a4ea6-ad61-496e-bad8-765a5236150e.png)
-rw-r--r--crates/hir-ty/src/infer/closure.rs20
-rw-r--r--crates/hir-ty/src/lib.rs5
-rw-r--r--crates/hir/src/lib.rs37
-rw-r--r--crates/ide/src/hover/render.rs13
-rw-r--r--crates/ide/src/inlay_hints.rs28
-rw-r--r--crates/ide/src/inlay_hints/closure_captures.rs192
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--crates/rust-analyzer/src/to_proto.rs6
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
11 files changed, 278 insertions, 37 deletions
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index df2ad7af343..6d03c76eb6a 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -148,7 +148,7 @@ impl HirPlace {
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub(crate) enum CaptureKind {
+pub enum CaptureKind {
     ByRef(BorrowKind),
     ByValue,
 }
@@ -166,23 +166,11 @@ impl CapturedItem {
         self.place.local
     }
 
-    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 kind(&self) -> CaptureKind {
+        self.kind
     }
 
-    pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
-        let owner = db.lookup_intern_closure(owner.into()).0;
+    pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
         let body = db.body(owner);
         let mut result = body[self.place.local].name.to_string();
         let mut field_need_paren = false;
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 6e726042f6c..28a2bf2838d 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -61,8 +61,9 @@ pub use autoderef::autoderef;
 pub use builder::{ParamKind, TyBuilder};
 pub use chalk_ext::*;
 pub use infer::{
-    closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode,
-    InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
+    closure::{CaptureKind, 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 b3a8a33cac9..1fac95ae5e3 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2611,6 +2611,10 @@ impl LocalSource {
         self.source.file_id.original_file(db.upcast())
     }
 
+    pub fn file(&self) -> HirFileId {
+        self.source.file_id
+    }
+
     pub fn name(&self) -> Option<ast::Name> {
         self.source.value.name()
     }
@@ -3210,7 +3214,11 @@ impl Closure {
         let owner = db.lookup_intern_closure((self.id).into()).0;
         let infer = &db.infer(owner);
         let info = infer.closure_info(&self.id);
-        info.0.iter().cloned().map(|capture| ClosureCapture { owner, capture }).collect()
+        info.0
+            .iter()
+            .cloned()
+            .map(|capture| ClosureCapture { owner, closure: self.id, capture })
+            .collect()
     }
 
     pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
@@ -3224,6 +3232,7 @@ impl Closure {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct ClosureCapture {
     owner: DefWithBodyId,
+    closure: ClosureId,
     capture: hir_ty::CapturedItem,
 }
 
@@ -3232,15 +3241,33 @@ impl ClosureCapture {
         Local { parent: self.owner, binding_id: self.capture.local() }
     }
 
-    pub fn display_kind(&self) -> &'static str {
-        self.capture.display_kind()
+    pub fn kind(&self) -> CaptureKind {
+        match self.capture.kind() {
+            hir_ty::CaptureKind::ByRef(
+                hir_ty::mir::BorrowKind::Shallow | hir_ty::mir::BorrowKind::Shared,
+            ) => CaptureKind::SharedRef,
+            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Unique) => {
+                CaptureKind::UniqueSharedRef
+            }
+            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut { .. }) => {
+                CaptureKind::MutableRef
+            }
+            hir_ty::CaptureKind::ByValue => CaptureKind::Move,
+        }
     }
 
-    pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
-        self.capture.display_place(owner, db)
+    pub fn display_place(&self, db: &dyn HirDatabase) -> String {
+        self.capture.display_place(self.owner, db)
     }
 }
 
+pub enum CaptureKind {
+    SharedRef,
+    UniqueSharedRef,
+    MutableRef,
+    Move,
+}
+
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub struct Type {
     env: Arc<TraitEnvironment>,
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index dc03df180ab..53226db7ccd 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -3,7 +3,8 @@ use std::fmt::Display;
 
 use either::Either;
 use hir::{
-    Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo,
+    Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasAttrs, HasSource, HirDisplay, Semantics,
+    TypeInfo,
 };
 use ide_db::{
     base_db::SourceDatabase,
@@ -58,8 +59,14 @@ pub(super) fn closure_expr(
     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())
+        .map(|it| {
+            let borrow_kind=   match it.kind() {
+                CaptureKind::SharedRef => "immutable borrow",
+                CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
+                CaptureKind::MutableRef => "mutable borrow",
+                CaptureKind::Move => "move",
+            };
+            format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
         })
         .join("\n");
     if captures.trim().is_empty() {
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 7a8edfea835..c326688ae6a 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -20,16 +20,17 @@ use text_edit::TextEdit;
 
 use crate::{navigation_target::TryToNav, FileId};
 
-mod closing_brace;
-mod implicit_static;
-mod fn_lifetime_fn;
-mod closure_ret;
 mod adjustment;
-mod chaining;
-mod param_name;
-mod binding_mode;
 mod bind_pat;
+mod binding_mode;
+mod chaining;
+mod closing_brace;
+mod closure_ret;
+mod closure_captures;
 mod discriminant;
+mod fn_lifetime_fn;
+mod implicit_static;
+mod param_name;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct InlayHintsConfig {
@@ -42,6 +43,7 @@ pub struct InlayHintsConfig {
     pub adjustment_hints_mode: AdjustmentHintsMode,
     pub adjustment_hints_hide_outside_unsafe: bool,
     pub closure_return_type_hints: ClosureReturnTypeHints,
+    pub closure_capture_hints: bool,
     pub binding_mode_hints: bool,
     pub lifetime_elision_hints: LifetimeElisionHints,
     pub param_names_for_lifetime_elision_hints: bool,
@@ -88,6 +90,8 @@ pub enum AdjustmentHintsMode {
     PreferPostfix,
 }
 
+// FIXME: Clean up this mess, the kinds are mainly used for setting different rendering properties in the lsp layer
+// We should probably turns this into such a property holding struct. Or clean this up in some other form.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum InlayKind {
     BindingMode,
@@ -98,6 +102,7 @@ pub enum InlayKind {
     Adjustment,
     AdjustmentPostfix,
     Lifetime,
+    ClosureCapture,
     Parameter,
     Type,
     Discriminant,
@@ -444,10 +449,10 @@ fn hints(
                     ast::Expr::MethodCallExpr(it) => {
                         param_name::hints(hints, sema, config, ast::Expr::from(it))
                     }
-                    ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, famous_defs, 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),
+                    ast::Expr::ClosureExpr(it) => {
+                        closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
+                        closure_ret::hints(hints, famous_defs, config, file_id, it)
+                    },
                     _ => None,
                 }
             },
@@ -535,6 +540,7 @@ mod tests {
         chaining_hints: false,
         lifetime_elision_hints: LifetimeElisionHints::Never,
         closure_return_type_hints: ClosureReturnTypeHints::Never,
+        closure_capture_hints: false,
         adjustment_hints: AdjustmentHints::Never,
         adjustment_hints_mode: AdjustmentHintsMode::Prefix,
         adjustment_hints_hide_outside_unsafe: false,
diff --git a/crates/ide/src/inlay_hints/closure_captures.rs b/crates/ide/src/inlay_hints/closure_captures.rs
new file mode 100644
index 00000000000..60c4fe411f4
--- /dev/null
+++ b/crates/ide/src/inlay_hints/closure_captures.rs
@@ -0,0 +1,192 @@
+//! Implementation of "closure return type" inlay hints.
+//!
+//! Tests live in [`bind_pat`][super::bind_pat] module.
+use ide_db::{base_db::FileId, famous_defs::FamousDefs};
+use syntax::ast::{self, AstNode};
+use text_edit::{TextRange, TextSize};
+
+use crate::{InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind};
+
+pub(super) fn hints(
+    acc: &mut Vec<InlayHint>,
+    FamousDefs(sema, _): &FamousDefs<'_, '_>,
+    config: &InlayHintsConfig,
+    _file_id: FileId,
+    closure: ast::ClosureExpr,
+) -> Option<()> {
+    if !config.closure_capture_hints {
+        return None;
+    }
+    let ty = &sema.type_of_expr(&closure.clone().into())?.original;
+    let c = ty.as_closure()?;
+    let captures = c.captured_items(sema.db);
+
+    if captures.is_empty() {
+        return None;
+    }
+
+    let move_kw_range = match closure.move_token() {
+        Some(t) => t.text_range(),
+        None => {
+            let range = closure.syntax().first_token()?.prev_token()?.text_range();
+            let range = TextRange::new(range.end() - TextSize::from(1), range.end());
+            acc.push(InlayHint {
+                range,
+                kind: InlayKind::ClosureCapture,
+                label: InlayHintLabel::simple("move", None, None),
+                text_edit: None,
+            });
+            range
+        }
+    };
+    acc.push(InlayHint {
+        range: move_kw_range,
+        kind: InlayKind::ClosureCapture,
+        label: InlayHintLabel::from("("),
+        text_edit: None,
+    });
+    let last = captures.len() - 1;
+    for (idx, capture) in captures.into_iter().enumerate() {
+        let local = capture.local();
+        let source = local.primary_source(sema.db);
+
+        // force cache the source file, otherwise sema lookup will potentially panic
+        _ = sema.parse_or_expand(source.file());
+
+        acc.push(InlayHint {
+            range: move_kw_range,
+            kind: InlayKind::ClosureCapture,
+            label: InlayHintLabel::simple(
+                format!(
+                    "{}{}",
+                    match capture.kind() {
+                        hir::CaptureKind::SharedRef => "&",
+                        hir::CaptureKind::UniqueSharedRef => "&unique ",
+                        hir::CaptureKind::MutableRef => "&mut ",
+                        hir::CaptureKind::Move => "",
+                    },
+                    capture.display_place(sema.db)
+                ),
+                None,
+                source.name().and_then(|name| sema.original_range_opt(name.syntax())),
+            ),
+            text_edit: None,
+        });
+
+        if idx != last {
+            acc.push(InlayHint {
+                range: move_kw_range,
+                kind: InlayKind::ClosureCapture,
+                label: InlayHintLabel::simple(", ", None, None),
+                text_edit: None,
+            });
+        }
+    }
+    acc.push(InlayHint {
+        range: move_kw_range,
+        kind: InlayKind::ClosureCapture,
+        label: InlayHintLabel::from(")"),
+        text_edit: None,
+    });
+
+    Some(())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+        InlayHintsConfig,
+    };
+
+    #[test]
+    fn all_capture_kinds() {
+        check_with_config(
+            InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
+            r#"
+//- minicore: copy, derive
+
+
+#[derive(Copy, Clone)]
+struct Copy;
+
+struct NonCopy;
+
+fn main() {
+    let foo = Copy;
+    let bar = NonCopy;
+    let mut baz = NonCopy;
+    let qux = &mut NonCopy;
+    || {
+// ^ move
+// ^ (
+// ^ &foo
+// ^ , $
+// ^ bar
+// ^ , $
+// ^ baz
+// ^ , $
+// ^ qux
+// ^ )
+        foo;
+        bar;
+        baz;
+        qux;
+    };
+    || {
+// ^ move
+// ^ (
+// ^ &foo
+// ^ , $
+// ^ &bar
+// ^ , $
+// ^ &baz
+// ^ , $
+// ^ &qux
+// ^ )
+        &foo;
+        &bar;
+        &baz;
+        &qux;
+    };
+    || {
+// ^ move
+// ^ (
+// ^ &mut baz
+// ^ )
+        &mut baz;
+    };
+    || {
+// ^ move
+// ^ (
+// ^ &mut baz
+// ^ , $
+// ^ &mut *qux
+// ^ )
+        baz = NonCopy;
+        *qux = NonCopy;
+    };
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn move_token() {
+        check_with_config(
+            InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
+            r#"
+//- minicore: copy, derive
+fn main() {
+    let foo = u32;
+    move || {
+//  ^^^^ (
+//  ^^^^ foo
+//  ^^^^ )
+        foo;
+    };
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index dd6ec21ffff..e7b223caab4 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -122,6 +122,7 @@ impl StaticIndex<'_> {
                     param_names_for_lifetime_elision_hints: false,
                     binding_mode_hints: false,
                     max_length: Some(25),
+                    closure_capture_hints: false,
                     closing_brace_hints_min_lines: Some(25),
                 },
                 file_id,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b72f9202c67..9d5aa0c8d2a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -340,6 +340,8 @@ config_data! {
         /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
         /// to always show them).
         inlayHints_closingBraceHints_minLines: usize               = "25",
+        /// Whether to show inlay hints for closure captures.
+        inlayHints_closureCaptureHints_enable: bool                          = "false",
         /// Whether to show inlay type hints for return types of closures.
         inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef  = "\"never\"",
         /// Closure notation in type and chaining inlay hints.
@@ -1314,6 +1316,7 @@ impl Config {
                 ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId,
                 ClosureStyle::Hide => hir::ClosureStyle::Hide,
             },
+            closure_capture_hints: self.data.inlayHints_closureCaptureHints_enable,
             adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
                 AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
                 AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index b9d853e202a..06f8ba3fb8c 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -456,6 +456,7 @@ pub(crate) fn inlay_hint(
             | InlayKind::BindingMode => position(line_index, inlay_hint.range.start()),
             // after annotated thing
             InlayKind::ClosureReturnType
+            | InlayKind::ClosureCapture
             | InlayKind::Type
             | InlayKind::Discriminant
             | InlayKind::Chaining
@@ -469,6 +470,7 @@ pub(crate) fn inlay_hint(
             InlayKind::Type => !render_colons,
             InlayKind::Chaining | InlayKind::ClosingBrace => true,
             InlayKind::ClosingParenthesis
+            | InlayKind::ClosureCapture
             | InlayKind::Discriminant
             | InlayKind::OpeningParenthesis
             | InlayKind::BindingMode
@@ -490,6 +492,9 @@ pub(crate) fn inlay_hint(
             | InlayKind::Type
             | InlayKind::Discriminant
             | InlayKind::ClosingBrace => false,
+            InlayKind::ClosureCapture => {
+                matches!(&label, lsp_types::InlayHintLabel::String(s) if s == ")")
+            }
             InlayKind::BindingMode => {
                 matches!(&label, lsp_types::InlayHintLabel::String(s) if s != "&")
             }
@@ -501,6 +506,7 @@ pub(crate) fn inlay_hint(
                 Some(lsp_types::InlayHintKind::TYPE)
             }
             InlayKind::ClosingParenthesis
+            | InlayKind::ClosureCapture
             | InlayKind::Discriminant
             | InlayKind::OpeningParenthesis
             | InlayKind::BindingMode
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index e75b97061e0..6a2da3d90e3 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -479,6 +479,11 @@ Whether to show inlay hints after a closing `}` to indicate what item it belongs
 Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
 to always show them).
 --
+[[rust-analyzer.inlayHints.closureCaptureHints.enable]]rust-analyzer.inlayHints.closureCaptureHints.enable (default: `false`)::
++
+--
+Whether to show inlay hints for closure captures.
+--
 [[rust-analyzer.inlayHints.closureReturnTypeHints.enable]]rust-analyzer.inlayHints.closureReturnTypeHints.enable (default: `"never"`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index c9de94f0227..fdcd707a07c 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1037,6 +1037,11 @@
                     "type": "integer",
                     "minimum": 0
                 },
+                "rust-analyzer.inlayHints.closureCaptureHints.enable": {
+                    "markdownDescription": "Whether to show inlay hints for closure captures.",
+                    "default": false,
+                    "type": "boolean"
+                },
                 "rust-analyzer.inlayHints.closureReturnTypeHints.enable": {
                     "markdownDescription": "Whether to show inlay type hints for return types of closures.",
                     "default": "never",