about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-03-06 21:41:46 +0000
committerbors <bors@rust-lang.org>2023-03-06 21:41:46 +0000
commit31c12ec282de5e2d4e835f320f8858277b3ba133 (patch)
tree1a58ce9670845fd22ce88acde9c151e861eb7d83
parent1bfe96ec667c9acafa52c5d4bb43ad1bc2e9c3ab (diff)
parent0ce06088f8b79ba53002d6a7254e6bb01b687552 (diff)
downloadrust-31c12ec282de5e2d4e835f320f8858277b3ba133.tar.gz
rust-31c12ec282de5e2d4e835f320f8858277b3ba133.zip
Auto merge of #14266 - Veykril:generalize-eager-lazy, r=Veykril
feature: Make replace_or_with_or_else assists more generally applicable
-rw-r--r--crates/hir/src/lib.rs10
-rw-r--r--crates/ide-assists/src/handlers/replace_method_eager_lazy.rs310
-rw-r--r--crates/ide-assists/src/handlers/replace_or_with_or_else.rs364
-rw-r--r--crates/ide-assists/src/lib.rs6
-rw-r--r--crates/ide-assists/src/tests/generated.rs80
-rw-r--r--crates/ide-ssr/src/matching.rs2
-rw-r--r--crates/ide/src/inlay_hints/bind_pat.rs117
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs12
-rw-r--r--crates/ide/src/signature_help.rs2
-rw-r--r--crates/test-utils/src/minicore.rs34
10 files changed, 415 insertions, 522 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index df6484db536..a5df94885f6 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1677,6 +1677,10 @@ impl Function {
             .collect()
     }
 
+    pub fn num_params(self, db: &dyn HirDatabase) -> usize {
+        db.function_data(self.id).params.len()
+    }
+
     pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> {
         if self.self_param(db).is_none() {
             return None;
@@ -3857,11 +3861,13 @@ impl Type {
     }
 }
 
+// FIXME: Document this
 #[derive(Debug)]
 pub struct Callable {
     ty: Type,
     sig: CallableSig,
     callee: Callee,
+    /// Whether this is a method that was called with method call syntax.
     pub(crate) is_bound_method: bool,
 }
 
@@ -3895,14 +3901,14 @@ impl Callable {
             Other => CallableKind::Other,
         }
     }
