Skip to content

Commit 1974d11

Browse files
committed
Fix loses label for convert_for_to_while_let
Fixes: - loses label for `convert_while_to_loop` and `convert_for_to_while_let` - loses attributes for `convert_while_to_loop` and `convert_for_to_while_let` - bad indent for `convert_while_to_loop` Examples --- ```rust fn main() { #[allow(unused)] #[deny(unsafe_code)] 'a: while$0 let Some(x) = cond { foo(); break 'a; } } ``` **Before this PR**: ```rust fn main() { loop { if let Some(x) = cond { foo(); break 'a; } else { break; } } } ``` **After this PR**: ```rust fn main() { #[allow(unused)] #[deny(unsafe_code)] 'a: loop { if let Some(x) = cond { foo(); break 'a; } else { break; } } } ``` --- ```rust fn main() { let mut x = vec![1, 2, 3]; #[allow(unused)] #[deny(unsafe_code)] 'a: for $0v in x { v *= 2; break 'a; }; } ``` **Before this PR**: ```rust fn main() { let mut x = vec![1, 2, 3]; let mut tmp = x.into_iter(); while let Some(v) = tmp.next() { v *= 2; break 'a; }; } ``` **After this PR**: ```rust fn main() { let mut x = vec![1, 2, 3]; let mut tmp = x.into_iter(); #[allow(unused)] #[deny(unsafe_code)] 'a: while let Some(v) = tmp.next() { v *= 2; break 'a; }; } ```
1 parent 7de38d3 commit 1974d11

File tree

4 files changed

+292
-27
lines changed

4 files changed

+292
-27
lines changed

