Fix syntax_editor duplicated changed element

Example
---
```rust
let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
let target_expr = make::expr_literal("3").clone_for_update();

for arg in arg_list.args() {
    editor.replace(arg.syntax(), target_expr.syntax());
}

let edit = editor.finish();
let expect = expect![["(3, 3)"]];
expect.assert_eq(&edit.new_root.to_string());
```

**Before this PR**

```text
(, )3
```

**After this PR**

```text
(3, 3)
```
This commit is contained in:
A4-Tacks 2025-11-11 18:46:45 +08:00
parent 4bf516ee5a
commit b8bbebf4f0
No known key found for this signature in database
GPG Key ID: 9E63F956E66DD9C7
3 changed files with 110 additions and 0 deletions

View File

@ -1197,4 +1197,57 @@ fn foo() {
"#,
);
}
#[test]
fn regression_issue_21020() {
check_assist(
convert_tuple_struct_to_named_struct,
r#"
pub struct S$0(pub ());
trait T {
fn id(&self) -> usize;
}
trait T2 {
fn foo(&self) -> usize;
}
impl T for S {
fn id(&self) -> usize {
self.0.len()
}
}
impl T2 for S {
fn foo(&self) -> usize {
self.0.len()
}
}
"#,
r#"
pub struct S { pub field1: () }
trait T {
fn id(&self) -> usize;
}
trait T2 {
fn foo(&self) -> usize;
}
impl T for S {
fn id(&self) -> usize {
self.field1.len()
}
}
impl T2 for S {
fn foo(&self) -> usize {
self.field1.len()
}
}
"#,
);
}
}

View File

@ -653,4 +653,40 @@ mod tests {
let expect = expect![["fn it() {\n \n}"]];
expect.assert_eq(&edit.new_root.to_string());
}
#[test]
fn test_more_times_replace_node_to_mutable() {
let arg_list =
make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
let target_expr = make::expr_literal("3").clone_for_update();
for arg in arg_list.args() {
editor.replace(arg.syntax(), target_expr.syntax());
}
let edit = editor.finish();
let expect = expect![["(3, 3)"]];
expect.assert_eq(&edit.new_root.to_string());
}
#[test]
fn test_more_times_insert_node_to_mutable() {
let arg_list =
make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]);
let mut editor = SyntaxEditor::new(arg_list.syntax().clone());
let target_expr = make::ext::expr_unit().clone_for_update();
for arg in arg_list.args() {
editor.insert(Position::before(arg.syntax()), target_expr.syntax());
}
let edit = editor.finish();
let expect = expect![["(()1, ()2)"]];
expect.assert_eq(&edit.new_root.to_string());
}
}

View File

@ -150,6 +150,15 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
// Map change targets to the correct syntax nodes
let tree_mutator = TreeMutator::new(&root);
let mut changed_elements = vec![];
let mut changed_elements_set = rustc_hash::FxHashSet::default();
let mut deduplicate_node = |node_or_token: &mut SyntaxElement| {
let SyntaxElement::Node(node) = node_or_token else { return };
if changed_elements_set.contains(node) {
*node = node.clone_subtree().clone_for_update();
} else {
changed_elements_set.insert(node.clone());
}
};
for index in independent_changes {
match &mut changes[index as usize] {
@ -180,6 +189,18 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
}
}
match &mut changes[index as usize] {
Change::Insert(_, element) | Change::Replace(_, Some(element)) => {
deduplicate_node(element);
}
Change::InsertAll(_, elements)
| Change::ReplaceWithMany(_, elements)
| Change::ReplaceAll(_, elements) => {
elements.iter_mut().for_each(&mut deduplicate_node);
}
Change::Replace(_, None) => (),
}
// Collect changed elements
match &changes[index as usize] {
Change::Insert(_, element) => changed_elements.push(element.clone()),