-    pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> {
+    pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(ast::SelfParam, Type)> {
         let func = match self.callee {
             Callee::Def(CallableDefId::FunctionId(it)) if self.is_bound_method => it,
             _ => return None,
         };
         let src = func.lookup(db.upcast()).source(db.upcast());
         let param_list = src.value.param_list()?;
-        param_list.self_param()
+        Some((param_list.self_param()?, self.ty.derived(self.sig.params()[0].clone())))
     }
     pub fn n_params(&self) -> usize {
         self.sig.params().len() - if self.is_bound_method { 1 } else { 0 }
diff --git a/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs
new file mode 100644
index 00000000000..a7e3ed793f1
--- /dev/null
+++ b/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs
@@ -0,0 +1,310 @@
+use ide_db::assists::{AssistId, AssistKind};
+use syntax::{
+    ast::{self, make, Expr, HasArgList},
+    AstNode,
+};
+
+use crate::{AssistContext, Assists};
+
+// Assist: replace_with_lazy_method
+//
+// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`.
+//
+// ```
+// # //- minicore:option, fn
+// fn foo() {
+//     let a = Some(1);
+//     a.unwra$0p_or(2);
+// }
+// ```
+// ->
+// ```
+// fn foo() {
+//     let a = Some(1);
+//     a.unwrap_or_else(|| 2);
+// }
+// ```
+pub(crate) fn replace_with_lazy_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+    let scope = ctx.sema.scope(call.syntax())?;
+
+    let last_arg = call.arg_list()?.args().next()?;
+    let method_name = call.name_ref()?;
+
+    let callable = ctx.sema.resolve_method_call_as_callable(&call)?;
+    let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
+    let n_params = callable.n_params() + 1;
+
+    let method_name_lazy = format!(
+        "{method_name}{}",
+        if method_name.text().ends_with("or") { "_else" } else { "_with" }
+    );
+
+    receiver_ty.iterate_method_candidates_with_traits(
+        ctx.sema.db,
+        &scope,
+        &scope.visible_traits().0,
+        None,
+        None,
+        |func| {
+            let valid = func.name(ctx.sema.db).as_str() == Some(&*method_name_lazy)
+                && func.num_params(ctx.sema.db) == n_params
+                && {
+                    let params = func.params_without_self(ctx.sema.db);
+                    let last_p = params.first()?;
+                    // FIXME: Check that this has the form of `() -> T` where T is the current type of the argument
+                    last_p.ty().impls_fnonce(ctx.sema.db)
+                };
+            valid.then_some(func)
+        },
+    )?;
+
+    acc.add(
+        AssistId("replace_with_lazy_method", AssistKind::RefactorRewrite),
+        format!("Replace {method_name} with {method_name_lazy}"),
+        call.syntax().text_range(),
+        |builder| {
+            builder.replace(method_name.syntax().text_range(), method_name_lazy);
+            let closured = into_closure(&last_arg);
+            builder.replace_ast(last_arg, closured);
+        },
+    )
+}
+
+fn into_closure(param: &Expr) -> Expr {
+    (|| {
+        if let ast::Expr::CallExpr(call) = param {
+            if call.arg_list()?.args().count() == 0 {
+                Some(call.expr()?)
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    })()
+    .unwrap_or_else(|| make::expr_closure(None, param.clone()))
+}
+
+// Assist: replace_with_eager_method
+//
+// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`.
+//
+// ```
+// # //- minicore:option, fn
+// fn foo() {
+//     let a = Some(1);
+//     a.unwra$0p_or_else(|| 2);
+// }
+// ```
+// ->
+// ```
+// fn foo() {
+//     let a = Some(1);
+//     a.unwrap_or(2);
+// }
+// ```
+pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+    let scope = ctx.sema.scope(call.syntax())?;
+
+    let last_arg = call.arg_list()?.args().next()?;
+    let method_name = call.name_ref()?;
+
+    let callable = ctx.sema.resolve_method_call_as_callable(&call)?;
+    let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
+    let n_params = callable.n_params() + 1;
+    let params = callable.params(ctx.sema.db);
+
+    // FIXME: Check that the arg is of the form `() -> T`
+    if !params.first()?.1.impls_fnonce(ctx.sema.db) {
+        return None;
+    }
+
+    let method_name_text = method_name.text();
+    let method_name_eager = method_name_text
+        .strip_suffix("_else")
+        .or_else(|| method_name_text.strip_suffix("_with"))?;
+
+    receiver_ty.iterate_method_candidates_with_traits(
+        ctx.sema.db,
+        &scope,
+        &scope.visible_traits().0,
+        None,
+        None,
+        |func| {
+            let valid = func.name(ctx.sema.db).as_str() == Some(&*method_name_eager)
+                && func.num_params(ctx.sema.db) == n_params;
+            valid.then_some(func)
+        },
+    )?;
+
+    acc.add(
+        AssistId("replace_with_eager_method", AssistKind::RefactorRewrite),
+        format!("Replace {method_name} with {method_name_eager}"),
+        call.syntax().text_range(),
+        |builder| {
+            builder.replace(method_name.syntax().text_range(), method_name_eager);
+            let called = into_call(&last_arg);
+            builder.replace_ast(last_arg, called);
+        },
+    )
+}
+
+fn into_call(param: &Expr) -> Expr {
+    (|| {
+        if let ast::Expr::ClosureExpr(closure) = param {
+            if closure.param_list()?.params().count() == 0 {
+                Some(closure.body()?)
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    })()
+    .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())))
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::check_assist;
+
+    use super::*;
+
+    #[test]
+    fn replace_or_with_or_else_simple() {
+        check_assist(
+            replace_with_lazy_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_$0or(2);
+}
+"#,
+            r#"
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_or_else(|| 2);
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_or_with_or_else_call() {
+        check_assist(
+            replace_with_lazy_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_$0or(x());
+}
+"#,
+            r#"
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_or_else(x);
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_or_with_or_else_block() {
+        check_assist(
+            replace_with_lazy_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_$0or({
+        let mut x = bar();
+        for i in 0..10 {
+            x += i;
+        }
+        x
+    });
+}
+"#,
+            r#"
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_or_else(|| {
+        let mut x = bar();
+        for i in 0..10 {
+            x += i;
+        }
+        x
+    });
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_or_else_with_or_simple() {
+        check_assist(
+            replace_with_eager_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_$0or_else(|| 2);
+}
+"#,
+            r#"
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_or(2);
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_or_else_with_or_call() {
+        check_assist(
+            replace_with_eager_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_$0or_else(x);
+}
+
+fn x() -> i32 { 0 }
+"#,
+            r#"
+fn foo() {
+    let foo = Some(1);
+    return foo.unwrap_or(x());
+}
+
+fn x() -> i32 { 0 }
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_or_else_with_or_map() {
+        check_assist(
+            replace_with_eager_method,
+            r#"
+//- minicore: option, fn
+fn foo() {
+    let foo = Some("foo");
+    return foo.map$0_or_else(|| 42, |v| v.len());
+}
+"#,
+            r#"
+fn foo() {
+    let foo = Some("foo");
+    return foo.map_or(42, |v| v.len());
+}
+"#,
+        )
+    }
+}
diff --git a/crates/ide-assists/src/handlers/replace_or_with_or_else.rs b/crates/ide-assists/src/handlers/replace_or_with_or_else.rs
deleted file mode 100644
index f0ed3c4fe6f..00000000000
--- a/crates/ide-assists/src/handlers/replace_or_with_or_else.rs
+++ /dev/null
@@ -1,364 +0,0 @@
-use ide_db::{
-    assists::{AssistId, AssistKind},
-    famous_defs::FamousDefs,
-};
-use syntax::{
-    ast::{self, make, Expr, HasArgList},
-    AstNode,
-};
-
-use crate::{AssistContext, Assists};
-
-// Assist: replace_or_with_or_else
-//
-// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`.
-//
-// ```
-// # //- minicore:option
-// fn foo() {
-//     let a = Some(1);
-//     a.unwra$0p_or(2);
-// }
-// ```
-// ->
-// ```
-// fn foo() {
-//     let a = Some(1);
-//     a.unwrap_or_else(|| 2);
-// }
-// ```
-pub(crate) fn replace_or_with_or_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
-    let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
-
-    let kind = is_option_or_result(call.receiver()?, ctx)?;
-
-    let (name, arg_list) = (call.name_ref()?, call.arg_list()?);
-
-    let mut map_or = false;
-
-    let replace = match &*name.text() {
-        "unwrap_or" => "unwrap_or_else".to_string(),
-        "or" => "or_else".to_string(),
-        "ok_or" if kind == Kind::Option => "ok_or_else".to_string(),
-        "map_or" => {
-            map_or = true;
-            "map_or_else".to_string()
-        }
-        _ => return None,
-    };
-
-    let arg = match arg_list.args().collect::<Vec<_>>().as_slice() {
-        [] => make::arg_list(Vec::new()),
-        [first] => {
-            let param = into_closure(first);
-            make::arg_list(vec![param])
-        }
-        [first, second] if map_or => {
-            let param = into_closure(first);
-            make::arg_list(vec![param, second.clone()])
-        }
-        _ => return None,
-    };
-
-    acc.add(
-        AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite),
-        format!("Replace {name} with {replace}"),
-        call.syntax().text_range(),
-        |builder| {
-            builder.replace(name.syntax().text_range(), replace);
-            builder.replace_ast(arg_list, arg)
-        },
-    )
-}
-
-fn into_closure(param: &Expr) -> Expr {
-    (|| {
-        if let ast::Expr::CallExpr(call) = param {
-            if call.arg_list()?.args().count() == 0 {
-                Some(call.expr()?)
-            } else {
-                None
-            }
-        } else {
-            None
-        }
-    })()
-    .unwrap_or_else(|| make::expr_closure(None, param.clone()))
-}
-
-// Assist: replace_or_else_with_or
-//
-// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`.
-//
-// ```
-// # //- minicore:option
-// fn foo() {
-//     let a = Some(1);
-//     a.unwra$0p_or_else(|| 2);
-// }
-// ```
-// ->
-// ```
-// fn foo() {
-//     let a = Some(1);
-//     a.unwrap_or(2);
-// }
-// ```
-pub(crate) fn replace_or_else_with_or(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
-    let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
-
-    let kind = is_option_or_result(call.receiver()?, ctx)?;
-
-    let (name, arg_list) = (call.name_ref()?, call.arg_list()?);
-
-    let mut map_or = false;
-    let replace = match &*name.text() {
-        "unwrap_or_else" => "unwrap_or".to_string(),
-        "or_else" => "or".to_string(),
-        "ok_or_else" if kind == Kind::Option => "ok_or".to_string(),
-        "map_or_else" => {
-            map_or = true;
-            "map_or".to_string()
-        }
-        _ => return None,
-    };
-
-    let arg = match arg_list.args().collect::<Vec<_>>().as_slice() {
-        [] => make::arg_list(Vec::new()),
-        [first] => {
-            let param = into_call(first);
-            make::arg_list(vec![param])
-        }
-        [first, second] if map_or => {
-            let param = into_call(first);
-            make::arg_list(vec![param, second.clone()])
-        }
-        _ => return None,
-    };
-
-    acc.add(
-        AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite),
-        format!("Replace {name} with {replace}"),
-        call.syntax().text_range(),
-        |builder| {
-            builder.replace(name.syntax().text_range(), replace);
-            builder.replace_ast(arg_list, arg)
-        },
-    )
-}
-
-fn into_call(param: &Expr) -> Expr {
-    (|| {
-        if let ast::Expr::ClosureExpr(closure) = param {
-            if closure.param_list()?.params().count() == 0 {
-                Some(closure.body()?)
-            } else {
-                None
-            }
-        } else {
-            None
-        }
-    })()
-    .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())))
-}
-
-#[derive(PartialEq, Eq)]
-enum Kind {
-    Option,
-    Result,
-}
-
-fn is_option_or_result(receiver: Expr, ctx: &AssistContext<'_>) -> Option<Kind> {
-    let ty = ctx.sema.type_of_expr(&receiver)?.adjusted().as_adt()?.as_enum()?;
-    let option_enum =
-        FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_option_Option();
-
-    if let Some(option_enum) = option_enum {
-        if ty == option_enum {
-            return Some(Kind::Option);
-        }
-    }
-
-    let result_enum =
-        FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_result_Result();
-
-    if let Some(result_enum) = result_enum {
-        if ty == result_enum {
-            return Some(Kind::Result);
-        }
-    }
-
-    None
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_assist, check_assist_not_applicable};
-
-    use super::*;
-
-    #[test]
-    fn replace_or_with_or_else_simple() {
-        check_assist(
-            replace_or_with_or_else,
-            r#"
-//- minicore: option
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_$0or(2);
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_or_else(|| 2);
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_with_or_else_call() {
-        check_assist(
-            replace_or_with_or_else,
-            r#"
-//- minicore: option
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_$0or(x());
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_or_else(x);
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_with_or_else_block() {
-        check_assist(
-            replace_or_with_or_else,
-            r#"
-//- minicore: option
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_$0or({
-        let mut x = bar();
-        for i in 0..10 {
-            x += i;
-        }
-        x
-    });
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_or_else(|| {
-        let mut x = bar();
-        for i in 0..10 {
-            x += i;
-        }
-        x
-    });
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_else_with_or_simple() {
-        check_assist(
-            replace_or_else_with_or,
-            r#"
-//- minicore: option
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_$0or_else(|| 2);
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_or(2);
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_else_with_or_call() {
-        check_assist(
-            replace_or_else_with_or,
-            r#"
-//- minicore: option
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_$0or_else(x);
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Some(1);
-    return foo.unwrap_or(x());
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_else_with_or_result() {
-        check_assist(
-            replace_or_else_with_or,
-            r#"
-//- minicore: result
-fn foo() {
-    let foo = Ok(1);
-    return foo.unwrap_$0or_else(x);
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Ok(1);
-    return foo.unwrap_or(x());
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_else_with_or_map() {
-        check_assist(
-            replace_or_else_with_or,
-            r#"
-//- minicore: result
-fn foo() {
-    let foo = Ok("foo");
-    return foo.map$0_or_else(|| 42, |v| v.len());
-}
-"#,
-            r#"
-fn foo() {
-    let foo = Ok("foo");
-    return foo.map_or(42, |v| v.len());
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_or_else_with_or_not_applicable() {
-        check_assist_not_applicable(
-            replace_or_else_with_or,
-            r#"
-fn foo() {
-    let foo = Ok(1);
-    return foo.unwrap_$0or_else(x);
-}
-"#,
-        )
-    }
-}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 276cf5f5dd0..4d489b62b5c 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -188,7 +188,7 @@ mod handlers {
     mod replace_try_expr_with_match;
     mod replace_derive_with_manual_impl;
     mod replace_if_let_with_match;
-    mod replace_or_with_or_else;
+    mod replace_method_eager_lazy;
     mod replace_arith_op;
     mod introduce_named_generic;
     mod replace_let_with_if_let;
@@ -297,8 +297,8 @@ mod handlers {
             replace_if_let_with_match::replace_if_let_with_match,
             replace_if_let_with_match::replace_match_with_if_let,
             replace_let_with_if_let::replace_let_with_if_let,
-            replace_or_with_or_else::replace_or_else_with_or,
-            replace_or_with_or_else::replace_or_with_or_else,
+            replace_method_eager_lazy::replace_with_eager_method,
+            replace_method_eager_lazy::replace_with_lazy_method,
             replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
             replace_arith_op::replace_arith_with_wrapping,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 524af200130..e5a8d675a9e 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -2314,46 +2314,6 @@ fn handle(action: Action) {
 }
 
 #[test]
-fn doctest_replace_or_else_with_or() {
-    check_doc_test(
-        "replace_or_else_with_or",
-        r#####"
-//- minicore:option
-fn foo() {
-    let a = Some(1);
-    a.unwra$0p_or_else(|| 2);
-}
-"#####,
-        r#####"
-fn foo() {
-    let a = Some(1);
-    a.unwrap_or(2);
-}
-"#####,
-    )
-}
-
-#[test]
-fn doctest_replace_or_with_or_else() {
-    check_doc_test(
-        "replace_or_with_or_else",
-        r#####"
-//- minicore:option
-fn foo() {
-    let a = Some(1);
-    a.unwra$0p_or(2);
-}
-"#####,
-        r#####"
-fn foo() {
-    let a = Some(1);
-    a.unwrap_or_else(|| 2);
-}
-"#####,
-    )
-}
-
-#[test]
 fn doctest_replace_qualified_name_with_use() {
     check_doc_test(
         "replace_qualified_name_with_use",
@@ -2428,6 +2388,46 @@ fn main() {
 }
 
 #[test]
+fn doctest_replace_with_eager_method() {
+    check_doc_test(
+        "replace_with_eager_method",
+        r#####"
+//- minicore:option, fn
+fn foo() {
+    let a = Some(1);
+    a.unwra$0p_or_else(|| 2);
+}
+"#####,
+        r#####"
+fn foo() {
+    let a = Some(1);
+    a.unwrap_or(2);
+}
+"#####,
+    )
+}
+
+#[test]
+fn doctest_replace_with_lazy_method() {
+    check_doc_test(
+        "replace_with_lazy_method",
+        r#####"
+//- minicore:option, fn
+fn foo() {
+    let a = Some(1);
+    a.unwra$0p_or(2);
+}
+"#####,
+        r#####"
+fn foo() {
+    let a = Some(1);
+    a.unwrap_or_else(|| 2);
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_sort_items() {
     check_doc_test(
         "sort_items",
diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs
index 57b5ab6abda..a8e88369088 100644
--- a/crates/ide-ssr/src/matching.rs
+++ b/crates/ide-ssr/src/matching.rs
@@ -561,7 +561,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
                             .sema
                             .resolve_method_call_as_callable(code)
                             .and_then(|callable| callable.receiver_param(self.sema.db))
-                            .map(|self_param| self_param.kind())
+                            .map(|(self_param, _)| self_param.kind())
                             .unwrap_or(ast::SelfParamKind::Owned);
                     }
                 }
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs
index 4af7f9bdb73..6a50927333d 100644
--- a/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/crates/ide/src/inlay_hints/bind_pat.rs
@@ -176,15 +176,12 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir
 mod tests {
     // This module also contains tests for super::closure_ret
 
-    use expect_test::expect;
     use syntax::{TextRange, TextSize};
     use test_utils::extract_annotations;
 
     use crate::{fixture, inlay_hints::InlayHintsConfig};
 
-    use crate::inlay_hints::tests::{
-        check, check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
-    };
+    use crate::inlay_hints::tests::{check, check_with_config, DISABLED_CONFIG, TEST_CONFIG};
     use crate::ClosureReturnTypeHints;
 
     #[track_caller]
@@ -278,8 +275,7 @@ fn main() {
     #[test]
     fn iterator_hint_regression_issue_12674() {
         // Ensure we don't crash while solving the projection type of iterators.
-        check_expect(
-            InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+        let (analysis, file_id) = fixture::file(
             r#"
 //- minicore: iterators
 struct S<T>(T);
@@ -302,107 +298,18 @@ impl<'a, T> Iterator for SliceIter<'a, T> {
 
 fn main(a: SliceIter<'_, Container>) {
     a
-    .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v))))
-    .map(|e| e);
+        .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v))))
+        .map(|e| e);
 }
-            "#,
-            expect![[r#"
-                [
-                    InlayHint {
-                        range: 484..554,
-                        kind: Chaining,
-                        label: [
-                            "impl ",
-                            InlayHintLabelPart {
-                                text: "Iterator",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            1,
-                                        ),
-                                        range: 2611..2619,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            "<",
-                            InlayHintLabelPart {
-                                text: "Item",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            1,
-                                        ),
-                                        range: 2643..2647,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            " = impl ",
-                            InlayHintLabelPart {
-                                text: "Iterator",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            1,
-                                        ),
-                                        range: 2611..2619,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            "<",
-                            InlayHintLabelPart {
-                                text: "Item",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            1,
-                                        ),
-                                        range: 2643..2647,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            " = &&str>>",
-                        ],
-                    },
-                    InlayHint {
-                        range: 484..485,
-                        kind: Chaining,
-                        label: [
-                            "",
-                            InlayHintLabelPart {
-                                text: "SliceIter",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            0,
-                                        ),
-                                        range: 289..298,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            "<",
-                            InlayHintLabelPart {
-                                text: "Container",
-                                linked_location: Some(
-                                    FileRange {
-                                        file_id: FileId(
-                                            0,
-                                        ),
-                                        range: 238..247,
-                                    },
-                                ),
-                                tooltip: "",
-                            },
-                            ">",
-                        ],
-                    },
-                ]
-            "#]],
+"#,
         );
+        analysis
+            .inlay_hints(
+                &InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+                file_id,
+                None,
+            )
+            .unwrap();
     }
 
     #[test]
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 0c54f084c19..0a7513e465a 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -435,7 +435,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2611..2619,
+                                        range: 3386..3394,
                                     },
                                 ),
                                 tooltip: "",
@@ -448,7 +448,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2643..2647,
+                                        range: 3418..3422,
                                     },
                                 ),
                                 tooltip: "",
@@ -468,7 +468,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2611..2619,
+                                        range: 3386..3394,
                                     },
                                 ),
                                 tooltip: "",
@@ -481,7 +481,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2643..2647,
+                                        range: 3418..3422,
                                     },
                                 ),
                                 tooltip: "",
@@ -501,7 +501,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2611..2619,
+                                        range: 3386..3394,
                                     },
                                 ),
                                 tooltip: "",
@@ -514,7 +514,7 @@ fn main() {
                                         file_id: FileId(
                                             1,
                                         ),
-                                        range: 2643..2647,
+                                        range: 3418..3422,
                                     },
                                 ),
                                 tooltip: "",
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index d32ae83c8f7..2c08c457b33 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -172,7 +172,7 @@ fn signature_help_for_call(
 
     res.signature.push('(');
     {
-        if let Some(self_param) = callable.receiver_param(db) {
+        if let Some((self_param, _)) = callable.receiver_param(db) {
             format_to!(res.signature, "{}", self_param)
         }
         let mut buf = String::new();
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 7b48e42489c..b6336e2216f 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -534,6 +534,40 @@ pub mod option {
                 None => panic!("called `Option::unwrap()` on a `None` value"),
             }
         }
+
+        pub fn and<U>(self, optb: Option<U>) -> Option<U> {
+            loop {}
+        }
+        pub fn unwrap_or(self, default: T) -> T {
+            loop {}
+        }
+        // region:fn
+        pub fn and_then<U, F>(self, f: F) -> Option<U>
+        where
+            F: FnOnce(T) -> Option<U>,
+        {
+            loop {}
+        }
+        pub fn unwrap_or_else<F>(self, f: F) -> T
+        where
+            F: FnOnce() -> T,
+        {
+            loop {}
+        }
+        pub fn map_or<U, F>(self, default: U, f: F) -> U
+        where
+            F: FnOnce(T) -> U,
+        {
+            loop {}
+        }
+        pub fn map_or_else<U, D, F>(self, default: D, f: F) -> U
+        where
+            D: FnOnce() -> U,
+            F: FnOnce(T) -> U,
+        {
+            loop {}
+        }
+        // endregion:fn
     }
 }
 // endregion:option