Skip to content

Commit eabb84b

Browse files
Merge pull request #21744 from A4-Tacks/add-match-arms-inc-edit
fix: keep comments for 'Fill match arms'
2 parents 8034699 + c21acb8 commit eabb84b

6 files changed

Lines changed: 153 additions & 47 deletions

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

Lines changed: 142 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use ide_db::RootDatabase;
66
use ide_db::syntax_helpers::suggest_name;
77
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
88
use itertools::Itertools;
9-
use syntax::ToSmolStr;
10-
use syntax::ast::edit::{AstNodeEdit, IndentLevel};
9+
use syntax::ast::edit::IndentLevel;
1110
use syntax::ast::syntax_factory::SyntaxFactory;
1211
use syntax::ast::{self, AstNode, MatchArmList, MatchExpr, Pat, make};
12+
use syntax::syntax_editor::{Position, SyntaxEditor};
13+
use syntax::{SyntaxKind, SyntaxNode, ToSmolStr};
1314

1415
use crate::{AssistContext, AssistId, Assists, utils};
1516

@@ -223,32 +224,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
223224
// having any hidden variants means that we need a catch-all arm
224225
needs_catch_all_arm |= has_hidden_variants;
225226

226-
let missing_arms = missing_pats
227+
let mut missing_arms = missing_pats
227228
.filter(|(_, hidden)| {
228229
// filter out hidden patterns because they're handled by the catch-all arm
229230
!hidden
230231
})
231-
.map(|(pat, _)| make.match_arm(pat, None, utils::expr_fill_default(ctx.config)));
232-
233-
let mut arms: Vec<_> = match_arm_list
234-
.arms()
235-
.filter(|arm| {
236-
if matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))) {
237-
if arm.expr().is_none_or(is_empty_expr) {
238-
false
239-
} else {
240-
cov_mark::hit!(add_missing_match_arms_empty_expr);
241-
true
242-
}
243-
} else {
244-
true
245-
}
246-
})
247-
.map(|arm| arm.reset_indent().indent(IndentLevel(1)))
248-
.collect();
249-
250-
let first_new_arm_idx = arms.len();
251-
arms.extend(missing_arms);
232+
.map(|(pat, _)| make.match_arm(pat, None, utils::expr_fill_default(ctx.config)))
233+
.collect::<Vec<_>>();
252234

253235
if needs_catch_all_arm && !has_catch_all_arm {
254236
cov_mark::hit!(added_wildcard_pattern);
@@ -257,13 +239,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
257239
None,
258240
utils::expr_fill_default(ctx.config),
259241
);
260-
arms.push(arm);
242+
missing_arms.push(arm);
261243
}
262244

263-
let new_match_arm_list = make.match_arm_list(arms);
264-
265245
// FIXME: Hack for syntax trees not having great support for macros
266-
// Just replace the element that the original range came from
246+
// Just edit the element that the original range came from
267247
let old_place = {
268248
// Find the original element
269249
let file = ctx.sema.parse(arm_list_range.file_id);
@@ -280,25 +260,27 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
280260
};
281261

282262
let mut editor = builder.make_editor(&old_place);
283-
let new_match_arm_list = new_match_arm_list.indent(IndentLevel::from_node(&old_place));
284-
editor.replace(old_place, new_match_arm_list.syntax());
263+
let mut arms_edit = ArmsEdit { match_arm_list, place: old_place, last_arm: None };
264+
265+
arms_edit.remove_wildcard_arms(ctx, &mut editor);
266+
arms_edit.add_comma_after_last_arm(ctx, &make, &mut editor);
267+
arms_edit.append_arms(&missing_arms, &make, &mut editor);
285268

