Repository: rust-lang/rust-analyzer
Pull request #13825 (2022-12-22 18:55:59)

feat: Add `unqualify_method_call` assist
...which is the inverse of `qualify_method_call` assist.

![Peek 2022-12-22 22-47](https://user-images.githubusercontent.com/38225716/209206554-8f067206-6fa6-48f8-849e-f6d36ee2e5a1.gif)

Optional future work:
- import the trait if needed
- remove excess references when auto-ref is possible

flodiebold: I think the acronym "UFCS" is a bit unfriendly, maybe something like "Convert to `.add` method call"? (Also btw, why is "Qualify method call" under "Inline"?)
Veykril: +1 to `UFCS` being a bad choice for the user visible text

> Also btw, why is "Qualify method call" under "Inline"?

Probably because both target the same range and our assists are ordered alphabetically by default (with a few exceptions, where this would probably be a good choice to do so as well)
flodiebold: > Probably because both target the same range and our assists are ordered alphabetically by default (with a few exceptions, where this would probably be a good choice to do so as well)

I didn't mean the order, rather that it's under the `Inline...` group (whereas the new assist is `Rewrite...`) :sweat_smile: 

Patch:
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
index c8d0493d09..c2c5bf3aab 100644
--- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs
+++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
@@ -104,11 +104,9 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
                     make::name_ref(&method_name.to_string()),
                     arg_list,
                 );
+                let body = make::block_expr([], Some(tail_expr));
                 let ret_type = method_source.ret_type();
                 let is_async = method_source.async_token().is_some();
-                let tail_expr_finished =
-                    if is_async { make::expr_await(tail_expr) } else { tail_expr };
-                let body = make::block_expr([], Some(tail_expr_finished));
                 let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async)
                     .indent(ast::edit::IndentLevel(1))
                     .clone_for_update();
@@ -308,7 +306,7 @@ struct Person<T> {
 
 impl<T> Person<T> {
     $0pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
-        self.age.age(ty, arg).await
+        self.age.age(ty, arg)
     }
 }"#,
         );
