Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #20293 from Hmikihiro/migrate_replace_derive_with_manual_impl
Migrate `replace derive with manual impl` and `add_missing_impl_members` to use `SyntaxEditor`
Shoyu Vanilla (Flint) 9 months ago
parent 48ccbe0 · parent 82dfdac · commit 51e77c9
-rw-r--r--crates/ide-assists/src/handlers/add_missing_impl_members.rs107
-rw-r--r--crates/ide-assists/src/handlers/generate_impl.rs51
-rw-r--r--crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs164
-rw-r--r--crates/ide-assists/src/utils.rs128
-rw-r--r--crates/ide-assists/src/utils/gen_trait_fn_body.rs69
-rw-r--r--crates/syntax/src/ast/make.rs2
-rw-r--r--crates/syntax/src/syntax_editor/edits.rs36
7 files changed, 319 insertions, 238 deletions
diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 9f9d21923f..ab183ac708 100644
--- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/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/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs
index 168eba22b3..31cadcf5ea 100644
--- a/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/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/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 806c8fba9e..45bb6ce912 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/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/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 31649a71b9..3457367645 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/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/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/crates/ide-assists/src/utils/gen_trait_fn_body.rs
index 026209efc4..87e90e8519 100644
--- a/crates/ide-assists/src/utils/gen_trait_fn_body.rs
+++ b/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/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 66aae101eb..2a7b51c3c2 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/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/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs
index d66ea8aa28..840e769797 100644
--- a/crates/syntax/src/syntax_editor/edits.rs
+++ b/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();