From 78c8ab35e8713f77191c0af3d3e6c12b0a304b2c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 11 Feb 2026 13:39:13 +0530 Subject: [PATCH] migrate destructure tuple binding to new syntaxEditor --- .../src/handlers/destructure_tuple_binding.rs | 85 +++++---- .../ide-assists/src/utils/ref_field_expr.rs | 18 +- .../crates/syntax/src/ast/edit_in_place.rs | 173 ++++++++++++------ 3 files changed, 181 insertions(+), 95 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index e2afc0bf130e..b8dc59f87dee 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -8,8 +8,8 @@ use ide_db::{ use itertools::Itertools; use syntax::{ T, - ast::{self, AstNode, FieldExpr, HasName, IdentPat, make}, - ted, + ast::{self, AstNode, FieldExpr, HasName, IdentPat, syntax_factory::SyntaxFactory}, + syntax_editor::{Position, SyntaxEditor}, }; use crate::{ @@ -89,13 +89,20 @@ fn destructure_tuple_edit_impl( data: &TupleData, in_sub_pattern: bool, ) { - let assignment_edit = edit_tuple_assignment(ctx, edit, data, in_sub_pattern); - let current_file_usages_edit = edit_tuple_usages(data, edit, ctx, in_sub_pattern); + let mut syntax_editor = edit.make_editor(data.ident_pat.syntax()); + let syntax_factory = SyntaxFactory::with_mappings(); - assignment_edit.apply(); + let assignment_edit = + edit_tuple_assignment(ctx, edit, &mut syntax_editor, &syntax_factory, data, in_sub_pattern); + let current_file_usages_edit = edit_tuple_usages(data, ctx, &syntax_factory, in_sub_pattern); + + assignment_edit.apply(&mut syntax_editor, &syntax_factory); if let Some(usages_edit) = current_file_usages_edit { - usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit)) + usages_edit.into_iter().for_each(|usage_edit| usage_edit.apply(edit, &mut syntax_editor)) } + + syntax_editor.add_mappings(syntax_factory.finish_with_mappings()); + edit.add_file_edits(ctx.vfs_file_id(), syntax_editor); } fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option { @@ -165,11 +172,11 @@ struct TupleData { fn edit_tuple_assignment( ctx: &AssistContext<'_>, edit: &mut SourceChangeBuilder, + editor: &mut SyntaxEditor, + make: &SyntaxFactory, data: &TupleData, in_sub_pattern: bool, ) -> AssignmentEdit { - let ident_pat = edit.make_mut(data.ident_pat.clone()); - let tuple_pat = { let original = &data.ident_pat; let is_ref = original.ref_token().is_some(); @@ -177,10 +184,11 @@ fn edit_tuple_assignment( let fields = data .field_names .iter() - .map(|name| ast::Pat::from(make::ident_pat(is_ref, is_mut, make::name(name)))); - make::tuple_pat(fields).clone_for_update() + .map(|name| ast::Pat::from(make.ident_pat(is_ref, is_mut, make.name(name)))); + make.tuple_pat(fields) }; - let is_shorthand_field = ident_pat + let is_shorthand_field = data + .ident_pat .name() .as_ref() .and_then(ast::RecordPatField::for_field_name) @@ -189,14 +197,20 @@ fn edit_tuple_assignment( if let Some(cap) = ctx.config.snippet_cap { // place cursor on first tuple name if let Some(ast::Pat::IdentPat(first_pat)) = tuple_pat.fields().next() { - edit.add_tabstop_before( - cap, - first_pat.name().expect("first ident pattern should have a name"), - ) + let annotation = edit.make_tabstop_before(cap); + editor.add_annotation( + first_pat.name().expect("first ident pattern should have a name").syntax(), + annotation, + ); } } - AssignmentEdit { ident_pat, tuple_pat, in_sub_pattern, is_shorthand_field } + AssignmentEdit { + ident_pat: data.ident_pat.clone(), + tuple_pat, + in_sub_pattern, + is_shorthand_field, + } } struct AssignmentEdit { ident_pat: ast::IdentPat, @@ -206,23 +220,30 @@ struct AssignmentEdit { } impl AssignmentEdit { - fn apply(self) { + fn apply(self, syntax_editor: &mut SyntaxEditor, syntax_mapping: &SyntaxFactory) { // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)` if self.in_sub_pattern { - self.ident_pat.set_pat(Some(self.tuple_pat.into())) + self.ident_pat.set_pat_with_editor( + Some(self.tuple_pat.into()), + syntax_editor, + syntax_mapping, + ) } else if self.is_shorthand_field { - ted::insert(ted::Position::after(self.ident_pat.syntax()), self.tuple_pat.syntax()); - ted::insert_raw(ted::Position::after(self.ident_pat.syntax()), make::token(T![:])); + syntax_editor.insert(Position::after(self.ident_pat.syntax()), self.tuple_pat.syntax()); + syntax_editor + .insert(Position::after(self.ident_pat.syntax()), syntax_mapping.whitespace(" ")); + syntax_editor + .insert(Position::after(self.ident_pat.syntax()), syntax_mapping.token(T![:])); } else { - ted::replace(self.ident_pat.syntax(), self.tuple_pat.syntax()) + syntax_editor.replace(self.ident_pat.syntax(), self.tuple_pat.syntax()) } } } fn edit_tuple_usages( data: &TupleData, - edit: &mut SourceChangeBuilder, ctx: &AssistContext<'_>, + make: &SyntaxFactory, in_sub_pattern: bool, ) -> Option> { // We need to collect edits first before actually applying them @@ -238,20 +259,20 @@ fn edit_tuple_usages( .as_ref()? .as_slice() .iter() - .filter_map(|r| edit_tuple_usage(ctx, edit, r, data, in_sub_pattern)) + .filter_map(|r| edit_tuple_usage(ctx, make, r, data, in_sub_pattern)) .collect_vec(); Some(edits) } fn edit_tuple_usage( ctx: &AssistContext<'_>, - builder: &mut SourceChangeBuilder, + make: &SyntaxFactory, usage: &FileReference, data: &TupleData, in_sub_pattern: bool, ) -> Option { match detect_tuple_index(usage, data) { - Some(index) => Some(edit_tuple_field_usage(ctx, builder, data, index)), + Some(index) => Some(edit_tuple_field_usage(ctx, make, data, index)), None if in_sub_pattern => { cov_mark::hit!(destructure_tuple_call_with_subpattern); None @@ -262,20 +283,18 @@ fn edit_tuple_usage( fn edit_tuple_field_usage( ctx: &AssistContext<'_>, - builder: &mut SourceChangeBuilder, + make: &SyntaxFactory, data: &TupleData, index: TupleIndex, ) -> EditTupleUsage { let field_name = &data.field_names[index.index]; - let field_name = make::expr_path(make::ext::ident_path(field_name)); + let field_name = make.expr_path(make.ident_path(field_name)); if data.ref_type.is_some() { let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &index.field_expr); - let replace_expr = builder.make_mut(replace_expr); - EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name)) + EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr_with_factory(field_name, make)) } else { - let field_expr = builder.make_mut(index.field_expr); - EditTupleUsage::ReplaceExpr(field_expr.into(), field_name) + EditTupleUsage::ReplaceExpr(index.field_expr.into(), field_name) } } enum EditTupleUsage { @@ -291,14 +310,14 @@ enum EditTupleUsage { } impl EditTupleUsage { - fn apply(self, edit: &mut SourceChangeBuilder) { + fn apply(self, edit: &mut SourceChangeBuilder, syntax_editor: &mut SyntaxEditor) { match self { EditTupleUsage::NoIndex(range) => { edit.insert(range.start(), "/*"); edit.insert(range.end(), "*/"); } EditTupleUsage::ReplaceExpr(target_expr, replace_with) => { - ted::replace(target_expr.syntax(), replace_with.clone_for_update().syntax()) + syntax_editor.replace(target_expr.syntax(), replace_with.syntax()) } } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs index 840b26a7ad58..df8ad4111234 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/ref_field_expr.rs @@ -5,7 +5,7 @@ //! based on the parent of the existing expression. use syntax::{ AstNode, T, - ast::{self, FieldExpr, MethodCallExpr, make}, + ast::{self, FieldExpr, MethodCallExpr, make, syntax_factory::SyntaxFactory}, }; use crate::AssistContext; @@ -130,4 +130,20 @@ impl RefData { expr } + + pub(crate) fn wrap_expr_with_factory( + &self, + mut expr: ast::Expr, + syntax_factory: &SyntaxFactory, + ) -> ast::Expr { + if self.needs_deref { + expr = syntax_factory.expr_prefix(T![*], expr).into(); + } + + if self.needs_parentheses { + expr = syntax_factory.expr_paren(expr).into(); + } + + expr + } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs index 1cd8146f6863..2b7dc5cd76ab 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs @@ -9,8 +9,9 @@ use crate::{ SyntaxKind::{ATTR, COMMENT, WHITESPACE}, SyntaxNode, SyntaxToken, algo::{self, neighbor}, - ast::{self, HasGenericParams, edit::IndentLevel, make}, - ted::{self, Position}, + ast::{self, HasGenericParams, edit::IndentLevel, make, syntax_factory::SyntaxFactory}, + syntax_editor::{Position, SyntaxEditor}, + ted, }; use super::{GenericParam, HasName}; @@ -26,13 +27,13 @@ impl GenericParamsOwnerEdit for ast::Fn { Some(it) => it, None => { let position = if let Some(name) = self.name() { - Position::after(name.syntax) + ted::Position::after(name.syntax) } else if let Some(fn_token) = self.fn_token() { - Position::after(fn_token) + ted::Position::after(fn_token) } else if let Some(param_list) = self.param_list() { - Position::before(param_list.syntax) + ted::Position::before(param_list.syntax) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_generic_param_list(position) } @@ -42,11 +43,11 @@ impl GenericParamsOwnerEdit for ast::Fn { fn get_or_create_where_clause(&self) -> ast::WhereClause { if self.where_clause().is_none() { let position = if let Some(ty) = self.ret_type() { - Position::after(ty.syntax()) + ted::Position::after(ty.syntax()) } else if let Some(param_list) = self.param_list() { - Position::after(param_list.syntax()) + ted::Position::after(param_list.syntax()) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_where_clause(position); } @@ -60,8 +61,8 @@ impl GenericParamsOwnerEdit for ast::Impl { Some(it) => it, None => { let position = match self.impl_token() { - Some(imp_token) => Position::after(imp_token), - None => Position::last_child_of(self.syntax()), + Some(imp_token) => ted::Position::after(imp_token), + None => ted::Position::last_child_of(self.syntax()), }; create_generic_param_list(position) } @@ -71,8 +72,8 @@ impl GenericParamsOwnerEdit for ast::Impl { fn get_or_create_where_clause(&self) -> ast::WhereClause { if self.where_clause().is_none() { let position = match self.assoc_item_list() { - Some(items) => Position::before(items.syntax()), - None => Position::last_child_of(self.syntax()), + Some(items) => ted::Position::before(items.syntax()), + None => ted::Position::last_child_of(self.syntax()), }; create_where_clause(position); } @@ -86,11 +87,11 @@ impl GenericParamsOwnerEdit for ast::Trait { Some(it) => it, None => { let position = if let Some(name) = self.name() { - Position::after(name.syntax) + ted::Position::after(name.syntax) } else if let Some(trait_token) = self.trait_token() { - Position::after(trait_token) + ted::Position::after(trait_token) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_generic_param_list(position) } @@ -100,9 +101,9 @@ impl GenericParamsOwnerEdit for ast::Trait { fn get_or_create_where_clause(&self) -> ast::WhereClause { if self.where_clause().is_none() { let position = match (self.assoc_item_list(), self.semicolon_token()) { - (Some(items), _) => Position::before(items.syntax()), - (_, Some(tok)) => Position::before(tok), - (None, None) => Position::last_child_of(self.syntax()), + (Some(items), _) => ted::Position::before(items.syntax()), + (_, Some(tok)) => ted::Position::before(tok), + (None, None) => ted::Position::last_child_of(self.syntax()), }; create_where_clause(position); } @@ -116,11 +117,11 @@ impl GenericParamsOwnerEdit for ast::TypeAlias { Some(it) => it, None => { let position = if let Some(name) = self.name() { - Position::after(name.syntax) + ted::Position::after(name.syntax) } else if let Some(trait_token) = self.type_token() { - Position::after(trait_token) + ted::Position::after(trait_token) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_generic_param_list(position) } @@ -130,10 +131,10 @@ impl GenericParamsOwnerEdit for ast::TypeAlias { fn get_or_create_where_clause(&self) -> ast::WhereClause { if self.where_clause().is_none() { let position = match self.eq_token() { - Some(tok) => Position::before(tok), + Some(tok) => ted::Position::before(tok), None => match self.semicolon_token() { - Some(tok) => Position::before(tok), - None => Position::last_child_of(self.syntax()), + Some(tok) => ted::Position::before(tok), + None => ted::Position::last_child_of(self.syntax()), }, }; create_where_clause(position); @@ -148,11 +149,11 @@ impl GenericParamsOwnerEdit for ast::Struct { Some(it) => it, None => { let position = if let Some(name) = self.name() { - Position::after(name.syntax) + ted::Position::after(name.syntax) } else if let Some(struct_token) = self.struct_token() { - Position::after(struct_token) + ted::Position::after(struct_token) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_generic_param_list(position) } @@ -166,13 +167,13 @@ impl GenericParamsOwnerEdit for ast::Struct { ast::FieldList::TupleFieldList(it) => Some(it), }); let position = if let Some(tfl) = tfl { - Position::after(tfl.syntax()) + ted::Position::after(tfl.syntax()) } else if let Some(gpl) = self.generic_param_list() { - Position::after(gpl.syntax()) + ted::Position::after(gpl.syntax()) } else if let Some(name) = self.name() { - Position::after(name.syntax()) + ted::Position::after(name.syntax()) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_where_clause(position); } @@ -186,11 +187,11 @@ impl GenericParamsOwnerEdit for ast::Enum { Some(it) => it, None => { let position = if let Some(name) = self.name() { - Position::after(name.syntax) + ted::Position::after(name.syntax) } else if let Some(enum_token) = self.enum_token() { - Position::after(enum_token) + ted::Position::after(enum_token) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_generic_param_list(position) } @@ -200,11 +201,11 @@ impl GenericParamsOwnerEdit for ast::Enum { fn get_or_create_where_clause(&self) -> ast::WhereClause { if self.where_clause().is_none() { let position = if let Some(gpl) = self.generic_param_list() { - Position::after(gpl.syntax()) + ted::Position::after(gpl.syntax()) } else if let Some(name) = self.name() { - Position::after(name.syntax()) + ted::Position::after(name.syntax()) } else { - Position::last_child_of(self.syntax()) + ted::Position::last_child_of(self.syntax()) }; create_where_clause(position); } @@ -212,12 +213,12 @@ impl GenericParamsOwnerEdit for ast::Enum { } } -fn create_where_clause(position: Position) { +fn create_where_clause(position: ted::Position) { let where_clause = make::where_clause(empty()).clone_for_update(); ted::insert(position, where_clause.syntax()); } -fn create_generic_param_list(position: Position) -> ast::GenericParamList { +fn create_generic_param_list(position: ted::Position) -> ast::GenericParamList { let gpl = make::generic_param_list(empty()).clone_for_update(); ted::insert_raw(position, gpl.syntax()); gpl @@ -253,7 +254,7 @@ impl ast::GenericParamList { pub fn add_generic_param(&self, generic_param: ast::GenericParam) { match self.generic_params().last() { Some(last_param) => { - let position = Position::after(last_param.syntax()); + let position = ted::Position::after(last_param.syntax()); let elements = vec![ make::token(T![,]).into(), make::tokens::single_space().into(), @@ -262,7 +263,7 @@ impl ast::GenericParamList { ted::insert_all(position, elements); } None => { - let after_l_angle = Position::after(self.l_angle_token().unwrap()); + let after_l_angle = ted::Position::after(self.l_angle_token().unwrap()); ted::insert(after_l_angle, generic_param.syntax()); } } @@ -412,7 +413,7 @@ impl ast::UseTree { match self.use_tree_list() { Some(it) => it, None => { - let position = Position::last_child_of(self.syntax()); + let position = ted::Position::last_child_of(self.syntax()); let use_tree_list = make::use_tree_list(empty()).clone_for_update(); let mut elements = Vec::with_capacity(2); if self.coloncolon_token().is_none() { @@ -458,7 +459,7 @@ impl ast::UseTree { // Next, transform 'suffix' use tree into 'prefix::{suffix}' let subtree = self.clone_subtree().clone_for_update(); ted::remove_all_iter(self.syntax().children_with_tokens()); - ted::insert(Position::first_child_of(self.syntax()), prefix.syntax()); + ted::insert(ted::Position::first_child_of(self.syntax()), prefix.syntax()); self.get_or_create_use_tree_list().add_use_tree(subtree); fn split_path_prefix(prefix: &ast::Path) -> Option<()> { @@ -507,7 +508,7 @@ impl ast::UseTreeList { pub fn add_use_tree(&self, use_tree: ast::UseTree) { let (position, elements) = match self.use_trees().last() { Some(last_tree) => ( - Position::after(last_tree.syntax()), + ted::Position::after(last_tree.syntax()), vec![ make::token(T![,]).into(), make::tokens::single_space().into(), @@ -516,8 +517,8 @@ impl ast::UseTreeList { ), None => { let position = match self.l_curly_token() { - Some(l_curly) => Position::after(l_curly), - None => Position::last_child_of(self.syntax()), + Some(l_curly) => ted::Position::after(l_curly), + None => ted::Position::last_child_of(self.syntax()), }; (position, vec![use_tree.syntax.into()]) } @@ -582,15 +583,15 @@ impl ast::AssocItemList { let (indent, position, whitespace) = match self.assoc_items().last() { Some(last_item) => ( IndentLevel::from_node(last_item.syntax()), - Position::after(last_item.syntax()), + ted::Position::after(last_item.syntax()), "\n\n", ), None => match self.l_curly_token() { Some(l_curly) => { normalize_ws_between_braces(self.syntax()); - (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n") + (IndentLevel::from_token(&l_curly) + 1, ted::Position::after(&l_curly), "\n") } - None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"), + None => (IndentLevel::single(), ted::Position::last_child_of(self.syntax()), "\n"), }, }; let elements: Vec = vec![ @@ -618,17 +619,17 @@ impl ast::RecordExprFieldList { let position = match self.fields().last() { Some(last_field) => { let comma = get_or_insert_comma_after(last_field.syntax()); - Position::after(comma) + ted::Position::after(comma) } None => match self.l_curly_token() { - Some(it) => Position::after(it), - None => Position::last_child_of(self.syntax()), + Some(it) => ted::Position::after(it), + None => ted::Position::last_child_of(self.syntax()), }, }; ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]); if is_multiline { - ted::insert(Position::after(field.syntax()), ast::make::token(T![,])); + ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,])); } } } @@ -656,7 +657,7 @@ impl ast::RecordExprField { ast::make::tokens::single_space().into(), expr.syntax().clone().into(), ]; - ted::insert_all_raw(Position::last_child_of(self.syntax()), children); + ted::insert_all_raw(ted::Position::last_child_of(self.syntax()), children); } } } @@ -679,17 +680,17 @@ impl ast::RecordPatFieldList { Some(last_field) => { let syntax = last_field.syntax(); let comma = get_or_insert_comma_after(syntax); - Position::after(comma) + ted::Position::after(comma) } None => match self.l_curly_token() { - Some(it) => Position::after(it), - None => Position::last_child_of(self.syntax()), + Some(it) => ted::Position::after(it), + None => ted::Position::last_child_of(self.syntax()), }, }; ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]); if is_multiline { - ted::insert(Position::after(field.syntax()), ast::make::token(T![,])); + ted::insert(ted::Position::after(field.syntax()), ast::make::token(T![,])); } } } @@ -703,7 +704,7 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken { Some(it) => it, None => { let comma = ast::make::token(T![,]); - ted::insert(Position::after(syntax), &comma); + ted::insert(ted::Position::after(syntax), &comma); comma } } @@ -728,7 +729,7 @@ fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> { } } Some(ws) if ws.kind() == T!['}'] => { - ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{indent}"))); + ted::insert(ted::Position::after(l), make::tokens::whitespace(&format!("\n{indent}"))); } _ => (), } @@ -780,6 +781,56 @@ impl ast::IdentPat { } } } + + pub fn set_pat_with_editor( + &self, + pat: Option, + syntax_editor: &mut SyntaxEditor, + syntax_factory: &SyntaxFactory, + ) { + match pat { + None => { + if let Some(at_token) = self.at_token() { + // Remove `@ Pat` + let start = at_token.clone().into(); + let end = self + .pat() + .map(|it| it.syntax().clone().into()) + .unwrap_or_else(|| at_token.into()); + syntax_editor.delete_all(start..=end); + + // Remove any trailing ws + if let Some(last) = + self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) + { + last.detach(); + } + } + } + Some(pat) => { + if let Some(old_pat) = self.pat() { + // Replace existing pattern + syntax_editor.replace(old_pat.syntax(), pat.syntax()) + } else if let Some(at_token) = self.at_token() { + // Have an `@` token but not a pattern yet + syntax_editor.insert(Position::after(at_token), pat.syntax()); + } else { + // Don't have an `@`, should have a name + let name = self.name().unwrap(); + + syntax_editor.insert_all( + Position::after(name.syntax()), + vec![ + syntax_factory.whitespace(" ").into(), + syntax_factory.token(T![@]).into(), + syntax_factory.whitespace(" ").into(), + pat.syntax().clone().into(), + ], + ) + } + } + } + } } pub trait HasVisibilityEdit: ast::HasVisibility {