diff --git a/crates/ide-assists/src/handlers/unqualify_method_call.rs b/crates/ide-assists/src/handlers/unqualify_method_call.rs
new file mode 100644
index 0000000000..e9d4e270cd
--- /dev/null
+++ b/crates/ide-assists/src/handlers/unqualify_method_call.rs
@@ -0,0 +1,211 @@
+use syntax::{
+    ast::{self, make, AstNode, HasArgList},
+    TextRange,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: unqualify_method_call
+//
+// Transforms universal function call syntax into a method call.
+//
+// ```
+// fn main() {
+//     std::ops::Add::add$0(1, 2);
+// }
+// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
+// ```
+// ->
+// ```
+// fn main() {
+//     1.add(2);
+// }
+// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
+// ```
+pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let call = ctx.find_node_at_offset::<ast::CallExpr>()?;
+    let ast::Expr::PathExpr(path_expr) = call.expr()? else { return None };
+    let path = path_expr.path()?;
+
+    let cursor_in_range = path.syntax().text_range().contains_range(ctx.selection_trimmed());
+    if !cursor_in_range {
+        return None;
+    }
+
+    let args = call.arg_list()?;
+    let l_paren = args.l_paren_token()?;
+    let mut args_iter = args.args();
+    let first_arg = args_iter.next()?;
+    let second_arg = args_iter.next();
+
+    _ = path.qualifier()?;
+    let method_name = path.segment()?.name_ref()?;
+
+    let res = ctx.sema.resolve_path(&path)?;
+    let hir::PathResolution::Def(hir::ModuleDef::Function(fun)) = res else { return None };
+    if !fun.has_self_param(ctx.sema.db) {
+        return None;
+    }
+
+    // `core::ops::Add::add(` -> ``
+    let delete_path =
+        TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end());
+
+    // Parens around `expr` if needed
+    let parens = needs_parens_as_receiver(&first_arg).then(|| {
+        let range = first_arg.syntax().text_range();
+        (range.start(), range.end())
+    });
+
+    // `, ` -> `.add(`
+    let replace_comma = TextRange::new(
+        first_arg.syntax().text_range().end(),
+        second_arg
+            .map(|a| a.syntax().text_range().start())
+            .unwrap_or_else(|| first_arg.syntax().text_range().end()),
+    );
+
+    acc.add(
+        AssistId("unqualify_method_call", AssistKind::RefactorRewrite),
+        "Unqualify method call",
+        call.syntax().text_range(),
+        |edit| {
+            edit.delete(delete_path);
+            if let Some((open, close)) = parens {
+                edit.insert(open, "(");
+                edit.insert(close, ")");
+            }
+            edit.replace(replace_comma, format!(".{method_name}("));
+        },
+    )
+}
+
+fn needs_parens_as_receiver(expr: &ast::Expr) -> bool {
+    // Make `(expr).dummy()`
+    let dummy_call = make::expr_method_call(
+        make::expr_paren(expr.clone()),
+        make::name_ref("dummy"),
+        make::arg_list([]),
+    );
+
+    // Get the `expr` clone with the right parent back
+    // (unreachable!s are fine since we've just constructed the expression)
+    let ast::Expr::MethodCallExpr(call) = &dummy_call else { unreachable!() };
+    let Some(receiver) = call.receiver() else { unreachable!() };
+    let ast::Expr::ParenExpr(parens) = receiver else { unreachable!() };
+    let Some(expr) = parens.expr() else { unreachable!() };
+
+    expr.needs_parens_in(dummy_call.syntax().clone())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn unqualify_method_call_simple() {
+        check_assist(
+            unqualify_method_call,
+            r#"
+struct S;
+impl S { fn f(self, S: S) {} }
+fn f() { S::$0f(S, S); }"#,
+            r#"
+struct S;
+impl S { fn f(self, S: S) {} }
+fn f() { S.f(S); }"#,
+        );
+    }
+
+    #[test]
+    fn unqualify_method_call_trait() {
+        check_assist(
+            unqualify_method_call,
+            r#"
+//- minicore: add
+fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#,
+            r#"
+fn f() { 2.add(2); }"#,
+        );
+
+        check_assist(
+            unqualify_method_call,
+            r#"
+//- minicore: add
+fn f() { core::ops::Add::$0add(2, 2); }"#,
+            r#"
+fn f() { 2.add(2); }"#,
+        );
+
+        check_assist(
+            unqualify_method_call,
+            r#"
+//- minicore: add
+use core::ops::Add;
+fn f() { <_>::$0add(2, 2); }"#,
+            r#"
+use core::ops::Add;
+fn f() { 2.add(2); }"#,
+        );
+    }
+
+    #[test]
+    fn unqualify_method_call_single_arg() {
+        check_assist(
+            unqualify_method_call,
+            r#"
+        struct S;
+        impl S { fn f(self) {} }
+        fn f() { S::$0f(S); }"#,
+            r#"
+        struct S;
+        impl S { fn f(self) {} }
+        fn f() { S.f(); }"#,
+        );
+    }
+
+    #[test]
+    fn unqualify_method_call_parens() {
+        check_assist(
+            unqualify_method_call,
+            r#"
+//- minicore: deref
+struct S;
+impl core::ops::Deref for S {
+    type Target = S;
+    fn deref(&self) -> &S { self }
+}
+fn f() { core::ops::Deref::$0deref(&S); }"#,
+            r#"
+struct S;
+impl core::ops::Deref for S {
+    type Target = S;
+    fn deref(&self) -> &S { self }
+}
+fn f() { (&S).deref(); }"#,
+        );
+    }
+
+    #[test]
+    fn unqualify_method_call_doesnt_apply_with_cursor_not_on_path() {
+        check_assist_not_applicable(
+            unqualify_method_call,
+            r#"
+//- minicore: add
+fn f() { core::ops::Add::add(2,$0 2); }"#,
+        );
+    }
+
+    #[test]
+    fn unqualify_method_call_doesnt_apply_with_no_self() {
+        check_assist_not_applicable(
+            unqualify_method_call,
+            r#"
+struct S;
+impl S { fn assoc(S: S, S: S) {} }
+fn f() { S::assoc$0(S, S); }"#,
+        );
+    }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index f7ac9d8fd6..6747b95007 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -201,6 +201,7 @@ mod handlers {
     mod unnecessary_async;
     mod unwrap_block;
     mod unwrap_result_return_type;
+    mod unqualify_method_call;
     mod wrap_return_type_in_result;
 
     pub(crate) fn all() -> &'static [Handler] {
@@ -306,6 +307,7 @@ mod handlers {
             unwrap_block::unwrap_block,
             unwrap_result_return_type::unwrap_result_return_type,
             unwrap_tuple::unwrap_tuple,
+            unqualify_method_call::unqualify_method_call,
             wrap_return_type_in_result::wrap_return_type_in_result,
             // These are manually sorted for better priorities. By default,
             // priority is determined by the size of the target range (smaller
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 48a7bbfecf..86d25e2f5a 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -35,7 +35,6 @@ pub struct InlayHintsConfig {
     pub parameter_hints: bool,
     pub chaining_hints: bool,
     pub adjustment_hints: AdjustmentHints,
-    pub adjustment_hints_mode: AdjustmentHintsMode,
     pub adjustment_hints_hide_outside_unsafe: bool,
     pub closure_return_type_hints: ClosureReturnTypeHints,
     pub binding_mode_hints: bool,
@@ -75,14 +74,6 @@ pub enum AdjustmentHints {
     Never,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum AdjustmentHintsMode {
-    Prefix,
-    Postfix,
-    PreferPrefix,
-    PreferPostfix,
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum InlayKind {
     BindingModeHint,
@@ -91,7 +82,6 @@ pub enum InlayKind {
     ClosureReturnTypeHint,
     GenericParamListHint,
     AdjustmentHint,
-    AdjustmentHintPostfix,
     LifetimeHint,
     ParameterHint,
     TypeHint,
@@ -440,7 +430,7 @@ mod tests {
     use itertools::Itertools;
     use test_utils::extract_annotations;
 
-    use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode};
+    use crate::inlay_hints::AdjustmentHints;
     use crate::DiscriminantHints;
     use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
 
@@ -456,7 +446,6 @@ mod tests {
         lifetime_elision_hints: LifetimeElisionHints::Never,
         closure_return_type_hints: ClosureReturnTypeHints::Never,
         adjustment_hints: AdjustmentHints::Never,
-        adjustment_hints_mode: AdjustmentHintsMode::Prefix,
         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 bdd7c05e00..1c13f31cf2 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -5,13 +5,9 @@
 //! ```
 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, AdjustmentHintsMode, InlayHint, InlayHintsConfig, InlayKind};
+use crate::{AdjustmentHints, InlayHint, InlayHintsConfig, InlayKind};
 
 pub(super) fn hints(
     acc: &mut Vec<InlayHint>,
@@ -36,47 +32,36 @@ 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 (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 {
-            range: expr.syntax().text_range(),
-            kind: InlayKind::OpeningParenthesis,
-            label: "(".into(),
-            tooltip: None,
-        });
-    }
-
-    if postfix && needs_inner_parens {
+    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 {
         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 postfix {
-        tmp0 = adjustments.into_iter();
-        &mut tmp0
-    } else {
-        tmp1 = adjustments.into_iter().rev();
-        &mut tmp1
-    };
-
-    for adjustment in iter {
+    for adjustment in adjustments.into_iter().rev() {
         if adjustment.source == adjustment.target {
             continue;
         }
@@ -112,30 +97,12 @@ pub(super) fn hints(
         };
         acc.push(InlayHint {
             range: expr.syntax().text_range(),
-            kind: if postfix {
-                InlayKind::AdjustmentHintPostfix
-            } else {
-                InlayKind::AdjustmentHint
-            },
-            label: if postfix { format!(".{}", text.trim_end()).into() } else { text.into() },
-            tooltip: None,
-        });
-    }
-    if !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(),
+            kind: InlayKind::AdjustmentHint,
+            label: text.into(),
             tooltip: None,
         });
     }
-    if needs_outer_parens {
+    if needs_parens {
         acc.push(InlayHint {
             range: expr.syntax().text_range(),
             kind: InlayKind::ClosingParenthesis,
@@ -146,111 +113,11 @@ pub(super) fn hints(
     Some(())
 }
 
-/// Returns whatever the hint should be postfix and if 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 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 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.
-    //
-    // 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 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::{
         inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
-        AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
+        AdjustmentHints, InlayHintsConfig,
     };
 
     #[test]
@@ -258,7 +125,7 @@ mod tests {
         check_with_config(
             InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
             r#"
-//- minicore: coerce_unsized, fn
+//- minicore: coerce_unsized
 fn main() {
     let _: u32         = loop {};
                        //^^^^^^^<never-to-any>
@@ -281,16 +148,12 @@ 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 $
@@ -343,11 +206,6 @@ fn main() {
                                        //^^^^^^^<unsize>
                                        //^^^^^^^&mut $
                                        //^^^^^^^*
-
-    let _: &mut dyn Fn() = &mut || ();
-                         //^^^^^^^^^^<unsize>
-                         //^^^^^^^^^^&mut $
-                         //^^^^^^^^^^*
 }
 
 #[derive(Copy, Clone)]
@@ -357,153 +215,12 @@ 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_mode: AdjustmentHintsMode::Postfix,
-                ..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 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() {
         check_with_config(
@@ -611,20 +328,4 @@ fn a() {
             "#,
         );
     }
-
-    #[test]
-    fn bug() {
-        check_with_config(
-            InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
-            r#"
-fn main() {
-    // These should be identical, but they are not...
-
-    let () = return;
-    let (): () = return;
-               //^^^^^^<never-to-any>
-}
-            "#,
-        )
-    }
 }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 239456cb28..200958a433 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -81,8 +81,8 @@ pub use crate::{
     highlight_related::{HighlightRelatedConfig, HighlightedRange},
     hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
     inlay_hints::{
-        AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint,
-        InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
+        AdjustmentHints, ClosureReturnTypeHints, DiscriminantHints, InlayHint, InlayHintLabel,
+        InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
     },
     join_lines::JoinLinesConfig,
     markup::Markup,
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index a6b30ba139..6e31a1420e 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -13,7 +13,6 @@ use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
 
 use crate::{
     hover::hover_for_definition,
-    inlay_hints::AdjustmentHintsMode,
     moniker::{def_to_moniker, MonikerResult},
     parent_module::crates_for,
     Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
@@ -116,7 +115,6 @@ impl StaticIndex<'_> {
                     closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
                     lifetime_elision_hints: crate::LifetimeElisionHints::Never,
                     adjustment_hints: crate::AdjustmentHints::Never,
-                    adjustment_hints_mode: AdjustmentHintsMode::Prefix,
                     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 27a86db382..0f515a6d5c 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -333,8 +333,6 @@ 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 as postfix ops (`.*` instead of `*`, etc).
-        inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"",
         /// 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.
@@ -1254,12 +1252,6 @@ impl Config {
                 },
                 AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
             },
-            adjustment_hints_mode: match self.data.inlayHints_expressionAdjustmentHints_mode {
-                AdjustmentHintsModeDef::Prefix => ide::AdjustmentHintsMode::Prefix,
-                AdjustmentHintsModeDef::Postfix => ide::AdjustmentHintsMode::Postfix,
-                AdjustmentHintsModeDef::PreferPrefix => ide::AdjustmentHintsMode::PreferPrefix,
-                AdjustmentHintsModeDef::PreferPostfix => ide::AdjustmentHintsMode::PreferPostfix,
-            },
             adjustment_hints_hide_outside_unsafe: self
                 .data
                 .inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,
@@ -1776,15 +1768,6 @@ enum DiscriminantHintsDef {
     Fieldless,
 }
 
-#[derive(Deserialize, Debug, Clone)]
-#[serde(rename_all = "snake_case")]
-enum AdjustmentHintsModeDef {
-    Prefix,
-    Postfix,
-    PreferPrefix,
-    PreferPostfix,
-}
-
 #[derive(Deserialize, Debug, Clone)]
 #[serde(rename_all = "snake_case")]
 enum FilesWatcherDef {
@@ -2118,21 +2101,6 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 "Only show discriminant hints on fieldless enum variants."
             ]
         },
-        "AdjustmentHintsModeDef" => set! {
-            "type": "string",
-            "enum": [
-                "prefix",
-                "postfix",
-                "prefer_prefix",
-                "prefer_postfix",
-            ],
-            "enumDescriptions": [
-                "Always show adjustment hints as prefix (`*expr`).",
-                "Always show adjustment hints as postfix (`expr.*`).",
-                "Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
-                "Show prefix or postfix depending on which uses less parenthesis, prefering postfix.",
-            ]
-        },
         "CargoFeaturesDef" => set! {
             "anyOf": [
                 {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index e736b2ff9a..a12bd3952c 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -452,7 +452,6 @@ pub(crate) fn inlay_hint(
             | InlayKind::ChainingHint
             | InlayKind::GenericParamListHint
             | InlayKind::ClosingParenthesis
-            | InlayKind::AdjustmentHintPostfix
             | InlayKind::LifetimeHint
             | InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()),
         },
@@ -466,7 +465,6 @@ pub(crate) fn inlay_hint(
             | InlayKind::ClosureReturnTypeHint
             | InlayKind::GenericParamListHint
             | InlayKind::AdjustmentHint
-            | InlayKind::AdjustmentHintPostfix
             | InlayKind::LifetimeHint
             | InlayKind::ParameterHint => false,
         }),
@@ -477,7 +475,6 @@ pub(crate) fn inlay_hint(
             | InlayKind::ClosureReturnTypeHint
             | InlayKind::GenericParamListHint
             | InlayKind::AdjustmentHint
-            | InlayKind::AdjustmentHintPostfix
             | InlayKind::TypeHint
             | InlayKind::DiscriminantHint
             | InlayKind::ClosingBraceHint => false,
@@ -496,7 +493,6 @@ 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 0aaf07ebf3..d9794e7052 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -469,11 +469,6 @@ Whether to show inlay hints for type adjustments.
 --
 Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
 --
-[[rust-analyzer.inlayHints.expressionAdjustmentHints.mode]]rust-analyzer.inlayHints.expressionAdjustmentHints.mode (default: `"prefix"`)::
-+
---
-Whether to show inlay hints 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 5ffce2f553..f508dde4f6 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1000,23 +1000,6 @@
                     "default": false,
                     "type": "boolean"
                 },
-                "rust-analyzer.inlayHints.expressionAdjustmentHints.mode": {
-                    "markdownDescription": "Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc).",
-                    "default": "prefix",
-                    "type": "string",
-                    "enum": [
-                        "prefix",
-                        "postfix",
-                        "prefer_prefix",
-                        "prefer_postfix"
-                    ],
-                    "enumDescriptions": [
-                        "Always show adjustment hints as prefix (`*expr`).",
-                        "Always show adjustment hints as postfix (`expr.*`).",
-                        "Show prefix or postfix depending on which uses less parenthesis, prefering prefix.",
-                        "Show prefix or postfix depending on which uses less parenthesis, prefering postfix."
-                    ]
-                },
                 "rust-analyzer.inlayHints.lifetimeElisionHints.enable": {
                     "markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.",
                     "default": "never",


Test patch:
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 210df6999d..2644e7dd11 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -2533,6 +2533,25 @@ pub async fn bar() { foo() }
     )
 }
 
+#[test]
+fn doctest_unqualify_method_call() {
+    check_doc_test(
+        "unqualify_method_call",
+        r#####"
+fn main() {
+    std::ops::Add::add$0(1, 2);
+}
+mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
+"#####,
+        r#####"
+fn main() {
+    1.add(2);
+}
+mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
+"#####,
+    )
+}
+
 #[test]
 fn doctest_unwrap_block() {
     check_doc_test(