ra_ap_ide_assists/handlers/
replace_method_eager_lazy.rs1use ide_db::assists::AssistId;
2use syntax::{
3 AstNode,
4 ast::{self, Expr, HasArgList, make},
5};
6
7use crate::{AssistContext, Assists};
8
9pub(crate) fn replace_with_lazy_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28 let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
29 let scope = ctx.sema.scope(call.syntax())?;
30
31 let last_arg = call.arg_list()?.args().next()?;
32 let method_name = call.name_ref()?;
33
34 let callable = ctx.sema.resolve_method_call_as_callable(&call)?;
35 let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
36 let n_params = callable.n_params() + 1;
37
38 let method_name_lazy = format!(
39 "{method_name}{}",
40 if method_name.text().ends_with("or") { "_else" } else { "_with" }
41 );
42
43 receiver_ty.iterate_method_candidates_with_traits(
44 ctx.sema.db,
45 &scope,
46 &scope.visible_traits().0,
47 None,
48 None,
49 |func| {
50 let valid = func.name(ctx.sema.db).as_str() == &*method_name_lazy
51 && func.num_params(ctx.sema.db) == n_params
52 && {
53 let params = func.params_without_self(ctx.sema.db);
54 let last_p = params.first()?;
55 last_p.ty().impls_fnonce(ctx.sema.db)
57 };
58 valid.then_some(func)
59 },
60 )?;
61
62 acc.add(
63 AssistId::refactor_rewrite("replace_with_lazy_method"),
64 format!("Replace {method_name} with {method_name_lazy}"),
65 call.syntax().text_range(),
66 |builder| {
67 builder.replace(method_name.syntax().text_range(), method_name_lazy);
68 let closured = into_closure(&last_arg);
69 builder.replace_ast(last_arg, closured);
70 },
71 )
72}
73
74fn into_closure(param: &Expr) -> Expr {
75 (|| {
76 if let ast::Expr::CallExpr(call) = param {
77 if call.arg_list()?.args().count() == 0 { Some(call.expr()?) } else { None }
78 } else {
79 None
80 }
81 })()
82 .unwrap_or_else(|| make::expr_closure(None, param.clone()).into())
83}
84
85pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
104 let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
105 let scope = ctx.sema.scope(call.syntax())?;
106
107 let last_arg = call.arg_list()?.args().next()?;
108 let method_name = call.name_ref()?;
109
110 let callable = ctx.sema.resolve_method_call_as_callable(&call)?;
111 let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?;
112 let n_params = callable.n_params() + 1;
113 let params = callable.params();
114
115 if !params.first()?.ty().impls_fnonce(ctx.sema.db) {
117 return None;
118 }
119
120 let method_name_text = method_name.text();
121 let method_name_eager = method_name_text
122 .strip_suffix("_else")
123 .or_else(|| method_name_text.strip_suffix("_with"))?;
124
125 receiver_ty.iterate_method_candidates_with_traits(
126 ctx.sema.db,
127 &scope,
128 &scope.visible_traits().0,
129 None,
130 None,
131 |func| {
132 let valid = func.name(ctx.sema.db).as_str() == method_name_eager
133 && func.num_params(ctx.sema.db) == n_params;
134 valid.then_some(func)
135 },
136 )?;
137
138 acc.add(
139 AssistId::refactor_rewrite("replace_with_eager_method"),
140 format!("Replace {method_name} with {method_name_eager}"),
141 call.syntax().text_range(),
142 |builder| {
143 builder.replace(method_name.syntax().text_range(), method_name_eager);
144 let called = into_call(&last_arg);
145 builder.replace_ast(last_arg, called);
146 },
147 )
148}
149
150fn into_call(param: &Expr) -> Expr {
151 (|| {
152 if let ast::Expr::ClosureExpr(closure) = param {
153 if closure.param_list()?.params().count() == 0 { Some(closure.body()?) } else { None }
154 } else {
155 None
156 }
157 })()
158 .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())).into())
159}
160
161#[cfg(test)]
162mod tests {
163 use crate::tests::check_assist;
164
165 use super::*;
166
167 #[test]
168 fn replace_or_with_or_else_simple() {
169 check_assist(
170 replace_with_lazy_method,
171 r#"
172//- minicore: option, fn
173fn foo() {
174 let foo = Some(1);
175 return foo.unwrap_$0or(2);
176}
177"#,
178 r#"
179fn foo() {
180 let foo = Some(1);
181 return foo.unwrap_or_else(|| 2);
182}
183"#,
184 )
185 }
186
187 #[test]
188 fn replace_or_with_or_else_call() {
189 check_assist(
190 replace_with_lazy_method,
191 r#"
192//- minicore: option, fn
193fn foo() {
194 let foo = Some(1);
195 return foo.unwrap_$0or(x());
196}
197"#,
198 r#"
199fn foo() {
200 let foo = Some(1);
201 return foo.unwrap_or_else(x);
202}
203"#,
204 )
205 }
206
207 #[test]
208 fn replace_or_with_or_else_block() {
209 check_assist(
210 replace_with_lazy_method,
211 r#"
212//- minicore: option, fn
213fn foo() {
214 let foo = Some(1);
215 return foo.unwrap_$0or({
216 let mut x = bar();
217 for i in 0..10 {
218 x += i;
219 }
220 x
221 });
222}
223"#,
224 r#"
225fn foo() {
226 let foo = Some(1);
227 return foo.unwrap_or_else(|| {
228 let mut x = bar();
229 for i in 0..10 {
230 x += i;
231 }
232 x
233 });
234}
235"#,
236 )
237 }
238
239 #[test]
240 fn replace_or_else_with_or_simple() {
241 check_assist(
242 replace_with_eager_method,
243 r#"
244//- minicore: option, fn
245fn foo() {
246 let foo = Some(1);
247 return foo.unwrap_$0or_else(|| 2);
248}
249"#,
250 r#"
251fn foo() {
252 let foo = Some(1);
253 return foo.unwrap_or(2);
254}
255"#,
256 )
257 }
258
259 #[test]
260 fn replace_or_else_with_or_call() {
261 check_assist(
262 replace_with_eager_method,
263 r#"
264//- minicore: option, fn
265fn foo() {
266 let foo = Some(1);
267 return foo.unwrap_$0or_else(x);
268}
269
270fn x() -> i32 { 0 }
271"#,
272 r#"
273fn foo() {
274 let foo = Some(1);
275 return foo.unwrap_or(x());
276}
277
278fn x() -> i32 { 0 }
279"#,
280 )
281 }
282
283 #[test]
284 fn replace_or_else_with_or_map() {
285 check_assist(
286 replace_with_eager_method,
287 r#"
288//- minicore: option, fn
289fn foo() {
290 let foo = Some("foo");
291 return foo.map$0_or_else(|| 42, |v| v.len());
292}
293"#,
294 r#"
295fn foo() {
296 let foo = Some("foo");
297 return foo.map_or(42, |v| v.len());
298}
299"#,
300 )
301 }
302}