src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
use hir::{
2-
Name,
3-
sym::{self},
4-
};
1+
use hir::{Name, sym};
52
use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name};
63
use syntax::{
74
AstNode,
8-
ast::{self, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
5+
ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
96
syntax_editor::Position,
107
};
118

@@ -82,6 +79,18 @@ pub(crate) fn convert_for_loop_to_while_let(
8279
Some(iterable),
8380
);
8481
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+
8594
editor.insert(
8695
Position::before(for_loop.syntax()),
8796
make::tokens::whitespace(format!("\n{indent}").as_str()),
@@ -186,6 +195,56 @@ fn main() {
186195
)
187196
}
188197

198+
#[test]
199+
fn each_to_for_with_label() {
200+
check_assist(
201+
convert_for_loop_to_while_let,
202+
r"
203+
fn 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"
211+
fn 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"
227+
fn 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"
236+
fn 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+
189248
#[test]
190249
fn each_to_for_for_in_range() {
191250
check_assist(

src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_while_to_loop.rs

Lines changed: 204 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::iter;
22

3-
use either::Either;
43
use ide_db::syntax_helpers::node_ext::is_pattern_cond;
54
use syntax::{
65
AstNode, T,
@@ -9,6 +8,7 @@ use syntax::{
98
edit::{AstNodeEdit, IndentLevel},
109
make,
1110
},
11+
syntax_editor::{Element, Position},
1212
};
1313

1414
use crate::{
@@ -44,43 +44,53 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
4444
let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
4545
let while_body = while_expr.loop_body()?;
4646
let while_cond = while_expr.condition()?;
47+
let l_curly = while_body.stmt_list()?.l_curly_token()?;
4748

4849
let target = while_expr.syntax().text_range();
4950
acc.add(
5051
AssistId::refactor_rewrite("convert_while_to_loop"),
5152
"Convert while to loop",
5253
target,
53-
|edit| {
54+
|builder| {
55+
let mut edit = builder.make_editor(while_expr.syntax());
5456
let while_indent_level = IndentLevel::from_node(while_expr.syntax());
5557

5658
let break_block = make::block_expr(
5759
iter::once(make::expr_stmt(make::expr_break(None, None)).into()),
5860
None,
5961
)
60-
.indent(while_indent_level);
61-
let block_expr = if is_pattern_cond(while_cond.clone()) {
62-
let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
62+
.indent(IndentLevel(1));
63+
64+
edit.replace_all(
65+
while_kw.syntax_element()..=while_cond.syntax().syntax_element(),
66+
vec![make::token(T![loop]).syntax_element()],
67+
);
68+
69+
if is_pattern_cond(while_cond.clone()) {
70+
let then_branch = while_body.reset_indent().indent(IndentLevel(1));
71+
let if_expr = make::expr_if(while_cond, then_branch, Some(break_block.into()));
6372
let stmts = iter::once(make::expr_stmt(if_expr.into()).into());
64-
make::block_expr(stmts, None)
73+
let block_expr = make::block_expr(stmts, None);
74+
edit.replace(while_body.syntax(), block_expr.indent(while_indent_level).syntax());
6575
} else {
6676
let if_cond = invert_boolean_expression_legacy(while_cond);
67-
let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into();
68-
let elements = while_body.stmt_list().map_or_else(
69-
|| Either::Left(iter::empty()),
70-
|stmts| {
71-
Either::Right(stmts.syntax().children_with_tokens().filter(|node_or_tok| {
72-
// Filter out the trailing expr
73-
!node_or_tok
74-
.as_node()
75-
.is_some_and(|node| ast::Expr::can_cast(node.kind()))
76-
}))
77-
},
77+
let if_expr = make::expr_if(if_cond, break_block, None).indent(while_indent_level);
78+
if !while_body.syntax().text().contains_char('\n') {
79+
edit.insert(
80+
Position::after(&l_curly),
81+
make::tokens::whitespace(&format!("\n{while_indent_level}")),
82+
);
83+
}
84+
edit.insert_all(
85+
Position::after(&l_curly),
86+
vec![
87+
make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(),
88+
if_expr.syntax().syntax_element(),
89+
],
7890
);
79-
make::hacky_block_expr(iter::once(if_expr).chain(elements), while_body.tail_expr())
8091
};
8192

82-
let replacement = make::expr_loop(block_expr.indent(while_indent_level));
83-
edit.replace(target, replacement.syntax().text())
93+
builder.add_file_edits(ctx.vfs_file_id(), edit);
8494
},
8595
)
8696
}
@@ -115,6 +125,110 @@ fn main() {
115125
);
116126
}
117127

128+
#[test]
129+
fn convert_with_label() {
130+
check_assist(
131+
convert_while_to_loop,
132+
r#"
133+
fn main() {
134+
'x: while$0 cond {
135+
foo();
136+
break 'x
137+
}
138+
}
139+
"#,
140+
r#"
141+
fn main() {
142+
'x: loop {
143+
if !cond {
144+
break;
145+
}
146+
foo();
147+
break 'x
148+
}
149+
}
150+
"#,
151+
);
152+
153+
check_assist(
154+
convert_while_to_loop,
155+
r#"
156+
fn main() {
157+
'x: while$0 let Some(x) = cond {
158+
foo();
159+
break 'x
160+
}
161+
}
162+
"#,
163+
r#"
164+
fn main() {
165+
'x: loop {
166+
if let Some(x) = cond {
167+
foo();
168+
break 'x
169+
} else {
170+
break;
171+
}
172+
}
173+
}
174+
"#,
175+
);
176+
}
177+
178+
#[test]
179+
fn convert_with_attributes() {
180+
check_assist(
181+
convert_while_to_loop,
182+
r#"
183+
fn main() {
184+
#[allow(unused)]
185+
while$0 cond {
186+
foo();
187+
break 'x
188+
}
189+
}
190+
"#,
191+
r#"
192+
fn main() {
193+
#[allow(unused)]
194+
loop {
195+
if !cond {
196+
break;
197+
}
198+
foo();
199+
break 'x
200+
}
201+
}
202+
"#,
203+
);
204+
205+
check_assist(
206+
convert_while_to_loop,
207+
r#"
208+
fn main() {
209+
#[allow(unused)]
210+
#[deny(unsafe_code)]
211+
while$0 let Some(x) = cond {
212+
foo();
213+
}
214+
}
215+
"#,
216+
r#"
217+
fn main() {
218+
#[allow(unused)]
219+
#[deny(unsafe_code)]
220+
loop {
221+
if let Some(x) = cond {
222+
foo();
223+
} else {
224+
break;
225+
}
226+
}
227+
}
228+
"#,
229+
);
230+
}
231+
118232
#[test]
119233
fn convert_busy_wait() {
120234
check_assist(
@@ -185,6 +299,76 @@ fn main() {
185299
);
186300
}
187301

302+
#[test]
303+
fn indentation() {
304+
check_assist(
305+
convert_while_to_loop,
306+
r#"
307+
fn main() {
308+
{
309+
{
310+
while$0 cond {
311+
foo(
312+
"xxx",
313+
);
314+
}
315+
}
316+
}
317+
}
318+
"#,
319+
r#"
320+
fn main() {
321+
{
322+
{
323+
loop {
324+
if !cond {
325+
break;
326+
}
327+
foo(
328+
"xxx",
329+
);
330+
}
331+
}
332+
}
333+
}
334+
"#,
335+
);
336+
337+
check_assist(
338+
convert_while_to_loop,
339+
r#"
340+
fn main() {
341+
{
342+
{
343+
while$0 let Some(_) = foo() {
344+
bar(
345+
"xxx",
346+
);
347+
}
348+
}
349+
}
350+
}
351+
"#,
352+
r#"
353+
fn main() {
354+
{
355+
{
356+
loop {
357+
if let Some(_) = foo() {
358+
bar(
359+
"xxx",
360+
);
361+
} else {
362+
break;
363+
}
364+
}
365+
}
366+
}
367+
}
368+
"#,
369+
);
370+
}
371+
188372
#[test]
189373
fn ignore_cursor_in_body() {
190374
check_assist_not_applicable(

0 commit comments

Comments
 (0)