Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/utils.rs')
-rw-r--r--crates/ide-assists/src/utils.rs147
1 files changed, 77 insertions, 70 deletions
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index fbce1d31ea..15c7a6a3fc 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -23,10 +23,11 @@ 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,
},
+ syntax_editor::SyntaxEditor,
ted,
};
@@ -178,6 +179,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 +187,66 @@ 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 mut 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()
}
- 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
- });
-
- 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.clone_for_update().syntax())
+ .reset_indent();
+
+ 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());
+ cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap();
}
- 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
+ })
+ .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 {
@@ -334,7 +331,7 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex
fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
match expr {
ast::Expr::BinExpr(bin) => {
- let bin = bin.clone_for_update();
+ let bin = bin.clone_subtree();
let op_token = bin.op_token()?;
let rev_token = match op_token.kind() {
T![==] => T![!=],
@@ -350,8 +347,9 @@ fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
);
}
};
- ted::replace(op_token, make::token(rev_token));
- Some(bin.into())
+ let mut bin_editor = SyntaxEditor::new(bin.syntax().clone());
+ bin_editor.replace(op_token, make::token(rev_token));
+ ast::Expr::cast(bin_editor.finish().new_root().clone())
}
ast::Expr::MethodCallExpr(mce) => {
let receiver = mce.receiver()?;
@@ -664,16 +662,23 @@ fn generate_impl_text_inner(
/// Generates the corresponding `impl Type {}` including type and lifetime
/// parameters.
+pub(crate) fn generate_impl_with_item(
+ adt: &ast::Adt,
+ body: Option<ast::AssocItemList>,
+) -> ast::Impl {
+ generate_impl_inner(false, adt, None, true, body)
+}
+
pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
- generate_impl_inner(adt, None, true)
+ 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)
+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
@@ -681,13 +686,15 @@ 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)
+ 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,
+ body: Option<ast::AssocItemList>,
) -> ast::Impl {
// Ensure lifetime params are before type & const params
let generic_params = adt.generic_param_list().map(|generic_params| {
@@ -727,7 +734,7 @@ fn generate_impl_inner(
let impl_ = match trait_ {
Some(trait_) => make::impl_trait(
- false,
+ is_unsafe,
None,
None,
generic_params,
@@ -737,9 +744,9 @@ fn generate_impl_inner(
ty,
None,
adt.where_clause(),
- None,
+ body,
),
- None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), None),
+ None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), body),
}
.clone_for_update();