Skip to content

Commit 1f7625d

Browse files
Merge pull request #21514 from A4-Tacks/range-for-to-while-handle-continue
fix: Fix incorrect continue for convert_range_for_to_while
2 parents 93b28dd + 3a842dc commit 1f7625d

1 file changed

Lines changed: 151 additions & 6 deletions

File tree

crates/ide-assists/src/handlers/convert_range_for_to_while.rs

Lines changed: 151 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use ide_db::assists::AssistId;
22
use itertools::Itertools;
33
use syntax::{
4-
AstNode, T,
4+
AstNode, SyntaxElement,
5+
SyntaxKind::WHITESPACE,
6+
T,
57
algo::previous_non_trivia_token,
68
ast::{
79
self, HasArgList, HasLoopBody, HasName, RangeItem, edit::AstNodeEdit, make,
810
syntax_factory::SyntaxFactory,
911
},
10-
syntax_editor::{Element, Position},
12+
syntax_editor::{Element, Position, SyntaxEditor},
1113
};
1214

1315
use crate::assist_context::{AssistContext, Assists};
@@ -40,8 +42,8 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext<
4042
let iterable = for_.iterable()?;
4143
let (start, end, step, inclusive) = extract_range(&iterable)?;
4244
let name = pat.name()?;
43-
let body = for_.loop_body()?;
44-
let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?;
45+
let body = for_.loop_body()?.stmt_list()?;
46+
let label = for_.label();
4547

4648
let description = if end.is_some() {
4749
"Replace with while expression"
@@ -90,8 +92,10 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext<
9092
);
9193

9294
let op = ast::BinaryOp::Assignment { op: Some(ast::ArithOp::Add) };
93-
edit.insert_all(
94-
Position::after(last),
95+
process_loop_body(
96+
body,
97+
label,
98+
&mut edit,
9599
vec![
96100
make.whitespace(&format!("\n{}", indent + 1)).syntax_element(),
97101
make.expr_bin(var_expr, op, step).syntax().syntax_element(),
@@ -121,6 +125,86 @@ fn extract_range(iterable: &ast::Expr) -> Option<(ast::Expr, Option<ast::Expr>,
121125
})
122126
}
123127

128+
fn process_loop_body(
129+
body: ast::StmtList,
130+
label: Option<ast::Label>,
131+
edit: &mut SyntaxEditor,
132+
incrementer: Vec<SyntaxElement>,
133+
) -> Option<()> {
134+
let last = previous_non_trivia_token(body.r_curly_token()?)?.syntax_element();
135+
136+
let new_body = body.indent(1.into()).clone_subtree();
137+
let mut continues = vec![];
138+
collect_continue_to(
139+
&mut continues,
140+
&label.and_then(|it| it.lifetime()),
141+
new_body.syntax(),
142+
false,
143+
);
144+
145+
if continues.is_empty() {
146+
edit.insert_all(Position::after(last), incrementer);
147+
return Some(());
148+
}
149+
150+
let mut children = body
151+
.syntax()
152+
.children_with_tokens()
153+
.filter(|it| !matches!(it.kind(), WHITESPACE | T!['{'] | T!['}']));
154+
let first = children.next()?;
155+
let block_content = first.clone()..=children.last().unwrap_or(first);
156+
157+
let continue_label = make::lifetime("'cont");
158+
let break_expr = make::expr_break(Some(continue_label.clone()), None).clone_for_update();
159+
let mut new_edit = SyntaxEditor::new(new_body.syntax().clone());
160+
for continue_expr in &continues {
161+
new_edit.replace(continue_expr.syntax(), break_expr.syntax());
162+
}
163+
let new_body = new_edit.finish().new_root().clone();
164+
let elements = itertools::chain(
165+
[
166+
continue_label.syntax().clone_for_update().syntax_element(),
167+
make::token(T![:]).syntax_element(),
168+
make::tokens::single_space().syntax_element(),
169+
new_body.syntax_element(),
170+
],
171+
incrementer,
172+
);
173+
edit.replace_all(block_content, elements.collect());
174+
175+
Some(())
176+
}
177+
178+
fn collect_continue_to(
179+
acc: &mut Vec<ast::ContinueExpr>,
180+
label: &Option<ast::Lifetime>,
181+
node: &syntax::SyntaxNode,
182+
only_label: bool,
183+
) {
184+
let match_label = |it: &Option<ast::Lifetime>, label: &Option<ast::Lifetime>| match (it, label)
185+
{
186+
(None, _) => !only_label,
187+
(Some(a), Some(b)) if a.text() == b.text() => true,
188+
_ => false,
189+
};
190+
if let Some(expr) = ast::ContinueExpr::cast(node.clone())
191+
&& match_label(&expr.lifetime(), label)
192+
{
193+
acc.push(expr);
194+
} else if let Some(any_loop) = ast::AnyHasLoopBody::cast(node.clone()) {
195+
if match_label(label, &any_loop.label().and_then(|it| it.lifetime())) {
196+
return;
197+
}
198+
for children in node.children() {
199+
collect_continue_to(acc, label, &children, true);
200+
}
201+
} else {
202+
for children in node.children() {
203+
collect_continue_to(acc, label, &children, only_label);
204+
}
205+
}
206+
}
207+
124208
#[cfg(test)]
125209
mod tests {
126210
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -219,6 +303,67 @@ fn foo() {
219303
);
220304
}
221305

306+
#[test]
307+
fn test_convert_range_for_to_while_with_continue() {
308+
check_assist(
309+
convert_range_for_to_while,
310+
"
311+
fn foo() {
312+
$0for mut i in 3..7 {
313+
foo(i);
314+
continue;
315+
loop { break; continue }
316+
bar(i);
317+
}
318+
}
319+
",
320+
"
321+
fn foo() {
322+
let mut i = 3;
323+
while i < 7 {
324+
'cont: {
325+
foo(i);
326+
break 'cont;
327+
loop { break; continue }
328+
bar(i);
329+
}
330+
i += 1;
331+
}
332+
}
333+
",
334+
);
335+
336+
check_assist(
337+
convert_range_for_to_while,
338+
"
339+
fn foo() {
340+
'x: $0for mut i in 3..7 {
341+
foo(i);
342+
continue 'x;
343+
loop { break; continue 'x }
344+
'x: loop { continue 'x }
345+
bar(i);
346+
}
347+
}
348+
",
349+
"
350+
fn foo() {
351+
let mut i = 3;
352+
'x: while i < 7 {
353+
'cont: {
354+
foo(i);
355+
break 'cont;
356+
loop { break; break 'cont }
357+
'x: loop { continue 'x }
358+
bar(i);
359+
}
360+
i += 1;
361+
}
362+
}
363+
",
364+
);
365+
}
366+
222367
#[test]
223368
fn test_convert_range_for_to_while_step_by() {
224369
check_assist(

0 commit comments

Comments
 (0)