ide_assists/handlers/
replace_is_method_with_if_let_method.rs

1use either::Either;
2use ide_db::syntax_helpers::suggest_name;
3use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory};
4
5use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain};
6
7// Assist: replace_is_some_with_if_let_some
8//
9// Replace `if x.is_some()` with `if let Some(_tmp) = x` or `if x.is_ok()` with `if let Ok(_tmp) = x`.
10//
11// ```
12// fn main() {
13//     let x = Some(1);
14//     if x.is_som$0e() {}
15// }
16// ```
17// ->
18// ```
19// fn main() {
20//     let x = Some(1);
21//     if let Some(${0:x1}) = x {}
22// }
23// ```
24pub(crate) fn replace_is_method_with_if_let_method(
25    acc: &mut Assists,
26    ctx: &AssistContext<'_>,
27) -> Option<()> {
28    let has_cond = ctx.find_node_at_offset::<Either<ast::IfExpr, ast::WhileExpr>>()?;
29
30    let cond = either::for_both!(&has_cond, it => it.condition())?;
31    let cond = cover_let_chain(cond, ctx.selection_trimmed())?;
32    let call_expr = match cond {
33        ast::Expr::MethodCallExpr(call) => call,
34        _ => return None,
35    };
36
37    let name_ref = call_expr.name_ref()?;
38    match name_ref.text().as_str() {
39        "is_some" | "is_ok" => {
40            let receiver = call_expr.receiver()?;
41
42            let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals(
43                ctx.sema.scope(has_cond.syntax()),
44            );
45            let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() {
46                name_generator.suggest_name(&path_expr.path()?.to_string())
47            } else {
48                name_generator.for_variable(&receiver, &ctx.sema)
49            };
50
51            let (assist_id, message, text) = if name_ref.text() == "is_some" {
52                ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some")
53            } else {
54                ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok")
55            };
56
57            acc.add(
58                AssistId::refactor_rewrite(assist_id),
59                message,
60                call_expr.syntax().text_range(),
61                |edit| {
62                    let make = SyntaxFactory::with_mappings();
63                    let mut editor = edit.make_editor(call_expr.syntax());
64
65                    let var_pat = make.ident_pat(false, false, make.name(&var_name));
66                    let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat.into()]);
67                    let let_expr = make.expr_let(pat.into(), receiver);
68
69                    if let Some(cap) = ctx.config.snippet_cap
70                        && let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat()
71                        && let Some(first_var) = pat.fields().next()
72                    {
73                        let placeholder = edit.make_placeholder_snippet(cap);
74                        editor.add_annotation(first_var.syntax(), placeholder);
75                    }
76
77                    editor.replace(call_expr.syntax(), let_expr.syntax());
78                    editor.add_mappings(make.finish_with_mappings());
79                    edit.add_file_edits(ctx.vfs_file_id(), editor);
80                },
81            )
82        }
83        _ => None,
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use crate::tests::{check_assist, check_assist_not_applicable};
90
91    use super::replace_is_method_with_if_let_method;
92
93    #[test]
94    fn replace_is_some_with_if_let_some_works() {
95        check_assist(
96            replace_is_method_with_if_let_method,
97            r#"
98fn main() {
99    let x = Some(1);
100    if x.is_som$0e() {}
101}
102"#,
103            r#"
104fn main() {
105    let x = Some(1);
106    if let Some(${0:x1}) = x {}
107}
108"#,
109        );
110
111        check_assist(
112            replace_is_method_with_if_let_method,
113            r#"
114fn test() -> Option<i32> {
115    Some(1)
116}
117fn main() {
118    if test().is_som$0e() {}
119}
120"#,
121            r#"
122fn test() -> Option<i32> {
123    Some(1)
124}
125fn main() {
126    if let Some(${0:test}) = test() {}
127}
128"#,
129        );
130    }
131
132    #[test]
133    fn replace_is_some_with_if_let_some_not_applicable() {
134        check_assist_not_applicable(
135            replace_is_method_with_if_let_method,
136            r#"
137fn main() {
138    let x = Some(1);
139    if x.is_non$0e() {}
140}
141"#,
142        );
143    }
144
145    #[test]
146    fn replace_is_ok_with_if_let_ok_works() {
147        check_assist(
148            replace_is_method_with_if_let_method,
149            r#"
150fn main() {
151    let x = Ok(1);
152    if x.is_o$0k() {}
153}
154"#,
155            r#"
156fn main() {
157    let x = Ok(1);
158    if let Ok(${0:x1}) = x {}
159}
160"#,
161        );
162
163        check_assist(
164            replace_is_method_with_if_let_method,
165            r#"
166fn test() -> Result<i32> {
167    Ok(1)
168}
169fn main() {
170    if test().is_o$0k() {}
171}
172"#,
173            r#"
174fn test() -> Result<i32> {
175    Ok(1)
176}
177fn main() {
178    if let Ok(${0:test}) = test() {}
179}
180"#,
181        );
182    }
183
184    #[test]
185    fn replace_is_ok_with_if_let_ok_not_applicable() {
186        check_assist_not_applicable(
187            replace_is_method_with_if_let_method,
188            r#"
189fn main() {
190    let x = Ok(1);
191    if x.is_e$0rr() {}
192}
193"#,
194        );
195    }
196
197    #[test]
198    fn replace_is_some_with_if_let_some_in_let_chain() {
199        check_assist(
200            replace_is_method_with_if_let_method,
201            r#"
202fn main() {
203    let x = Some(1);
204    let cond = true;
205    if cond && x.is_som$0e() {}
206}
207"#,
208            r#"
209fn main() {
210    let x = Some(1);
211    let cond = true;
212    if cond && let Some(${0:x1}) = x {}
213}
214"#,
215        );
216
217        check_assist(
218            replace_is_method_with_if_let_method,
219            r#"
220fn main() {
221    let x = Some(1);
222    let cond = true;
223    if x.is_som$0e() && cond {}
224}
225"#,
226            r#"
227fn main() {
228    let x = Some(1);
229    let cond = true;
230    if let Some(${0:x1}) = x && cond {}
231}
232"#,
233        );
234
235        check_assist(
236            replace_is_method_with_if_let_method,
237            r#"
238fn main() {
239    let x = Some(1);
240    let cond = true;
241    if cond && x.is_som$0e() && cond {}
242}
243"#,
244            r#"
245fn main() {
246    let x = Some(1);
247    let cond = true;
248    if cond && let Some(${0:x1}) = x && cond {}
249}
250"#,
251        );
252    }
253
254    #[test]
255    fn replace_is_some_with_while_let_some() {
256        check_assist(
257            replace_is_method_with_if_let_method,
258            r#"
259fn main() {
260    let mut x = Some(1);
261    while x.is_som$0e() { x = None }
262}
263"#,
264            r#"
265fn main() {
266    let mut x = Some(1);
267    while let Some(${0:x1}) = x { x = None }
268}
269"#,
270        );
271    }
272
273    #[test]
274    fn replace_is_some_with_if_let_some_not_applicable_after_l_curly() {
275        check_assist_not_applicable(
276            replace_is_method_with_if_let_method,
277            r#"
278fn main() {
279    let x = Some(1);
280    if x.is_some() {
281        ()$0
282    }
283}
284"#,
285        );
286    }
287}