286269
if let Some(cap) = ctx.config.snippet_cap {
287-
if let Some(it) = new_match_arm_list
288-
.arms()
289-
.nth(first_new_arm_idx)
270+
if let Some(it) = missing_arms
271+
.first()
290272
.and_then(|arm| arm.syntax().descendants().find_map(ast::WildcardPat::cast))
291273
{
292274
editor.add_annotation(it.syntax(), builder.make_placeholder_snippet(cap));
293275
}
294276

295-
for arm in new_match_arm_list.arms().skip(first_new_arm_idx) {
277+
for arm in &missing_arms {
296278
if let Some(expr) = arm.expr() {
297279
editor.add_annotation(expr.syntax(), builder.make_placeholder_snippet(cap));
298280
}
299281
}
300282

301-
if let Some(arm) = new_match_arm_list.arms().skip(first_new_arm_idx).last() {
283+
if let Some(arm) = missing_arms.last() {
302284
editor.add_annotation(arm.syntax(), builder.make_tabstop_after(cap));
303285
}
304286
}
@@ -357,6 +339,101 @@ fn cursor_at_trivial_match_arm_list(
357339
None
358340
}
359341

342+
struct ArmsEdit {
343+
match_arm_list: MatchArmList,
344+
place: SyntaxNode,
345+
last_arm: Option<ast::MatchArm>,
346+
}
347+
348+
impl ArmsEdit {
349+
fn remove_wildcard_arms(&mut self, ctx: &AssistContext<'_>, editor: &mut SyntaxEditor) {
350+
for arm in self.match_arm_list.arms() {
351+
if !matches!(arm.pat(), Some(Pat::WildcardPat(_))) {
352+
self.last_arm = Some(arm);
353+
continue;
354+
}
355+
if !arm.expr().is_none_or(is_empty_expr) {
356+
cov_mark::hit!(add_missing_match_arms_empty_expr);
357+
self.last_arm = Some(arm);
358+
continue;
359+
}
360+
let Some(range) = self.cover_edit_range(ctx, &arm) else { continue };
361+
362+
let prev = match range.start() {
363+
syntax::NodeOrToken::Node(node) => {
364+
node.first_token().and_then(|it| it.prev_token())
365+
}
366+
syntax::NodeOrToken::Token(tok) => tok.prev_token(),
367+
};
368+
if let Some(prev) = prev
369+
&& prev.kind() == SyntaxKind::WHITESPACE
370+
{
371+
editor.delete(prev);
372+
}
373+
374+
editor.delete_all(range);
375+
}
376+
}
377+
378+
fn append_arms(&self, arms: &[ast::MatchArm], make: &SyntaxFactory, editor: &mut SyntaxEditor) {
379+
let Some(mut before) = self.place.last_token() else {
380+
stdx::never!("match arm list not contain any token");
381+
return;
382+
};
383+
if let Some(prev) = before.prev_token()
384+
&& prev.kind() == SyntaxKind::WHITESPACE
385+
{
386+
before = prev;
387+
}
388+
let open_curly =
389+
!self.place.text().contains_char('\n') || before.kind() == SyntaxKind::WHITESPACE;
390+
let indent = IndentLevel::from_node(&self.place);
391+
let arm_indent = indent + 1;
392+
let indent = make.whitespace(&format!("\n{indent}"));
393+
let arm_indent = make.whitespace(&format!("\n{arm_indent}"));
394+
let elements = arms
395+
.iter()
396+
.flat_map(|arm| [arm_indent.clone().into(), arm.syntax().clone().into()])
397+
.chain(open_curly.then(|| indent.clone().into()))
398+
.collect();
399+
400+
if before.kind() == SyntaxKind::WHITESPACE {
401+
editor.replace_with_many(before, elements);
402+
} else {
403+
editor.insert_all(Position::before(before), elements);
404+
}
405+
}
406+
407+
fn add_comma_after_last_arm(
408+
&self,
409+
ctx: &AssistContext<'_>,
410+
make: &SyntaxFactory,
411+
editor: &mut SyntaxEditor,
412+
) {
413+
if let Some(last_arm) = &self.last_arm
414+
&& last_arm.comma_token().is_none()
415+
&& last_arm.expr().is_none_or(|it| !it.is_block_like())
416+
&& let Some(range) = self.cover_edit_range(ctx, last_arm)
417+
{
418+
editor.insert(Position::after(range.end()), make.token(syntax::T![,]));
419+
}
420+
}
421+
422+
fn cover_edit_range(
423+
&self,
424+
ctx: &AssistContext<'_>,
425+
node: &impl AstNode,
426+
) -> Option<std::ops::RangeInclusive<syntax::SyntaxElement>> {
427+
let range = ctx.sema.original_range_opt(node.syntax())?;
428+
429+
if !self.place.text_range().contains_range(range.range) {
430+
return None;
431+
}
432+
433+
Some(utils::cover_edit_range(&self.place, range.range))
434+
}
435+
}
436+
360437
fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
361438
!existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
362439
}
@@ -1734,7 +1811,7 @@ enum Test {
17341811
17351812
fn foo(t: Test) {
17361813
m!(match t {
1737-
Test::A=>(),
1814+
Test::A => (),
17381815
Test::B => ${1:todo!()},
17391816
Test::C => ${2:todo!()},$0
17401817
});
@@ -2172,6 +2249,35 @@ fn foo(t: E) {
21722249
);
21732250
}
21742251

2252+
#[test]
2253+
fn keep_comments() {
2254+
check_assist(
2255+
add_missing_match_arms,
2256+
r#"
2257+
enum E { A, B, C }
2258+
2259+
fn foo(t: E) -> i32 {
2260+
match $0t {
2261+
// variant a
2262+
E::A => 2
2263+
// comment on end
2264+
}
2265+
}"#,
2266+
r#"
2267+
enum E { A, B, C }
2268+
2269+
fn foo(t: E) -> i32 {
2270+
match t {
2271+
// variant a
2272+
E::A => 2,
2273+
// comment on end
2274+
E::B => ${1:todo!()},
2275+
E::C => ${2:todo!()},$0
2276+
}
2277+
}"#,
2278+
);
2279+
}
2280+
21752281
#[test]
21762282
fn not_applicable_when_match_arm_list_cannot_be_upmapped() {
21772283
check_assist_not_applicable(

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ where
242242
{
243243
let make = SyntaxFactory::without_mappings();
244244
let orig = ctx.sema.original_range_opt(field_list.syntax())?;
245-
let list_range = cover_edit_range(source, orig.range);
245+
let list_range = cover_edit_range(source.syntax(), orig.range);
246246

247247
let l_curly = match list_range.start() {
248248
NodeOrToken::Node(node) => node.first_token()?,
@@ -265,7 +265,7 @@ where
265265

266266
for name_ref in fields(&field_list) {
267267
let Some(orig) = ctx.sema.original_range_opt(name_ref.syntax()) else { continue };
268-
let name_range = cover_edit_range(source, orig.range);
268+
let name_range = cover_edit_range(source.syntax(), orig.range);
269269

270270
if let Some(colon) = next_non_trivia_token(name_range.end().clone())
271271
&& colon.kind() == T![:]
@@ -306,7 +306,7 @@ fn edit_field_references(
306306
// Only edit the field reference if it's part of a `.field` access
307307
if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() {
308308
edit.replace_all(
309-
cover_edit_range(&source, r.range),
309+
cover_edit_range(source.syntax(), r.range),
310310
vec![make.name_ref(&index.to_string()).syntax().clone().into()],
311311
);
312312
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ fn process_struct_name_reference(
191191
full_path,
192192
generate_record_pat_list(&tuple_struct_pat, names),
193193
);
194-
editor.replace_all(cover_edit_range(source, range), vec![new.syntax().clone().into()]);
194+
editor.replace_all(cover_edit_range(source.syntax(), range), vec![new.syntax().clone().into()]);
195195
},
196196
ast::PathExpr(path_expr) => {
197197
let call_expr = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
@@ -207,7 +207,7 @@ fn process_struct_name_reference(
207207
let mut first_insert = vec![];
208208
for (expr, name) in arg_list.args().zip(names) {
209209
let range = ctx.sema.original_range_opt(expr.syntax())?.range;
210-
let place = cover_edit_range(source, range);
210+
let place = cover_edit_range(source.syntax(), range);
211211
let elements = vec![
212212
make.name_ref(&name.text()).syntax().clone().into(),
213213
make.token(T![:]).into(),
@@ -236,7 +236,7 @@ fn process_delimiter(
236236
first_insert: Vec<syntax::SyntaxElement>,
237237
) {
238238
let Some(range) = ctx.sema.original_range_opt(list.syntax()) else { return };
239-
let place = cover_edit_range(source, range.range);
239+
let place = cover_edit_range(source.syntax(), range.range);
240240

241241
let l_paren = match place.start() {
242242
syntax::NodeOrToken::Node(node) => node.first_token(),
@@ -290,7 +290,7 @@ fn edit_field_references(
290290
&& let Some(original) = ctx.sema.original_range_opt(name_ref.syntax())
291291
{
292292
editor.replace_all(
293-
cover_edit_range(&source, original.range),
293+
cover_edit_range(source.syntax(), original.range),
294294
vec![name.syntax().clone().into()],
295295
);
296296
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ fn update_usages(
358358
data: &StructEditData,
359359
field_names: &FxHashMap<SmolStr, SmolStr>,
360360
) {
361-
let source = ctx.source_file();
361+
let source = ctx.source_file().syntax();
362362
let make = SyntaxFactory::with_mappings();
363363
let edits = data
364364
.usages

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ impl EditTupleUsage {
326326
}
327327
EditTupleUsage::ReplaceExpr(target_expr, replace_with) => {
328328
if let Some(range) = ctx.sema.original_range_opt(target_expr.syntax()) {
329-
let source = ctx.source_file();
329+
let source = ctx.source_file().syntax();
330330
syntax_editor.replace_all(
331331
cover_edit_range(source, range.range),
332332
vec![replace_with.syntax().clone().into()],

crates/ide-assists/src/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,10 +1416,10 @@ pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option<a
14161416
}
14171417

14181418
pub(crate) fn cover_edit_range(
1419-
source: &impl AstNode,
1419+
source: &SyntaxNode,
14201420
range: TextRange,
14211421
) -> std::ops::RangeInclusive<syntax::SyntaxElement> {
1422-
let node = match source.syntax().covering_element(range) {
1422+
let node = match source.covering_element(range) {
14231423
NodeOrToken::Node(node) => node,
14241424
NodeOrToken::Token(t) => t.parent().unwrap(),
14251425
};

0 commit comments

Comments
 (0)