Skip to content

Commit 251da19

Browse files
committed
refactor: migrate let_else_to_match to editor
Signed-off-by: Prajwal S N <[email protected]>
1 parent c588273 commit 251da19

File tree

8 files changed

+338
-145
lines changed

8 files changed

+338
-145
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,8 @@ fn build_pat(
480480
hir::StructKind::Record => {
481481
let fields = fields
482482
.into_iter()
483-
.map(|f| make.name_ref(f.name(db).as_str()))
484-
.map(|name_ref| make.record_pat_field_shorthand(name_ref));
483+
.map(|f| make.ident_pat(false, false, make.name(f.name(db).as_str())))
484+
.map(|ident| make.record_pat_field_shorthand(ident.into()));
485485
let fields = make.record_pat_field_list(fields, None);
486486
make.record_pat_with_fields(path, fields).into()
487487
}

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

+175-124
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use hir::Semantics;
2-
use ide_db::RootDatabase;
31
use syntax::T;
42
use syntax::ast::RangeItem;
5-
use syntax::ast::{AstNode, HasName, LetStmt, Name, Pat, edit::AstNodeEdit};
3+
use syntax::ast::edit::IndentLevel;
4+
use syntax::ast::edit_in_place::Indent;
5+
use syntax::ast::syntax_factory::SyntaxFactory;
6+
use syntax::ast::{self, AstNode, HasName, LetStmt, Pat};
67

78
use crate::{AssistContext, AssistId, Assists};
89

@@ -25,155 +26,205 @@ use crate::{AssistContext, AssistId, Assists};
2526
// }
2627
// ```
2728
pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28-
// should focus on else token to trigger
29+
// Should focus on the `else` token to trigger
2930
let let_stmt = ctx
3031
.find_token_syntax_at_offset(T![else])
3132
.and_then(|it| it.parent()?.parent())
3233
.or_else(|| ctx.find_token_syntax_at_offset(T![let])?.parent())?;
3334
let let_stmt = LetStmt::cast(let_stmt)?;
34-
let let_else_block = let_stmt.let_else()?.block_expr()?;
35-
let let_init = let_stmt.initializer()?;
35+
let else_block = let_stmt.let_else()?.block_expr()?;
36+
let else_expr = if else_block.statements().next().is_none() {
37+
else_block.tail_expr()?
38+
} else {
39+
else_block.into()
40+
};
41+
let init = let_stmt.initializer()?;
42+
// Ignore let stmt with type annotation
3643
if let_stmt.ty().is_some() {
37-
// don't support let with type annotation
3844
return None;
3945
}
4046
let pat = let_stmt.pat()?;
41-
let mut binders = Vec::new();
42-
binders_in_pat(&mut binders, &pat, &ctx.sema)?;
4347

44-
let target = let_stmt.syntax().text_range();
48+
let make = SyntaxFactory::with_mappings();
49+
let mut idents = Vec::default();
50+
let pat_without_mut = remove_mut_and_collect_idents(&make, &pat, &mut idents)?;
51+
let bindings = idents
52+
.into_iter()
53+
.filter_map(|ref pat| {
54+
// Identifiers which resolve to constants are not bindings
55+
if ctx.sema.resolve_bind_pat_to_const(pat).is_none() {
56+
Some((pat.name()?, pat.ref_token().is_none() && pat.mut_token().is_some()))
57+
} else {
58+
None
59+
}
60+
})
61+
.collect::<Vec<_>>();
62+
4563
acc.add(
4664
AssistId::refactor_rewrite("convert_let_else_to_match"),
47-
"Convert let-else to let and match",
48-
target,
49-
|edit| {
50-
let indent_level = let_stmt.indent_level().0 as usize;
51-
let indent = " ".repeat(indent_level);
52-
let indent1 = " ".repeat(indent_level + 1);
65+
if bindings.is_empty() {
66+
"Convert let-else to match"
67+
} else {
68+
"Convert let-else to let and match"
69+
},
70+
let_stmt.syntax().text_range(),
71+
|builder| {
72+
let mut editor = builder.make_editor(let_stmt.syntax());
5373

54-
let binders_str = binders_to_str(&binders, false);
55-
let binders_str_mut = binders_to_str(&binders, true);
74+
let binding_paths = bindings
75+
.iter()
76+
.map(|(name, _)| make.expr_path(make.ident_path(&name.to_string())))
77+
.collect::<Vec<_>>();
5678

57-
let init_expr = let_init.syntax().text();
58-
let mut pat_no_mut = pat.syntax().text().to_string();
59-
// remove the mut from the pattern
60-
for (b, ismut) in binders.iter() {
61-
if *ismut {
62-
pat_no_mut = pat_no_mut.replace(&format!("mut {b}"), &b.to_string());
63-
}
64-
}
79+
let binding_arm = make.match_arm(
80+
pat_without_mut,
81+
None,
82+
// There are three possible cases:
83+
//
84+
// - No bindings: `None => {}`
85+
// - Single binding: `Some(it) => it`
86+
// - Multiple bindings: `Foo::Bar { a, b, .. } => (a, b)`
87+
match binding_paths.len() {
88+
0 => make.expr_empty_block().into(),
6589

66-
let only_expr = let_else_block.statements().next().is_none();
67-
let branch2 = match &let_else_block.tail_expr() {
68-
Some(tail) if only_expr => format!("{tail},"),
69-
_ => let_else_block.syntax().text().to_string(),
70-
};
71-
let replace = if binders.is_empty() {
72-
format!(
73-
"match {init_expr} {{
74-
{indent1}{pat_no_mut} => {binders_str}
75-
{indent1}_ => {branch2}
76-
{indent}}}"
77-
)
90+
1 => binding_paths[0].clone(),
91+
_ => make.expr_tuple(binding_paths).into(),
92+
},
93+
);
94+
let else_arm = make.match_arm(make.wildcard_pat().into(), None, else_expr);
95+
let match_ = make.expr_match(init, make.match_arm_list([binding_arm, else_arm]));
96+
match_.reindent_to(IndentLevel::from_node(let_stmt.syntax()));
97+
98+
if bindings.is_empty() {
99+
editor.replace(let_stmt.syntax(), match_.syntax());
78100
} else {
79-
format!(
80-
"let {binders_str_mut} = match {init_expr} {{
81-
{indent1}{pat_no_mut} => {binders_str},
82-
{indent1}_ => {branch2}
83-
{indent}}};"
84-
)
85-
};
86-
edit.replace(target, replace);
101+
let ident_pats = bindings
102+
.into_iter()
103+
.map(|(name, is_mut)| make.ident_pat(false, is_mut, name).into())
104+
.collect::<Vec<Pat>>();
105+
let new_let_stmt = make.let_stmt(
106+
if ident_pats.len() == 1 {
107+
ident_pats[0].clone()
108+
} else {
109+
make.tuple_pat(ident_pats).into()
110+
},
111+
None,
112+
Some(match_.into()),
113+
);
114+
editor.replace(let_stmt.syntax(), new_let_stmt.syntax());
115+
}
116+
117+
editor.add_mappings(make.finish_with_mappings());
118+
builder.add_file_edits(ctx.file_id(), editor);
87119
},
88120
)
89121
}
90122

