Skip to content

Commit c0459c5

Browse files
bors[bot]saiintbrissonVeykril
authored
Merge #7956
7956: Add assist to convert for_each into for loops r=Veykril a=SaiintBrisson This PR resolves #7821. Adds an assist to that converts an `Iterator::for_each` into a for loop: ```rust fn main() { let vec = vec![(1, 2), (2, 3), (3, 4)]; x.iter().for_each(|(x, y)| { println!("x: {}, y: {}", x, y); }) } ``` becomes ```rust fn main() { let vec = vec![(1, 2), (2, 3), (3, 4)]; for (x, y) in x.iter() { println!("x: {}, y: {}", x, y); }); } ``` Co-authored-by: Luiz Carlos Mourão Paes de Carvalho <[email protected]> Co-authored-by: Luiz Carlos <[email protected]> Co-authored-by: Lukas Wirth <[email protected]>
2 parents 19dd1fd + 6d35c67 commit c0459c5

File tree

4 files changed

+283
-0
lines changed

4 files changed

+283
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use ide_db::helpers::FamousDefs;
2+
use syntax::{
3+
ast::{self, edit::AstNodeEdit, make, ArgListOwner},
4+
AstNode,
5+
};
6+
7+
use crate::{AssistContext, AssistId, AssistKind, Assists};
8+
9+
// Assist: convert_iter_for_each_to_for
10+
//
11+
// Converts an Iterator::for_each function into a for loop.
12+
//
13+
// ```
14+
// # //- /lib.rs crate:core
15+
// # pub mod iter { pub mod traits { pub mod iterator { pub trait Iterator {} } } }
16+
// # pub struct SomeIter;
17+
// # impl self::iter::traits::iterator::Iterator for SomeIter {}
18+
// # //- /lib.rs crate:main deps:core
19+
// # use core::SomeIter;
20+
// fn main() {
21+
// let iter = SomeIter;
22+
// iter.for_each$0(|(x, y)| {
23+
// println!("x: {}, y: {}", x, y);
24+
// });
25+
// }
26+
// ```
27+
// ->
28+
// ```
29+
// # use core::SomeIter;
30+
// fn main() {
31+
// let iter = SomeIter;
32+
// for (x, y) in iter {
33+
// println!("x: {}, y: {}", x, y);
34+
// }
35+
// }
36+
// ```
37+
38+
pub(crate) fn convert_iter_for_each_to_for(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39+
let method = ctx.find_node_at_offset::<ast::MethodCallExpr>()?;
40+
41+
let closure = match method.arg_list()?.args().next()? {
42+
ast::Expr::ClosureExpr(expr) => expr,
43+
_ => return None,
44+
};
45+
46+
let (method, receiver) = validate_method_call_expr(ctx, method)?;
47+
48+
let param_list = closure.param_list()?;
49+
let param = param_list.params().next()?.pat()?;
50+
let body = closure.body()?;
51+
52+
let stmt = method.syntax().parent().and_then(ast::ExprStmt::cast);
53+
let syntax = stmt.as_ref().map_or(method.syntax(), |stmt| stmt.syntax());
54+
55+
acc.add(
56+
AssistId("convert_iter_for_each_to_for", AssistKind::RefactorRewrite),
57+
"Replace this `Iterator::for_each` with a for loop",
58+
syntax.text_range(),
59+
|builder| {
60+
let indent = stmt.as_ref().map_or(method.indent_level(), |stmt| stmt.indent_level());
61+
62+
let block = match body {
63+
ast::Expr::BlockExpr(block) => block,
64+
_ => make::block_expr(Vec::new(), Some(body)),
65+
}
66+
.reset_indent()
67+
.indent(indent);
68+
69+
let expr_for_loop = make::expr_for_loop(param, receiver, block);
70+
builder.replace(syntax.text_range(), expr_for_loop.syntax().text())
71+
},
72+
)
73+
}
74+
75+
fn validate_method_call_expr(
76+
ctx: &AssistContext,
77+
expr: ast::MethodCallExpr,
78+
) -> Option<(ast::Expr, ast::Expr)> {
79+
let name_ref = expr.name_ref()?;
80+
if name_ref.syntax().text_range().intersect(ctx.frange.range).is_none()
81+
|| name_ref.text() != "for_each"
82+
{
83+
return None;
84+
}
85+
86+
let sema = &ctx.sema;
87+
88+
let receiver = expr.receiver()?;
89+
let expr = ast::Expr::MethodCallExpr(expr);
90+
91+
let it_type = sema.type_of_expr(&receiver)?;
92+
let module = sema.scope(receiver.syntax()).module()?;
93+
let krate = module.krate();
94+
95+
let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?;
96+
it_type.impls_trait(sema.db, iter_trait, &[]).then(|| (expr, receiver))
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use crate::tests::{self, check_assist};
102+
103+
use super::*;
104+
105+
const EMPTY_ITER_FIXTURE: &'static str = r"
106+
//- /lib.rs deps:core crate:empty_iter
107+
pub struct EmptyIter;
108+
impl Iterator for EmptyIter {
109+
type Item = usize;
110+
fn next(&mut self) -> Option<Self::Item> { None }
111+
}
112+
pub struct Empty;
113+
impl Empty {
114+
pub fn iter(&self) -> EmptyIter { EmptyIter }
115+
}
116+
";
117+
118+
fn check_assist_with_fixtures(before: &str, after: &str) {
119+
let before = &format!(
120+
"//- /main.rs crate:main deps:core,empty_iter{}{}{}",
121+
before,
122+
EMPTY_ITER_FIXTURE,
123+
FamousDefs::FIXTURE,
124+
);
125+
check_assist(convert_iter_for_each_to_for, before, after);
126+
}
127+
128+
fn check_assist_not_applicable(before: &str) {
129+
let before = &format!(
130+
"//- /main.rs crate:main deps:core,empty_iter{}{}{}",
131+
before,
132+
EMPTY_ITER_FIXTURE,
133+
FamousDefs::FIXTURE,
134+
);
135+
tests::check_assist_not_applicable(convert_iter_for_each_to_for, before);
136+
}
137+
138+
#[test]
139+
fn test_for_each_in_method_stmt() {
140+
check_assist_with_fixtures(
141+
r#"
142+
use empty_iter::*;
143+
fn main() {
144+
let x = Empty;
145+
x.iter().$0for_each(|(x, y)| {
146+
println!("x: {}, y: {}", x, y);
147+
});
148+
}"#,
149+
r#"
150+
use empty_iter::*;
151+
fn main() {
152+
let x = Empty;
153+
for (x, y) in x.iter() {
154+
println!("x: {}, y: {}", x, y);
155+
}
156+
}
157+
"#,
158+
)
159+
}
160+
161+
#[test]
162+
fn test_for_each_in_method() {
163+
check_assist_with_fixtures(
164+
r#"
165+
use empty_iter::*;
166+
fn main() {
167+
let x = Empty;
168+
x.iter().$0for_each(|(x, y)| {
169+
println!("x: {}, y: {}", x, y);
170+
})
171+
}"#,
172+
r#"
173+
use empty_iter::*;
174+
fn main() {
175+
let x = Empty;
176+
for (x, y) in x.iter() {
177+
println!("x: {}, y: {}", x, y);
178+
}
179+
}
180+
"#,
181+
)
182+
}
183+
184+
#[test]
185+
fn test_for_each_in_iter_stmt() {
186+
check_assist_with_fixtures(
187+
r#"
188+
use empty_iter::*;
189+
fn main() {
190+
let x = Empty.iter();
191+
x.$0for_each(|(x, y)| {
192+
println!("x: {}, y: {}", x, y);
193+
});
194+
}"#,
195+
r#"
196+
use empty_iter::*;
197+
fn main() {
198+
let x = Empty.iter();
199+
for (x, y) in x {
200+
println!("x: {}, y: {}", x, y);
201+
}
202+
}
203+
"#,
204+
)
205+
}
206+
207+
#[test]
208+
fn test_for_each_without_braces_stmt() {
209+
check_assist_with_fixtures(
210+
r#"
211+
use empty_iter::*;
212+
fn main() {
213+
let x = Empty;
214+
x.iter().$0for_each(|(x, y)| println!("x: {}, y: {}", x, y));
215+
}"#,
216+
r#"
217+
use empty_iter::*;
218+
fn main() {
219+
let x = Empty;
220+
for (x, y) in x.iter() {
221+
println!("x: {}, y: {}", x, y)
222+
}
223+
}
224+
"#,
225+
)
226+
}
227+
228+
#[test]
229+
fn test_for_each_not_applicable() {
230+
check_assist_not_applicable(
231+
r#"
232+
fn main() {
233+
().$0for_each(|x| println!("{}", x));
234+
}"#,
235+
)
236+
}
237+
238+
#[test]
239+
fn test_for_each_not_applicable_invalid_cursor_pos() {
240+
check_assist_not_applicable(
241+
r#"
242+
use empty_iter::*;
243+
fn main() {
244+
Empty.iter().for_each(|(x, y)| $0println!("x: {}, y: {}", x, y));
245+
}"#,
246+
)
247+
}
248+
}

