From 8123a39c82f31d86cdfd1adf92cff44b8391d0b5 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Fri, 2 Jun 2023 11:51:11 +0200 Subject: [PATCH] Generate delegate trait --- .../src/handlers/generate_delegate_trait.rs | 1064 +++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 63 + 3 files changed, 1129 insertions(+) create mode 100644 crates/ide-assists/src/handlers/generate_delegate_trait.rs diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs new file mode 100644 index 00000000000..ce8f4256c08 --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -0,0 +1,1064 @@ +use std::ops::Not; + +use crate::{ + assist_context::{AssistContext, Assists}, + utils::convert_param_list_to_arg_list, +}; +use either::Either; +use hir::{db::HirDatabase, HasVisibility}; +use ide_db::{ + assists::{AssistId, GroupLabel}, + path_transform::PathTransform, +}; +use syntax::{ + ast::{ + self, + edit::{self, AstNodeEdit}, + make, AssocItem, HasGenericParams, HasName, HasVisibility as astHasVisibility, Path, + }, + ted::{self, Position}, + AstNode, NodeOrToken, SyntaxKind, +}; + +// 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<()> { + 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. +struct Field { + name: String, + ty: ast::Type, + range: syntax::TextRange, + impls: Vec, +} + +impl Field { + pub fn new( + ctx: &AssistContext<'_>, + f: Either, + ) -> Option { + let db = ctx.sema.db; + let name: String; + let range: syntax::TextRange; + let ty: ast::Type; + + let module = ctx.sema.to_module_def(ctx.file_id())?; + + match f { + Either::Left(f) => { + name = f.name()?.to_string(); + ty = f.ty()?; + range = f.syntax().text_range(); + } + Either::Right((f, l)) => { + name = l.fields().position(|it| it == f)?.to_string(); + ty = f.ty()?; + range = f.syntax().text_range(); + } + }; + + 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()); + let type_param = hir_ty.as_type_param(db); + + if let Some(tp) = type_param { + for tb in tp.trait_bounds(db) { + impls.push(Delegee::Bound(BoundCase { 0: tb })); + } + }; + + for imp in type_impls { + match imp.trait_(db) { + Some(tr) => { + if tr.is_visible_from(db, module) { + impls.push(Delegee::Impls(ImplCase { 0: tr, 1: imp })) + } + } + None => { + continue; + } + } + } + + Some(Field { name, ty, range, impls }) + } +} + +/// 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. +enum Delegee { + Bound(BoundCase), + Impls(ImplCase), +} + +struct BoundCase(hir::Trait); +struct ImplCase(hir::Trait, hir::Impl); + +/// When we list traits we can implement for the enclosing struct +/// we use the absolute path of a trait. This trait consists of a single +/// method that produces this path. +trait Signatured { + fn signature(&self, db: &dyn HirDatabase) -> String; +} + +impl Signatured for Delegee { + fn signature(&self, db: &dyn HirDatabase) -> String { + let mut s = String::new(); + let trait_: hir::Trait; + + match self { + Delegee::Bound(b) => trait_ = b.0, + Delegee::Impls(i) => trait_ = i.0, + }; + + for m in trait_.module(db).path_to_root(db).iter().rev() { + if let Some(name) = m.name(db) { + s.push_str(&format!("{}::", name.to_smol_str())); + } else { + continue; + } + } + + s.push_str(&trait_.name(db).to_smol_str()); + s + } +} + +/// A utility struct that is used for the enclosing struct. +struct Struct { + strukt: ast::Struct, + name: ast::Name, +} + +impl Struct { + pub fn new(s: ast::Struct) -> Option { + let name = s.name()?; + Some(Struct { name, strukt: s }) + } + + pub fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistContext<'_>) { + let db = ctx.db(); + for delegee in &field.impls { + // FIXME : We can omit already implemented impl_traits + // But we don't know what the &[hir::Type] argument should look like. + + // let trait_ = match delegee { + // Delegee::Bound(b) => b.0, + // Delegee::Impls(i) => i.1, + // }; + + // if self.hir_ty.impls_trait(db, trait_, &[]) { + // continue; + // } + let signature = delegee.signature(db); + let delegate = generate_impl(ctx, self, &field.ty, &field.name, delegee); + + acc.add_group( + &GroupLabel("Generate delegate traits...".to_owned()), + AssistId("generate_delegate_trait", ide_db::assists::AssistKind::Generate), + format!("Generate delegate 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: &String, + delegee: &Delegee, +) -> ast::Impl { + let delegate: ast::Impl; + let source: ast::Impl; + let genpar: Option; + let db = ctx.db(); + let base_path = make::path_from_text(&field_ty.to_string().as_str()); + let s_path = make::ext::ident_path(&strukt.name.to_string()); + + match delegee { + Delegee::Bound(delegee) => { + let in_file = ctx.sema.source(delegee.0.to_owned()).unwrap(); + let source: ast::Trait = in_file.value; + + delegate = make::impl_trait( + delegee.0.is_unsafe(db), + None, + None, + strukt.strukt.generic_param_list(), + None, + delegee.0.is_auto(db), + make::ty(&delegee.0.name(db).to_smol_str()), + make::ty_path(s_path), + source.where_clause(), + strukt.strukt.where_clause(), + None, + ) + .clone_for_update(); + + genpar = source.generic_param_list(); + let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); + let gen_args: String = + genpar.map_or_else(String::new, |params| params.to_generic_args().to_string()); + + // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths + let qualified_path_type = make::path_from_text(&format!( + "<{} as {}{}>", + base_path.to_string(), + delegee.0.name(db).to_smol_str(), + gen_args.to_string() + )); + + match source.assoc_item_list() { + Some(ai) => { + ai.assoc_items() + .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) + .for_each(|item| { + let assoc = + process_assoc_item(item, qualified_path_type.clone(), &field_name); + if let Some(assoc) = assoc { + delegate_assoc_items.add_item(assoc); + } + }); + } + None => {} + }; + + let target = ctx.sema.scope(strukt.strukt.syntax()).unwrap(); + let source = ctx.sema.scope_for_def(delegee.0); + + let transform = + PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); + transform.apply(&delegate.syntax()); + } + Delegee::Impls(delegee) => { + let in_file = ctx.sema.source(delegee.1.to_owned()).unwrap(); + source = in_file.value; + delegate = make::impl_trait( + delegee.0.is_unsafe(db), + source.generic_param_list(), + None, + None, + None, + delegee.0.is_auto(db), + make::ty(&delegee.0.name(db).to_smol_str()), + make::ty_path(s_path), + source.where_clause(), + strukt.strukt.where_clause(), + None, + ) + .clone_for_update(); + genpar = source.generic_param_list(); + let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); + let gen_args: String = + genpar.map_or_else(String::new, |params| params.to_generic_args().to_string()); + + // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths + let qualified_path_type = make::path_from_text(&format!( + "<{} as {}{}>", + base_path.to_string().as_str(), + delegee.0.name(db).to_smol_str(), + gen_args.to_string().as_str() + )); + + source + .get_or_create_assoc_item_list() + .assoc_items() + .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) + .for_each(|item| { + let assoc = process_assoc_item(item, qualified_path_type.clone(), &field_name); + if let Some(assoc) = assoc { + delegate_assoc_items.add_item(assoc); + } + }); + + let target = ctx.sema.scope(strukt.strukt.syntax()).unwrap(); + let source = ctx.sema.scope_for_def(delegee.0); + + let transform = + PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); + transform.apply(&delegate.syntax()); + } + } + + delegate +} + +fn process_assoc_item( + item: syntax::ast::AssocItem, + qual_path_ty: ast::Path, + base_name: &str, +) -> Option { + match item { + AssocItem::Const(c) => Some(const_assoc_item(c, qual_path_ty)), + AssocItem::Fn(f) => Some(func_assoc_item(f, qual_path_ty, base_name)), + AssocItem::MacroCall(_) => { + // FIXME : Handle MacroCall case. + // return Some(macro_assoc_item(mac, qual_path_ty)); + None + } + AssocItem::TypeAlias(ta) => Some(ty_assoc_item(ta, qual_path_ty)), + } +} + +fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> AssocItem { + let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); + + // We want rhs of the const assignment to be a qualified path + // The general case for const assigment 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 qualpath = qualpath(qual_path_ty, path_expr_segment); + let inner = make::item_const( + item.visibility(), + item.name().unwrap(), + item.ty().unwrap(), + make::expr_path(qualpath), + ) + .clone_for_update(); + + AssocItem::Const(inner) +} + +fn func_assoc_item(item: syntax::ast::Fn, qual_path_ty: Path, base_name: &str) -> AssocItem { + let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); + let qualpath = qualpath(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 mut self_kw = make::expr_path(make::path_from_text("self")); + self_kw = make::expr_field(self_kw, base_name); + + 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), + }; + + let param_count = l.params().count(); + let args = convert_param_list_to_arg_list(l).clone_for_update(); + + if param_count > 0 { + // Add SelfParam and a TOKEN::COMMA + ted::insert_all( + Position::after(args.l_paren_token().unwrap()), + vec![ + NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), + NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), + NodeOrToken::Token(make::token(SyntaxKind::COMMA)), + ], + ); + } else { + // Add SelfParam only + ted::insert( + Position::after(args.l_paren_token().unwrap()), + NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), + ); + } + + make::expr_call(make::expr_path(qualpath), args) + } + None => make::expr_call(make::expr_path(qualpath), convert_param_list_to_arg_list(l)), + }, + None => make::expr_call( + make::expr_path(qualpath), + convert_param_list_to_arg_list(make::param_list(None, Vec::new())), + ), + } + .clone_for_update(); + + let body = make::block_expr(vec![], Some(call)).clone_for_update(); + let func = make::fn_( + item.visibility(), + item.name().unwrap(), + item.generic_param_list(), + item.where_clause(), + item.param_list().unwrap(), + body, + item.ret_type(), + item.async_token().is_some(), + item.const_token().is_some(), + item.unsafe_token().is_some(), + ) + .clone_for_update(); + + AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update()) +} + +fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> AssocItem { + let path_expr_segment = make::path_from_text(item.name().unwrap().to_string().as_str()); + let qualpath = qualpath(qual_path_ty, path_expr_segment); + let ty = make::ty_path(qualpath); + let ident = item.name().unwrap().to_string(); + + let alias = make::ty_alias( + ident.as_str(), + item.generic_param_list(), + None, + item.where_clause(), + Some((ty, None)), + ) + .clone_for_update(); + + AssocItem::TypeAlias(alias) +} + +fn qualpath(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path { + make::path_from_text(&format!("{}::{}", qual_path_ty.to_string(), path_expr_seg.to_string())) +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[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_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_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_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 ) + } +}"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 111753bf309..dc0a69971ef 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -145,6 +145,7 @@ mod handlers { mod generate_constant; mod generate_default_from_enum_variant; mod generate_default_from_new; + mod generate_delegate_trait; mod generate_deref; mod generate_derive; mod generate_documentation_template; @@ -251,6 +252,7 @@ pub(crate) fn all() -> &'static [Handler] { generate_constant::generate_constant, generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, + generate_delegate_trait::generate_delegate_trait, generate_derive::generate_derive, generate_documentation_template::generate_documentation_template, generate_documentation_template::generate_doc_example, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index c097e073980..c6c70cc64a6 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1015,6 +1015,69 @@ impl Person { ) } +#[test] +fn doctest_generate_delegate_trait() { + check_doc_test( + "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 doctest_generate_deref() { check_doc_test(