ide_assists/handlers/
convert_for_to_while_let.rs

1use hir::{Name, sym};
2use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name};
3use syntax::{
4    AstNode,
5    ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
6    syntax_editor::Position,
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11// Assist: convert_for_loop_to_while_let
12//
13// Converts a for loop into a while let on the Iterator.
14//
15// ```
16// fn main() {
17//     let x = vec![1, 2, 3];
18//     for$0 v in x {
19//         let y = v * 2;
20//     };
21// }
22// ```
23// ->
24// ```
25// fn main() {
26//     let x = vec![1, 2, 3];
27//     let mut tmp = x.into_iter();
28//     while let Some(v) = tmp.next() {
29//         let y = v * 2;
30//     };
31// }
32// ```
33pub(crate) fn convert_for_loop_to_while_let(
34    acc: &mut Assists,
35    ctx: &AssistContext<'_>,
36) -> Option<()> {
37    let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?;
38    let iterable = for_loop.iterable()?;
39    let pat = for_loop.pat()?;
40    let body = for_loop.loop_body()?;
41    if body.syntax().text_range().start() < ctx.offset() {
42        cov_mark::hit!(not_available_in_body);
43        return None;
44    }
45
46    acc.add(
47        AssistId::refactor_rewrite("convert_for_loop_to_while_let"),
48        "Replace this for loop with `while let`",
49        for_loop.syntax().text_range(),
50        |builder| {
51            let make = SyntaxFactory::with_mappings();
52            let mut editor = builder.make_editor(for_loop.syntax());
53
54            let (iterable, method) = if impls_core_iter(&ctx.sema, &iterable) {
55                (iterable, None)
56            } else if let Some((expr, method)) = is_ref_and_impls_iter_method(&ctx.sema, &iterable)
57            {
58                (expr, Some(make.name_ref(method.as_str())))
59            } else if let ast::Expr::RefExpr(_) = iterable {
60                (make::expr_paren(iterable).into(), Some(make.name_ref("into_iter")))
61            } else {
62                (iterable, Some(make.name_ref("into_iter")))
63            };
64
65            let iterable = if let Some(method) = method {
66                make::expr_method_call(iterable, method, make::arg_list([])).into()
67            } else {
68                iterable
69            };
70
71            let mut new_name = suggest_name::NameGenerator::new_from_scope_locals(
72                ctx.sema.scope(for_loop.syntax()),
73            );
74            let tmp_var = new_name.suggest_name("tmp");
75
76            let mut_expr = make.let_stmt(
77                make.ident_pat(false, true, make.name(&tmp_var)).into(),
78                None,
79                Some(iterable),
80            );
81            let indent = IndentLevel::from_node(for_loop.syntax());
82
83            if let Some(label) = for_loop.label() {
84                let label = label.syntax().clone_for_update();
85                editor.insert(Position::before(for_loop.syntax()), make.whitespace(" "));
86                editor.insert(Position::before(for_loop.syntax()), label);
87            }
88            crate::utils::insert_attributes(
89                for_loop.syntax(),
90                &mut editor,
91                for_loop.attrs().map(|it| it.clone_for_update()),
92            );
93
94            editor.insert(
95                Position::before(for_loop.syntax()),
96                make::tokens::whitespace(format!("\n{indent}").as_str()),
97            );
98            editor.insert(Position::before(for_loop.syntax()), mut_expr.syntax());
99
100            let opt_pat = make.tuple_struct_pat(make::ext::ident_path("Some"), [pat]);
101            let iter_next_expr = make.expr_method_call(
102                make.expr_path(make::ext::ident_path(&tmp_var)),
103                make.name_ref("next"),
104                make.arg_list([]),
105            );
106            let cond = make.expr_let(opt_pat.into(), iter_next_expr.into());
107
108            let while_loop = make.expr_while_loop(cond.into(), body);
109
110            editor.replace(for_loop.syntax(), while_loop.syntax());
111
112            editor.add_mappings(make.finish_with_mappings());
113            builder.add_file_edits(ctx.vfs_file_id(), editor);
114        },
115    )
116}
117
118/// If iterable is a reference where the expression behind the reference implements a method
119/// returning an Iterator called iter or iter_mut (depending on the type of reference) then return
120/// the expression behind the reference and the method name
121fn is_ref_and_impls_iter_method(
122    sema: &hir::Semantics<'_, ide_db::RootDatabase>,
123    iterable: &ast::Expr,
124) -> Option<(ast::Expr, hir::Name)> {
125    let ref_expr = match iterable {
126        ast::Expr::RefExpr(r) => r,
127        _ => return None,
128    };
129    let wanted_method = Name::new_symbol_root(if ref_expr.mut_token().is_some() {
130        sym::iter_mut
131    } else {
132        sym::iter
133    });
134    let expr_behind_ref = ref_expr.expr()?;
135    let ty = sema.type_of_expr(&expr_behind_ref)?.adjusted();
136    let scope = sema.scope(iterable.syntax())?;
137    let krate = scope.krate();
138    let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
139
140    let has_wanted_method = ty
141        .iterate_method_candidates(sema.db, &scope, Some(&wanted_method), |func| {
142            if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) {
143                return Some(());
144            }
145            None
146        })
147        .is_some();
148    if !has_wanted_method {
149        return None;
150    }
151
152    Some((expr_behind_ref, wanted_method))
153}
154
155/// Whether iterable implements core::Iterator
156fn impls_core_iter(sema: &hir::Semantics<'_, ide_db::RootDatabase>, iterable: &ast::Expr) -> bool {
157    (|| {
158        let it_typ = sema.type_of_expr(iterable)?.adjusted();
159
160        let module = sema.scope(iterable.syntax())?.module();
161
162        let krate = module.krate(sema.db);
163        let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
164        cov_mark::hit!(test_already_impls_iterator);
165        Some(it_typ.impls_trait(sema.db, iter_trait, &[]))
166    })()
167    .unwrap_or(false)
168}
169
170#[cfg(test)]
171mod tests {
172    use crate::tests::{check_assist, check_assist_not_applicable};
173
174    use super::*;
175
176    #[test]
177    fn each_to_for_simple_for() {
178        check_assist(
179            convert_for_loop_to_while_let,
180            r"
181fn main() {
182    let mut x = vec![1, 2, 3];
183    for $0v in x {
184        v *= 2;
185    };
186}",
187            r"
188fn main() {
189    let mut x = vec![1, 2, 3];
190    let mut tmp = x.into_iter();
191    while let Some(v) = tmp.next() {
192        v *= 2;
193    };
194}",
195        )
196    }
197
198    #[test]
199    fn each_to_for_with_label() {
200        check_assist(
201            convert_for_loop_to_while_let,
202            r"
203fn main() {
204    let mut x = vec![1, 2, 3];
205    'a: for $0v in x {
206        v *= 2;
207        break 'a;
208    };
209}",
210            r"
211fn main() {
212    let mut x = vec![1, 2, 3];
213    let mut tmp = x.into_iter();
214    'a: while let Some(v) = tmp.next() {
215        v *= 2;
216        break 'a;
217    };
218}",
219        )
220    }
221
222    #[test]
223    fn each_to_for_with_attributes() {
224        check_assist(
225            convert_for_loop_to_while_let,
226            r"
227fn main() {
228    let mut x = vec![1, 2, 3];
229    #[allow(unused)]
230    #[deny(unsafe_code)]
231    for $0v in x {
232        v *= 2;
233    };
234}",
235            r"
236fn main() {
237    let mut x = vec![1, 2, 3];
238    let mut tmp = x.into_iter();
239    #[allow(unused)]
240    #[deny(unsafe_code)]
241    while let Some(v) = tmp.next() {
242        v *= 2;
243    };
244}",
245        )
246    }
247
248    #[test]
249    fn each_to_for_for_in_range() {
250        check_assist(
251            convert_for_loop_to_while_let,
252            r#"
253//- minicore: range, iterators
254impl<T> core::iter::Iterator for core::ops::Range<T> {
255    type Item = T;
256
257    fn next(&mut self) -> Option<Self::Item> {
258        None
259    }
260}
261
262fn main() {
263    for $0x in 0..92 {
264        print!("{}", x);
265    }
266}"#,
267            r#"
268impl<T> core::iter::Iterator for core::ops::Range<T> {
269    type Item = T;
270
271    fn next(&mut self) -> Option<Self::Item> {
272        None
273    }
274}
275
276fn main() {
277    let mut tmp = 0..92;
278    while let Some(x) = tmp.next() {
279        print!("{}", x);
280    }
281}"#,
282        )
283    }
284
285    #[test]
286    fn each_to_for_not_available_in_body() {
287        cov_mark::check!(not_available_in_body);
288        check_assist_not_applicable(
289            convert_for_loop_to_while_let,
290            r"
291fn main() {
292    let mut x = vec![1, 2, 3];
293    for v in x {
294        $0v *= 2;
295    }
296}",
297        )
298    }
299
300    #[test]
301    fn each_to_for_for_borrowed() {
302        check_assist(
303            convert_for_loop_to_while_let,
304            r#"
305//- minicore: iterators
306use core::iter::{Repeat, repeat};
307
308struct S;
309impl S {
310    fn iter(&self) -> Repeat<i32> { repeat(92) }
311    fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
312}
313
314fn main() {
315    let x = S;
316    for $0v in &x {
317        let a = v * 2;
318    }
319}
320"#,
321            r#"
322use core::iter::{Repeat, repeat};
323
324struct S;
325impl S {
326    fn iter(&self) -> Repeat<i32> { repeat(92) }
327    fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
328}
329
330fn main() {
331    let x = S;
332    let mut tmp = x.iter();
333    while let Some(v) = tmp.next() {
334        let a = v * 2;
335    }
336}
337"#,
338        )
339    }
340
341    #[test]
342    fn each_to_for_for_borrowed_no_iter_method() {
343        check_assist(
344            convert_for_loop_to_while_let,
345            r"
346struct NoIterMethod;
347fn main() {
348    let x = NoIterMethod;
349    for $0v in &x {
350        let a = v * 2;
351    }
352}
353",
354            r"
355struct NoIterMethod;
356fn main() {
357    let x = NoIterMethod;
358    let mut tmp = (&x).into_iter();
359    while let Some(v) = tmp.next() {
360        let a = v * 2;
361    }
362}
363",
364        )
365    }
366
367    #[test]
368    fn each_to_for_for_borrowed_no_iter_method_mut() {
369        check_assist(
370            convert_for_loop_to_while_let,
371            r"
372struct NoIterMethod;
373fn main() {
374    let x = NoIterMethod;
375    for $0v in &mut x {
376        let a = v * 2;
377    }
378}
379",
380            r"
381struct NoIterMethod;
382fn main() {
383    let x = NoIterMethod;
384    let mut tmp = (&mut x).into_iter();
385    while let Some(v) = tmp.next() {
386        let a = v * 2;
387    }
388}
389",
390        )
391    }
392
393    #[test]
394    fn each_to_for_for_borrowed_mut() {
395        check_assist(
396            convert_for_loop_to_while_let,
397            r#"
398//- minicore: iterators
399use core::iter::{Repeat, repeat};
400
401struct S;
402impl S {
403    fn iter(&self) -> Repeat<i32> { repeat(92) }
404    fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
405}
406
407fn main() {
408    let x = S;
409    for $0v in &mut x {
410        let a = v * 2;
411    }
412}
413"#,
414            r#"
415use core::iter::{Repeat, repeat};
416
417struct S;
418impl S {
419    fn iter(&self) -> Repeat<i32> { repeat(92) }
420    fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
421}
422
423fn main() {
424    let x = S;
425    let mut tmp = x.iter_mut();
426    while let Some(v) = tmp.next() {
427        let a = v * 2;
428    }
429}
430"#,
431        )
432    }
433
434    #[test]
435    fn each_to_for_for_borrowed_mut_behind_var() {
436        check_assist(
437            convert_for_loop_to_while_let,
438            r"
439fn main() {
440    let mut x = vec![1, 2, 3];
441    let y = &mut x;
442    for $0v in y {
443        *v *= 2;
444    }
445}",
446            r"
447fn main() {
448    let mut x = vec![1, 2, 3];
449    let y = &mut x;
450    let mut tmp = y.into_iter();
451    while let Some(v) = tmp.next() {
452        *v *= 2;
453    }
454}",
455        )
456    }
457
458    #[test]
459    fn each_to_for_already_impls_iterator() {
460        cov_mark::check!(test_already_impls_iterator);
461        check_assist(
462            convert_for_loop_to_while_let,
463            r#"
464//- minicore: iterators
465fn main() {
466    for$0 a in core::iter::repeat(92).take(1) {
467        println!("{}", a);
468    }
469}
470"#,
471            r#"
472fn main() {
473    let mut tmp = core::iter::repeat(92).take(1);
474    while let Some(a) = tmp.next() {
475        println!("{}", a);
476    }
477}
478"#,
479        );
480    }
481}