Unnamed repository; edit this file 'description' to name the repository.
Implement postfix adjustment hints
I'd say "First stab at implementing..." but I've been working on this for a month already lol
Maybe Waffle 2023-01-09
parent ae65912 · commit b89c4f0
-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 86d25e2f5a..7315a37ebc 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 1c13f31cf2..367bd2f661 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 6e31a1420e..c6cca0d869 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 0f515a6d5c..4d11a84091 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 a12bd3952c..e736b2ff9a 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 d9794e7052..60c16ecadf 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 f508dde4f6..aeb1d97c5f 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",