1 //! Implementation of "adjustment" inlay hints:
3 //! let _: u32 = /* <never-to-any> */ loop {};
4 //! let _: &u32 = /* &* */ &mut 0;
6 use hir
::{Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, PointerCast, Safety, Semantics}
;
7 use ide_db
::RootDatabase
;
11 ast
::{self, make, AstNode}
,
16 AdjustmentHints
, AdjustmentHintsMode
, InlayHint
, InlayHintLabel
, InlayHintsConfig
, InlayKind
,
21 acc
: &mut Vec
<InlayHint
>,
22 sema
: &Semantics
<'_
, RootDatabase
>,
23 config
: &InlayHintsConfig
,
26 if config
.adjustment_hints_hide_outside_unsafe
&& !sema
.is_inside_unsafe(expr
) {
30 if config
.adjustment_hints
== AdjustmentHints
::Never
{
34 // ParenExpr resolve to their contained expressions HIR so they will dupe these hints
35 if let ast
::Expr
::ParenExpr(_
) = expr
{
38 if let ast
::Expr
::BlockExpr(b
) = expr
{
39 if !b
.is_standalone() {
44 let descended
= sema
.descend_node_into_attributes(expr
.clone()).pop();
45 let desc_expr
= descended
.as_ref().unwrap_or(expr
);
46 let adjustments
= sema
.expr_adjustments(desc_expr
).filter(|it
| !it
.is_empty())?
;
48 if let ast
::Expr
::BlockExpr(_
) | ast
::Expr
::IfExpr(_
) | ast
::Expr
::MatchExpr(_
) = desc_expr
{
49 if let [Adjustment { kind: Adjust::Deref(_), source, .. }
, Adjustment { kind: Adjust::Borrow(_), source: _, target }
] =
52 // Don't show unnecessary reborrows for these, they will just repeat the inner ones again
59 let (postfix
, needs_outer_parens
, needs_inner_parens
) =
60 mode_and_needs_parens_for_adjustment_hints(expr
, config
.adjustment_hints_mode
);
62 if needs_outer_parens
{
63 acc
.push(InlayHint
::opening_paren(expr
.syntax().text_range()));
66 if postfix
&& needs_inner_parens
{
67 acc
.push(InlayHint
::opening_paren(expr
.syntax().text_range()));
68 acc
.push(InlayHint
::closing_paren(expr
.syntax().text_range()));
71 let (mut tmp0
, mut tmp1
);
72 let iter
: &mut dyn Iterator
<Item
= _
> = if postfix
{
73 tmp0
= adjustments
.into_iter();
76 tmp1
= adjustments
.into_iter().rev();
80 for Adjustment { source, target, kind }
in iter
{
82 cov_mark
::hit
!(same_type_adjustment
);
86 // FIXME: Add some nicer tooltips to each of these
87 let (text
, coercion
) = match kind
{
88 Adjust
::NeverToAny
if config
.adjustment_hints
== AdjustmentHints
::Always
=> {
89 ("<never-to-any>", "never to any")
91 Adjust
::Deref(_
) => ("*", "dereference"),
92 Adjust
::Borrow(AutoBorrow
::Ref(Mutability
::Shared
)) => ("&", "borrow"),
93 Adjust
::Borrow(AutoBorrow
::Ref(Mutability
::Mut
)) => ("&mut ", "unique borrow"),
94 Adjust
::Borrow(AutoBorrow
::RawPtr(Mutability
::Shared
)) => {
95 ("&raw const ", "const pointer borrow")
97 Adjust
::Borrow(AutoBorrow
::RawPtr(Mutability
::Mut
)) => {
98 ("&raw mut ", "mut pointer borrow")
100 // some of these could be represented via `as` casts, but that's not too nice and
101 // handling everything as a prefix expr makes the `(` and `)` insertion easier
102 Adjust
::Pointer(cast
) if config
.adjustment_hints
== AdjustmentHints
::Always
=> {
104 PointerCast
::ReifyFnPointer
=> {
105 ("<fn-item-to-fn-pointer>", "fn item to fn pointer")
107 PointerCast
::UnsafeFnPointer
=> (
108 "<safe-fn-pointer-to-unsafe-fn-pointer>",
109 "safe fn pointer to unsafe fn pointer",
111 PointerCast
::ClosureFnPointer(Safety
::Unsafe
) => {
112 ("<closure-to-unsafe-fn-pointer>", "closure to unsafe fn pointer")
114 PointerCast
::ClosureFnPointer(Safety
::Safe
) => {
115 ("<closure-to-fn-pointer>", "closure to fn pointer")
117 PointerCast
::MutToConstPointer
=> {
118 ("<mut-ptr-to-const-ptr>", "mut ptr to const ptr")
120 PointerCast
::ArrayToPointer
=> ("<array-ptr-to-element-ptr>", ""),
121 PointerCast
::Unsize
=> ("<unsize>", "unsize"),
127 range
: expr
.syntax().text_range(),
128 kind
: if postfix { InlayKind::AdjustmentPostfix }
else { InlayKind::Adjustment }
,
129 label
: InlayHintLabel
::simple(
130 if postfix { format!(".{}
", text.trim_end()) } else { text.to_owned() },
131 Some(InlayTooltip::Markdown(format!(
132 "`{}` → `{}`
({coercion} coercion
)",
133 source.display(sema.db),
134 target.display(sema.db),
140 if !postfix && needs_inner_parens {
141 acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
142 acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
144 if needs_outer_parens {
145 acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
150 /// Returns whatever the hint should be postfix and if we need to add paretheses on the inside and/or outside of `expr`,
151 /// if we are going to add (`postfix`) adjustments hints to it.
152 fn mode_and_needs_parens_for_adjustment_hints(
154 mode: AdjustmentHintsMode,
155 ) -> (bool, bool, bool) {
156 use {std::cmp::Ordering::*, AdjustmentHintsMode::*};
159 Prefix | Postfix => {
160 let postfix = matches!(mode, Postfix);
161 let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix);
162 (postfix, inside, outside)
164 PreferPrefix | PreferPostfix => {
165 let prefer_postfix = matches!(mode, PreferPostfix);
167 let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false);
168 let prefix = (false, pre_inside, pre_outside);
169 let pre_count = pre_inside as u8 + pre_outside as u8;
171 let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true);
172 let postfix = (true, post_inside, post_outside);
173 let post_count = post_inside as u8 + post_outside as u8;
175 match pre_count.cmp(&post_count) {
178 Equal if prefer_postfix => postfix,
185 /// Returns whatever we need to add paretheses on the inside and/or outside of `expr`,
186 /// if we are going to add (`postfix`) adjustments hints to it.
187 fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
188 // This is a very miserable pile of hacks...
190 // `Expr::needs_parens_in` requires that the expression is the child of the other expression,
191 // that is supposed to be its parent.
193 // But we want to check what would happen if we add `*`/`.*` to the inner expression.
194 // To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
195 // to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
196 // where "expr
" is the `expr` parameter, `*expr` is the editted `expr`,
197 // and "parent
" is the parent of the original expression...
199 // For this we utilize mutable mutable trees, which is a HACK, but it works.
201 // FIXME: comeup with a better API for `needs_parens_in`, so that we don't have to do *this*
203 // Make `&expr`/`expr?`
205 // `make::*` function go through a string, so they parse wrongly.
206 // for example `` make::expr_try(`|| a`) `` would result in a
207 // `|| (a?)` and not `(|| a)?`.
209 // Thus we need dummy parens to preserve the relationship we want.
210 // The parens are then simply ignored by the following code.
211 let dummy_paren = make::expr_paren(expr.clone());
213 make::expr_try(dummy_paren)
215 make::expr_ref(dummy_paren, false)
219 // Do the dark mutable tree magic.
220 // This essentially makes `dummy_expr` and `expr` switch places (families),
221 // so that `expr`'s parent is not `dummy_expr`'s parent.
222 let dummy_expr = dummy_expr.clone_for_update();
223 let expr = expr.clone_for_update();
224 ted::replace(expr.syntax(), dummy_expr.syntax());
226 let parent = dummy_expr.syntax().parent();
227 let Some(expr) = (|| {
229 let ast::Expr::TryExpr(e) = &dummy_expr else { return None };
230 let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
234 let ast::Expr::RefExpr(e) = &dummy_expr else { return None };
235 let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
240 never!("broken syntax tree?
\n{:?}
\n{:?}
", expr, dummy_expr);
245 // - `parent` is the parrent of the original expression
246 // - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
247 // - `expr` is the clone of the original expression (with `dummy_expr` as the parent)
249 let needs_outer_parens = parent.map_or(false, |p| dummy_expr.needs_parens_in(p));
250 let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
252 (needs_outer_parens, needs_inner_parens)
258 inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
259 AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
263 fn adjustment_hints() {
265 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
267 //- minicore: coerce_unsized, fn, eq
269 let _
: u32 = loop {}
;
270 //^^^^^^^<never-to-any>
271 let _
: &u32 = &mut 0;
274 let _
: &mut u32 = &mut 0;
277 let _
: *const u32 = &mut 0;
280 let _
: *mut u32 = &mut 0;
284 //^^^^<fn-item-to-fn-pointer>
285 let _
: unsafe fn() = main
;
286 //^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
287 //^^^^<fn-item-to-fn-pointer>
288 let _
: unsafe fn() = main
as fn();
289 //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
293 //^^^^^<closure-to-fn-pointer>
294 let _
: unsafe fn() = || {}
;
295 //^^^^^<closure-to-unsafe-fn-pointer>
296 let _
: *const u32 = &mut 0u32 as *mut u32;
297 //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
298 //^^^^^^^^^^^^^^^^^^^^^(
299 //^^^^^^^^^^^^^^^^^^^^^)
300 let _
: &mut [_
] = &mut [0; 0];
301 //^^^^^^^^^^^<unsize>
319 (&mut Struct
).consume();
321 (&mut Struct
).by_ref();
324 (&mut Struct
).by_ref_mut();
326 // Check that block-like expressions don't duplicate hints
327 let _
: &mut [u32] = (&mut []);
331 let _
: &mut [u32] = { &mut [] }
;
335 let _
: &mut [u32] = unsafe { &mut [] }
;
339 let _
: &mut [u32] = if true {
346 //^^^^^^^<never-to-any>
348 let _
: &mut [u32] = match () { () => &mut [] }
;
353 let _
: &mut dyn Fn() = &mut || ();
365 #[derive(Copy, Clone)]
370 fn by_ref_mut(&mut self) {}
377 fn adjustment_hints_postfix() {
380 adjustment_hints: AdjustmentHints::Always,
381 adjustment_hints_mode: AdjustmentHintsMode::Postfix,
385 //- minicore: coerce_unsized, fn, eq
400 (&mut Struct
).consume();
404 (&mut Struct
).by_ref();
409 (&mut Struct
).by_ref_mut();
411 // Check that block-like expressions don't duplicate hints
412 let _
: &mut [u32] = (&mut []);
418 let _
: &mut [u32] = { &mut [] }
;
424 let _
: &mut [u32] = unsafe { &mut [] }
;
430 let _
: &mut [u32] = if true {
439 //^^^^^^^.<never-to-any>
441 let _
: &mut [u32] = match () { () => &mut [] }
;
448 let _
: &mut dyn Fn() = &mut || ();
453 //^^^^^^^^^^.<unsize>
462 #[derive(Copy, Clone)]
467 fn by_ref_mut(&mut self) {}
474 fn adjustment_hints_prefer_prefix() {
477 adjustment_hints: AdjustmentHints::Always,
478 adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix,
483 let _
: u32 = loop {}
;
484 //^^^^^^^<never-to-any>
489 let (): () = return ();
490 //^^^^^^^^^<never-to-any>
493 impl Struct { fn by_ref(&self) {}
}
500 fn adjustment_hints_prefer_postfix() {
503 adjustment_hints: AdjustmentHints::Always,
504 adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix,
509 let _
: u32 = loop {}
;
510 //^^^^^^^.<never-to-any>
515 let (): () = return ();
516 //^^^^^^^^^<never-to-any>
519 impl Struct { fn by_ref(&self) {}
}
526 fn never_to_never_is_never_shown() {
527 cov_mark::check!(same_type_adjustment);
529 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
536 let () = () else { return }
;
543 fn adjustment_hints_unsafe_only() {
546 adjustment_hints: AdjustmentHints::Always,
547 adjustment_hints_hide_outside_unsafe: true,
551 unsafe fn enabled() {
584 static STATIC
: () = {
596 Disable
= { f(&&()); 0 }
,
597 Enable
= unsafe { f(&&()); 1 }
,
603 const fn f(_
: &()) {}
609 fn adjustment_hints_unsafe_only_with_item() {
612 adjustment_hints: AdjustmentHints::Always,
613 adjustment_hints_hide_outside_unsafe: true,
625 _
= unsafe { Struct.by_ref() }
;
635 fn let_stmt_explicit_ty() {
637 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
641 //^^^^^^<never-to-any>
643 //^^^^^^<never-to-any>