| //! Implementation of "adjustment" inlay hints: |
| //! ```no_run |
| //! let _: u32 = /* <never-to-any> */ loop {}; |
| //! let _: &u32 = /* &* */ &mut 0; |
| //! ``` |
| use either::Either; |
| use hir::{ |
| Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, OverloadedDeref, PointerCast, Safety, |
| Semantics, |
| }; |
| use ide_db::RootDatabase; |
| |
| use stdx::never; |
| use syntax::{ |
| ast::{self, make, AstNode}, |
| ted, |
| }; |
| |
| use crate::{ |
| AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintPosition, |
| InlayHintsConfig, InlayKind, InlayTooltip, |
| }; |
| |
| pub(super) fn hints( |
| acc: &mut Vec<InlayHint>, |
| sema: &Semantics<'_, RootDatabase>, |
| config: &InlayHintsConfig, |
| expr: &ast::Expr, |
| ) -> Option<()> { |
| if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) { |
| return None; |
| } |
| |
| if config.adjustment_hints == AdjustmentHints::Never { |
| return None; |
| } |
| |
| // ParenExpr resolve to their contained expressions HIR so they will dupe these hints |
| if let ast::Expr::ParenExpr(_) = expr { |
| return None; |
| } |
| if let ast::Expr::BlockExpr(b) = expr { |
| if !b.is_standalone() { |
| return None; |
| } |
| } |
| |
| 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())?; |
| |
| if let ast::Expr::BlockExpr(_) | ast::Expr::IfExpr(_) | ast::Expr::MatchExpr(_) = desc_expr { |
| if let [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), source: _, target }] = |
| &*adjustments |
| { |
| // Don't show unnecessary reborrows for these, they will just repeat the inner ones again |
| if source == target { |
| return None; |
| } |
| } |
| } |
| |
| let (postfix, needs_outer_parens, needs_inner_parens) = |
| mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode); |
| |
| if needs_outer_parens { |
| acc.push(InlayHint::opening_paren_before( |
| InlayKind::Adjustment, |
| expr.syntax().text_range(), |
| )); |
| } |
| |
| if postfix && needs_inner_parens { |
| acc.push(InlayHint::opening_paren_before( |
| InlayKind::Adjustment, |
| expr.syntax().text_range(), |
| )); |
| acc.push(InlayHint::closing_paren_after(InlayKind::Adjustment, expr.syntax().text_range())); |
| } |
| |
| let mut iter = if postfix { |
| Either::Left(adjustments.into_iter()) |
| } else { |
| Either::Right(adjustments.into_iter().rev()) |
| }; |
| let iter: &mut dyn Iterator<Item = _> = iter.as_mut().either(|it| it as _, |it| it as _); |
| |
| for Adjustment { source, target, kind } in iter { |
| if source == target { |
| cov_mark::hit!(same_type_adjustment); |
| continue; |
| } |
| |
| // FIXME: Add some nicer tooltips to each of these |
| let (text, coercion) = match kind { |
| Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => { |
| ("<never-to-any>", "never to any") |
| } |
| Adjust::Deref(None) => ("*", "dereference"), |
| Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => { |
| ("*", "`Deref` dereference") |
| } |
| Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => { |
| ("*", "`DerefMut` dereference") |
| } |
| Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => ("&", "borrow"), |
| Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => ("&mut ", "unique borrow"), |
| Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => { |
| ("&raw const ", "const pointer borrow") |
| } |
| Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => { |
| ("&raw mut ", "mut pointer borrow") |
| } |
| // some of these could be represented via `as` casts, but that's not too nice and |
| // handling everything as a prefix expr makes the `(` and `)` insertion easier |
| Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => { |
| match cast { |
| PointerCast::ReifyFnPointer => { |
| ("<fn-item-to-fn-pointer>", "fn item to fn pointer") |
| } |
| PointerCast::UnsafeFnPointer => ( |
| "<safe-fn-pointer-to-unsafe-fn-pointer>", |
| "safe fn pointer to unsafe fn pointer", |
| ), |
| PointerCast::ClosureFnPointer(Safety::Unsafe) => { |
| ("<closure-to-unsafe-fn-pointer>", "closure to unsafe fn pointer") |
| } |
| PointerCast::ClosureFnPointer(Safety::Safe) => { |
| ("<closure-to-fn-pointer>", "closure to fn pointer") |
| } |
| PointerCast::MutToConstPointer => { |
| ("<mut-ptr-to-const-ptr>", "mut ptr to const ptr") |
| } |
| PointerCast::ArrayToPointer => ("<array-ptr-to-element-ptr>", ""), |
| PointerCast::Unsize => ("<unsize>", "unsize"), |
| } |
| } |
| _ => continue, |
| }; |
| acc.push(InlayHint { |
| range: expr.syntax().text_range(), |
| pad_left: false, |
| pad_right: false, |
| position: if postfix { InlayHintPosition::After } else { InlayHintPosition::Before }, |
| kind: InlayKind::Adjustment, |
| label: InlayHintLabel::simple( |
| if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() }, |
| Some(InlayTooltip::Markdown(format!( |
| "`{}` → `{}` ({coercion} coercion)", |
| source.display(sema.db), |
| target.display(sema.db), |
| ))), |
| None, |
| ), |
| text_edit: None, |
| }); |
| } |
| if !postfix && needs_inner_parens { |
| acc.push(InlayHint::opening_paren_before( |
| InlayKind::Adjustment, |
| expr.syntax().text_range(), |
| )); |
| acc.push(InlayHint::closing_paren_after(InlayKind::Adjustment, expr.syntax().text_range())); |
| } |
| if needs_outer_parens { |
| acc.push(InlayHint::closing_paren_after(InlayKind::Adjustment, expr.syntax().text_range())); |
| } |
| Some(()) |
| } |
| |
| /// Returns whatever the hint should be postfix and if we need to add parentheses on the inside and/or outside of `expr`, |
| /// if we are going to add (`postfix`) adjustments hints to it. |
| fn mode_and_needs_parens_for_adjustment_hints( |
| expr: &ast::Expr, |
| mode: AdjustmentHintsMode, |
| ) -> (bool, bool, bool) { |
| use {std::cmp::Ordering::*, AdjustmentHintsMode::*}; |
| |
| match mode { |
| Prefix | Postfix => { |
| let postfix = matches!(mode, Postfix); |
| let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix); |
| (postfix, inside, outside) |
| } |
| PreferPrefix | PreferPostfix => { |
| let prefer_postfix = matches!(mode, PreferPostfix); |
| |
| let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false); |
| let prefix = (false, pre_inside, pre_outside); |
| let pre_count = pre_inside as u8 + pre_outside as u8; |
| |
| let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true); |
| let postfix = (true, post_inside, post_outside); |
| let post_count = post_inside as u8 + post_outside as u8; |
| |
| match pre_count.cmp(&post_count) { |
| Less => prefix, |
| Greater => postfix, |
| Equal if prefer_postfix => postfix, |
| Equal => prefix, |
| } |
| } |
| } |
| } |
| |
| /// Returns whatever we need to add parentheses 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 edited `expr`, |
| // and "parent" is the parent of the original expression... |
| // |
| // For this we utilize mutable trees, which is a HACK, but it works. |
| // |
| // FIXME: comeup with a better API for `needs_parens_in`, so that we don't have to do *this* |
| |
| // 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 Some(expr) = (|| { |
| if postfix { |
| let ast::Expr::TryExpr(e) = &dummy_expr else { return None }; |
| let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None }; |
| |
| e.expr() |
| } else { |
| let ast::Expr::RefExpr(e) = &dummy_expr else { return None }; |
| let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None }; |
| |
| e.expr() |
| } |
| })() else { |
| never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr); |
| return (true, true); |
| }; |
| |
| // At this point |
| // - `parent` is the parent 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::{ |
| inlay_hints::tests::{check_with_config, DISABLED_CONFIG}, |
| AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig, |
| }; |
| |
| #[test] |
| fn adjustment_hints() { |
| check_with_config( |
| InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, |
| r#" |
| //- minicore: coerce_unsized, fn, eq, index |
| fn main() { |
| let _: u32 = loop {}; |
| //^^^^^^^<never-to-any> |
| let _: &u32 = &mut 0; |
| //^^^^^^& |
| //^^^^^^* |
| let _: &mut u32 = &mut 0; |
| //^^^^^^&mut $ |
| //^^^^^^* |
| let _: *const u32 = &mut 0; |
| //^^^^^^&raw const $ |
| //^^^^^^* |
| let _: *mut u32 = &mut 0; |
| //^^^^^^&raw mut $ |
| //^^^^^^* |
| let _: fn() = main; |
| //^^^^<fn-item-to-fn-pointer> |
| let _: unsafe fn() = main; |
| //^^^^<safe-fn-pointer-to-unsafe-fn-pointer> |
| //^^^^<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 $ |
| //^^^^^^^^^^^* |
| |
| 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(); |
| //^^^^^^^^^^^&mut $ |
| //^^^^^^^^^^^* |
| |
| // Check that block-like expressions don't duplicate hints |
| let _: &mut [u32] = (&mut []); |
| //^^^^^^^<unsize> |
| //^^^^^^^&mut $ |
| //^^^^^^^* |
| let _: &mut [u32] = { &mut [] }; |
| //^^^^^^^<unsize> |
| //^^^^^^^&mut $ |
| //^^^^^^^* |
| let _: &mut [u32] = unsafe { &mut [] }; |
| //^^^^^^^<unsize> |
| //^^^^^^^&mut $ |
| //^^^^^^^* |
| let _: &mut [u32] = if true { |
| &mut [] |
| //^^^^^^^<unsize> |
| //^^^^^^^&mut $ |
| //^^^^^^^* |
| } else { |
| loop {} |
| //^^^^^^^<never-to-any> |
| }; |
| let _: &mut [u32] = match () { () => &mut [] }; |
| //^^^^^^^<unsize> |
| //^^^^^^^&mut $ |
| //^^^^^^^* |
| |
| let _: &mut dyn Fn() = &mut || (); |
| //^^^^^^^^^^<unsize> |
| //^^^^^^^^^^&mut $ |
| //^^^^^^^^^^* |
| () == (); |
| // ^^& |
| // ^^& |
| (()) == {()}; |
| // ^^& |
| // ^^^^& |
| let closure: dyn Fn = || (); |
| closure(); |
| //^^^^^^^( |
| //^^^^^^^& |
| //^^^^^^^) |
| Struct[0]; |
| //^^^^^^( |
| //^^^^^^& |
| //^^^^^^) |
| &mut Struct[0]; |
| //^^^^^^( |
| //^^^^^^&mut $ |
| //^^^^^^) |
| } |
| |
| #[derive(Copy, Clone)] |
| struct Struct; |
| impl Struct { |
| fn consume(self) {} |
| fn by_ref(&self) {} |
| fn by_ref_mut(&mut self) {} |
| } |
| struct StructMut; |
| impl core::ops::Index<usize> for Struct { |
| type Output = (); |
| } |
| impl core::ops::IndexMut for Struct {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn adjustment_hints_postfix() { |
| check_with_config( |
| InlayHintsConfig { |
| adjustment_hints: AdjustmentHints::Always, |
| adjustment_hints_mode: AdjustmentHintsMode::Postfix, |
| ..DISABLED_CONFIG |
| }, |
| r#" |
| //- minicore: coerce_unsized, fn, eq, index |
| 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(); |
| //^^^^^^^^^^^( |
| //^^^^^^^^^^^) |
| //^^^^^^^^^^^.* |
| //^^^^^^^^^^^.&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> |
| () == (); |
| // ^^.& |
| // ^^.& |
| (()) == {()}; |
| // ^^.& |
| // ^^^^.& |
| let closure: dyn Fn = || (); |
| closure(); |
| //^^^^^^^.& |
| Struct[0]; |
| //^^^^^^.& |
| &mut Struct[0]; |
| //^^^^^^.&mut |
| } |
| |
| #[derive(Copy, Clone)] |
| struct Struct; |
| impl Struct { |
| fn consume(self) {} |
| fn by_ref(&self) {} |
| fn by_ref_mut(&mut self) {} |
| } |
| struct StructMut; |
| impl core::ops::Index<usize> for Struct { |
| type Output = (); |
| } |
| impl core::ops::IndexMut for Struct {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn adjustment_hints_prefer_prefix() { |
| check_with_config( |
| InlayHintsConfig { |
| adjustment_hints: AdjustmentHints::Always, |
| adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix, |
| ..DISABLED_CONFIG |
| }, |
| r#" |
| fn main() { |
| let _: u32 = loop {}; |
| //^^^^^^^<never-to-any> |
| |
| Struct.by_ref(); |
| //^^^^^^.& |
| |
| let (): () = return (); |
| //^^^^^^^^^<never-to-any> |
| |
| struct Struct; |
| impl Struct { fn by_ref(&self) {} } |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn adjustment_hints_prefer_postfix() { |
| check_with_config( |
| InlayHintsConfig { |
| adjustment_hints: AdjustmentHints::Always, |
| adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix, |
| ..DISABLED_CONFIG |
| }, |
| r#" |
| fn main() { |
| let _: u32 = loop {}; |
| //^^^^^^^.<never-to-any> |
| |
| Struct.by_ref(); |
| //^^^^^^.& |
| |
| let (): () = return (); |
| //^^^^^^^^^<never-to-any> |
| |
| struct Struct; |
| impl Struct { fn by_ref(&self) {} } |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn never_to_never_is_never_shown() { |
| cov_mark::check!(same_type_adjustment); |
| check_with_config( |
| InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, |
| r#" |
| fn never() -> ! { |
| return loop {}; |
| } |
| |
| fn or_else() { |
| let () = () else { return }; |
| } |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn adjustment_hints_unsafe_only() { |
| check_with_config( |
| InlayHintsConfig { |
| adjustment_hints: AdjustmentHints::Always, |
| adjustment_hints_hide_outside_unsafe: true, |
| ..DISABLED_CONFIG |
| }, |
| r#" |
| unsafe fn enabled() { |
| f(&&()); |
| //^^^^& |
| //^^^^* |
| //^^^^* |
| } |
| |
| fn disabled() { |
| f(&&()); |
| } |
| |
| fn mixed() { |
| f(&&()); |
| |
| unsafe { |
| f(&&()); |
| //^^^^& |
| //^^^^* |
| //^^^^* |
| } |
| } |
| |
| const _: () = { |
| f(&&()); |
| |
| unsafe { |
| f(&&()); |
| //^^^^& |
| //^^^^* |
| //^^^^* |
| } |
| }; |
| |
| static STATIC: () = { |
| f(&&()); |
| |
| unsafe { |
| f(&&()); |
| //^^^^& |
| //^^^^* |
| //^^^^* |
| } |
| }; |
| |
| enum E { |
| Disable = { f(&&()); 0 }, |
| Enable = unsafe { f(&&()); 1 }, |
| //^^^^& |
| //^^^^* |
| //^^^^* |
| } |
| |
| const fn f(_: &()) {} |
| "#, |
| ) |
| } |
| |
| #[test] |
| fn adjustment_hints_unsafe_only_with_item() { |
| check_with_config( |
| InlayHintsConfig { |
| adjustment_hints: AdjustmentHints::Always, |
| adjustment_hints_hide_outside_unsafe: true, |
| ..DISABLED_CONFIG |
| }, |
| r#" |
| fn a() { |
| struct Struct; |
| impl Struct { |
| fn by_ref(&self) {} |
| } |
| |
| _ = Struct.by_ref(); |
| |
| _ = unsafe { Struct.by_ref() }; |
| //^^^^^^( |
| //^^^^^^& |
| //^^^^^^) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn let_stmt_explicit_ty() { |
| check_with_config( |
| InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, |
| r#" |
| fn main() { |
| let () = return; |
| //^^^^^^<never-to-any> |
| let (): () = return; |
| //^^^^^^<never-to-any> |
| } |
| "#, |
| ) |
| } |
| } |