mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
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:
parent
ae659125a5
commit
b89c4f0a05
7 changed files with 246 additions and 27 deletions
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"`)::
|
||||
+
|
||||
--
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue