Merge pull request #19155 from ShoyuVanilla/migrate-missing-match-arms
internal: Remove mutable syntax tree usages from `add_missing_match_arms` assist
This commit is contained in:
commit
c8a5743775
3 changed files with 72 additions and 113 deletions
|
@ -6,7 +6,9 @@ use ide_db::syntax_helpers::suggest_name;
|
||||||
use ide_db::RootDatabase;
|
use ide_db::RootDatabase;
|
||||||
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
|
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::ast::edit_in_place::Removable;
|
use syntax::ast::edit::IndentLevel;
|
||||||
|
use syntax::ast::edit_in_place::Indent;
|
||||||
|
use syntax::ast::syntax_factory::SyntaxFactory;
|
||||||
use syntax::ast::{self, make, AstNode, MatchArmList, MatchExpr, Pat};
|
use syntax::ast::{self, make, AstNode, MatchArmList, MatchExpr, Pat};
|
||||||
|
|
||||||
use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
|
use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
|
||||||
|
@ -200,8 +202,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
AssistId("add_missing_match_arms", AssistKind::QuickFix),
|
AssistId("add_missing_match_arms", AssistKind::QuickFix),
|
||||||
"Fill match arms",
|
"Fill match arms",
|
||||||
ctx.sema.original_range(match_expr.syntax()).range,
|
ctx.sema.original_range(match_expr.syntax()).range,
|
||||||
|edit| {
|
|builder| {
|
||||||
let new_match_arm_list = match_arm_list.clone_for_update();
|
let make = SyntaxFactory::new();
|
||||||
|
|
||||||
// having any hidden variants means that we need a catch-all arm
|
// having any hidden variants means that we need a catch-all arm
|
||||||
needs_catch_all_arm |= has_hidden_variants;
|
needs_catch_all_arm |= has_hidden_variants;
|
||||||
|
@ -211,89 +213,85 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
// filter out hidden patterns because they're handled by the catch-all arm
|
// filter out hidden patterns because they're handled by the catch-all arm
|
||||||
!hidden
|
!hidden
|
||||||
})
|
})
|
||||||
.map(|(pat, _)| {
|
.map(|(pat, _)| make.match_arm(pat, None, make::ext::expr_todo()));
|
||||||
make::match_arm(pat, None, make::ext::expr_todo()).clone_for_update()
|
|
||||||
});
|
|
||||||
|
|
||||||
let catch_all_arm = new_match_arm_list
|
let mut arms: Vec<_> = match_arm_list
|
||||||
.arms()
|
.arms()
|
||||||
.find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
|
.filter(|arm| {
|
||||||
if let Some(arm) = catch_all_arm {
|
if matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))) {
|
||||||
let is_empty_expr = arm.expr().is_none_or(|e| match e {
|
let is_empty_expr = arm.expr().is_none_or(|e| match e {
|
||||||
ast::Expr::BlockExpr(b) => {
|
ast::Expr::BlockExpr(b) => {
|
||||||
b.statements().next().is_none() && b.tail_expr().is_none()
|
b.statements().next().is_none() && b.tail_expr().is_none()
|
||||||
|
}
|
||||||
|
ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
if is_empty_expr {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
cov_mark::hit!(add_missing_match_arms_empty_expr);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
|
})
|
||||||
_ => false,
|
.collect();
|
||||||
});
|
|
||||||
if is_empty_expr {
|
|
||||||
arm.remove();
|
|
||||||
} else {
|
|
||||||
cov_mark::hit!(add_missing_match_arms_empty_expr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut added_arms = Vec::new();
|
let first_new_arm_idx = arms.len();
|
||||||
let mut todo_placeholders = Vec::new();
|
arms.extend(missing_arms);
|
||||||
for arm in missing_arms {
|
|
||||||
todo_placeholders.push(arm.expr().unwrap());
|
|
||||||
added_arms.push(arm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if needs_catch_all_arm && !has_catch_all_arm {
|
if needs_catch_all_arm && !has_catch_all_arm {
|
||||||
cov_mark::hit!(added_wildcard_pattern);
|
cov_mark::hit!(added_wildcard_pattern);
|
||||||
let arm =
|
let arm = make.match_arm(make::wildcard_pat().into(), None, make::ext::expr_todo());
|
||||||
make::match_arm(make::wildcard_pat().into(), None, make::ext::expr_todo())
|
arms.push(arm);
|
||||||
.clone_for_update();
|
|
||||||
todo_placeholders.push(arm.expr().unwrap());
|
|
||||||
added_arms.push(arm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_new_arm = added_arms.first().cloned();
|
let new_match_arm_list = make.match_arm_list(arms);
|
||||||
let last_new_arm = added_arms.last().cloned();
|
|
||||||
|
|
||||||
for arm in added_arms {
|
// FIXME: Hack for syntax trees not having great support for macros
|
||||||
new_match_arm_list.add_arm(arm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cap) = ctx.config.snippet_cap {
|
|
||||||
if let Some(it) = first_new_arm
|
|
||||||
.and_then(|arm| arm.syntax().descendants().find_map(ast::WildcardPat::cast))
|
|
||||||
{
|
|
||||||
edit.add_placeholder_snippet(cap, it);
|
|
||||||
}
|
|
||||||
|
|
||||||
for placeholder in todo_placeholders {
|
|
||||||
edit.add_placeholder_snippet(cap, placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(arm) = last_new_arm {
|
|
||||||
edit.add_tabstop_after(cap, arm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Hack for mutable syntax trees not having great support for macros
|
|
||||||
// Just replace the element that the original range came from
|
// Just replace the element that the original range came from
|
||||||
let old_place = {
|
let old_place = {
|
||||||
// Find the original element
|
// Find the original element
|
||||||
let file = ctx.sema.parse(arm_list_range.file_id);
|
let file = ctx.sema.parse(arm_list_range.file_id);
|
||||||
let old_place = file.syntax().covering_element(arm_list_range.range);
|
let old_place = file.syntax().covering_element(arm_list_range.range);
|
||||||
|
|
||||||
// Make `old_place` mut
|
|
||||||
match old_place {
|
match old_place {
|
||||||
syntax::SyntaxElement::Node(it) => {
|
syntax::SyntaxElement::Node(it) => it,
|
||||||
syntax::SyntaxElement::from(edit.make_syntax_mut(it))
|
|
||||||
}
|
|
||||||
syntax::SyntaxElement::Token(it) => {
|
syntax::SyntaxElement::Token(it) => {
|
||||||
// If a token is found, it is '{' or '}'
|
// If a token is found, it is '{' or '}'
|
||||||
// The parent is `{ ... }`
|
// The parent is `{ ... }`
|
||||||
let parent = it.parent().expect("Token must have a parent.");
|
it.parent().expect("Token must have a parent.")
|
||||||
syntax::SyntaxElement::from(edit.make_syntax_mut(parent))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
syntax::ted::replace(old_place, new_match_arm_list.syntax());
|
let mut editor = builder.make_editor(&old_place);
|
||||||
|
new_match_arm_list.indent(IndentLevel::from_node(&old_place));
|
||||||
|
editor.replace(old_place, new_match_arm_list.syntax());
|
||||||
|
|
||||||
|
if let Some(cap) = ctx.config.snippet_cap {
|
||||||
|
if let Some(it) = new_match_arm_list
|
||||||
|
.arms()
|
||||||
|
.nth(first_new_arm_idx)
|
||||||
|
.and_then(|arm| arm.syntax().descendants().find_map(ast::WildcardPat::cast))
|
||||||
|
{
|
||||||
|
editor.add_annotation(it.syntax(), builder.make_placeholder_snippet(cap));
|
||||||
|
}
|
||||||
|
|
||||||
|
for arm in new_match_arm_list.arms().skip(first_new_arm_idx) {
|
||||||
|
if let Some(expr) = arm.expr() {
|
||||||
|
editor.add_annotation(expr.syntax(), builder.make_placeholder_snippet(cap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arm) = new_match_arm_list.arms().skip(first_new_arm_idx).last() {
|
||||||
|
editor.add_annotation(arm.syntax(), builder.make_tabstop_after(cap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.add_mappings(make.finish_with_mappings());
|
||||||
|
builder.add_file_edits(ctx.file_id(), editor);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1377,6 +1375,9 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Preserving comments is quite hard in the current transitional syntax editing model.
|
||||||
|
// Once we migrate to new trivia model addressed in #6854, remove the ignore attribute.
|
||||||
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn add_missing_match_arms_preserves_comments() {
|
fn add_missing_match_arms_preserves_comments() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
@ -1405,6 +1406,9 @@ fn foo(a: A) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Preserving comments is quite hard in the current transitional syntax editing model.
|
||||||
|
// Once we migrate to new trivia model addressed in #6854, remove the ignore attribute.
|
||||||
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn add_missing_match_arms_preserves_comments_empty() {
|
fn add_missing_match_arms_preserves_comments_empty() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
@ -1502,10 +1506,10 @@ enum Test {
|
||||||
|
|
||||||
fn foo(t: Test) {
|
fn foo(t: Test) {
|
||||||
m!(match t {
|
m!(match t {
|
||||||
Test::A => ${1:todo!()},
|
Test::A => ${1:todo!()},
|
||||||
Test::B => ${2:todo!()},
|
Test::B => ${2:todo!()},
|
||||||
Test::C => ${3:todo!()},$0
|
Test::C => ${3:todo!()},$0
|
||||||
});
|
});
|
||||||
}"#,
|
}"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -710,52 +710,6 @@ impl ast::Fn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Removable for ast::MatchArm {
|
|
||||||
fn remove(&self) {
|
|
||||||
if let Some(sibling) = self.syntax().prev_sibling_or_token() {
|
|
||||||
if sibling.kind() == SyntaxKind::WHITESPACE {
|
|
||||||
ted::remove(sibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(sibling) = self.syntax().next_sibling_or_token() {
|
|
||||||
if sibling.kind() == T![,] {
|
|
||||||
ted::remove(sibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ted::remove(self.syntax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ast::MatchArmList {
|
|
||||||
pub fn add_arm(&self, arm: ast::MatchArm) {
|
|
||||||
normalize_ws_between_braces(self.syntax());
|
|
||||||
let mut elements = Vec::new();
|
|
||||||
let position = match self.arms().last() {
|
|
||||||
Some(last_arm) => {
|
|
||||||
if needs_comma(&last_arm) {
|
|
||||||
ted::append_child(last_arm.syntax(), make::token(SyntaxKind::COMMA));
|
|
||||||
}
|
|
||||||
Position::after(last_arm.syntax().clone())
|
|
||||||
}
|
|
||||||
None => match self.l_curly_token() {
|
|
||||||
Some(it) => Position::after(it),
|
|
||||||
None => Position::last_child_of(self.syntax()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let indent = IndentLevel::from_node(self.syntax()) + 1;
|
|
||||||
elements.push(make::tokens::whitespace(&format!("\n{indent}")).into());
|
|
||||||
elements.push(arm.syntax().clone().into());
|
|
||||||
if needs_comma(&arm) {
|
|
||||||
ted::append_child(arm.syntax(), make::token(SyntaxKind::COMMA));
|
|
||||||
}
|
|
||||||
ted::insert_all(position, elements);
|
|
||||||
|
|
||||||
fn needs_comma(arm: &ast::MatchArm) -> bool {
|
|
||||||
arm.expr().is_some_and(|e| !e.is_block_like()) && arm.comma_token().is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ast::LetStmt {
|
impl ast::LetStmt {
|
||||||
pub fn set_ty(&self, ty: Option<ast::Type>) {
|
pub fn set_ty(&self, ty: Option<ast::Type>) {
|
||||||
match ty {
|
match ty {
|
||||||
|
|
|
@ -837,7 +837,8 @@ pub fn match_guard(condition: ast::Expr) -> ast::MatchGuard {
|
||||||
|
|
||||||
pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
|
pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
|
||||||
let arms_str = arms.into_iter().fold(String::new(), |mut acc, arm| {
|
let arms_str = arms.into_iter().fold(String::new(), |mut acc, arm| {
|
||||||
let needs_comma = arm.expr().is_none_or(|it| !it.is_block_like());
|
let needs_comma =
|
||||||
|
arm.comma_token().is_none() && arm.expr().is_none_or(|it| !it.is_block_like());
|
||||||
let comma = if needs_comma { "," } else { "" };
|
let comma = if needs_comma { "," } else { "" };
|
||||||
let arm = arm.syntax();
|
let arm = arm.syntax();
|
||||||
format_to_acc!(acc, " {arm}{comma}\n")
|
format_to_acc!(acc, " {arm}{comma}\n")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue