about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs107
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs51
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs164
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs128
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs69
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs2
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs36
7 files changed, 319 insertions, 238 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 9f9d21923ff..ab183ac7089 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -2,6 +2,7 @@ use hir::HasSource;
 use syntax::{
     Edition,
     ast::{self, AstNode, make},
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::{
@@ -147,45 +148,78 @@ fn add_missing_impl_members_inner(
 
     let target = impl_def.syntax().text_range();
     acc.add(AssistId::quick_fix(assist_id), label, target, |edit| {
-        let new_impl_def = edit.make_mut(impl_def.clone());
-        let first_new_item = add_trait_assoc_items_to_impl(
+        let new_item = add_trait_assoc_items_to_impl(
             &ctx.sema,
             ctx.config,
             &missing_items,
             trait_,
-            &new_impl_def,
+            &impl_def,
             &target_scope,
         );
 
+        let Some((first_new_item, other_items)) = new_item.split_first() else {
+            return;
+        };
+
+        let mut first_new_item = if let DefaultMethods::No = mode
+            && let ast::AssocItem::Fn(func) = &first_new_item
+            && let Some(body) = try_gen_trait_body(
+                ctx,
+                func,
+                trait_ref,
+                &impl_def,
+                target_scope.krate().edition(ctx.sema.db),
+            )
+            && let Some(func_body) = func.body()
+        {
+            let mut func_editor = SyntaxEditor::new(first_new_item.syntax().clone_subtree());
+            func_editor.replace(func_body.syntax(), body.syntax());
+            ast::AssocItem::cast(func_editor.finish().new_root().clone())
+        } else {
+            Some(first_new_item.clone())
+        };
+
+        let new_assoc_items = first_new_item
+            .clone()
+            .into_iter()
+            .chain(other_items.iter().cloned())
+            .map(either::Either::Right)
+            .collect::<Vec<_>>();
+
+        let mut editor = edit.make_editor(impl_def.syntax());
+        if let Some(assoc_item_list) = impl_def.assoc_item_list() {
+            let items = new_assoc_items.into_iter().filter_map(either::Either::right).collect();
+            assoc_item_list.add_items(&mut editor, items);
+        } else {
+            let assoc_item_list = make::assoc_item_list(Some(new_assoc_items)).clone_for_update();
+            editor.insert_all(
+                Position::after(impl_def.syntax()),
+                vec![make::tokens::whitespace(" ").into(), assoc_item_list.syntax().clone().into()],
+            );
+            first_new_item = assoc_item_list.assoc_items().next();
+        }
+
         if let Some(cap) = ctx.config.snippet_cap {
             let mut placeholder = None;
             if let DefaultMethods::No = mode {
-                if let ast::AssocItem::Fn(func) = &first_new_item {
-                    if try_gen_trait_body(
-                        ctx,
-                        func,
-                        trait_ref,
-                        &impl_def,
-                        target_scope.krate().edition(ctx.sema.db),
-                    )
-                    .is_none()
+                if let Some(ast::AssocItem::Fn(func)) = &first_new_item {
+                    if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
+                        && m.syntax().text() == "todo!()"
                     {
-                        if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
-                        {
-                            if m.syntax().text() == "todo!()" {
-                                placeholder = Some(m);
-                            }
-                        }
+                        placeholder = Some(m);
                     }
                 }
             }
 
             if let Some(macro_call) = placeholder {
-                edit.add_placeholder_snippet(cap, macro_call);
-            } else {
-                edit.add_tabstop_before(cap, first_new_item);
+                let placeholder = edit.make_placeholder_snippet(cap);
+                editor.add_annotation(macro_call.syntax(), placeholder);
+            } else if let Some(first_new_item) = first_new_item {
+                let tabstop = edit.make_tabstop_before(cap);
+                editor.add_annotation(first_new_item.syntax(), tabstop);
             };
         };
+        edit.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
@@ -195,7 +229,7 @@ fn try_gen_trait_body(
     trait_ref: hir::TraitRef<'_>,
     impl_def: &ast::Impl,
     edition: Edition,
-) -> Option<()> {
+) -> Option<ast::BlockExpr> {
     let trait_path = make::ext::ident_path(
         &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(),
     );
@@ -322,7 +356,7 @@ impl Foo for S {
     }
 
     #[test]
-    fn test_impl_def_without_braces() {
+    fn test_impl_def_without_braces_macro() {
         check_assist(
             add_missing_impl_members,
             r#"
@@ -341,6 +375,33 @@ impl Foo for S {
     }
 
     #[test]
+    fn test_impl_def_without_braces_tabstop_first_item() {
+        check_assist(
+            add_missing_impl_members,
+            r#"
+trait Foo {
+    type Output;
+    fn foo(&self);
+}
+struct S;
+impl Foo for S { $0 }"#,
+            r#"
+trait Foo {
+    type Output;
+    fn foo(&self);
+}
+struct S;
+impl Foo for S {
+    $0type Output;
+
+    fn foo(&self) {
+        todo!()
+    }
+}"#,
+        );
+    }
+
+    #[test]
     fn fill_in_type_params_1() {
         check_assist(
             add_missing_impl_members,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
index 168eba22b3b..31cadcf5ea8 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
@@ -167,25 +167,33 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) ->
                 DefaultMethods::No,
                 IgnoreAssocItems::DocHiddenAttrPresent,
             );
-            let impl_ = make::impl_trait(
-                trait_.unsafe_token().is_some(),
-                None,
-                trait_.generic_param_list().map(|list| {
-                    make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
-                }),
-                None,
-                None,
-                false,
-                make::ty(&name.text()),
-                make::ty_placeholder(),
-                None,
-                None,
-                None,
-            )
-            .clone_for_update();
-
-            if !missing_items.is_empty() {
-                utils::add_trait_assoc_items_to_impl(
+
+            let trait_gen_args = trait_.generic_param_list().map(|list| {
+                make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
+            });
+
+            let make_impl_ = |body| {
+                make::impl_trait(
+                    trait_.unsafe_token().is_some(),
+                    None,
+                    trait_gen_args.clone(),
+                    None,
+                    None,
+                    false,
+                    make::ty(&name.text()),
+                    make::ty_placeholder(),
+                    None,
+                    None,
+                    body,
+                )
+                .clone_for_update()
+            };
+
+            let impl_ = if missing_items.is_empty() {
+                make_impl_(None)
+            } else {
+                let impl_ = make_impl_(None);
+                let assoc_items = utils::add_trait_assoc_items_to_impl(
                     &ctx.sema,
                     ctx.config,
                     &missing_items,
@@ -193,7 +201,10 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) ->
                     &impl_,
                     &target_scope,
                 );
-            }
+                let assoc_items = assoc_items.into_iter().map(either::Either::Right).collect();
+                let assoc_item_list = make::assoc_item_list(Some(assoc_items));
+                make_impl_(Some(assoc_item_list))
+            };
 
             if let Some(cap) = ctx.config.snippet_cap {
                 if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 806c8fba9ea..45bb6ce9129 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -5,12 +5,12 @@ use syntax::{
     SyntaxKind::WHITESPACE,
     T,
     ast::{self, AstNode, HasName, make},
-    ted::{self, Position},
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::{
     AssistConfig, AssistId,
-    assist_context::{AssistContext, Assists, SourceChangeBuilder},
+    assist_context::{AssistContext, Assists},
     utils::{
         DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items,
         gen_trait_fn_body, generate_trait_impl,
@@ -126,98 +126,56 @@ fn add_assist(
     let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`");
 
     acc.add(AssistId::refactor("replace_derive_with_manual_impl"), label, target, |builder| {
-        let insert_after = ted::Position::after(builder.make_mut(adt.clone()).syntax());
+        let insert_after = Position::after(adt.syntax());
         let impl_is_unsafe = trait_.map(|s| s.is_unsafe(ctx.db())).unwrap_or(false);
-        let impl_def_with_items = impl_def_from_trait(
+        let impl_def = impl_def_from_trait(
             &ctx.sema,
             ctx.config,
             adt,
             &annotated_name,
             trait_,
             replace_trait_path,
+            impl_is_unsafe,
         );
-        update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
 
-        let trait_path = make::ty_path(replace_trait_path.clone());
+        let mut editor = builder.make_editor(attr.syntax());
+        update_attribute(&mut editor, old_derives, old_tree, old_trait_path, attr);
 
-        match (ctx.config.snippet_cap, impl_def_with_items) {
-            (None, None) => {
-                let impl_def = generate_trait_impl(adt, trait_path);
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
+        let trait_path = make::ty_path(replace_trait_path.clone());
 
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (None, Some((impl_def, _))) => {
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (Some(cap), None) => {
-                let impl_def = generate_trait_impl(adt, trait_path);
-
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
+        let (impl_def, first_assoc_item) = if let Some(impl_def) = impl_def {
+            (
+                impl_def.clone(),
+                impl_def.assoc_item_list().and_then(|list| list.assoc_items().next()),
+            )
+        } else {
+            (generate_trait_impl(impl_is_unsafe, adt, trait_path), None)
+        };
 
-                if let Some(l_curly) = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
+        if let Some(cap) = ctx.config.snippet_cap {
+            if let Some(first_assoc_item) = first_assoc_item {
+                if let ast::AssocItem::Fn(ref func) = first_assoc_item
+                    && let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
+                    && m.syntax().text() == "todo!()"
                 {
-                    builder.add_tabstop_after_token(cap, l_curly);
-                }
-
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
-            }
-            (Some(cap), Some((impl_def, first_assoc_item))) => {
-                let mut added_snippet = false;
-
-                if impl_is_unsafe {
-                    ted::insert(
-                        Position::first_child_of(impl_def.syntax()),
-                        make::token(T![unsafe]),
-                    );
-                }
-
-                if let ast::AssocItem::Fn(ref func) = first_assoc_item {
-                    if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
-                        if m.syntax().text() == "todo!()" {
-                            // Make the `todo!()` a placeholder
-                            builder.add_placeholder_snippet(cap, m);
-                            added_snippet = true;
-                        }
-                    }
-                }
-
-                if !added_snippet {
+                    // Make the `todo!()` a placeholder
+                    builder.add_placeholder_snippet(cap, m);
+                } else {
                     // If we haven't already added a snippet, add a tabstop before the generated function
                     builder.add_tabstop_before(cap, first_assoc_item);
                 }
-
-                ted::insert_all(
-                    insert_after,
-                    vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
-                );
+            } else if let Some(l_curly) =
+                impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
+            {
+                builder.add_tabstop_after_token(cap, l_curly);
             }
-        };
+        }
+
+        editor.insert_all(
+            insert_after,
+            vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
+        );
+        builder.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
@@ -228,7 +186,8 @@ fn impl_def_from_trait(
     annotated_name: &ast::Name,
     trait_: Option<hir::Trait>,
     trait_path: &ast::Path,
-) -> Option<(ast::Impl, ast::AssocItem)> {
+    impl_is_unsafe: bool,
+) -> Option<ast::Impl> {
     let trait_ = trait_?;
     let target_scope = sema.scope(annotated_name.syntax())?;
 
@@ -245,21 +204,43 @@ fn impl_def_from_trait(
     if trait_items.is_empty() {
         return None;
     }
-    let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone()));
+    let impl_def = generate_trait_impl(impl_is_unsafe, adt, make::ty_path(trait_path.clone()));
 
-    let first_assoc_item =
+    let assoc_items =
         add_trait_assoc_items_to_impl(sema, config, &trait_items, trait_, &impl_def, &target_scope);
+    let assoc_item_list = if let Some((first, other)) =
+        assoc_items.split_first().map(|(first, other)| (first.clone_subtree(), other))
+    {
+        let first_item = if let ast::AssocItem::Fn(ref func) = first
+            && let Some(body) = gen_trait_fn_body(func, trait_path, adt, None)
+            && let Some(func_body) = func.body()
+        {
+            let mut editor = SyntaxEditor::new(first.syntax().clone());
+            editor.replace(func_body.syntax(), body.syntax());
+            ast::AssocItem::cast(editor.finish().new_root().clone())
+        } else {
+            Some(first.clone())
+        };
+        let items = first_item
+            .into_iter()
+            .chain(other.iter().cloned())
+            .map(either::Either::Right)
+            .collect();
+        make::assoc_item_list(Some(items))
+    } else {
+        make::assoc_item_list(None)
+    }
+    .clone_for_update();
 
-    // Generate a default `impl` function body for the derived trait.
-    if let ast::AssocItem::Fn(ref func) = first_assoc_item {
-        let _ = gen_trait_fn_body(func, trait_path, adt, None);
-    };
-
-    Some((impl_def, first_assoc_item))
+    let impl_def = impl_def.clone_subtree();
+    let mut editor = SyntaxEditor::new(impl_def.syntax().clone());
+    editor.replace(impl_def.assoc_item_list()?.syntax(), assoc_item_list.syntax());
+    let impl_def = ast::Impl::cast(editor.finish().new_root().clone())?;
+    Some(impl_def)
 }
 
 fn update_attribute(
-    builder: &mut SourceChangeBuilder,
+    editor: &mut SyntaxEditor,
     old_derives: &[ast::Path],
     old_tree: &ast::TokenTree,
     old_trait_path: &ast::Path,
@@ -272,8 +253,6 @@ fn update_attribute(
     let has_more_derives = !new_derives.is_empty();
 
     if has_more_derives {
-        let old_tree = builder.make_mut(old_tree.clone());
-
         // Make the paths into flat lists of tokens in a vec
         let tt = new_derives.iter().map(|path| path.syntax().clone()).map(|node| {
             node.descendants_with_tokens()
@@ -288,18 +267,17 @@ fn update_attribute(
         let tt = tt.collect::<Vec<_>>();
 
         let new_tree = make::token_tree(T!['('], tt).clone_for_update();
-        ted::replace(old_tree.syntax(), new_tree.syntax());
+        editor.replace(old_tree.syntax(), new_tree.syntax());
     } else {
         // Remove the attr and any trailing whitespace
-        let attr = builder.make_mut(attr.clone());
 
         if let Some(line_break) =
             attr.syntax().next_sibling_or_token().filter(|t| t.kind() == WHITESPACE)
         {
-            ted::remove(line_break)
+            editor.delete(line_break)
         }
 
-        ted::remove(attr.syntax())
+        editor.delete(attr.syntax())
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index 31649a71b9a..3457367645f 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -23,7 +23,7 @@ use syntax::{
     ast::{
         self, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
         edit::{AstNodeEdit, IndentLevel},
-        edit_in_place::{AttrsOwnerEdit, Indent, Removable},
+        edit_in_place::{AttrsOwnerEdit, Removable},
         make,
         syntax_factory::SyntaxFactory,
     },
@@ -178,6 +178,7 @@ pub fn filter_assoc_items(
 /// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it,
 /// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got
 /// inserted.
+#[must_use]
 pub fn add_trait_assoc_items_to_impl(
     sema: &Semantics<'_, RootDatabase>,
     config: &AssistConfig,
@@ -185,71 +186,65 @@ pub fn add_trait_assoc_items_to_impl(
     trait_: hir::Trait,
     impl_: &ast::Impl,
     target_scope: &hir::SemanticsScope<'_>,
-) -> ast::AssocItem {
+) -> Vec<ast::AssocItem> {
     let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
-    let items = original_items.iter().map(|InFile { file_id, value: original_item }| {
-        let cloned_item = {
-            if let Some(macro_file) = file_id.macro_file() {
-                let span_map = sema.db.expansion_span_map(macro_file);
-                let item_prettified = prettify_macro_expansion(
-                    sema.db,
-                    original_item.syntax().clone(),
-                    &span_map,
-                    target_scope.krate().into(),
-                );
-                if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
-                    return formatted;
-                } else {
-                    stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
+    original_items
+        .iter()
+        .map(|InFile { file_id, value: original_item }| {
+            let cloned_item = {
+                if let Some(macro_file) = file_id.macro_file() {
+                    let span_map = sema.db.expansion_span_map(macro_file);
+                    let item_prettified = prettify_macro_expansion(
+                        sema.db,
+                        original_item.syntax().clone(),
+                        &span_map,
+                        target_scope.krate().into(),
+                    );
+                    if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
+                        return formatted;
+                    } else {
+                        stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
+                    }
                 }
-            }
-            original_item.clone_for_update()
-        };
-
-        if let Some(source_scope) = sema.scope(original_item.syntax()) {
-            // FIXME: Paths in nested macros are not handled well. See
-            // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
-            let transform =
-                PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
-            transform.apply(cloned_item.syntax());
-        }
-        cloned_item.remove_attrs_and_docs();
-        cloned_item.reindent_to(new_indent_level);
-        cloned_item
-    });
+                original_item.clone_for_update()
+            };
 
-    let assoc_item_list = impl_.get_or_create_assoc_item_list();
-
-    let mut first_item = None;
-    for item in items {
-        first_item.get_or_insert_with(|| item.clone());
-        match &item {
-            ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
-                let body = AstNodeEdit::indent(
-                    &make::block_expr(
-                        None,
-                        Some(match config.expr_fill_default {
-                            ExprFillDefaultMode::Todo => make::ext::expr_todo(),
-                            ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
-                            ExprFillDefaultMode::Default => make::ext::expr_todo(),
-                        }),
-                    ),
-                    new_indent_level,
-                );
-                ted::replace(fn_.get_or_create_body().syntax(), body.syntax())
+            if let Some(source_scope) = sema.scope(original_item.syntax()) {
+                // FIXME: Paths in nested macros are not handled well. See
+                // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
+                let transform =
+                    PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
+                transform.apply(cloned_item.syntax());
             }
-            ast::AssocItem::TypeAlias(type_alias) => {
-                if let Some(type_bound_list) = type_alias.type_bound_list() {
-                    type_bound_list.remove()
+            cloned_item.remove_attrs_and_docs();
+            cloned_item.reset_indent()
+        })
+        .map(|item| {
+            match &item {
+                ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
+                    let body = AstNodeEdit::indent(
+                        &make::block_expr(
+                            None,
+                            Some(match config.expr_fill_default {
+                                ExprFillDefaultMode::Todo => make::ext::expr_todo(),
+                                ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
+                                ExprFillDefaultMode::Default => make::ext::expr_todo(),
+                            }),
+                        ),
+                        IndentLevel::single(),
+                    );
+                    ted::replace(fn_.get_or_create_body().syntax(), body.syntax());
+                }
+                ast::AssocItem::TypeAlias(type_alias) => {
+                    if let Some(type_bound_list) = type_alias.type_bound_list() {
+                        type_bound_list.remove()
+                    }
                 }
+                _ => {}
             }
-            _ => {}
-        }
-
-        assoc_item_list.add_item(item)
-    }
-
-    first_item.unwrap()
+            AstNodeEdit::indent(&item, new_indent_level)
+        })
+        .collect()
 }
 
 pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -668,19 +663,19 @@ pub(crate) fn generate_impl_with_item(
     adt: &ast::Adt,
     body: Option<ast::AssocItemList>,
 ) -> ast::Impl {
-    generate_impl_inner(adt, None, true, body)
+    generate_impl_inner(false, adt, None, true, body)
 }
 
 pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
-    generate_impl_inner(adt, None, true, None)
+    generate_impl_inner(false, adt, None, true, None)
 }
 
 /// Generates the corresponding `impl <trait> for Type {}` including type
 /// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
 ///
 /// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
-pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
-    generate_impl_inner(adt, Some(trait_), true, None)
+pub(crate) fn generate_trait_impl(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
+    generate_impl_inner(is_unsafe, adt, Some(trait_), true, None)
 }
 
 /// Generates the corresponding `impl <trait> for Type {}` including type
@@ -688,10 +683,11 @@ pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Imp
 ///
 /// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
 pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
-    generate_impl_inner(adt, Some(trait_), false, None)
+    generate_impl_inner(false, adt, Some(trait_), false, None)
 }
 
 fn generate_impl_inner(
+    is_unsafe: bool,
     adt: &ast::Adt,
     trait_: Option<ast::Type>,
     trait_is_transitive: bool,
@@ -735,7 +731,7 @@ fn generate_impl_inner(
 
     let impl_ = match trait_ {
         Some(trait_) => make::impl_trait(
-            false,
+            is_unsafe,
             None,
             None,
             generic_params,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
index 026209efc44..87e90e85193 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs
@@ -1,10 +1,7 @@
 //! This module contains functions to generate default trait impl function bodies where possible.
 
 use hir::TraitRef;
-use syntax::{
-    ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make},
-    ted,
-};
+use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make};
 
 /// Generate custom trait bodies without default implementation where possible.
 ///
@@ -18,21 +15,33 @@ pub(crate) fn gen_trait_fn_body(
     trait_path: &ast::Path,
     adt: &ast::Adt,
     trait_ref: Option<TraitRef<'_>>,
-) -> Option<()> {
+) -> Option<ast::BlockExpr> {
+    let _ = func.body()?;
     match trait_path.segment()?.name_ref()?.text().as_str() {
-        "Clone" => gen_clone_impl(adt, func),
-        "Debug" => gen_debug_impl(adt, func),
-        "Default" => gen_default_impl(adt, func),
-        "Hash" => gen_hash_impl(adt, func),
-        "PartialEq" => gen_partial_eq(adt, func, trait_ref),
-        "PartialOrd" => gen_partial_ord(adt, func, trait_ref),
+        "Clone" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
+            gen_clone_impl(adt)
+        }
+        "Debug" => gen_debug_impl(adt),
+        "Default" => gen_default_impl(adt),
+        "Hash" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
+            gen_hash_impl(adt)
+        }
+        "PartialEq" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
+            gen_partial_eq(adt, trait_ref)
+        }
+        "PartialOrd" => {
+            stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
+            gen_partial_ord(adt, trait_ref)
+        }
         _ => None,
     }
 }
 
 /// Generate a `Clone` impl based on the fields and members of the target type.
-fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
+fn gen_clone_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_clone_call(target: ast::Expr) -> ast::Expr {
         let method = make::name_ref("clone");
         make::expr_method_call(target, method, make::arg_list(None)).into()
@@ -139,12 +148,11 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
         }
     };
     let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-    ted::replace(func.body()?.syntax(), body.syntax());
-    Some(())
+    Some(body)
 }
 
 /// Generate a `Debug` impl based on the fields and members of the target type.
-fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+fn gen_debug_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     let annotated_name = adt.name()?;
     match adt {
         // `Debug` cannot be derived for unions, so no default impl can be provided.
@@ -248,8 +256,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
 
             let body = make::block_expr(None, Some(match_expr.into()));
             let body = body.indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.syntax());
-            Some(())
+            Some(body)
         }
 
         ast::Adt::Struct(strukt) => {
@@ -296,14 +303,13 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
             let method = make::name_ref("finish");
             let expr = make::expr_method_call(expr, method, make::arg_list(None)).into();
             let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.syntax());
-            Some(())
+            Some(body)
         }
     }
 }
 
 /// Generate a `Debug` impl based on the fields and members of the target type.
-fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
+fn gen_default_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_default_call() -> Option<ast::Expr> {
         let fn_name = make::ext::path_from_idents(["Default", "default"])?;
         Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)).into())
@@ -342,15 +348,13 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
                 }
             };
             let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
-            ted::replace(func.body()?.syntax(), body.syntax());
-            Some(())
+            Some(body)
         }
     }
 }
 
 /// Generate a `Hash` impl based on the fields and members of the target type.
-fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
+fn gen_hash_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
     fn gen_hash_call(target: ast::Expr) -> ast::Stmt {
         let method = make::name_ref("hash");
         let arg = make::expr_path(make::ext::ident_path("state"));
@@ -400,13 +404,11 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.syntax());
-    Some(())
+    Some(body)
 }
 
 /// Generate a `PartialEq` impl based on the fields and members of the target type.
-fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
+fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
     fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> {
         match expr {
             Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)),
@@ -595,12 +597,10 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.syntax());
-    Some(())
+    Some(body)
 }
 
-fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> {
-    stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
+fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
     fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> {
         let mut arms = vec![];
 
@@ -686,8 +686,7 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_
         },
     };
 
-    ted::replace(func.body()?.syntax(), body.syntax());
-    Some(())
+    Some(body)
 }
 
 fn make_discriminant() -> Option<ast::Expr> {
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index 66aae101eb0..2a7b51c3c24 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -237,7 +237,7 @@ pub fn assoc_item_list(
     let body_indent = if is_break_braces { "    ".to_owned() } else { String::new() };
 
     let body = match body {
-        Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""),
+        Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n    "),
         None => String::new(),
     };
     ast_from_text(&format!("impl C for D {{{body_newline}{body_indent}{body}{body_newline}}}"))
diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
index d66ea8aa28c..840e7697979 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs
@@ -92,6 +92,42 @@ fn get_or_insert_comma_after(editor: &mut SyntaxEditor, syntax: &SyntaxNode) ->
     }
 }
 
+impl ast::AssocItemList {
+    /// Adds a new associated item after all of the existing associated items.
+    ///
+    /// Attention! This function does align the first line of `item` with respect to `self`,
+    /// but it does _not_ change indentation of other lines (if any).
+    pub fn add_items(&self, editor: &mut SyntaxEditor, items: Vec<ast::AssocItem>) {
+        let (indent, position, whitespace) = match self.assoc_items().last() {
+            Some(last_item) => (
+                IndentLevel::from_node(last_item.syntax()),
+                Position::after(last_item.syntax()),
+                "\n\n",
+            ),
+            None => match self.l_curly_token() {
+                Some(l_curly) => {
+                    normalize_ws_between_braces(editor, self.syntax());
+                    (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
+                }
+                None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
+            },
+        };
+
+        let elements: Vec<SyntaxElement> = items
+            .into_iter()
+            .enumerate()
+            .flat_map(|(i, item)| {
+                let whitespace = if i != 0 { "\n\n" } else { whitespace };
+                vec![
+                    make::tokens::whitespace(&format!("{whitespace}{indent}")).into(),
+                    item.syntax().clone().into(),
+                ]
+            })
+            .collect();
+        editor.insert_all(position, elements);
+    }
+}
+
 impl ast::VariantList {
     pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) {
         let make = SyntaxFactory::without_mappings();