1use either::Either;
2use ide_db::syntax_helpers::suggest_name;
3use syntax::{
4 ast::{self, AstNode, HasArgList, prec::ExprPrecedence, syntax_factory::SyntaxFactory},
5 syntax_editor::SyntaxEditor,
6};
7
8use crate::{
9 AssistContext, AssistId, Assists,
10 utils::{cover_let_chain, wrap_paren, wrap_paren_in_call},
11};
12
13pub(crate) fn replace_is_method_with_if_let_method(
31 acc: &mut Assists,
32 ctx: &AssistContext<'_, '_>,
33) -> Option<()> {
34 let has_cond = ctx.find_node_at_offset::<Either<ast::IfExpr, ast::WhileExpr>>()?;
35
36 let cond = either::for_both!(&has_cond, it => it.condition())?;
37 let cond = cover_let_chain(cond, ctx.selection_trimmed())?;
38 let call_expr = match cond {
39 ast::Expr::MethodCallExpr(call) => call,
40 _ => return None,
41 };
42
43 let token = call_expr.name_ref()?.ident_token()?;
44 let method_kind = token.text().strip_suffix("_and").unwrap_or(token.text());
45 match method_kind {
46 "is_some" | "is_ok" => {
47 let (editor, _) = SyntaxEditor::new(ctx.source_file().syntax().clone());
48 let make = editor.make();
49 let receiver = call_expr.receiver()?;
50 let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals(
51 ctx.sema.scope(has_cond.syntax()),
52 );
53 let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() {
54 name_generator.suggest_name(&path_expr.path()?.to_string())
55 } else {
56 name_generator.for_variable(&receiver, &ctx.sema)
57 };
58 let (pat, predicate) = method_predicate(&call_expr, &var_name, make);
59
60 let (assist_id, message, text) = if method_kind == "is_some" {
61 ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some")
62 } else {
63 ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok")
64 };
65
66 acc.add(
67 AssistId::refactor_rewrite(assist_id),
68 message,
69 call_expr.syntax().text_range(),
70 |edit| {
71 let make = editor.make();
72 let pat = make.tuple_struct_pat(make.ident_path(text), [pat]).into();
73 let let_expr = make.expr_let(pat, receiver);
74
75 if let Some(cap) = ctx.config.snippet_cap
76 && let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat()
77 && let Some(first_var) = pat.fields().next()
78 && predicate.is_none()
79 {
80 let placeholder = edit.make_placeholder_snippet(cap);
81 editor.add_annotation(first_var.syntax(), placeholder);
82 }
83
84 let new_expr = if let Some(predicate) = predicate {
85 let op = ast::BinaryOp::LogicOp(ast::LogicOp::And);
86 let predicate = wrap_paren(predicate, make, ExprPrecedence::LAnd);
87 make.expr_bin(let_expr.into(), op, predicate).into()
88 } else {
89 ast::Expr::from(let_expr)
90 };
91 editor.replace(call_expr.syntax(), new_expr.syntax());
92 edit.add_file_edits(ctx.vfs_file_id(), editor);
93 },
94 )
95 }
96 _ => None,
97 }
98}
99
100fn method_predicate(
101 call_expr: &ast::MethodCallExpr,
102 name: &str,
103 make: &SyntaxFactory,
104) -> (ast::Pat, Option<ast::Expr>) {
105 let argument = call_expr.arg_list().and_then(|it| it.args().next());
106 if let Some(ast::Expr::ClosureExpr(it)) = argument.clone()
107 && let Some(pat) = it.param_list().and_then(|it| it.params().next()?.pat())
108 {
109 (pat, it.body())
110 } else {
111 let pat = make.ident_pat(false, false, make.name(name));
112 let expr = argument.map(|expr| {
113 let arg_list = make.arg_list([make.expr_path(make.ident_path(name))]);
114 make.expr_call(wrap_paren_in_call(expr, make), arg_list).into()
115 });
116 (pat.into(), expr)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use crate::tests::{check_assist, check_assist_not_applicable};
123
124 use super::replace_is_method_with_if_let_method;
125
126 #[test]
127 fn replace_is_some_with_if_let_some_works() {
128 check_assist(
129 replace_is_method_with_if_let_method,
130 r#"
131fn main() {
132 let x = Some(1);
133 if x.is_som$0e() {}
134}
135"#,
136 r#"
137fn main() {
138 let x = Some(1);
139 if let Some(${0:x1}) = x {}
140}
141"#,
142 );
143
144 check_assist(
145 replace_is_method_with_if_let_method,
146 r#"
147fn test() -> Option<i32> {
148 Some(1)
149}
150fn main() {
151 if test().is_som$0e() {}
152}
153"#,
154 r#"
155fn test() -> Option<i32> {
156 Some(1)
157}
158fn main() {
159 if let Some(${0:test}) = test() {}
160}
161"#,
162 );
163 }
164
165 #[test]
166 fn replace_is_some_with_if_let_some_not_applicable() {
167 check_assist_not_applicable(
168 replace_is_method_with_if_let_method,
169 r#"
170fn main() {
171 let x = Some(1);
172 if x.is_non$0e() {}
173}
174"#,
175 );
176 }
177
178 #[test]
179 fn replace_is_ok_with_if_let_ok_works() {
180 check_assist(
181 replace_is_method_with_if_let_method,
182 r#"
183fn main() {
184 let x = Ok(1);
185 if x.is_o$0k() {}
186}
187"#,
188 r#"
189fn main() {
190 let x = Ok(1);
191 if let Ok(${0:x1}) = x {}
192}
193"#,
194 );
195
196 check_assist(
197 replace_is_method_with_if_let_method,
198 r#"
199fn test() -> Result<i32> {
200 Ok(1)
201}
202fn main() {
203 if test().is_o$0k() {}
204}
205"#,
206 r#"
207fn test() -> Result<i32> {
208 Ok(1)
209}
210fn main() {
211 if let Ok(${0:test}) = test() {}
212}
213"#,
214 );
215 }
216
217 #[test]
218 fn replace_is_ok_with_if_let_ok_not_applicable() {
219 check_assist_not_applicable(
220 replace_is_method_with_if_let_method,
221 r#"
222fn main() {
223 let x = Ok(1);
224 if x.is_e$0rr() {}
225}
226"#,
227 );
228 }
229
230 #[test]
231 fn replace_is_some_and_with_if_let_chain_some_works() {
232 check_assist(
233 replace_is_method_with_if_let_method,
234 r#"
235fn main() {
236 let x = Some(1);
237 if x.is_som$0e_and(|it| it != 3) {}
238}
239"#,
240 r#"
241fn main() {
242 let x = Some(1);
243 if let Some(it) = x && it != 3 {}
244}
245"#,
246 );
247
248 check_assist(
249 replace_is_method_with_if_let_method,
250 r#"
251fn main() {
252 let x = Some(1);
253 if x.is_som$0e_and(|it| it != 3 || it > 10) {}
254}
255"#,
256 r#"
257fn main() {
258 let x = Some(1);
259 if let Some(it) = x && (it != 3 || it > 10) {}
260}
261"#,
262 );
263
264 check_assist(
265 replace_is_method_with_if_let_method,
266 r#"
267fn main() {
268 let x = Some(1);
269 if x.is_som$0e_and(predicate) {}
270}
271"#,
272 r#"
273fn main() {
274 let x = Some(1);
275 if let Some(x1) = x && predicate(x1) {}
276}
277"#,
278 );
279
280 check_assist(
281 replace_is_method_with_if_let_method,
282 r#"
283fn main() {
284 let x = Some(1);
285 if x.is_som$0e_and(func.f) {}
286}
287"#,
288 r#"
289fn main() {
290 let x = Some(1);
291 if let Some(x1) = x && (func.f)(x1) {}
292}
293"#,
294 );
295 }
296
297 #[test]
298 fn replace_is_some_with_if_let_some_in_let_chain() {
299 check_assist(
300 replace_is_method_with_if_let_method,
301 r#"
302fn main() {
303 let x = Some(1);
304 let cond = true;
305 if cond && x.is_som$0e() {}
306}
307"#,
308 r#"
309fn main() {
310 let x = Some(1);
311 let cond = true;
312 if cond && let Some(${0:x1}) = x {}
313}
314"#,
315 );
316
317 check_assist(
318 replace_is_method_with_if_let_method,
319 r#"
320fn main() {
321 let x = Some(1);
322 let cond = true;
323 if x.is_som$0e() && cond {}
324}
325"#,
326 r#"
327fn main() {
328 let x = Some(1);
329 let cond = true;
330 if let Some(${0:x1}) = x && cond {}
331}
332"#,
333 );
334
335 check_assist(
336 replace_is_method_with_if_let_method,
337 r#"
338fn main() {
339 let x = Some(1);
340 let cond = true;
341 if cond && x.is_som$0e() && cond {}
342}
343"#,
344 r#"
345fn main() {
346 let x = Some(1);
347 let cond = true;
348 if cond && let Some(${0:x1}) = x && cond {}
349}
350"#,
351 );
352 }
353
354 #[test]
355 fn replace_is_some_with_while_let_some() {
356 check_assist(
357 replace_is_method_with_if_let_method,
358 r#"
359fn main() {
360 let mut x = Some(1);
361 while x.is_som$0e() { x = None }
362}
363"#,
364 r#"
365fn main() {
366 let mut x = Some(1);
367 while let Some(${0:x1}) = x { x = None }
368}
369"#,
370 );
371 }
372
373 #[test]
374 fn replace_is_some_with_if_let_some_not_applicable_after_l_curly() {
375 check_assist_not_applicable(
376 replace_is_method_with_if_let_method,
377 r#"
378fn main() {
379 let x = Some(1);
380 if x.is_some() {
381 ()$0
382 }
383}
384"#,
385 );
386 }
387}