about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMaybe Waffle <waffle.lapkin@gmail.com>2022-12-20 22:07:00 +0000
committerMaybe Waffle <waffle.lapkin@gmail.com>2023-01-09 13:27:59 +0000
commitb89c4f0a0529ca90dbe13d41fdd5e52331770900 (patch)
tree015a8a739e51d6cdcda8200486949dc699f645b8
parentae659125a509967f09665b96d06f6ce6bf1ddd1b (diff)
downloadrust-b89c4f0a0529ca90dbe13d41fdd5e52331770900.tar.gz
rust-b89c4f0a0529ca90dbe13d41fdd5e52331770900.zip
Implement postfix adjustment hints
I'd say "First stab at implementing..." but I've been working on this
for a month already lol
-rw-r--r--crates/ide/src/inlay_hints.rs3
-rw-r--r--crates/ide/src/inlay_hints/adjustment.rs252
-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.rs4
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
7 files changed, 246 insertions, 27 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 86d25e2f5ad..7315a37ebc7 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -35,6 +35,7 @@ pub struct InlayHintsConfig {
     pub parameter_hints: bool,
     pub chaining_hints: bool,
     pub adjustment_hints: AdjustmentHints,
+    pub adjustment_hints_postfix: bool,
     pub adjustment_hints_hide_outside_unsafe: bool,
     pub closure_return_type_hints: ClosureReturnTypeHints,
     pub binding_mode_hints: bool,
@@ -82,6 +83,7 @@ pub enum InlayKind {
     ClosureReturnTypeHint,
     GenericParamListHint,
     AdjustmentHint,
+    AdjustmentHintPostfix,
     LifetimeHint,
     ParameterHint,
     TypeHint,
@@ -446,6 +448,7 @@ mod tests {
         lifetime_elision_hints: LifetimeElisionHints::Never,
         closure_return_type_hints: ClosureReturnTypeHints::Never,
         adjustment_hints: AdjustmentHints::Never,
+        adjustment_hints_postfix: false,
         adjustment_hints_hide_outside_unsafe: false,
         binding_mode_hints: false,
         hide_named_constructor_hints: false,
diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs
index 1c13f31cf24..367bd2f6619 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -5,7 +5,11 @@
 //! ```
 use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics};
 use ide_db::RootDatabase;
-use syntax::ast::{self, AstNode};
+
+use syntax::{
+    ast::{self, make, AstNode},
+    ted,
+};
 
 use crate::{AdjustmentHints, InlayHint, InlayHintsConfig, InlayKind};
 
@@ -32,28 +36,14 @@ pub(super) fn hints(
         return None;
     }
 
-    let parent = expr.syntax().parent().and_then(ast::Expr::cast);
     let descended = sema.descend_node_into_attributes(expr.clone()).pop();
     let desc_expr = descended.as_ref().unwrap_or(expr);
     let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
-    let needs_parens = match parent {
-        Some(parent) => {
-            match parent {
-                ast::Expr::AwaitExpr(_)
-                | ast::Expr::CallExpr(_)
-                | ast::Expr::CastExpr(_)
-                | ast::Expr::FieldExpr(_)
-                | ast::Expr::MethodCallExpr(_)
-                | ast::Expr::TryExpr(_) => true,
-                // FIXME: shorthands need special casing, though not sure if adjustments are even valid there
-                ast::Expr::RecordExpr(_) => false,
-                ast::Expr::IndexExpr(index) => index.base().as_ref() == Some(expr),
-                _ => false,
-            }
-        }
-        None => false,
-    };
-    if needs_parens {
+
+    let (needs_outer_parens, needs_inner_parens) =
+        needs_parens_for_adjustment_hints(expr, config.adjustment_hints_postfix);
+
+    if needs_outer_parens {
         acc.push(InlayHint {
             range: expr.syntax().text_range(),
             kind: InlayKind::OpeningParenthesis,
@@ -61,7 +51,32 @@ pub(super) fn hints(
             tooltip: None,
         });
     }
-    for adjustment in adjustments.into_iter().rev() {
+
+    if config.adjustment_hints_postfix && needs_inner_parens {
+        acc.push(InlayHint {
+            range: expr.syntax().text_range(),
+            kind: InlayKind::OpeningParenthesis,
+            label: "(".into(),
+            tooltip: None,
+        });
+        acc.push(InlayHint {
+            range: expr.syntax().text_range(),
+            kind: InlayKind::ClosingParenthesis,
+            label: ")".into(),
+            tooltip: None,
+        });
+    }
+
+    let (mut tmp0, mut tmp1);
+    let iter: &mut dyn Iterator<Item = _> = if config.adjustment_hints_postfix {
+        tmp0 = adjustments.into_iter();
+        &mut tmp0
+    } else {
+        tmp1 = adjustments.into_iter().rev();
+        &mut tmp1
+    };
+
+    for adjustment in iter {
         if adjustment.source == adjustment.target {
             continue;
         }
@@ -97,12 +112,34 @@ pub(super) fn hints(
         };
         acc.push(InlayHint {
             range: expr.syntax().text_range(),
-            kind: InlayKind::AdjustmentHint,
-            label: text.into(),
+            kind: if config.adjustment_hints_postfix {
+                InlayKind::AdjustmentHintPostfix
+            } else {
+                InlayKind::AdjustmentHint
+            },
+            label: if config.adjustment_hints_postfix {
+                format!(".{}", text.trim_end()).into()
+            } else {
+                text.into()
+            },
             tooltip: None,
         });
     }
-    if needs_parens {
+    if !config.adjustment_hints_postfix && needs_inner_parens {
+        acc.push(InlayHint {
+            range: expr.syntax().text_range(),
+            kind: InlayKind::OpeningParenthesis,
+            label: "(".into(),
+            tooltip: None,
+        });
+        acc.push(InlayHint {
+            range: expr.syntax().text_range(),
+            kind: InlayKind::ClosingParenthesis,
+            label: ")".into(),
+            tooltip: None,
+        });
+    }
+    if needs_outer_parens {
         acc.push(InlayHint {
             range: expr.syntax().text_range(),
             kind: InlayKind::ClosingParenthesis,
@@ -113,6 +150,69 @@ pub(super) fn hints(
     Some(())
 }
 
+/// Returns whatever we need to add paretheses on the inside and/or outside of `expr`,
+/// if we are going to add (`postfix`) adjustments hints to it.
+fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
+    // This is a very miserable pile of hacks...
+    //
+    // `Expr::needs_parens_in` requires that the expression is the child of the other expression,
+    // that is supposed to be its parent.
+    //
+    // But we want to check what would happen if we add `*`/`.*` to the inner expression.
+    // To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
+    // to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
+    // where "expr" is the `expr` parameter, `*expr` is the editted `expr`,
+    // and "parent" is the parent of the original expression...
+    //
+    // For this we utilize mutable mutable trees, which is a HACK, but it works.
+
+    // Make `&expr`/`expr?`
+    let dummy_expr = {
+        // `make::*` function go through a string, so they parse wrongly.
+        // for example `` make::expr_try(`|| a`) `` would result in a
+        // `|| (a?)` and not `(|| a)?`.
+        //
+        // Thus we need dummy parens to preserve the relationship we want.
+        // The parens are then simply ignored by the following code.
+        let dummy_paren = make::expr_paren(expr.clone());
+        if postfix {
+            make::expr_try(dummy_paren)
+        } else {
+            make::expr_ref(dummy_paren, false)
+        }
+    };
+
+    // Do the dark mutable tree magic.
+    // This essentially makes `dummy_expr` and `expr` switch places (families),
+    // so that `expr`'s parent is not `dummy_expr`'s parent.
+    let dummy_expr = dummy_expr.clone_for_update();
+    let expr = expr.clone_for_update();
+    ted::replace(expr.syntax(), dummy_expr.syntax());
+
+    let parent = dummy_expr.syntax().parent();
+    let expr = if postfix {
+        let ast::Expr::TryExpr(e) = &dummy_expr else { unreachable!() };
+        let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+
+        e.expr().unwrap()
+    } else {
+        let ast::Expr::RefExpr(e) = &dummy_expr else { unreachable!() };
+        let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+
+        e.expr().unwrap()
+    };
+
+    // At this point
+    // - `parent`     is the parrent of the original expression
+    // - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
+    // - `expr`       is the clone of the original expression (with `dummy_expr` as the parent)
+
+    let needs_outer_parens = parent.map_or(false, |p| dummy_expr.needs_parens_in(p));
+    let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
+
+    (needs_outer_parens, needs_inner_parens)
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{
@@ -125,7 +225,7 @@ mod tests {
         check_with_config(
             InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
             r#"
-//- minicore: coerce_unsized
+//- minicore: coerce_unsized, fn
 fn main() {
     let _: u32         = loop {};
                        //^^^^^^^<never-to-any>
@@ -148,12 +248,16 @@ fn main() {
                        //^^^^<fn-item-to-fn-pointer>
     let _: unsafe fn() = main as fn();
                        //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
+                       //^^^^^^^^^^^^(
+                       //^^^^^^^^^^^^)
     let _: fn()        = || {};
                        //^^^^^<closure-to-fn-pointer>
     let _: unsafe fn() = || {};
                        //^^^^^<closure-to-unsafe-fn-pointer>
     let _: *const u32  = &mut 0u32 as *mut u32;
                        //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
+                       //^^^^^^^^^^^^^^^^^^^^^(
+                       //^^^^^^^^^^^^^^^^^^^^^)
     let _: &mut [_]    = &mut [0; 0];
                        //^^^^^^^^^^^<unsize>
                        //^^^^^^^^^^^&mut $
@@ -206,6 +310,11 @@ fn main() {
                                        //^^^^^^^<unsize>
                                        //^^^^^^^&mut $
                                        //^^^^^^^*
+
+    let _: &mut dyn Fn() = &mut || ();
+                         //^^^^^^^^^^<unsize>
+                         //^^^^^^^^^^&mut $
+                         //^^^^^^^^^^*
 }
 
 #[derive(Copy, Clone)]
@@ -215,13 +324,102 @@ impl Struct {
     fn by_ref(&self) {}
     fn by_ref_mut(&mut self) {}
 }
-trait Trait {}
-impl Trait for Struct {}
 "#,
         )
     }
 
     #[test]
+    fn adjustment_hints_postfix() {
+        check_with_config(
+            InlayHintsConfig {
+                adjustment_hints: AdjustmentHints::Always,
+                adjustment_hints_postfix: true,
+                ..DISABLED_CONFIG
+            },
+            r#"
+//- minicore: coerce_unsized, fn
+fn main() {
+
+    Struct.consume();
+    Struct.by_ref();
+  //^^^^^^.&
+    Struct.by_ref_mut();
+  //^^^^^^.&mut
+
+    (&Struct).consume();
+   //^^^^^^^(
+   //^^^^^^^)
+   //^^^^^^^.*
+    (&Struct).by_ref();
+
+    (&mut Struct).consume();
+   //^^^^^^^^^^^(
+   //^^^^^^^^^^^)
+   //^^^^^^^^^^^.*
+    (&mut Struct).by_ref();
+   //^^^^^^^^^^^(
+   //^^^^^^^^^^^)
+   //^^^^^^^^^^^.*
+   //^^^^^^^^^^^.&
+    (&mut Struct).by_ref_mut();
+
+    // Check that block-like expressions don't duplicate hints
+    let _: &mut [u32] = (&mut []);
+                       //^^^^^^^(
+                       //^^^^^^^)
+                       //^^^^^^^.*
+                       //^^^^^^^.&mut
+                       //^^^^^^^.<unsize>
+    let _: &mut [u32] = { &mut [] };
+                        //^^^^^^^(
+                        //^^^^^^^)
+                        //^^^^^^^.*
+                        //^^^^^^^.&mut
+                        //^^^^^^^.<unsize>
+    let _: &mut [u32] = unsafe { &mut [] };
+                               //^^^^^^^(
+                               //^^^^^^^)
+                               //^^^^^^^.*
+                               //^^^^^^^.&mut
+                               //^^^^^^^.<unsize>
+    let _: &mut [u32] = if true {
+        &mut []
+      //^^^^^^^(
+      //^^^^^^^)
+      //^^^^^^^.*
+      //^^^^^^^.&mut
+      //^^^^^^^.<unsize>
+    } else {
+        loop {}
+      //^^^^^^^.<never-to-any>
+    };
+    let _: &mut [u32] = match () { () => &mut [] }
+                                       //^^^^^^^(
+                                       //^^^^^^^)
+                                       //^^^^^^^.*
+                                       //^^^^^^^.&mut
+                                       //^^^^^^^.<unsize>
+
+    let _: &mut dyn Fn() = &mut || ();
+                         //^^^^^^^^^^(
+                         //^^^^^^^^^^)
+                         //^^^^^^^^^^.*
+                         //^^^^^^^^^^.&mut
+                         //^^^^^^^^^^.<unsize>
+}
+
+#[derive(Copy, Clone)]
+struct Struct;
+impl Struct {
+    fn consume(self) {}
+    fn by_ref(&self) {}
+    fn by_ref_mut(&mut self) {}
+}
+"#,
+        );
+    }
+
+    #[test]
     fn never_to_never_is_never_shown() {
         check_with_config(
             InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 6e31a1420ea..c6cca0d8698 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -115,6 +115,7 @@ impl StaticIndex<'_> {
                     closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
                     lifetime_elision_hints: crate::LifetimeElisionHints::Never,
                     adjustment_hints: crate::AdjustmentHints::Never,
+                    adjustment_hints_postfix: false,
                     adjustment_hints_hide_outside_unsafe: false,
                     hide_named_constructor_hints: false,
                     hide_closure_initialization_hints: false,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 0f515a6d5cb..4d11a84091c 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -333,6 +333,8 @@ config_data! {
         inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
         /// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
         inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false",
+        /// Whether to show inlay hints for type adjustments as postfix ops (`.*` instead of `*`, etc).
+        inlayHints_expressionAdjustmentHints_postfix: bool = "false",
         /// Whether to show inlay type hints for elided lifetimes in function signatures.
         inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
         /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
@@ -1252,6 +1254,7 @@ impl Config {
                 },
                 AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
             },
+            adjustment_hints_postfix: self.data.inlayHints_expressionAdjustmentHints_postfix,
             adjustment_hints_hide_outside_unsafe: self
                 .data
                 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index a12bd3952cc..e736b2ff9a3 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -452,6 +452,7 @@ pub(crate) fn inlay_hint(
             | InlayKind::ChainingHint
             | InlayKind::GenericParamListHint
             | InlayKind::ClosingParenthesis
+            | InlayKind::AdjustmentHintPostfix
             | InlayKind::LifetimeHint
             | InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()),
         },
@@ -465,6 +466,7 @@ pub(crate) fn inlay_hint(
             | InlayKind::ClosureReturnTypeHint
             | InlayKind::GenericParamListHint
             | InlayKind::AdjustmentHint
+            | InlayKind::AdjustmentHintPostfix
             | InlayKind::LifetimeHint
             | InlayKind::ParameterHint => false,
         }),
@@ -475,6 +477,7 @@ pub(crate) fn inlay_hint(
             | InlayKind::ClosureReturnTypeHint
             | InlayKind::GenericParamListHint
             | InlayKind::AdjustmentHint
+            | InlayKind::AdjustmentHintPostfix
             | InlayKind::TypeHint
             | InlayKind::DiscriminantHint
             | InlayKind::ClosingBraceHint => false,
@@ -493,6 +496,7 @@ pub(crate) fn inlay_hint(
             | InlayKind::GenericParamListHint
             | InlayKind::LifetimeHint
             | InlayKind::AdjustmentHint
+            | InlayKind::AdjustmentHintPostfix
             | InlayKind::ClosingBraceHint => None,
         },
         text_edits: None,
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index d9794e7052a..60c16ecadf5 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -469,6 +469,11 @@ Whether to show inlay hints for type adjustments.
 --
 Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
 --
+[[rust-analyzer.inlayHints.expressionAdjustmentHints.postfix]]rust-analyzer.inlayHints.expressionAdjustmentHints.postfix (default: `false`)::
++
+--
+Whether to show inlay hints for type adjustments as postfix ops (`.*` instead of `*`, etc).
+--
 [[rust-analyzer.inlayHints.lifetimeElisionHints.enable]]rust-analyzer.inlayHints.lifetimeElisionHints.enable (default: `"never"`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index f508dde4f60..aeb1d97c5f6 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1000,6 +1000,11 @@
                     "default": false,
                     "type": "boolean"
                 },
+                "rust-analyzer.inlayHints.expressionAdjustmentHints.postfix": {
+                    "markdownDescription": "Whether to show inlay hints for type adjustments as postfix ops (`.*` instead of `*`, etc).",
+                    "default": false,
+                    "type": "boolean"
+                },
                 "rust-analyzer.inlayHints.lifetimeElisionHints.enable": {
                     "markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
                     "default": "never",