Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22310 from Shourya742/2026-05-07-remove-make-mut
Remove make mut
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_delegate_methods.rs | 1 | ||||
| -rw-r--r-- | crates/ide-assists/src/handlers/remove_unused_imports.rs | 17 | ||||
| -rw-r--r-- | crates/ide-db/src/imports/merge_imports.rs | 5 | ||||
| -rw-r--r-- | crates/ide-db/src/source_change.rs | 77 | ||||
| -rw-r--r-- | crates/syntax/src/ast/edit_in_place.rs | 65 | ||||
| -rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 13 | ||||
| -rw-r--r-- | crates/syntax/src/syntax_editor.rs | 42 |
7 files changed, 103 insertions, 117 deletions
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs index b5c6aec0bb..6c9808fb1c 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -213,7 +213,6 @@ pub(crate) fn generate_delegate_methods( let impl_def = impl_def.indent(indent); // Insert the impl block. - let strukt = edit.make_mut(strukt.clone()); editor.insert_all( Position::after(strukt.syntax()), vec![ diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs index c90623ceed..2958acc478 100644 --- a/crates/ide-assists/src/handlers/remove_unused_imports.rs +++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -111,19 +111,24 @@ pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_, ' is_path_per_ns_unused_in_scope(ctx, &u, scope, &res).then_some(u) } }) - .peekable(); + .collect::<Vec<_>>(); - // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist. - if unused.peek().is_some() { + // Terminate early unless an unused use is found. Only do the rest of the work if the user selects the assist. + if !unused.is_empty() { acc.add( AssistId::quick_fix("remove_unused_imports"), "Remove all unused imports", selected_el.text_range(), |builder| { - let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect(); - for node in unused { - node.remove_recursive(); + let editor = builder.make_editor(&selected_el); + unused.sort_by_key(|use_tree| use_tree.syntax().text_range().start()); + for node in &unused { + editor.delete(node.syntax()); } + for node in unused.iter().cloned() { + node.remove_recursive(&editor); + } + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } else { diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs index 76645464dd..bbd351a4cc 100644 --- a/crates/ide-db/src/imports/merge_imports.rs +++ b/crates/ide-db/src/imports/merge_imports.rs @@ -256,10 +256,7 @@ pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> O Some(use_item) } -pub fn try_normalize_use_tree_mut( - use_tree: &ast::UseTree, - style: NormalizationStyle, -) -> Option<()> { +fn try_normalize_use_tree_mut(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> { if style == NormalizationStyle::One { let mut modified = false; modified |= use_tree.wrap_in_tree_list().is_some(); diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index 3c5f703712..766d7251a1 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -5,7 +5,6 @@ use std::{collections::hash_map::Entry, fmt, iter, mem}; -use crate::imports::insert_use::{ImportScope, ImportScopeKind}; use crate::text_edit::{TextEdit, TextEditBuilder}; use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff}; use base_db::AnchoredPathBuf; @@ -16,7 +15,7 @@ use rustc_hash::FxHashMap; use span::FileId; use stdx::never; use syntax::{ - AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, + AstNode, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextSize, syntax_editor::{SyntaxAnnotation, SyntaxEditor}, }; @@ -245,23 +244,6 @@ pub struct SnippetBuilder { places: Vec<PlaceSnippet>, } -impl TreeMutator { - fn new(immutable: &SyntaxNode) -> TreeMutator { - let immutable = immutable.ancestors().last().unwrap(); - let mutable_clone = immutable.clone_for_update(); - TreeMutator { immutable, mutable_clone } - } - - fn make_mut<N: AstNode>(&self, node: &N) -> N { - N::cast(self.make_syntax_mut(node.syntax())).unwrap() - } - - fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode { - let ptr = SyntaxNodePtr::new(node); - ptr.to_node(&self.mutable_clone) - } -} - impl SourceChangeBuilder { pub fn new(file_id: impl Into<FileId>) -> SourceChangeBuilder { SourceChangeBuilder { @@ -366,34 +348,6 @@ impl SourceChangeBuilder { } } - pub fn make_mut<N: AstNode>(&mut self, node: N) -> N { - self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node) - } - - pub fn make_import_scope_mut(&mut self, scope: ImportScope) -> ImportScope { - ImportScope { - kind: match scope.kind.clone() { - ImportScopeKind::File(it) => ImportScopeKind::File(self.make_mut(it)), - ImportScopeKind::Module(it) => ImportScopeKind::Module(self.make_mut(it)), - ImportScopeKind::Block(it) => ImportScopeKind::Block(self.make_mut(it)), - }, - required_cfgs: scope.required_cfgs.iter().map(|it| self.make_mut(it.clone())).collect(), - } - } - /// Returns a copy of the `node`, suitable for mutation. - /// - /// Syntax trees in rust-analyzer are typically immutable, and mutating - /// operations panic at runtime. However, it is possible to make a copy of - /// the tree and mutate the copy freely. Mutation is based on interior - /// mutability, and different nodes in the same tree see the same mutations. - /// - /// The typical pattern for an assist is to find specific nodes in the read - /// phase, and then get their mutable counterparts using `make_mut` in the - /// mutable state. - pub fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode { - self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node) - } - /// Remove specified `range` of text. pub fn delete(&mut self, range: TextRange) { self.edit.delete(range) @@ -434,12 +388,6 @@ impl SourceChangeBuilder { self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into())); } - /// Adds a tabstop snippet to place the cursor after `node` - pub fn add_tabstop_after(&mut self, _cap: SnippetCap, node: impl AstNode) { - assert!(node.syntax().parent().is_some()); - self.add_snippet(PlaceSnippet::After(node.syntax().clone().into())); - } - /// Adds a tabstop snippet to place the cursor before `token` pub fn add_tabstop_before_token(&mut self, _cap: SnippetCap, token: SyntaxToken) { assert!(token.parent().is_some()); @@ -458,23 +406,6 @@ impl SourceChangeBuilder { self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into())) } - /// Adds a snippet to move the cursor selected over `token` - pub fn add_placeholder_snippet_token(&mut self, _cap: SnippetCap, token: SyntaxToken) { - assert!(token.parent().is_some()); - self.add_snippet(PlaceSnippet::Over(token.into())) - } - - /// Adds a snippet to move the cursor selected over `nodes` - /// - /// This allows for renaming newly generated items without having to go - /// through a separate rename step. - pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) { - assert!(nodes.iter().all(|node| node.parent().is_some())); - self.add_snippet(PlaceSnippet::OverGroup( - nodes.into_iter().map(|node| node.into()).collect(), - )) - } - fn add_snippet(&mut self, snippet: PlaceSnippet) { let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] }); snippet_builder.places.push(snippet); @@ -553,9 +484,6 @@ enum PlaceSnippet { After(SyntaxElement), /// Place a placeholder snippet in place of the element Over(SyntaxElement), - /// Place a group of placeholder snippets which are linked together - /// in place of the elements - OverGroup(Vec<SyntaxElement>), } impl PlaceSnippet { @@ -564,9 +492,6 @@ impl PlaceSnippet { PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())], PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())], PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())], - PlaceSnippet::OverGroup(it) => { - vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())] - } } } } diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 9f12f94ce1..4a8c9d450c 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -56,24 +56,41 @@ impl Removable for ast::UseTree { } impl ast::UseTree { + /// Editor variant of UseTree remove + fn remove_with_editor(&self, editor: &SyntaxEditor) { + for dir in [Direction::Next, Direction::Prev] { + if let Some(next_use_tree) = neighbor(self, dir) { + let separators = self + .syntax() + .siblings_with_tokens(dir) + .skip(1) + .take_while(|it| it.as_node() != Some(next_use_tree.syntax())); + for separator in separators { + editor.delete(separator); + } + break; + } + } + editor.delete(self.syntax()); + } + /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty. - pub fn remove_recursive(self) { + pub fn remove_recursive(self, editor: &SyntaxEditor) { let parent = self.syntax().parent(); - self.remove(); - if let Some(u) = parent.clone().and_then(ast::Use::cast) { - if u.use_tree().is_none() { - u.remove(); - } + u.remove(editor); } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) { - if u.use_trees().next().is_none() { - let parent = u.syntax().parent().and_then(ast::UseTree::cast); - if let Some(u) = parent { - u.remove_recursive(); - } + if u.use_trees().nth(1).is_none() + || u.use_trees().all(|use_tree| { + use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax()) + }) + { + u.parent_use_tree().remove_recursive(editor); + return; } - u.remove_unnecessary_braces(); + self.remove_with_editor(editor); + u.remove_unnecessary_braces(editor); } } @@ -224,8 +241,9 @@ impl ast::UseTreeList { } } -impl Removable for ast::Use { - fn remove(&self) { +impl ast::Use { + fn remove(&self, editor: &SyntaxEditor) { + let make = editor.make(); let next_ws = self .syntax() .next_sibling_or_token() @@ -234,10 +252,17 @@ impl Removable for ast::Use { if let Some(next_ws) = next_ws { let ws_text = next_ws.syntax().text(); if let Some(rest) = ws_text.strip_prefix('\n') { - if rest.is_empty() { - ted::remove(next_ws.syntax()); + let next_use_removed = next_ws + .syntax() + .next_sibling_or_token() + .and_then(|it| it.into_node()) + .and_then(ast::Use::cast) + .and_then(|use_| use_.use_tree()) + .is_some_and(|use_tree| editor.deleted(use_tree.syntax())); + if rest.is_empty() || next_use_removed { + editor.delete(next_ws.syntax()); } else { - ted::replace(next_ws.syntax(), make::tokens::whitespace(rest)); + editor.replace(next_ws.syntax(), make.whitespace(rest)); } } } @@ -251,13 +276,13 @@ impl Removable for ast::Use { let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0); let rest = &ws_text[0..prev_newline]; if rest.is_empty() { - ted::remove(prev_ws.syntax()); + editor.delete(prev_ws.syntax()); } else { - ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest)); + editor.replace(prev_ws.syntax(), make.whitespace(rest)); } } - ted::remove(self.syntax()); + editor.delete(self.syntax()); } } diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 751f8d7e1c..1eb658f4b8 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -16,7 +16,7 @@ use crate::{ self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName, HasTypeBounds, SyntaxNode, support, }, - ted, + syntax_editor::SyntaxEditor, }; use super::{GenericParam, RangeItem, RangeOp}; @@ -454,11 +454,12 @@ impl ast::UseTreeList { } /// Remove the unnecessary braces in current `UseTreeList` - pub fn remove_unnecessary_braces(mut self) { + pub fn remove_unnecessary_braces(mut self, editor: &SyntaxEditor) { // Returns true iff there is a single subtree and it is not the self keyword. The braces in // `use x::{self};` are necessary and so we should not remove them. let has_single_subtree_that_is_not_self = |u: &ast::UseTreeList| { - if let Some((single_subtree,)) = u.use_trees().collect_tuple() { + let use_trees = u.use_trees().filter(|use_tree| !editor.deleted(use_tree.syntax())); + if let Some((single_subtree,)) = use_trees.collect_tuple() { // We have a single subtree, check whether it is self. let is_self = single_subtree.path().as_ref().is_some_and(|path| { @@ -476,12 +477,12 @@ impl ast::UseTreeList { let remove_brace_in_use_tree_list = |u: &ast::UseTreeList| { if has_single_subtree_that_is_not_self(u) { if let Some(a) = u.l_curly_token() { - ted::remove(a) + editor.delete(a) } if let Some(a) = u.r_curly_token() { - ted::remove(a) + editor.delete(a) } - u.comma().for_each(ted::remove); + u.comma().for_each(|u| editor.delete(u)); } }; diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 7f499a241b..7d15195c6f 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -133,7 +133,19 @@ impl SyntaxEditor { !matches!(&element, SyntaxElement::Node(node) if node == &self.root), "should not delete root node" ); - self.changes.borrow_mut().push(Change::Replace(element.syntax_element(), None)); + let mut changes = self.changes.borrow_mut(); + for change in changes.iter_mut() { + if let Change::Replace(existing, replacement) = change + && *existing == element + { + if replacement.is_none() { + return; + } + *replacement = None; + return; + } + } + changes.push(Change::Replace(element, None)); } pub fn delete_all(&self, range: RangeInclusive<SyntaxElement>) { @@ -149,9 +161,23 @@ impl SyntaxEditor { pub fn replace(&self, old: impl Element, new: impl Element) { let old = old.syntax_element(); debug_assert!(is_ancestor_or_self_of_element(&old, &self.root)); - self.changes - .borrow_mut() - .push(Change::Replace(old.syntax_element(), Some(new.syntax_element()))); + let new = new.syntax_element(); + let mut changes = self.changes.borrow_mut(); + for change in changes.iter_mut() { + if let Change::Replace(existing, replacement) = change + && *existing == old + { + match replacement { + None => return, + Some(existing_new) if *existing_new == new => return, + Some(existing_new) => { + *existing_new = new; + return; + } + } + } + } + changes.push(Change::Replace(old, Some(new))); } pub fn replace_with_many(&self, old: impl Element, new: Vec<SyntaxElement>) { @@ -177,6 +203,14 @@ impl SyntaxEditor { pub fn finish(self) -> SyntaxEdit { edit_algo::apply_edits(self) } + + pub fn deleted(&self, element: impl Element) -> bool { + let element = element.syntax_element(); + self.changes + .borrow() + .iter() + .any(|change| matches!(change, Change::Replace(existing, None) if *existing == element)) + } } /// Represents a completed [`SyntaxEditor`] operation. |