91-
/// Gets a list of binders in a pattern, and whether they are mut.
92-
fn binders_in_pat(
93-
acc: &mut Vec<(Name, bool)>,
94-
pat: &Pat,
95-
sem: &Semantics<'_, RootDatabase>,
96-
) -> Option<()> {
97-
use Pat::*;
98-
match pat {
99-
IdentPat(p) => {
100-
let ident = p.name()?;
101-
let ismut = p.ref_token().is_none() && p.mut_token().is_some();
102-
// check for const reference
103-
if sem.resolve_bind_pat_to_const(p).is_none() {
104-
acc.push((ident, ismut));
105-
}
123+
fn remove_mut_and_collect_idents(
124+
make: &SyntaxFactory,
125+
pat: &ast::Pat,
126+
acc: &mut Vec<ast::IdentPat>,
127+
) -> Option<ast::Pat> {
128+
Some(match pat {
129+
ast::Pat::IdentPat(p) => {
130+
acc.push(p.clone());
131+
let non_mut_pat = make.ident_pat(
132+
p.ref_token().is_some(),
133+
p.ref_token().is_some() && p.mut_token().is_some(),
134+
p.name()?,
135+
);
106136
if let Some(inner) = p.pat() {
107-
binders_in_pat(acc, &inner, sem)?;
108-
}
109-
Some(())
110-
}
111-
BoxPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
112-
RestPat(_) | LiteralPat(_) | PathPat(_) | WildcardPat(_) | ConstBlockPat(_) => Some(()),
113-
OrPat(p) => {
114-
for p in p.pats() {
115-
binders_in_pat(acc, &p, sem)?;
137+
non_mut_pat.set_pat(remove_mut_and_collect_idents(make, &inner, acc));
116138
}
117-
Some(())
139+
non_mut_pat.into()
118140
}
119-
ParenPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
120-
RangePat(p) => {
121-
if let Some(st) = p.start() {
122-
binders_in_pat(acc, &st, sem)?
123-
}
124-
if let Some(ed) = p.end() {
125-
binders_in_pat(acc, &ed, sem)?
126-
}
127-
Some(())
128-
}
129-
RecordPat(p) => {
130-
for f in p.record_pat_field_list()?.fields() {
131-
let pat = f.pat()?;
132-
binders_in_pat(acc, &pat, sem)?;
133-
}
134-
Some(())
141+
ast::Pat::BoxPat(p) => {
142+
make.box_pat(remove_mut_and_collect_idents(make, &p.pat()?, acc)?).into()
135143
}
136-
RefPat(p) => p.pat().and_then(|p| binders_in_pat(acc, &p, sem)),
137-
SlicePat(p) => {
138-
for p in p.pats() {
139-
binders_in_pat(acc, &p, sem)?;
140-
}
141-
Some(())
142-
}
143-
TuplePat(p) => {
144-
for p in p.fields() {
145-
binders_in_pat(acc, &p, sem)?;
146-
}
147-
Some(())
144+
ast::Pat::OrPat(p) => make
145+
.or_pat(
146+
p.pats()
147+
.map(|pat| remove_mut_and_collect_idents(make, &pat, acc))
148+
.collect::<Option<Vec<_>>>()?,
149+
p.leading_pipe().is_some(),
150+
)
151+
.into(),
152+
ast::Pat::ParenPat(p) => {
153+
make.paren_pat(remove_mut_and_collect_idents(make, &p.pat()?, acc)?).into()
148154
}
149-
TupleStructPat(p) => {
150-
for p in p.fields() {
151-
binders_in_pat(acc, &p, sem)?;
155+
ast::Pat::RangePat(p) => make
156+
.range_pat(
157+
if let Some(start) = p.start() {
158+
Some(remove_mut_and_collect_idents(make, &start, acc)?)
159+
} else {
160+
None
161+
},
162+
if let Some(end) = p.end() {
163+
Some(remove_mut_and_collect_idents(make, &end, acc)?)
164+
} else {
165+
None
166+
},
167+
)
168+
.into(),
169+
ast::Pat::RecordPat(p) => make
170+
.record_pat_with_fields(
171+
p.path()?,
172+
make.record_pat_field_list(
173+
p.record_pat_field_list()?
174+
.fields()
175+
.map(|field| {
176+
remove_mut_and_collect_idents(make, &field.pat()?, acc).map(|pat| {
177+
if let Some(name_ref) = field.name_ref() {
178+
make.record_pat_field(name_ref, pat)
179+
} else {
180+
make.record_pat_field_shorthand(pat)
181+
}
182+
})
183+
})
184+
.collect::<Option<Vec<_>>>()?,
185+
p.record_pat_field_list()?.rest_pat(),
186+
),
187+
)
188+
.into(),
189+
ast::Pat::RefPat(p) => {
190+
let inner = p.pat()?;
191+
if let ast::Pat::IdentPat(ident) = inner {
192+
acc.push(ident);
193+
p.clone_for_update().into()
194+
} else {
195+
make.ref_pat(remove_mut_and_collect_idents(make, &inner, acc)?).into()
152196
}
153-
Some(())
154197
}
198+
ast::Pat::SlicePat(p) => make
199+
.slice_pat(
200+
p.pats()
201+
.map(|pat| remove_mut_and_collect_idents(make, &pat, acc))
202+
.collect::<Option<Vec<_>>>()?,
203+
)
204+
.into(),
205+
ast::Pat::TuplePat(p) => make
206+
.tuple_pat(
207+
p.fields()
208+
.map(|field| remove_mut_and_collect_idents(make, &field, acc))
209+
.collect::<Option<Vec<_>>>()?,
210+
)
211+
.into(),
212+
ast::Pat::TupleStructPat(p) => make
213+
.tuple_struct_pat(
214+
p.path()?,
215+
p.fields()
216+
.map(|field| remove_mut_and_collect_idents(make, &field, acc))
217+
.collect::<Option<Vec<_>>>()?,
218+
)
219+
.into(),
220+
ast::Pat::RestPat(_)
221+
| ast::Pat::LiteralPat(_)
222+
| ast::Pat::PathPat(_)
223+
| ast::Pat::WildcardPat(_)
224+
| ast::Pat::ConstBlockPat(_) => pat.clone(),
155225
// don't support macro pat yet
156-
MacroPat(_) => None,
157-
}
158-
}
159-
160-
fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
161-
let vars = binders
162-
.iter()
163-
.map(
164-
|(ident, ismut)| {
165-
if *ismut && addmut { format!("mut {ident}") } else { ident.to_string() }
166-
},
167-
)
168-
.collect::<Vec<_>>()
169-
.join(", ");
170-
if binders.is_empty() {
171-
String::from("{}")
172-
} else if binders.len() == 1 {
173-
vars
174-
} else {
175-
format!("({vars})")
176-
}
226+
ast::Pat::MacroPat(_) => return None,
227+
})
177228
}
178229

179230
#[cfg(test)]

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ fn build_assignment_edit(
197197
let fields = field_names.iter().map(|(old_name, new_name)| {
198198
// Use shorthand syntax if possible
199199
if old_name == new_name && !is_mut {
200-
ast::make::record_pat_field_shorthand(ast::make::name_ref(old_name))
200+
ast::make::record_pat_field_shorthand(
201+
ast::make::ident_pat(false, false, ast::make::name(old_name)).into(),
202+
)
201203
} else {
202204
ast::make::record_pat_field(
203205
ast::make::name_ref(old_name),

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ fn expand_record_rest_pattern(
6666
make::record_pat_field_list(old_field_list.fields(), None).clone_for_update();
6767
for (f, _) in missing_fields.iter() {
6868
let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db());
69-
let field = make::record_pat_field_shorthand(make::name_ref(
70-
&f.name(ctx.sema.db).display_no_db(edition).to_smolstr(),
71-
));
69+
let field = make::record_pat_field_shorthand(
70+
make::ident_pat(
71+
false,
72+
false,
73+
make::name(&f.name(ctx.sema.db).display_no_db(edition).to_smolstr()),
74+
)
75+
.into(),
76+
);
7277
new_field_list.add_field(field.clone_for_update());
7378
}
7479

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
5353
|edit| {
5454
let pats_after = pipe_token
5555
.siblings_with_tokens(Direction::Next)
56-
.filter_map(|it| ast::Pat::cast(it.into_node()?));
57-
let new_pat = make::or_pat(pats_after, or_pat.leading_pipe().is_some());
56+
.filter_map(|it| ast::Pat::cast(it.into_node()?))
57+
.collect::<Vec<_>>();
58+
// It is guaranteed that `pats_after` has at least one element
59+
let new_pat = if pats_after.len() == 1 {
60+
pats_after[0].clone()
61+
} else {
62+
make::or_pat(pats_after, or_pat.leading_pipe().is_some()).into()
63+
};
5864
let new_match_arm =
5965
make::match_arm(new_pat, match_arm.guard(), match_arm_body).clone_for_update();
6066

0 commit comments

Comments
 (0)