From 6e2306faaf556ada118115a8d10143b679f1544d Mon Sep 17 00:00:00 2001 From: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> Date: Sat, 26 Jul 2025 22:31:41 +0900 Subject: [PATCH 1/4] remove ted from convert_tuple_struct_to_named_struct Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> --- .../handlers/convert_tuple_struct_to_named_struct.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 80756197fb..44361560f4 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 @@ -3,7 +3,8 @@ use ide_db::defs::{Definition, NameRefClass}; use syntax::{ SyntaxKind, SyntaxNode, ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, - match_ast, ted, + match_ast, + syntax_editor::{Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; @@ -93,12 +94,13 @@ fn edit_struct_def( names: Vec, ) { let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { - let field = ast::make::record_field(f.visibility(), name, f.ty()?).clone_for_update(); - ted::insert_all( - ted::Position::first_child_of(field.syntax()), + let field = ast::make::record_field(f.visibility(), name, f.ty()?); + let mut editor = SyntaxEditor::new(field.syntax().clone()); + editor.insert_all( + Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), ); - Some(field) + ast::RecordField::cast(editor.finish().new_root().clone()) }); let record_fields = ast::make::record_field_list(record_fields); let tuple_fields_text_range = tuple_fields.syntax().text_range(); From d02bf21c47478b10d5f838ac33fd35c38325b960 Mon Sep 17 00:00:00 2001 From: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> Date: Sat, 26 Jul 2025 23:20:31 +0900 Subject: [PATCH 2/4] migrate `fn edit_struct_def` in `convert_tuple_struct_to_named_struct` to SyntaxEditor Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> --- .../convert_tuple_struct_to_named_struct.rs | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) 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 44361560f4..6d729c9ea9 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 @@ -1,10 +1,10 @@ use either::Either; use ide_db::defs::{Definition, NameRefClass}; use syntax::{ - SyntaxKind, SyntaxNode, + SyntaxKind, SyntaxNode, T, ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, match_ast, - syntax_editor::{Position, SyntaxEditor}, + syntax_editor::{Element, Position, SyntaxEditor}, }; use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; @@ -72,7 +72,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), }; let target = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); - + let syntax = strukt_or_variant.as_ref().either(|s| s.syntax(), |v| v.syntax()); acc.add( AssistId::refactor_rewrite("convert_tuple_struct_to_named_struct"), "Convert to named struct", @@ -81,58 +81,53 @@ pub(crate) fn convert_tuple_struct_to_named_struct( let names = generate_names(tuple_fields.fields()); edit_field_references(ctx, edit, tuple_fields.fields(), &names); edit_struct_references(ctx, edit, strukt_def, &names); - edit_struct_def(ctx, edit, &strukt_or_variant, tuple_fields, names); + let mut editor = edit.make_editor(syntax); + edit_struct_def(&mut editor, &strukt_or_variant, tuple_fields, names); + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } fn edit_struct_def( - ctx: &AssistContext<'_>, - edit: &mut SourceChangeBuilder, + editor: &mut SyntaxEditor, strukt: &Either, tuple_fields: ast::TupleFieldList, names: Vec, ) { let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { let field = ast::make::record_field(f.visibility(), name, f.ty()?); - let mut editor = SyntaxEditor::new(field.syntax().clone()); - editor.insert_all( + let mut field_editor = SyntaxEditor::new(field.syntax().clone()); + field_editor.insert_all( Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), ); - ast::RecordField::cast(editor.finish().new_root().clone()) + ast::RecordField::cast(field_editor.finish().new_root().clone()) }); - let record_fields = ast::make::record_field_list(record_fields); - let tuple_fields_text_range = tuple_fields.syntax().text_range(); - - edit.edit_file(ctx.vfs_file_id()); + let record_fields = ast::make::record_field_list(record_fields).clone_for_update(); + let tuple_fields_before = Position::before(tuple_fields.syntax()); if let Either::Left(strukt) = strukt { if let Some(w) = strukt.where_clause() { - edit.delete(w.syntax().text_range()); - edit.insert( - tuple_fields_text_range.start(), - ast::make::tokens::single_newline().text(), - ); - edit.insert(tuple_fields_text_range.start(), w.syntax().text()); + editor.delete(w.syntax()); + let mut insert_element = Vec::new(); + insert_element.push(ast::make::tokens::single_newline().syntax_element()); + insert_element.push(w.syntax().clone_for_update().syntax_element()); if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) { - edit.insert(tuple_fields_text_range.start(), ","); + insert_element.push(ast::make::token(T![,]).into()); } - edit.insert( - tuple_fields_text_range.start(), - ast::make::tokens::single_newline().text(), - ); + insert_element.push(ast::make::tokens::single_newline().syntax_element()); + editor.insert_all(tuple_fields_before, insert_element); } else { - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); + editor.insert(tuple_fields_before, ast::make::tokens::single_space()); } if let Some(t) = strukt.semicolon_token() { - edit.delete(t.text_range()); + editor.delete(t); } } else { - edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); + editor.insert(tuple_fields_before, ast::make::tokens::single_space()); } - edit.replace(tuple_fields_text_range, record_fields.to_string()); + editor.replace(tuple_fields.syntax(), record_fields.syntax()); } fn edit_struct_references( @@ -1015,8 +1010,7 @@ where pub struct $0Foo(#[my_custom_attr] u32); "#, r#" -pub struct Foo { #[my_custom_attr] -field1: u32 } +pub struct Foo { #[my_custom_attr]field1: u32 } "#, ); } From 475ebb8fefc8ed13c1f3cb0e9acb8bda4389d7c5 Mon Sep 17 00:00:00 2001 From: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> Date: Sun, 27 Jul 2025 13:24:08 +0900 Subject: [PATCH 3/4] Migrate `convert_tuple_struct_to_named_struct' assist to use `SyntaxEditor' Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com> --- .../convert_tuple_struct_to_named_struct.rs | 103 ++++++++++++------ 1 file changed, 68 insertions(+), 35 deletions(-) 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 6d729c9ea9..f4041f4941 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 @@ -1,7 +1,9 @@ use either::Either; +use hir::FileRangeWrapper; use ide_db::defs::{Definition, NameRefClass}; +use std::ops::RangeInclusive; use syntax::{ - SyntaxKind, SyntaxNode, T, + SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize, ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, match_ast, syntax_editor::{Element, Position, SyntaxEditor}, @@ -80,8 +82,8 @@ pub(crate) fn convert_tuple_struct_to_named_struct( |edit| { let names = generate_names(tuple_fields.fields()); edit_field_references(ctx, edit, tuple_fields.fields(), &names); - edit_struct_references(ctx, edit, strukt_def, &names); let mut editor = edit.make_editor(syntax); + edit_struct_references(ctx, edit, strukt_def, &names); edit_struct_def(&mut editor, &strukt_or_variant, tuple_fields, names); edit.add_file_edits(ctx.vfs_file_id(), editor); }, @@ -142,27 +144,21 @@ fn edit_struct_references( }; let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); - let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> { + let edit_node = |node: SyntaxNode| -> Option { match_ast! { match node { ast::TupleStructPat(tuple_struct_pat) => { - let file_range = ctx.sema.original_range_opt(&node)?; - edit.edit_file(file_range.file_id.file_id(ctx.db())); - edit.replace( - file_range.range, - ast::make::record_pat_with_fields( - tuple_struct_pat.path()?, - ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map( - |(pat, name)| { - ast::make::record_pat_field( - ast::make::name_ref(&name.to_string()), - pat, - ) - }, - ), None), - ) - .to_string(), - ); + Some(ast::make::record_pat_with_fields( + tuple_struct_pat.path()?, + ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map( + |(pat, name)| { + ast::make::record_pat_field( + ast::make::name_ref(&name.to_string()), + pat, + ) + }, + ), None), + ).syntax().clone_for_update()) }, // for tuple struct creations like Foo(42) ast::CallExpr(call_expr) => { @@ -179,8 +175,7 @@ fn edit_struct_references( let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; - edit.replace( - ctx.sema.original_range(&node).range, + Some( ast::make::record_expr( path, ast::make::record_expr_field_list(arg_list.args().zip(names).map( @@ -191,25 +186,58 @@ fn edit_struct_references( ) }, )), - ) - .to_string(), - ); + ).syntax().clone_for_update() + ) }, _ => return None, } } - Some(()) }; for (file_id, refs) in usages { - edit.edit_file(file_id.file_id(ctx.db())); - for r in refs { - for node in r.name.syntax().ancestors() { - if edit_node(edit, node).is_some() { - break; + let source = ctx.sema.parse(file_id); + let source = source.syntax(); + + let mut editor = edit.make_editor(source); + for r in refs.iter().rev() { + if let Some((old_node, new_node)) = r + .name + .syntax() + .ancestors() + .find_map(|node| Some((node.clone(), edit_node(node.clone())?))) + { + if let Some(old_node) = ctx.sema.original_syntax_node_rooted(&old_node) { + editor.replace(old_node, new_node); + } else { + let FileRangeWrapper { file_id: _, range } = ctx.sema.original_range(&old_node); + let parent = source.covering_element(range); + match parent { + SyntaxElement::Token(token) => { + editor.replace(token, new_node.syntax_element()); + } + SyntaxElement::Node(parent_node) => { + // replace the part of macro + // ``` + // foo!(a, Test::A(0)); + // ^^^^^^^^^^^^^^^ // parent_node + // ^^^^^^^^^^ // replace_range + // ``` + let start = parent_node + .children_with_tokens() + .find(|t| t.text_range().contains(range.start())); + let end = parent_node + .children_with_tokens() + .find(|t| t.text_range().contains(range.end() - TextSize::new(1))); + if let (Some(start), Some(end)) = (start, end) { + let replace_range = RangeInclusive::new(start, end); + editor.replace_all(replace_range, vec![new_node.into()]); + } + } + } } } } + edit.add_file_edits(file_id.file_id(ctx.db()), editor); } } @@ -227,12 +255,17 @@ fn edit_field_references( let def = Definition::Field(field); let usages = def.usages(&ctx.sema).all(); for (file_id, refs) in usages { - edit.edit_file(file_id.file_id(ctx.db())); + let source = ctx.sema.parse(file_id); + let source = source.syntax(); + let mut editor = edit.make_editor(source); for r in refs { - if let Some(name_ref) = r.name.as_name_ref() { - edit.replace(ctx.sema.original_range(name_ref.syntax()).range, name.text()); + if let Some(name_ref) = r.name.as_name_ref() + && let Some(original) = ctx.sema.original_ast_node(name_ref.clone()) + { + editor.replace(original.syntax(), name.syntax()); } } + edit.add_file_edits(file_id.file_id(ctx.db()), editor); } } } @@ -242,7 +275,7 @@ fn generate_names(fields: impl Iterator) -> Vec Date: Tue, 29 Jul 2025 20:59:50 +0900 Subject: [PATCH 4/4] add `SyntaxFactory::record_expr` to hide clone_for_update --- .../convert_tuple_struct_to_named_struct.rs | 20 +++++++++++-------- .../src/ast/syntax_factory/constructors.rs | 18 +++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) 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 f4041f4941..b27ebcaa4e 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 @@ -4,7 +4,9 @@ use ide_db::defs::{Definition, NameRefClass}; use std::ops::RangeInclusive; use syntax::{ SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize, - ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility}, + ast::{ + self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory, + }, match_ast, syntax_editor::{Element, Position, SyntaxEditor}, }; @@ -105,7 +107,8 @@ fn edit_struct_def( ); ast::RecordField::cast(field_editor.finish().new_root().clone()) }); - let record_fields = ast::make::record_field_list(record_fields).clone_for_update(); + let make = SyntaxFactory::without_mappings(); + let record_fields = make.record_field_list(record_fields); let tuple_fields_before = Position::before(tuple_fields.syntax()); if let Either::Left(strukt) = strukt { @@ -145,10 +148,11 @@ fn edit_struct_references( let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); let edit_node = |node: SyntaxNode| -> Option { + let make = SyntaxFactory::without_mappings(); match_ast! { match node { ast::TupleStructPat(tuple_struct_pat) => { - Some(ast::make::record_pat_with_fields( + Some(make.record_pat_with_fields( tuple_struct_pat.path()?, ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map( |(pat, name)| { @@ -158,7 +162,7 @@ fn edit_struct_references( ) }, ), None), - ).syntax().clone_for_update()) + ).syntax().clone()) }, // for tuple struct creations like Foo(42) ast::CallExpr(call_expr) => { @@ -174,9 +178,8 @@ fn edit_struct_references( } let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; - Some( - ast::make::record_expr( + make.record_expr( path, ast::make::record_expr_field_list(arg_list.args().zip(names).map( |(expr, name)| { @@ -186,7 +189,7 @@ fn edit_struct_references( ) }, )), - ).syntax().clone_for_update() + ).syntax().clone() ) }, _ => return None, @@ -271,11 +274,12 @@ fn edit_field_references( } fn generate_names(fields: impl Iterator) -> Vec { + let make = SyntaxFactory::without_mappings(); fields .enumerate() .map(|(i, _)| { let idx = i + 1; - ast::make::name(&format!("field{idx}")).clone_for_update() + make.name(&format!("field{idx}")) }) .collect() } diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 1ba6107315..738a26fed5 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -939,6 +939,24 @@ impl SyntaxFactory { ast } + pub fn record_expr( + &self, + path: ast::Path, + fields: ast::RecordExprFieldList, + ) -> ast::RecordExpr { + let ast = make::record_expr(path.clone(), fields.clone()).clone_for_update(); + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); + builder.map_node( + fields.syntax().clone(), + ast.record_expr_field_list().unwrap().syntax().clone(), + ); + builder.finish(&mut mapping); + } + ast + } + pub fn record_expr_field( &self, name: ast::NameRef,