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
11pub(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
118fn 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
155fn 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}