use std::ops::Not; use crate::{ assist_context::{AssistContext, Assists}, utils::convert_param_list_to_arg_list, }; use either::Either; use hir::{HasVisibility, db::HirDatabase}; use ide_db::{ FxHashMap, FxHashSet, assists::{AssistId, GroupLabel}, path_transform::PathTransform, syntax_helpers::suggest_name, }; use itertools::Itertools; use syntax::{ AstNode, Edition, SmolStr, SyntaxElement, SyntaxKind, ToSmolStr, ast::{ self, AssocItem, GenericArgList, GenericParamList, HasAttrs, HasGenericArgs, HasGenericParams, HasName, HasTypeBounds, HasVisibility as astHasVisibility, Path, WherePred, edit::{self, AstNodeEdit}, syntax_factory::SyntaxFactory, }, syntax_editor::SyntaxEditor, }; // Assist: generate_delegate_trait // // Generate delegate trait implementation for `StructField`s. // // ``` // trait SomeTrait { // type T; // fn fn_(arg: u32) -> u32; // fn method_(&mut self) -> bool; // } // struct A; // impl SomeTrait for A { // type T = u32; // // fn fn_(arg: u32) -> u32 { // 42 // } // // fn method_(&mut self) -> bool { // false // } // } // struct B { // a$0: A, // } // ``` // -> // ``` // trait SomeTrait { // type T; // fn fn_(arg: u32) -> u32; // fn method_(&mut self) -> bool; // } // struct A; // impl SomeTrait for A { // type T = u32; // // fn fn_(arg: u32) -> u32 { // 42 // } // // fn method_(&mut self) -> bool { // false // } // } // struct B { // a: A, // } // // impl SomeTrait for B { // type T = ::T; // // fn fn_(arg: u32) -> u32 { // ::fn_(arg) // } // // fn method_(&mut self) -> bool { // ::method_(&mut self.a) // } // } // ``` pub(crate) fn generate_delegate_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { if !ctx.config.code_action_grouping { return None; } let strukt = Struct::new(ctx.find_node_at_offset::()?)?; let field: Field = match ctx.find_node_at_offset::() { Some(field) => Field::new(ctx, Either::Left(field))?, None => { let field = ctx.find_node_at_offset::()?; let field_list = ctx.find_node_at_offset::()?; Field::new(ctx, either::Right((field, field_list)))? } }; strukt.delegate(field, acc, ctx); Some(()) } /// A utility object that represents a struct's field. #[derive(Debug)] struct Field { name: String, ty: ast::Type, range: syntax::TextRange, impls: Vec, edition: Edition, } impl Field { pub(crate) fn new( ctx: &AssistContext<'_>, f: Either, ) -> Option { let db = ctx.sema.db; let module = ctx.sema.file_to_module_def(ctx.vfs_file_id())?; let edition = module.krate(ctx.db()).edition(ctx.db()); let (name, range, ty) = match f { Either::Left(f) => { let name = f.name()?.to_string(); (name, f.syntax().text_range(), f.ty()?) } Either::Right((f, l)) => { let name = l.fields().position(|it| it == f)?.to_string(); (name, f.syntax().text_range(), f.ty()?) } }; let hir_ty = ctx.sema.resolve_type(&ty)?; let type_impls = hir::Impl::all_for_type(db, hir_ty.clone()); let mut impls = Vec::with_capacity(type_impls.len()); if let Some(tp) = hir_ty.as_type_param(db) { for tb in tp.trait_bounds(db) { impls.push(Delegee::Bound(tb)); } }; for imp in type_impls { if let Some(tr) = imp.trait_(db).filter(|tr| tr.is_visible_from(db, module)) { impls.push(Delegee::Impls(tr, imp)) } } Some(Field { name, ty, range, impls, edition }) } } /// A field that we want to delegate can offer the enclosing struct /// trait to implement in two ways. The first way is when the field /// actually implements the trait and the second way is when the field /// has a bound type parameter. We handle these cases in different ways /// hence the enum. #[derive(Debug)] enum Delegee { Bound(hir::Trait), Impls(hir::Trait, hir::Impl), } impl Delegee { fn trait_(&self) -> &hir::Trait { match self { Delegee::Bound(it) | Delegee::Impls(it, _) => it, } } fn signature(&self, db: &dyn HirDatabase, edition: Edition) -> String { let mut s = String::new(); let it = self.trait_(); for m in it.module(db).path_to_root(db).iter().rev() { if let Some(name) = m.name(db) { s.push_str(&format!("{}::", name.display_no_db(edition).to_smolstr())); } } s.push_str(&it.name(db).display_no_db(edition).to_smolstr()); s } } /// A utility struct that is used for the enclosing struct. struct Struct { strukt: ast::Struct, name: ast::Name, } impl Struct { pub(crate) fn new(s: ast::Struct) -> Option { let name = s.name()?; Some(Struct { name, strukt: s }) } pub(crate) fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistContext<'_>) { let db = ctx.db(); for (index, delegee) in field.impls.iter().enumerate() { let trait_ = delegee.trait_(); // Skip trait that has `Self` type, which cannot be delegated // // See [`test_self_ty`] if has_self_type(*trait_, ctx) { continue; } // FIXME : We can omit already implemented impl_traits // But we don't know what the &[hir::Type] argument should look like. // if self.hir_ty.impls_trait(db, trait_, &[]) { // continue; // } let signature = delegee.signature(db, field.edition); let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee, field.edition) else { continue; }; acc.add_group( &GroupLabel(format!("Generate delegate trait impls for field `{}`", field.name)), AssistId( "generate_delegate_trait", ide_db::assists::AssistKind::Generate, Some(index), ), format!("Generate delegate trait impl `{}` for `{}`", signature, field.name), field.range, |builder| { builder.insert( self.strukt.syntax().text_range().end(), format!("\n\n{}", delegate.syntax()), ); }, ); } } } fn generate_impl( ctx: &AssistContext<'_>, strukt: &Struct, field_ty: &ast::Type, field_name: &str, delegee: &Delegee, edition: Edition, ) -> Option { let make = SyntaxFactory::without_mappings(); let db = ctx.db(); let ast_strukt = &strukt.strukt; let strukt_ty = make.ty_path(make.ident_path(&strukt.name.to_string())).into(); let strukt_params = ast_strukt.generic_param_list(); match delegee { Delegee::Bound(delegee) => { let bound_def = ctx.sema.source(delegee.to_owned())?.value; let bound_params = bound_def.generic_param_list(); let delegate = make.impl_trait( None, delegee.is_unsafe(db), bound_params.clone(), bound_params.map(|params| params.to_generic_args()), strukt_params.clone(), strukt_params.map(|params| params.to_generic_args()), delegee.is_auto(db), make.ty(&delegee.name(db).display_no_db(edition).to_smolstr()), strukt_ty, bound_def.where_clause(), ast_strukt.where_clause(), None, ); // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); // Collect assoc items let assoc_items: Option> = bound_def.assoc_item_list().map(|ai| { ai.assoc_items() .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) .filter_map(|item| { process_assoc_item(item, qualified_path_type.clone(), field_name) }) .collect() }); let delegate = finalize_delegate(&make, &delegate, assoc_items, false)?; let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; let source_scope = ctx.sema.scope(bound_def.syntax())?; let transform = PathTransform::generic_transformation(&target_scope, &source_scope); ast::Impl::cast(transform.apply(delegate.syntax())) } Delegee::Impls(trait_, old_impl) => { let old_impl = ctx.sema.source(old_impl.to_owned())?.value; let old_impl_params = old_impl.generic_param_list(); // 1) Resolve conflicts between generic parameters in old_impl and // those in strukt. // // These generics parameters will also be used in `field_ty` and // `where_clauses`, so we should substitute arguments in them as well. let strukt_params = resolve_name_conflicts(strukt_params, &old_impl_params); let (field_ty, ty_where_clause) = match &strukt_params { Some(strukt_params) => { let args = strukt_params.to_generic_args(); let field_ty = rename_strukt_args(ctx, ast_strukt, field_ty, &args)?; let where_clause = ast_strukt .where_clause() .and_then(|wc| rename_strukt_args(ctx, ast_strukt, &wc, &args)); (field_ty, where_clause) } None => (field_ty.clone(), None), }; // 2) Handle instantiated generics in `field_ty`. // 2.1) Some generics used in `self_ty` may be instantiated, so they // are no longer generics, we should remove and instantiate those // generics in advance. // `old_trait_args` contains names of generic args for trait in `old_impl` let old_impl_trait_args = old_impl .trait_()? .generic_arg_list() .map(|l| l.generic_args().map(|arg| arg.to_string())) .map_or_else(FxHashSet::default, |it| it.collect()); let trait_gen_params = remove_instantiated_params( &old_impl.self_ty()?, old_impl_params.clone(), &old_impl_trait_args, ); // 2.2) Generate generic args applied on impl. let (transform_args, trait_gen_params) = generate_args_for_impl( old_impl_params, &old_impl.self_ty()?, &field_ty, trait_gen_params, &old_impl_trait_args, ); // 2.3) Instantiate generics with `transform_impl`, this step also // remove unused params. let trait_gen_args = old_impl.trait_()?.generic_arg_list().and_then(|mut trait_args| { let trait_args = &mut trait_args; if let Some(new_args) = transform_impl( ctx, ast_strukt, &old_impl, &transform_args, trait_args.clone_subtree(), ) { *trait_args = new_args.clone_subtree(); Some(new_args) } else { None } }); let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args()); let path_type = make.ty(&trait_.name(db).display_no_db(edition).to_smolstr()); let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?; // 3) Generate delegate trait impl let delegate = make.impl_trait( None, trait_.is_unsafe(db), trait_gen_params, trait_gen_args, strukt_params, type_gen_args, trait_.is_auto(db), path_type, strukt_ty, old_impl.where_clause(), ty_where_clause, None, ); // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = make.path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); // 4) Transform associated items in delegate trait impl let assoc_items: Option> = old_impl.assoc_item_list().map(|ail| { ail.assoc_items() .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) .filter_map(|item| { let item = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?; process_assoc_item(item, qualified_path_type.clone(), field_name) }) .collect() }); finalize_delegate(&make, &delegate, assoc_items, true) } } } fn transform_impl( ctx: &AssistContext<'_>, strukt: &ast::Struct, old_impl: &ast::Impl, args: &Option, syntax: N, ) -> Option { let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?; let target_scope = ctx.sema.scope(strukt.syntax())?; let hir_old_impl = ctx.sema.to_impl_def(old_impl)?; let transform = args.as_ref().map_or_else( || PathTransform::generic_transformation(&target_scope, &source_scope), |args| { PathTransform::impl_transformation( &target_scope, &source_scope, hir_old_impl, args.clone(), ) }, ); N::cast(transform.apply(syntax.syntax())) } /// Extracts the name from a generic parameter. fn generic_param_name(param: &ast::GenericParam) -> Option { match param { ast::GenericParam::TypeParam(t) => t.name().map(|n| n.to_string()), ast::GenericParam::ConstParam(c) => c.name().map(|n| n.to_string()), ast::GenericParam::LifetimeParam(l) => l.lifetime().map(|lt| lt.to_string()), } } /// Filters generic params, keeping only those whose names are not in `names_to_remove`. fn filter_generic_params( gpl: ast::GenericParamList, names_to_remove: &FxHashSet, ) -> Option { let remaining_params: Vec<_> = gpl .generic_params() .filter(|param| { generic_param_name(param).is_none_or(|name| !names_to_remove.contains(&name)) }) .collect(); if remaining_params.is_empty() { None } else { let make = SyntaxFactory::without_mappings(); Some(make.generic_param_list(remaining_params)) } } fn remove_instantiated_params( self_ty: &ast::Type, old_impl_params: Option, old_trait_args: &FxHashSet, ) -> Option { match self_ty { ast::Type::PathType(path_type) => { old_impl_params.and_then(|gpl| { // Collect generic args that should be removed (instantiated params) let args_to_remove: FxHashSet = path_type .path()? .segments() .filter_map(|seg| seg.generic_arg_list()) .flat_map(|it| it.generic_args()) // However, if the param is also used in the trait arguments, // it shouldn't be removed now, which will be instantiated in // later `path_transform` .filter(|arg| !old_trait_args.contains(&arg.to_string())) .map(|arg| arg.to_string()) .collect(); filter_generic_params(gpl, &args_to_remove) }) } _ => old_impl_params, } } fn remove_useless_where_clauses(editor: &mut SyntaxEditor, delegate: &ast::Impl) { let Some(wc) = delegate.where_clause() else { return; }; let (Some(trait_ty), Some(self_ty)) = (delegate.trait_(), delegate.self_ty()) else { return; }; let live_generics = [&trait_ty, &self_ty] .into_iter() .flat_map(|ty| ty.generic_arg_list()) .flat_map(|gal| gal.generic_args()) .map(|x| x.to_string()) .collect::>(); // Keep where-clauses that have generics after substitution, and remove the // rest. let has_no_live_generics = |pred: &WherePred| { pred.syntax() .descendants_with_tokens() .filter_map(|e| e.into_token()) .any(|e| e.kind() == SyntaxKind::IDENT && live_generics.contains(&e.to_string())) .not() }; let predicates_to_remove: Vec<_> = wc.predicates().filter(has_no_live_generics).collect(); let remaining_predicates = wc.predicates().count() - predicates_to_remove.len(); if remaining_predicates == 0 { // Remove the entire where clause editor.delete(wc.syntax().clone()); } else { // Remove only the useless predicates for pred in predicates_to_remove { // Also remove the comma before or after the predicate if let Some(previous) = pred.syntax().prev_sibling() { // Remove from after previous sibling to predicate (inclusive) if let Some(start) = previous.next_sibling_or_token() { let end: SyntaxElement = pred.syntax().clone().into(); editor.delete_all(start..=end); } } else if let Some(next) = pred.syntax().next_sibling() { // Remove from predicate to before next sibling (exclusive) if let Some(end) = next.prev_sibling_or_token() { let start: SyntaxElement = pred.syntax().clone().into(); editor.delete_all(start..=end); } } else { editor.delete(pred.syntax().clone()); } } } } /// Finalize the delegate impl by: /// 1. Replacing the assoc_item_list with new items (if any) /// 2. Removing useless where clauses fn finalize_delegate( make: &SyntaxFactory, delegate: &ast::Impl, assoc_items: Option>, remove_where_clauses: bool, ) -> Option { let has_items = assoc_items.as_ref().is_some_and(|items| !items.is_empty()); if !has_items && !remove_where_clauses { return Some(delegate.clone()); } let mut editor = SyntaxEditor::new(delegate.syntax().clone_subtree()); // 1. Replace assoc_item_list if we have new items if let Some(items) = assoc_items && !items.is_empty() { let new_assoc_item_list = make.assoc_item_list(items); if let Some(old_list) = delegate.assoc_item_list() { editor.replace(old_list.syntax(), new_assoc_item_list.syntax()); } } // 2. Remove useless where clauses if remove_where_clauses { remove_useless_where_clauses(&mut editor, delegate); } ast::Impl::cast(editor.finish().new_root().clone()) } // Generate generic args that should be apply to current impl. // // For example, say we have implementation `impl Trait for B`, // and `b: B` in struct `S`. Then the `A` should be instantiated to `T`. // While the last two generic args `B` and `C` doesn't change, it remains // ``. So we apply `` as generic arguments to impl. fn generate_args_for_impl( old_impl_gpl: Option, self_ty: &ast::Type, field_ty: &ast::Type, trait_params: Option, old_trait_args: &FxHashSet, ) -> (Option, Option) { let Some(old_impl_args) = old_impl_gpl.map(|gpl| gpl.to_generic_args().generic_args()) else { return (None, trait_params); }; // Create pairs of the args of `self_ty` and corresponding `field_ty` to // form the substitution list let mut arg_substs = FxHashMap::default(); if let field_ty @ ast::Type::PathType(_) = field_ty { let field_args = field_ty.generic_arg_list().map(|gal| gal.generic_args()); let self_ty_args = self_ty.generic_arg_list().map(|gal| gal.generic_args()); if let (Some(field_args), Some(self_ty_args)) = (field_args, self_ty_args) { self_ty_args.zip(field_args).for_each(|(self_ty_arg, field_arg)| { arg_substs.entry(self_ty_arg.to_string()).or_insert(field_arg); }) } } let mut params_to_remove = FxHashSet::default(); let args = old_impl_args .map(|old_arg| { arg_substs.get(&old_arg.to_string()).map_or_else( || old_arg.clone(), |replace_with| { // The old_arg will be replaced, so it becomes redundant if trait_params.is_some() && old_trait_args.contains(&old_arg.to_string()) { params_to_remove.insert(old_arg.to_string()); } replace_with.clone() }, ) }) .collect_vec(); let make = SyntaxFactory::without_mappings(); let result = args.is_empty().not().then(|| make.generic_arg_list(args, false)); let trait_params = trait_params.and_then(|gpl| filter_generic_params(gpl, ¶ms_to_remove)); (result, trait_params) } fn rename_strukt_args( ctx: &AssistContext<'_>, strukt: &ast::Struct, item: &N, args: &GenericArgList, ) -> Option where N: ast::AstNode, { let hir_strukt = ctx.sema.to_struct_def(strukt)?; let hir_adt = hir::Adt::from(hir_strukt); let scope = ctx.sema.scope(item.syntax())?; let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone()); N::cast(transform.apply(item.syntax())) } fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> bool { ctx.sema .source(trait_) .and_then(|src| { src.value .syntax() .descendants_with_tokens() .filter_map(|e| e.into_token()) .find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW) }) .is_some() } fn resolve_name_conflicts( strukt_params: Option, old_impl_params: &Option, ) -> Option { let make = SyntaxFactory::without_mappings(); match (strukt_params, old_impl_params) { (Some(old_strukt_params), Some(old_impl_params)) => { let mut new_params: Vec = Vec::new(); for old_strukt_param in old_strukt_params.generic_params() { // Get old name from `strukt` let name = SmolStr::from(generic_param_name(&old_strukt_param)?); // The new name cannot be conflicted with generics in trait, and the renamed names. let param_list_to_names = |param_list: &GenericParamList| { param_list.generic_params().flat_map(|param| match param { ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), p => Some(p.to_string()), }) }; let new_params_list = make.generic_param_list(new_params.clone()); let existing_names = param_list_to_names(old_impl_params) .chain(param_list_to_names(&new_params_list)) .collect_vec(); let mut name_generator = suggest_name::NameGenerator::new_with_names( existing_names.iter().map(|s| s.as_str()), ); let name = name_generator.suggest_name(&name); match old_strukt_param { ast::GenericParam::ConstParam(c) => { if let Some(const_ty) = c.ty() { let const_param = make.const_param(make.name(&name), const_ty); new_params.push(ast::GenericParam::ConstParam(const_param)); } } p @ ast::GenericParam::LifetimeParam(_) => { new_params.push(p.clone_for_update()); } ast::GenericParam::TypeParam(t) => { let type_bounds = t.type_bound_list(); let type_param = make.type_param(make.name(&name), type_bounds); new_params.push(ast::GenericParam::TypeParam(type_param)); } } } Some(make.generic_param_list(new_params)) } (Some(old_strukt_gpl), None) => Some(old_strukt_gpl), _ => None, } } fn process_assoc_item( item: syntax::ast::AssocItem, qual_path_ty: ast::Path, base_name: &str, ) -> Option { match item { AssocItem::Const(c) => const_assoc_item(c, qual_path_ty), AssocItem::Fn(f) => func_assoc_item(f, qual_path_ty, base_name), AssocItem::MacroCall(_) => { // FIXME : Handle MacroCall case. // macro_assoc_item(mac, qual_path_ty) None } AssocItem::TypeAlias(ta) => ty_assoc_item(ta, qual_path_ty), } } fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option { let make = SyntaxFactory::without_mappings(); let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); // We want rhs of the const assignment to be a qualified path // The general case for const assignment can be found [here](`https://doc.rust-lang.org/reference/items/constant-items.html`) // The qualified will have the following generic syntax : // >::ConstName; // FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it. // make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap()); let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); let inner = make.item_const( item.attrs(), item.visibility(), item.name()?, item.ty()?, make.expr_path(qualified_path), ); Some(AssocItem::Const(inner)) } fn func_assoc_item( item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str, ) -> Option { let make = SyntaxFactory::without_mappings(); let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); let call = match item.param_list() { // Methods and funcs should be handled separately. // We ask if the func has a `self` param. Some(l) => match l.self_param() { Some(slf) => { let self_kw = make.expr_path(make.path_from_text("self")); let self_kw = make.expr_field(self_kw, base_name).into(); let tail_expr_self = match slf.kind() { ast::SelfParamKind::Owned => self_kw, ast::SelfParamKind::Ref => make.expr_ref(self_kw, false), ast::SelfParamKind::MutRef => make.expr_ref(self_kw, true), }; // Build argument list with self expression prepended let other_args = convert_param_list_to_arg_list(l); let all_args: Vec = std::iter::once(tail_expr_self).chain(other_args.args()).collect(); let args = make.arg_list(all_args); make.expr_call(make.expr_path(qualified_path), args).into() } None => make .expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l)) .into(), }, None => make .expr_call( make.expr_path(qualified_path), convert_param_list_to_arg_list(make.param_list(None, Vec::new())), ) .into(), }; let body = make.block_expr(vec![], Some(call)); let func = make.fn_( item.attrs(), item.visibility(), item.name()?, item.generic_param_list(), item.where_clause(), item.param_list()?, body, item.ret_type(), item.async_token().is_some(), item.const_token().is_some(), item.unsafe_token().is_some(), item.gen_token().is_some(), ); Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)))) } fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option { let make = SyntaxFactory::without_mappings(); let path_expr_segment = make.path_from_text(item.name()?.to_string().as_str()); let qualified_path = make.path_from_text(&format!("{qual_path_ty}::{path_expr_segment}")); let ty = make.ty_path(qualified_path).into(); let ident = item.name()?.to_string(); let alias = make .ty_alias( item.attrs(), ident.as_str(), item.generic_param_list(), None, item.where_clause(), Some((ty, None)), ) .indent(edit::IndentLevel(1)); Some(AssocItem::TypeAlias(alias)) } #[cfg(test)] mod test { use super::*; use crate::tests::{ check_assist, check_assist_not_applicable, check_assist_not_applicable_no_grouping, }; #[test] fn test_tuple_struct_basic() { check_assist( generate_delegate_trait, r#" struct Base; struct S(B$0ase); trait Trait {} impl Trait for Base {} "#, r#" struct Base; struct S(Base); impl Trait for S {} trait Trait {} impl Trait for Base {} "#, ); } #[test] fn test_self_ty() { // trait with `Self` type cannot be delegated // // See the function `fn f() -> Self`. // It should be `fn f() -> Base` in `Base`, and `fn f() -> S` in `S` check_assist_not_applicable( generate_delegate_trait, r#" struct Base(()); struct S(B$0ase); trait Trait { fn f() -> Self; } impl Trait for Base { fn f() -> Base { Base(()) } } "#, ); } #[test] fn test_struct_struct_basic() { check_assist( generate_delegate_trait, r#" struct Base; struct S { ba$0se : Base } trait Trait {} impl Trait for Base {} "#, r#" struct Base; struct S { base : Base } impl Trait for S {} trait Trait {} impl Trait for Base {} "#, ) } // Structs need to be by def populated with fields // However user can invoke this assist while still editing // We therefore assert its non-applicability #[test] fn test_yet_empty_struct() { check_assist_not_applicable( generate_delegate_trait, r#" struct Base; struct S { $0 } impl Trait for S {} trait Trait {} impl Trait for Base {} "#, ) } #[test] fn test_yet_unspecified_field_type() { check_assist_not_applicable( generate_delegate_trait, r#" struct Base; struct S { ab$0c } impl Trait for S {} trait Trait {} impl Trait for Base {} "#, ); } #[test] fn test_unsafe_trait() { check_assist( generate_delegate_trait, r#" struct Base; struct S { ba$0se : Base } unsafe trait Trait {} unsafe impl Trait for Base {} "#, r#" struct Base; struct S { base : Base } unsafe impl Trait for S {} unsafe trait Trait {} unsafe impl Trait for Base {} "#, ); } #[test] fn test_unsafe_trait_with_unsafe_fn() { check_assist( generate_delegate_trait, r#" struct Base; struct S { ba$0se: Base, } unsafe trait Trait { unsafe fn a_func(); unsafe fn a_method(&self); } unsafe impl Trait for Base { unsafe fn a_func() {} unsafe fn a_method(&self) {} } "#, r#" struct Base; struct S { base: Base, } unsafe impl Trait for S { unsafe fn a_func() { ::a_func() } unsafe fn a_method(&self) { ::a_method(&self.base) } } unsafe trait Trait { unsafe fn a_func(); unsafe fn a_method(&self); } unsafe impl Trait for Base { unsafe fn a_func() {} unsafe fn a_method(&self) {} } "#, ); } #[test] fn test_struct_with_where_clause() { check_assist( generate_delegate_trait, r#" trait AnotherTrait {} struct S where T: AnotherTrait, { b$0 : T, }"#, r#" trait AnotherTrait {} struct S where T: AnotherTrait, { b : T, } impl AnotherTrait for S where T: AnotherTrait, { }"#, ); } #[test] fn test_fields_with_generics() { check_assist( generate_delegate_trait, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T1) -> T1 { a } } struct A {} struct S { b :$0 B, } "#, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T1) -> T1 { a } } struct A {} struct S { b : B, } impl Trait for S { fn f(&self, a: T1) -> T1 { as Trait>::f(&self.b, a) } } "#, ); } #[test] fn test_generics_with_conflict_names() { check_assist( generate_delegate_trait, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b : $0B, } "#, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b : B, } impl Trait for S { fn f(&self, a: T) -> T { as Trait>::f(&self.b, a) } } "#, ); } #[test] fn test_lifetime_with_conflict_names() { check_assist( generate_delegate_trait, r#" struct B<'a, T> { a: &'a T } trait Trait { fn f(&self, a: T) -> T; } impl<'a, T, T0> Trait for B<'a, T0> { fn f(&self, a: T) -> T { a } } struct S<'a, T> { b : $0B<'a, T>, } "#, r#" struct B<'a, T> { a: &'a T } trait Trait { fn f(&self, a: T) -> T; } impl<'a, T, T0> Trait for B<'a, T0> { fn f(&self, a: T) -> T { a } } struct S<'a, T> { b : B<'a, T>, } impl<'a, T, T1> Trait for S<'a, T1> { fn f(&self, a: T) -> T { as Trait>::f(&self.b, a) } } "#, ); } #[test] fn test_multiple_generics() { check_assist( generate_delegate_trait, r#" struct B { a: T1, b: T2 } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b :$0 B, } "#, r#" struct B { a: T1, b: T2 } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b : B, } impl Trait for S { fn f(&self, a: i32) -> i32 { as Trait>::f(&self.b, a) } } "#, ); } #[test] fn test_generics_multiplex() { check_assist( generate_delegate_trait, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b : $0B, } "#, r#" struct B { a: T } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S { b : B, } impl Trait for S { fn f(&self, a: T1) -> T1 { as Trait>::f(&self.b, a) } } "#, ); } #[test] fn test_complex_without_where() { check_assist( generate_delegate_trait, r#" trait Trait<'a, T, const C: usize> { type AssocType; const AssocConst: usize; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { field$0: Base } impl<'a, T, const C: usize> Trait<'a, T, C> for Base { type AssocType = (); const AssocConst: usize = 0; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} } "#, r#" trait Trait<'a, T, const C: usize> { type AssocType; const AssocConst: usize; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { field: Base } impl<'a, T, const C: usize> Trait<'a, T, C> for S { type AssocType = >::AssocType; const AssocConst: usize = >::AssocConst; fn assoc_fn(p: ()) { >::assoc_fn(p) } fn assoc_method(&self, p: ()) { >::assoc_method(&self.field, p) } } impl<'a, T, const C: usize> Trait<'a, T, C> for Base { type AssocType = (); const AssocConst: usize = 0; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} } "#, ); } #[test] fn test_complex_two() { check_assist( generate_delegate_trait, r" trait AnotherTrait {} trait Trait<'a, T, const C: usize> { type AssocType; const AssocConst: usize; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { fi$0eld: Base, } impl<'b, C, const D: usize> Trait<'b, C, D> for Base where C: AnotherTrait, { type AssocType = (); const AssocConst: usize = 0; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} }", r#" trait AnotherTrait {} trait Trait<'a, T, const C: usize> { type AssocType; const AssocConst: usize; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { field: Base, } impl<'b, C, const D: usize> Trait<'b, C, D> for S where C: AnotherTrait, { type AssocType = >::AssocType; const AssocConst: usize = >::AssocConst; fn assoc_fn(p: ()) { >::assoc_fn(p) } fn assoc_method(&self, p: ()) { >::assoc_method(&self.field, p) } } impl<'b, C, const D: usize> Trait<'b, C, D> for Base where C: AnotherTrait, { type AssocType = (); const AssocConst: usize = 0; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} }"#, ) } #[test] fn test_complex_three() { check_assist( generate_delegate_trait, r#" trait AnotherTrait {} trait YetAnotherTrait {} struct StructImplsAll(); impl AnotherTrait for StructImplsAll {} impl YetAnotherTrait for StructImplsAll {} trait Trait<'a, T, const C: usize> { type A; const ASSOC_CONST: usize = C; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { fi$0eld: Base, } impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for Base where A: AnotherTrait, { type A = i32; const ASSOC_CONST: usize = B; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} } "#, r#" trait AnotherTrait {} trait YetAnotherTrait {} struct StructImplsAll(); impl AnotherTrait for StructImplsAll {} impl YetAnotherTrait for StructImplsAll {} trait Trait<'a, T, const C: usize> { type A; const ASSOC_CONST: usize = C; fn assoc_fn(p: ()); fn assoc_method(&self, p: ()); } struct Base; struct S { field: Base, } impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for S where A: AnotherTrait, { type A = >::A; const ASSOC_CONST: usize = >::ASSOC_CONST; fn assoc_fn(p: ()) { >::assoc_fn(p) } fn assoc_method(&self, p: ()) { >::assoc_method(&self.field, p) } } impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for Base where A: AnotherTrait, { type A = i32; const ASSOC_CONST: usize = B; fn assoc_fn(p: ()) {} fn assoc_method(&self, p: ()) {} } "#, ) } #[test] fn test_type_bound() { check_assist( generate_delegate_trait, r#" trait AnotherTrait {} struct S where T: AnotherTrait, { b$0: T, }"#, r#" trait AnotherTrait {} struct S where T: AnotherTrait, { b: T, } impl AnotherTrait for S where T: AnotherTrait, { }"#, ); } #[test] fn test_type_bound_with_generics_1() { check_assist( generate_delegate_trait, r#" trait AnotherTrait {} struct B where T1: AnotherTrait { a: T, b: T1 } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S where T1: AnotherTrait { b : $0B, }"#, r#" trait AnotherTrait {} struct B where T1: AnotherTrait { a: T, b: T1 } trait Trait { fn f(&self, a: T) -> T; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S where T1: AnotherTrait { b : B, } impl Trait for S where T3: AnotherTrait { fn f(&self, a: T) -> T { as Trait>::f(&self.b, a) } }"#, ); } #[test] fn test_type_bound_with_generics_2() { check_assist( generate_delegate_trait, r#" trait AnotherTrait {} struct B where T1: AnotherTrait { b: T1 } trait Trait { fn f(&self, a: T1) -> T1; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S where T: AnotherTrait { b : $0B, }"#, r#" trait AnotherTrait {} struct B where T1: AnotherTrait { b: T1 } trait Trait { fn f(&self, a: T1) -> T1; } impl Trait for B { fn f(&self, a: T) -> T { a } } struct S where T: AnotherTrait { b : B, } impl Trait for S where T2: AnotherTrait { fn f(&self, a: T) -> T { as Trait>::f(&self.b, a) } }"#, ); } #[test] fn test_docstring_example() { check_assist( generate_delegate_trait, r#" trait SomeTrait { type T; fn fn_(arg: u32) -> u32; fn method_(&mut self) -> bool; } struct A; impl SomeTrait for A { type T = u32; fn fn_(arg: u32) -> u32 { 42 } fn method_(&mut self) -> bool { false } } struct B { a$0: A, } "#, r#" trait SomeTrait { type T; fn fn_(arg: u32) -> u32; fn method_(&mut self) -> bool; } struct A; impl SomeTrait for A { type T = u32; fn fn_(arg: u32) -> u32 { 42 } fn method_(&mut self) -> bool { false } } struct B { a: A, } impl SomeTrait for B { type T = ::T; fn fn_(arg: u32) -> u32 { ::fn_(arg) } fn method_(&mut self) -> bool { ::method_(&mut self.a) } } "#, ); } #[test] fn import_from_other_mod() { check_assist( generate_delegate_trait, r#" mod some_module { pub trait SomeTrait { type T; fn fn_(arg: u32) -> u32; fn method_(&mut self) -> bool; } pub struct A; impl SomeTrait for A { type T = u32; fn fn_(arg: u32) -> u32 { 42 } fn method_(&mut self) -> bool { false } } } struct B { a$0: some_module::A, }"#, r#" mod some_module { pub trait SomeTrait { type T; fn fn_(arg: u32) -> u32; fn method_(&mut self) -> bool; } pub struct A; impl SomeTrait for A { type T = u32; fn fn_(arg: u32) -> u32 { 42 } fn method_(&mut self) -> bool { false } } } struct B { a: some_module::A, } impl some_module::SomeTrait for B { type T = ::T; fn fn_(arg: u32) -> u32 { ::fn_(arg) } fn method_(&mut self) -> bool { ::method_(&mut self.a) } }"#, ) } #[test] fn test_fn_with_attrs() { check_assist( generate_delegate_trait, r#" struct A; trait T { #[cfg(test)] fn f(&self, a: u32); #[cfg(not(test))] fn f(&self, a: bool); } impl T for A { #[cfg(test)] fn f(&self, a: u32) {} #[cfg(not(test))] fn f(&self, a: bool) {} } struct B { a$0: A, } "#, r#" struct A; trait T { #[cfg(test)] fn f(&self, a: u32); #[cfg(not(test))] fn f(&self, a: bool); } impl T for A { #[cfg(test)] fn f(&self, a: u32) {} #[cfg(not(test))] fn f(&self, a: bool) {} } struct B { a: A, } impl T for B { #[cfg(test)] fn f(&self, a: u32) { ::f(&self.a, a) } #[cfg(not(test))] fn f(&self, a: bool) { ::f(&self.a, a) } } "#, ); } #[test] fn test_ty_alias_attrs() { check_assist( generate_delegate_trait, r#" struct A; trait T { #[cfg(test)] type t; #[cfg(not(test))] type t; } impl T for A { #[cfg(test)] type t = u32; #[cfg(not(test))] type t = bool; } struct B { a$0: A, } "#, r#" struct A; trait T { #[cfg(test)] type t; #[cfg(not(test))] type t; } impl T for A { #[cfg(test)] type t = u32; #[cfg(not(test))] type t = bool; } struct B { a: A, } impl T for B { #[cfg(test)] type t = ::t; #[cfg(not(test))] type t = ::t; } "#, ); } #[test] fn assoc_items_attributes_mutably_cloned() { check_assist( generate_delegate_trait, r#" pub struct A; pub trait C { #[allow(clippy::dead_code)] fn a_funk(&self) -> &D; } pub struct B> { has_dr$0ain: T, } "#, r#" pub struct A; pub trait C { #[allow(clippy::dead_code)] fn a_funk(&self) -> &D; } pub struct B> { has_drain: T, } impl> C for B { #[allow(clippy::dead_code)] fn a_funk(&self) -> &D { >::a_funk(&self.has_drain) } } "#, ) } #[test] fn delegate_trait_skipped_when_no_grouping() { check_assist_not_applicable_no_grouping( generate_delegate_trait, r#" trait SomeTrait { type T; fn fn_(arg: u32) -> u32; fn method_(&mut self) -> bool; } struct A; impl SomeTrait for A { type T = u32; fn fn_(arg: u32) -> u32 { 42 } fn method_(&mut self) -> bool { false } } struct B { a$0 : A, } "#, ); } }