double_derive/
lib.rs

1use quote::quote;
2use syn::{Ident, ItemTrait, parse_macro_input};
3
4mod double_trait;
5mod trait_impl;
6
7use self::{double_trait::double_trait, trait_impl::trait_impl};
8
9/// Generates a trait which replicates the original trait method for method. It does implement the
10/// original trait for each of its implementations, by means of forwarding the method calls. The
11/// utility comes from the fact that the generated trait has default implementations for each method
12/// using `unimplemented!()`, which makes it useful for testing purposes.
13///
14/// If a test requires an implementation of an original trait `Org` yet would only invoke one of its
15/// methods, implementing the mirrored method on an implementation of the generated trait `OrgDummy`
16/// is sufficient. The other methods would not be inovked in the test, so their default
17/// implementation using `unimplemented!()` would not be reached.
18///
19/// The argument passed to the attribute is used as the name of the generated trait.
20#[proc_macro_attribute]
21pub fn double(
22    attr: proc_macro::TokenStream,
23    item: proc_macro::TokenStream,
24) -> proc_macro::TokenStream {
25    let double_name = parse_macro_input!(attr as Ident);
26    let item = parse_macro_input!(item as ItemTrait);
27
28    let output = double_impl(double_name, item);
29
30    proc_macro::TokenStream::from(output)
31}
32
33/// The main implementation of [`crate::double`]. This function is not annotated with
34/// `#[proc_macro_attribute]` so it can exist in unit tests. It uses only APIs build on top of
35/// [`proc_macro2`] in order to be unit testable.
36fn double_impl(double_trait_name: Ident, org_trait: ItemTrait) -> proc_macro2::TokenStream {
37    let double_trait = double_trait(double_trait_name.clone(), org_trait.clone());
38    let trait_impl = trait_impl(double_trait_name, org_trait.clone());
39
40    // We generate three items as part of our output.
41    // 1. The orginal trait, which we put in the output unaltered.
42    // 2. The double trait, we genarate, which mirrors the original traits methods and provides
43    //    default implementations using `unimplemented!()`.
44    // 3. An implementation of the original trait for all types which implement the double trait.
45    //    This is done by forwarding the method calls to the double trait.
46    quote! {
47        #org_trait
48
49        #double_trait
50
51        #trait_impl
52    }
53}
54
55#[cfg(test)]
56mod tests {
57
58    use super::{Ident, double_impl};
59    use quote::quote;
60    use syn::{ItemTrait, parse2};
61
62    #[test]
63    fn generate_double_trait() {
64        let (attr, item) = given(quote! { MyTraitDummy }, quote! { trait MyTrait {} });
65
66        let output = double_impl(attr, item);
67
68        let expected = quote! {
69            trait MyTrait {}
70
71            trait MyTraitDummy {}
72
73            impl<T> MyTrait for T where T: MyTraitDummy {}
74        };
75        assert_eq!(expected.to_string(), output.to_string());
76    }
77
78    #[test]
79    fn forward_visibility() {
80        // Given a public trait
81        let (attr, item) = given(quote! { MyTraitDummy }, quote! { pub trait MyTrait {} });
82
83        // When generating the dummy
84        let output = double_impl(attr, item);
85
86        // Then the generated trait should be public, too
87        let expected = quote! {
88            pub trait MyTrait {}
89
90            pub trait MyTraitDummy {}
91
92            impl<T> MyTrait for T where T: MyTraitDummy {}
93        };
94        assert_eq!(expected.to_string(), output.to_string());
95    }
96
97    #[test]
98    fn forward_method() {
99        // Given a trait with a method
100        let (attr, item) = given(
101            quote! { MyTraitDummy },
102            quote! {
103                trait MyTrait {
104                    fn foobar(&self);
105                }
106            },
107        );
108
109        // When generating the dummy
110        let output = double_impl(attr, item);
111
112        // Then the generated trait should contain that method, too
113        let expected = quote! {
114            trait MyTrait {
115                fn foobar(&self);
116            }
117
118            trait MyTraitDummy {
119                fn foobar (&self) { unimplemented!() }
120            }
121
122            impl<T> MyTrait for T where T: MyTraitDummy {
123                fn foobar(&self) { <Self as MyTraitDummy>::foobar(self,) }
124            }
125        };
126        assert_eq!(expected.to_string(), output.to_string());
127    }
128
129    #[test]
130    fn respect_existing_default_impl() {
131        // Given a method with a default implementation in the original trait
132        let (attr, item) = given(
133            quote! { MyTraitDummy },
134            quote! {
135                pub trait MyTrait {
136                    fn foobar() { println!("Hello Default!") }
137                }
138            },
139        );
140
141        // When generating the dummy
142        let output = double_impl(attr, item);
143
144        // Then the generated trait should not overide the existing default
145        let expected = quote! {
146            pub trait MyTrait {
147                fn foobar() { println!("Hello Default!") }
148            }
149
150            pub trait MyTraitDummy {}
151
152            impl<T> MyTrait for T where T: MyTraitDummy {
153                fn foobar() { <Self as MyTraitDummy>::foobar() }
154            }
155        };
156        assert_eq!(expected.to_string(), output.to_string());
157    }
158
159    #[test]
160    fn forward_async_method() {
161        // Given a trait with a method
162        let (attr, item) = given(
163            quote! { MyTraitDummy },
164            quote! {
165                trait MyTrait {
166                    async fn foobar(&self);
167                }
168            },
169        );
170
171        // When generating the dummy
172        let output = double_impl(attr, item);
173
174        // Then the generated trait should contain that method, too
175        let expected = quote! {
176            trait MyTrait {
177                async fn foobar(&self);
178            }
179
180            trait MyTraitDummy {
181                async fn foobar (&self) { unimplemented!() }
182            }
183
184            impl<T> MyTrait for T where T: MyTraitDummy {
185                async fn foobar(&self) { <Self as MyTraitDummy>::foobar(self,).await }
186            }
187        };
188        assert_eq!(expected.to_string(), output.to_string());
189    }
190
191    fn given(attr: proc_macro2::TokenStream, item: proc_macro2::TokenStream) -> (Ident, ItemTrait) {
192        let attr: Ident = parse2(attr).unwrap();
193        let item: ItemTrait = parse2(item).unwrap();
194        (attr, item)
195    }
196}