about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAli Bektas <bektasali@protonmail.com>2023-06-02 11:51:11 +0200
committerAli Bektas <bektasali@protonmail.com>2023-06-17 13:52:34 +0200
commit8123a39c82f31d86cdfd1adf92cff44b8391d0b5 (patch)
treeeb5f6b837172d288d48b0f31fc7541d92f827a57
parentfcfc6afe0526123ff43086990356bf175664fdfa (diff)
downloadrust-8123a39c82f31d86cdfd1adf92cff44b8391d0b5.tar.gz
rust-8123a39c82f31d86cdfd1adf92cff44b8391d0b5.zip
Generate delegate trait
-rw-r--r--crates/ide-assists/src/handlers/generate_delegate_trait.rs1064
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs63
3 files changed, 1129 insertions, 0 deletions
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 = <A as SomeTrait>::T;
+//
+//     fn fn_(arg: u32) -> u32 {
+//         <A as SomeTrait>::fn_(arg)
+//     }
+//
+//     fn method_(&mut self) -> bool {
+//         <A as SomeTrait>::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::<ast::Struct>()?)?;
+
+    let field: Field = match ctx.find_node_at_offset::<ast::RecordField>() {
+        Some(field) => Field::new(&ctx, Either::Left(field))?,
+        None => {
+            let field = ctx.find_node_at_offset::<ast::TupleField>()?;
+            let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
+            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<Delegee>,
+}
+
+impl Field {
+    pub fn new(
+        ctx: &AssistContext<'_>,
+        f: Either<ast::RecordField, (ast::TupleField, ast::TupleFieldList)>,
+    ) -> Option<Field> {
+        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<Self> {
+        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<ast::GenericParamList>;
+    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<ast::AssocItem> {
+    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 :
+    // <Base as Trait<GenArgs>>::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() {
+        <Base as Trait>::a_func()
+    }
+
+    unsafe fn a_method(&self) {
+        <Base as Trait>::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<T>
+where
+    T: AnotherTrait,
+{
+    b$0 : T,
+}"#,
+            r#"
+trait AnotherTrait {}
+struct S<T>
+where
+    T: AnotherTrait,
+{
+    b : T,
+}
+
+impl<T> AnotherTrait for S<T>
+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 = <Base as Trait<'a, T, C>>::AssocType;
+
+    const AssocConst: usize = <Base as Trait<'a, T, C>>::AssocConst;
+
+    fn assoc_fn(p: ()) {
+        <Base as Trait<'a, T, C>>::assoc_fn(p)
+    }
+
+    fn assoc_method(&self, p: ()) {
+        <Base as Trait<'a, T, C>>::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 = <Base as Trait<'b, C, D>>::AssocType;
+
+    const AssocConst: usize = <Base as Trait<'b, C, D>>::AssocConst;
+
+    fn assoc_fn(p: ()) {
+        <Base as Trait<'b, C, D>>::assoc_fn(p)
+    }
+
+    fn assoc_method(&self, p: ()) {
+        <Base as Trait<'b, C, D>>::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 = <Base as Trait<'b, A, B>>::A;
+
+    const ASSOC_CONST: usize = <Base as Trait<'b, A, B>>::ASSOC_CONST;
+
+    fn assoc_fn(p: ()) {
+        <Base as Trait<'b, A, B>>::assoc_fn(p)
+    }
+
+    fn assoc_method(&self, p: ()) {
+        <Base as Trait<'b, A, B>>::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<T>
+where
+    T: AnotherTrait,
+{
+    b$0: T,
+}"#,
+            r#"
+trait AnotherTrait {}
+struct S<T>
+where
+    T: AnotherTrait,
+{
+    b: T,
+}
+
+impl<T> AnotherTrait for S<T>
+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 = <A as SomeTrait>::T;
+
+    fn fn_(arg: u32) -> u32 {
+        <A as SomeTrait>::fn_(arg)
+    }
+
+    fn method_(&mut self) -> bool {
+        <A as SomeTrait>::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 = <some_module::A as some_module::SomeTrait>::T;
+
+    fn fn_(arg: u32) -> u32 {
+        <some_module::A as some_module::SomeTrait>::fn_(arg)
+    }
+
+    fn method_(&mut self) -> bool {
+        <some_module::A as some_module::SomeTrait>::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 @@ mod handlers {
             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
@@ -1016,6 +1016,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 = <A as SomeTrait>::T;
+
+    fn fn_(arg: u32) -> u32 {
+        <A as SomeTrait>::fn_(arg)
+    }
+
+    fn method_(&mut self) -> bool {
+        <A as SomeTrait>::method_( &mut self.a )
+    }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_generate_deref() {
     check_doc_test(
         "generate_deref",