Implement postfix adjustment hints

I'd say "First stab at implementing..." but I've been working on this
for a month already lol
This commit is contained in:
Maybe Waffle 2022-12-20 22:07:00 +00:00
parent ae659125a5
commit b89c4f0a05
7 changed files with 246 additions and 27 deletions

View file

@ -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,

View file

@ -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,12 +324,101 @@ 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(

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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"`)::
+
--

View file

@ -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",