From b8bbebf4f0769871286798f0e17833aeefb4e9bd Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 11 Nov 2025 18:46:45 +0800 Subject: [PATCH 1/2] 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) ``` --- .../convert_tuple_struct_to_named_struct.rs | 53 +++++++++++++++++++ crates/syntax/src/syntax_editor.rs | 36 +++++++++++++ crates/syntax/src/syntax_editor/edit_algo.rs | 21 ++++++++ 3 files changed, 110 insertions(+) diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 61d844928a8a..f8b9bb68db81 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -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() + } +} + "#, + ); + } } diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 0b358878fcf2..a0ead400cd0b 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -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()); + } } diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index 01c1f0d49bfd..d54837b9f26f 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -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()), From d48361b47b4c6b9c0bad451897e9034c2c454d45 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 12 Nov 2025 13:33:50 +0800 Subject: [PATCH 2/2] Fix syntax_editor duplicated changed tokens --- crates/syntax/src/syntax_editor.rs | 18 +++++++++++++++ crates/syntax/src/syntax_editor/edit_algo.rs | 24 ++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index a0ead400cd0b..5683d891be7a 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -654,6 +654,24 @@ mod tests { expect.assert_eq(&edit.new_root.to_string()); } + #[test] + fn test_more_times_replace_node_to_mutable_token() { + 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::token(parser::SyntaxKind::UNDERSCORE); + + for arg in arg_list.args() { + editor.replace(arg.syntax(), &target_expr); + } + + let edit = editor.finish(); + + let expect = expect![["(_, _)"]]; + expect.assert_eq(&edit.new_root.to_string()); + } + #[test] fn test_more_times_replace_node_to_mutable() { let arg_list = diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index d54837b9f26f..e697d97061d9 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -152,9 +152,29 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { 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 }; + let node; + let node = match node_or_token { + SyntaxElement::Token(token) => match token.parent() { + None => return, + Some(parent) => { + node = parent; + &node + } + }, + SyntaxElement::Node(node) => node, + }; if changed_elements_set.contains(node) { - *node = node.clone_subtree().clone_for_update(); + let new_node = node.clone_subtree().clone_for_update(); + match node_or_token { + SyntaxElement::Node(node) => *node = new_node, + SyntaxElement::Token(token) => { + *token = new_node + .children_with_tokens() + .filter_map(SyntaxElement::into_token) + .find(|it| it.kind() == token.kind() && it.text() == token.text()) + .unwrap(); + } + } } else { changed_elements_set.insert(node.clone()); }