crates/ide_assists/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ mod handlers {
116116
mod change_visibility;
117117
mod convert_integer_literal;
118118
mod convert_comment_block;
119+
mod convert_iter_for_each_to_for;
119120
mod early_return;
120121
mod expand_glob_import;
121122
mod extract_function;
@@ -181,6 +182,7 @@ mod handlers {
181182
change_visibility::change_visibility,
182183
convert_integer_literal::convert_integer_literal,
183184
convert_comment_block::convert_comment_block,
185+
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
184186
early_return::convert_to_guarded_return,
185187
expand_glob_import::expand_glob_import,
186188
extract_struct_from_enum_variant::extract_struct_from_enum_variant,

crates/ide_assists/src/tests/generated.rs

+30
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,36 @@ const _: i32 = 0b1010;
205205
)
206206
}
207207

208+
#[test]
209+
fn doctest_convert_iter_for_each_to_for() {
210+
check_doc_test(
211+
"convert_iter_for_each_to_for",
212+
r#####"
213+
//- /lib.rs crate:core
214+
pub mod iter { pub mod traits { pub mod iterator { pub trait Iterator {} } } }
215+
pub struct SomeIter;
216+
impl self::iter::traits::iterator::Iterator for SomeIter {}
217+
//- /lib.rs crate:main deps:core
218+
use core::SomeIter;
219+
fn main() {
220+
let iter = SomeIter;
221+
iter.for_each$0(|(x, y)| {
222+
println!("x: {}, y: {}", x, y);
223+
});
224+
}
225+
"#####,
226+
r#####"
227+
use core::SomeIter;
228+
fn main() {
229+
let iter = SomeIter;
230+
for (x, y) in iter {
231+
println!("x: {}, y: {}", x, y);
232+
}
233+
}
234+
"#####,
235+
)
236+
}
237+
208238
#[test]
209239
fn doctest_convert_to_guarded_return() {
210240
check_doc_test(

crates/syntax/src/ast/make.rs

+3
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ pub fn expr_if(
222222
};
223223
expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
224224
}
225+
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr {
226+
expr_from_text(&format!("for {} in {} {}", pat, expr, block))
227+
}
225228
pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
226229
let token = token(op);
227230
expr_from_text(&format!("{}{}", token, expr))

0 commit comments

Comments
 (0)