Skip to main content

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