Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs | 45 | ||||
| -rw-r--r-- | crates/ide-assists/src/utils.rs | 201 | ||||
| -rw-r--r-- | crates/syntax/src/ast/syntax_factory/constructors.rs | 24 |
3 files changed, 251 insertions, 19 deletions
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 7c024263b4..f54f7a02d2 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 @@ -4,7 +4,7 @@ use itertools::Itertools; use syntax::{ SyntaxKind::WHITESPACE, T, - ast::{self, AstNode, HasName, make}, + ast::{self, AstNode, HasName, syntax_factory::SyntaxFactory}, syntax_editor::{Position, SyntaxEditor}, }; @@ -12,8 +12,8 @@ use crate::{ AssistConfig, AssistId, assist_context::{AssistContext, Assists}, utils::{ - DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items, - gen_trait_fn_body, generate_trait_impl, + DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, + filter_assoc_items, gen_trait_fn_body, generate_trait_impl, }, }; @@ -127,6 +127,7 @@ 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 make = SyntaxFactory::without_mappings(); 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 = impl_def_from_trait( @@ -142,7 +143,7 @@ fn add_assist( let mut editor = builder.make_editor(attr.syntax()); update_attribute(&mut editor, old_derives, old_tree, old_trait_path, attr); - let trait_path = make::ty_path(replace_trait_path.clone()); + let trait_path = make.ty_path(replace_trait_path.clone()).into(); let (impl_def, first_assoc_item) = if let Some(impl_def) = impl_def { ( @@ -150,7 +151,7 @@ fn add_assist( impl_def.assoc_item_list().and_then(|list| list.assoc_items().next()), ) } else { - (generate_trait_impl(impl_is_unsafe, adt, trait_path), None) + (generate_trait_impl(&make, impl_is_unsafe, adt, trait_path), None) }; if let Some(cap) = ctx.config.snippet_cap { @@ -174,7 +175,7 @@ fn add_assist( editor.insert_all( insert_after, - vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()], + vec![make.whitespace("\n\n").into(), impl_def.syntax().clone().into()], ); builder.add_file_edits(ctx.vfs_file_id(), editor); }) @@ -205,10 +206,19 @@ fn impl_def_from_trait( if trait_items.is_empty() { return None; } - let impl_def = generate_trait_impl(impl_is_unsafe, adt, make::ty_path(trait_path.clone())); - - let assoc_items = - add_trait_assoc_items_to_impl(sema, config, &trait_items, trait_, &impl_def, &target_scope); + let make = SyntaxFactory::without_mappings(); + let trait_ty = make.ty_path(trait_path.clone()).into(); + let impl_def = generate_trait_impl(&make, impl_is_unsafe, adt, trait_ty); + + let assoc_items = add_trait_assoc_items_to_impl_with_factory( + &make, + 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)) { @@ -222,12 +232,12 @@ fn impl_def_from_trait( } else { Some(first.clone()) }; - let items = first_item.into_iter().chain(other.iter().cloned()).collect(); - make::assoc_item_list(Some(items)) + let items: Vec<ast::AssocItem> = + first_item.into_iter().chain(other.iter().cloned()).collect(); + make.assoc_item_list(items) } else { - make::assoc_item_list(None) - } - .clone_for_update(); + make.assoc_item_list_empty() + }; let impl_def = impl_def.clone_subtree(); let mut editor = SyntaxEditor::new(impl_def.syntax().clone()); @@ -243,6 +253,7 @@ fn update_attribute( old_trait_path: &ast::Path, attr: &ast::Attr, ) { + let make = SyntaxFactory::without_mappings(); let new_derives = old_derives .iter() .filter(|t| t.to_string() != old_trait_path.to_string()) @@ -257,13 +268,13 @@ fn update_attribute( .collect::<Vec<_>>() }); // ...which are interspersed with ", " - let tt = Itertools::intersperse(tt, vec![make::token(T![,]), make::tokens::single_space()]); + let tt = Itertools::intersperse(tt, vec![make.token(T![,]), make.whitespace(" ")]); // ...wrap them into the appropriate `NodeOrToken` variant let tt = tt.flatten().map(syntax::NodeOrToken::Token); // ...and make them into a flat list of tokens let tt = tt.collect::<Vec<_>>(); - let new_tree = make::token_tree(T!['('], tt).clone_for_update(); + let new_tree = make.token_tree(T!['('], tt); editor.replace(old_tree.syntax(), new_tree.syntax()); } else { // Remove the attr and any trailing whitespace diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 0f28a20225..07811fb6f0 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -203,6 +203,9 @@ 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. +/// +/// Legacy: prefer [`add_trait_assoc_items_to_impl_with_factory`] when a [`SyntaxFactory`] is +/// available. #[must_use] pub fn add_trait_assoc_items_to_impl( sema: &Semantics<'_, RootDatabase>, @@ -271,6 +274,79 @@ pub fn add_trait_assoc_items_to_impl( .collect() } +/// [`SyntaxFactory`]-based variant of [`add_trait_assoc_items_to_impl`]. +#[must_use] +pub fn add_trait_assoc_items_to_impl_with_factory( + make: &SyntaxFactory, + sema: &Semantics<'_, RootDatabase>, + config: &AssistConfig, + original_items: &[InFile<ast::AssocItem>], + trait_: hir::Trait, + impl_: &ast::Impl, + target_scope: &hir::SemanticsScope<'_>, +) -> Vec<ast::AssocItem> { + let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; + 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 + } + .reset_indent(); + + if let Some(source_scope) = sema.scope(original_item.syntax()) { + let transform = + PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); + cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap(); + } + cloned_item.remove_attrs_and_docs(); + cloned_item + }) + .filter_map(|item| match item { + ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { + let fn_ = fn_.clone_subtree(); + let fill_expr: ast::Expr = match config.expr_fill_default { + ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), + ExprFillDefaultMode::Underscore => make.expr_underscore().into(), + }; + let new_body = make.block_expr(None::<ast::Stmt>, Some(fill_expr)); + let new_body = AstNodeEdit::indent(&new_body, IndentLevel::single()); + let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone()); + fn_.replace_or_insert_body(&mut fn_editor, new_body); + let new_fn_ = fn_editor.finish().new_root().clone(); + ast::AssocItem::cast(new_fn_) + } + ast::AssocItem::TypeAlias(type_alias) => { + let type_alias = type_alias.clone_subtree(); + if let Some(type_bound_list) = type_alias.type_bound_list() { + let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone()); + type_bound_list.remove(&mut type_alias_editor); + let type_alias = type_alias_editor.finish().new_root().clone(); + ast::AssocItem::cast(type_alias) + } else { + Some(ast::AssocItem::TypeAlias(type_alias)) + } + } + item => Some(item), + }) + .map(|item| AstNodeEdit::indent(&item, new_indent_level)) + .collect() +} + pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { node.children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) @@ -671,8 +747,13 @@ pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl { /// 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(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl { - generate_impl_inner(is_unsafe, adt, Some(trait_), true, None) +pub(crate) fn generate_trait_impl( + make: &SyntaxFactory, + is_unsafe: bool, + adt: &ast::Adt, + trait_: ast::Type, +) -> ast::Impl { + generate_impl_inner_with_factory(make, is_unsafe, adt, Some(trait_), true, None) } /// Generates the corresponding `impl <trait> for Type {}` including type @@ -753,6 +834,76 @@ fn generate_impl_inner( .clone_for_update() } +fn generate_impl_inner_with_factory( + make: &SyntaxFactory, + 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| { + let lifetime_params = + generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); + let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { + let param = match param { + ast::TypeOrConstParam::Type(param) => { + // remove defaults since they can't be specified in impls + let mut bounds = + param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect()); + if let Some(trait_) = &trait_ { + // Add the current trait to `bounds` if the trait is transitive, + // meaning `impl<T> Trait for U<T>` requires `T: Trait`. + if trait_is_transitive { + bounds.push(make.type_bound(trait_.clone())); + } + }; + // `{ty_param}: {bounds}` + let param = make.type_param(param.name()?, make.type_bound_list(bounds)); + ast::GenericParam::TypeParam(param) + } + ast::TypeOrConstParam::Const(param) => { + // remove defaults since they can't be specified in impls + let param = make.const_param(param.name()?, param.ty()?); + ast::GenericParam::ConstParam(param) + } + }; + Some(param) + }); + + make.generic_param_list(itertools::chain(lifetime_params, ty_or_const_params)) + }); + let generic_args = + generic_params.as_ref().map(|params| params.to_generic_args().clone_for_update()); + let adt_assoc_bounds = + trait_.as_ref().zip(generic_params.as_ref()).and_then(|(trait_, params)| { + generic_param_associated_bounds_with_factory(make, adt, trait_, params) + }); + + let ty: ast::Type = make.ty_path(make.ident_path(&adt.name().unwrap().text())).into(); + + let cfg_attrs = + adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + match trait_ { + Some(trait_) => make.impl_trait( + cfg_attrs, + is_unsafe, + None, + None, + generic_params, + generic_args, + false, + trait_, + ty, + adt_assoc_bounds, + adt.where_clause(), + body, + ), + None => make.impl_(cfg_attrs, generic_params, generic_args, ty, adt.where_clause(), body), + } +} + fn generic_param_associated_bounds( adt: &ast::Adt, trait_: &ast::Type, @@ -798,6 +949,52 @@ fn generic_param_associated_bounds( trait_where_clause.peek().is_some().then(|| make::where_clause(trait_where_clause)) } +fn generic_param_associated_bounds_with_factory( + make: &SyntaxFactory, + adt: &ast::Adt, + trait_: &ast::Type, + generic_params: &ast::GenericParamList, +) -> Option<ast::WhereClause> { + let in_type_params = |name: &ast::NameRef| { + generic_params + .generic_params() + .filter_map(|param| match param { + ast::GenericParam::TypeParam(type_param) => type_param.name(), + _ => None, + }) + .any(|param| param.text() == name.text()) + }; + let adt_body = match adt { + ast::Adt::Enum(e) => e.variant_list().map(|it| it.syntax().clone()), + ast::Adt::Struct(s) => s.field_list().map(|it| it.syntax().clone()), + ast::Adt::Union(u) => u.record_field_list().map(|it| it.syntax().clone()), + }; + let mut trait_where_clause = adt_body + .into_iter() + .flat_map(|it| it.descendants()) + .filter_map(ast::Path::cast) + .filter_map(|path| { + let qualifier = path.qualifier()?.as_single_segment()?; + let qualifier = qualifier + .name_ref() + .or_else(|| match qualifier.type_anchor()?.ty()? { + ast::Type::PathType(path_type) => path_type.path()?.as_single_name_ref(), + _ => None, + }) + .filter(in_type_params)?; + Some((qualifier, path.segment()?.name_ref()?)) + }) + .map(|(qualifier, assoc_name)| { + let segments = [qualifier, assoc_name].map(|nr| make.path_segment(nr)); + let path = make.path_from_segments(segments, false); + let bounds = [make.type_bound(trait_.clone())]; + make.where_pred(either::Either::Right(make.ty_path(path).into()), bounds) + }) + .unique_by(|it| it.syntax().to_string()) + .peekable(); + trait_where_clause.peek().is_some().then(|| make.where_clause(trait_where_clause)) +} + pub(crate) fn add_method_to_adt( builder: &mut SourceChangeBuilder, adt: &ast::Adt, diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 87ee3bc76e..3e5591d37a 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -59,6 +59,26 @@ impl SyntaxFactory { ast } + pub fn type_bound(&self, bound: ast::Type) -> ast::TypeBound { + make::type_bound(bound).clone_for_update() + } + + pub fn type_bound_list( + &self, + bounds: impl IntoIterator<Item = ast::TypeBound>, + ) -> Option<ast::TypeBoundList> { + let (bounds, input) = iterator_input(bounds); + let ast = make::type_bound_list(bounds)?.clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children(input, ast.bounds().map(|b| b.syntax().clone())); + builder.finish(&mut mapping); + } + + Some(ast) + } + pub fn type_param( &self, name: ast::Name, @@ -1761,6 +1781,10 @@ impl SyntaxFactory { ast } + pub fn assoc_item_list_empty(&self) -> ast::AssocItemList { + make::assoc_item_list(None).clone_for_update() + } + pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr { let ast = make::attr_outer(meta.clone()).clone_for_update(); |