1 //! This module provides two utility macros for testing custom derives. They can 2 //! be used together to eliminate some of the boilerplate required in order to 3 //! declare and test custom derive implementations. 4 5 // Re-exports used by the decl_derive! and test_derive! 6 pub use proc_macro2::TokenStream as TokenStream2; 7 pub use syn::{parse_str, DeriveInput}; 8 9 #[cfg(all( 10 not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), 11 feature = "proc-macro" 12 ))] 13 pub use proc_macro::TokenStream; 14 #[cfg(all( 15 not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), 16 feature = "proc-macro" 17 ))] 18 pub use syn::parse; 19 20 /// The `decl_derive!` macro declares a custom derive wrapper. It will parse the 21 /// incoming `TokenStream` into a `synstructure::Structure` object, and pass it 22 /// into the inner function. 23 /// 24 /// Your inner function should take a `synstructure::Structure` by value, and 25 /// return a type implementing `synstructure::MacroResult`, for example: 26 /// 27 /// ``` 28 /// fn derive_simple(input: synstructure::Structure) -> proc_macro2::TokenStream { 29 /// unimplemented!() 30 /// } 31 /// 32 /// fn derive_result(input: synstructure::Structure) 33 /// -> syn::Result<proc_macro2::TokenStream> 34 /// { 35 /// unimplemented!() 36 /// } 37 /// ``` 38 /// 39 /// # Usage 40 /// 41 /// ### Without Attributes 42 /// ``` 43 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream { 44 /// quote::quote! { ... } 45 /// } 46 /// 47 /// # const _IGNORE: &'static str = stringify! { 48 /// decl_derive!([Interesting] => derive_interesting); 49 /// # }; 50 /// ``` 51 /// 52 /// ### With Attributes 53 /// ``` 54 /// # fn main() {} 55 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream { 56 /// quote::quote! { ... } 57 /// } 58 /// 59 /// # const _IGNORE: &'static str = stringify! { 60 /// decl_derive!([Interesting, attributes(interesting_ignore)] => derive_interesting); 61 /// # }; 62 /// ``` 63 /// 64 /// ### Decl Attributes & Doc Comments 65 /// ``` 66 /// # fn main() {} 67 /// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream { 68 /// quote::quote! { ... } 69 /// } 70 /// 71 /// # const _IGNORE: &'static str = stringify! { 72 /// decl_derive! { 73 /// [Interesting] => 74 /// #[allow(some_lint)] 75 /// /// Documentation Comments 76 /// derive_interesting 77 /// } 78 /// # }; 79 /// ``` 80 /// 81 /// *This macro is available if `synstructure` is built with the `"proc-macro"` 82 /// feature.* 83 #[cfg(all( 84 not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), 85 feature = "proc-macro" 86 ))] 87 #[macro_export] 88 macro_rules! decl_derive { 89 // XXX: Switch to using this variant everywhere? 90 ([$derives:ident $($derive_t:tt)*] => $(#[$($attrs:tt)*])* $inner:path) => { 91 #[proc_macro_derive($derives $($derive_t)*)] 92 #[allow(non_snake_case)] 93 $(#[$($attrs)*])* 94 pub fn $derives( 95 i: $crate::macros::TokenStream 96 ) -> $crate::macros::TokenStream { 97 match $crate::macros::parse::<$crate::macros::DeriveInput>(i) { 98 Ok(p) => { 99 match $crate::Structure::try_new(&p) { 100 Ok(s) => $crate::MacroResult::into_stream($inner(s)), 101 Err(e) => e.to_compile_error().into(), 102 } 103 } 104 Err(e) => e.to_compile_error().into(), 105 } 106 } 107 }; 108 } 109 110 /// The `decl_attribute!` macro declares a custom attribute wrapper. It will 111 /// parse the incoming `TokenStream` into a `synstructure::Structure` object, 112 /// and pass it into the inner function. 113 /// 114 /// Your inner function should have the following type: 115 /// 116 /// ``` 117 /// fn attribute( 118 /// attr: proc_macro2::TokenStream, 119 /// structure: synstructure::Structure, 120 /// ) -> proc_macro2::TokenStream { 121 /// unimplemented!() 122 /// } 123 /// ``` 124 /// 125 /// # Usage 126 /// 127 /// ``` 128 /// fn attribute_interesting( 129 /// _attr: proc_macro2::TokenStream, 130 /// _structure: synstructure::Structure, 131 /// ) -> proc_macro2::TokenStream { 132 /// quote::quote! { ... } 133 /// } 134 /// 135 /// # const _IGNORE: &'static str = stringify! { 136 /// decl_attribute!([interesting] => attribute_interesting); 137 /// # }; 138 /// ``` 139 /// 140 /// *This macro is available if `synstructure` is built with the `"proc-macro"` 141 /// feature.* 142 #[cfg(all( 143 not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))), 144 feature = "proc-macro" 145 ))] 146 #[macro_export] 147 macro_rules! decl_attribute { 148 ([$attribute:ident] => $(#[$($attrs:tt)*])* $inner:path) => { 149 #[proc_macro_attribute] 150 $(#[$($attrs)*])* 151 pub fn $attribute( 152 attr: $crate::macros::TokenStream, 153 i: $crate::macros::TokenStream, 154 ) -> $crate::macros::TokenStream { 155 match $crate::macros::parse::<$crate::macros::DeriveInput>(i) { 156 Ok(p) => match $crate::Structure::try_new(&p) { 157 Ok(s) => $crate::MacroResult::into_stream($inner(attr.into(), s)), 158 Err(e) => e.to_compile_error().into(), 159 }, 160 Err(e) => e.to_compile_error().into(), 161 } 162 } 163 }; 164 } 165 166 /// Run a test on a custom derive. This macro expands both the original struct 167 /// and the expansion to ensure that they compile correctly, and confirms that 168 /// feeding the original struct into the named derive will produce the written 169 /// output. 170 /// 171 /// You can add `no_build` to the end of the macro invocation to disable 172 /// checking that the written code compiles. This is useful in contexts where 173 /// the procedural macro cannot depend on the crate where it is used during 174 /// tests. 175 /// 176 /// # Usage 177 /// 178 /// ``` 179 /// fn test_derive_example(_s: synstructure::Structure) 180 /// -> Result<proc_macro2::TokenStream, syn::Error> 181 /// { 182 /// Ok(quote::quote! { const YOUR_OUTPUT: &'static str = "here"; }) 183 /// } 184 /// 185 /// fn main() { 186 /// synstructure::test_derive!{ 187 /// test_derive_example { 188 /// struct A; 189 /// } 190 /// expands to { 191 /// const YOUR_OUTPUT: &'static str = "here"; 192 /// } 193 /// } 194 /// } 195 /// ``` 196 #[macro_export] 197 macro_rules! test_derive { 198 ($name:path { $($i:tt)* } expands to { $($o:tt)* }) => { 199 { 200 #[allow(dead_code)] 201 fn ensure_compiles() { 202 $($i)* 203 $($o)* 204 } 205 206 $crate::test_derive!($name { $($i)* } expands to { $($o)* } no_build); 207 } 208 }; 209 210 ($name:path { $($i:tt)* } expands to { $($o:tt)* } no_build) => { 211 { 212 let i = stringify!( $($i)* ); 213 let parsed = $crate::macros::parse_str::<$crate::macros::DeriveInput>(i) 214 .expect(concat!( 215 "Failed to parse input to `#[derive(", 216 stringify!($name), 217 ")]`", 218 )); 219 220 let raw_res = $name($crate::Structure::new(&parsed)); 221 let res = $crate::MacroResult::into_result(raw_res) 222 .expect(concat!( 223 "Procedural macro failed for `#[derive(", 224 stringify!($name), 225 ")]`", 226 )); 227 228 let expected = stringify!( $($o)* ) 229 .parse::<$crate::macros::TokenStream2>() 230 .expect("output should be a valid TokenStream"); 231 let mut expected_toks = $crate::macros::TokenStream2::from(expected); 232 if res.to_string() != expected_toks.to_string() { 233 panic!("\ 234 test_derive failed: 235 expected: 236 ``` 237 {} 238 ``` 239 240 got: 241 ``` 242 {} 243 ```\n", 244 $crate::unpretty_print(&expected_toks), 245 $crate::unpretty_print(&res), 246 ); 247 } 248 } 249 }; 250 } 251