Revision control
Copy as Markdown
Other Tools
//! This module provides two utility macros for testing custom derives. They can
//! be used together to eliminate some of the boilerplate required in order to
//! declare and test custom derive implementations.
// Re-exports used by the decl_derive! and test_derive!
pub use proc_macro2::TokenStream as TokenStream2;
pub use syn::{parse2, parse_str, DeriveInput};
pub use quote::quote;
#[cfg(all(
not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
feature = "proc-macro"
))]
pub use proc_macro::TokenStream;
#[cfg(all(
not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
feature = "proc-macro"
))]
pub use syn::parse;
/// The `decl_derive!` macro declares a custom derive wrapper. It will parse the
/// incoming `TokenStream` into a `synstructure::Structure` object, and pass it
/// into the inner function.
///
/// Your inner function should take a `synstructure::Structure` by value, and
/// return a type implementing `synstructure::MacroResult`, for example:
///
/// ```
/// fn derive_simple(input: synstructure::Structure) -> proc_macro2::TokenStream {
/// unimplemented!()
/// }
///
/// fn derive_result(input: synstructure::Structure)
/// -> syn::Result<proc_macro2::TokenStream>
/// {
/// unimplemented!()
/// }
/// ```
///
/// # Usage
///
/// ### Without Attributes
/// ```
/// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
/// quote::quote! { ... }
/// }
///
/// # const _IGNORE: &'static str = stringify! {
/// decl_derive!([Interesting] => derive_interesting);
/// # };
/// ```
///
/// ### With Attributes
/// ```
/// # fn main() {}
/// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
/// quote::quote! { ... }
/// }
///
/// # const _IGNORE: &'static str = stringify! {
/// decl_derive!([Interesting, attributes(interesting_ignore)] => derive_interesting);
/// # };
/// ```
///
/// ### Decl Attributes & Doc Comments
/// ```
/// # fn main() {}
/// fn derive_interesting(_input: synstructure::Structure) -> proc_macro2::TokenStream {
/// quote::quote! { ... }
/// }
///
/// # const _IGNORE: &'static str = stringify! {
/// decl_derive! {
/// [Interesting] =>
/// #[allow(some_lint)]
/// /// Documentation Comments
/// derive_interesting
/// }
/// # };
/// ```
///
/// *This macro is available if `synstructure` is built with the `"proc-macro"`
/// feature.*
#[cfg(all(
not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
feature = "proc-macro"
))]
#[macro_export]
macro_rules! decl_derive {
// XXX: Switch to using this variant everywhere?
([$derives:ident $($derive_t:tt)*] => $(#[$($attrs:tt)*])* $inner:path) => {
#[proc_macro_derive($derives $($derive_t)*)]
#[allow(non_snake_case)]
$(#[$($attrs)*])*
pub fn $derives(
i: $crate::macros::TokenStream
) -> $crate::macros::TokenStream {
match $crate::macros::parse::<$crate::macros::DeriveInput>(i) {
::core::result::Result::Ok(p) => {
match $crate::Structure::try_new(&p) {
::core::result::Result::Ok(s) => $crate::MacroResult::into_stream($inner(s)),
::core::result::Result::Err(e) => {
::core::convert::Into::into(e.to_compile_error())
}
}
}
::core::result::Result::Err(e) => {
::core::convert::Into::into(e.to_compile_error())
}
}
}
};
}
/// The `decl_attribute!` macro declares a custom attribute wrapper. It will
/// parse the incoming `TokenStream` into a `synstructure::Structure` object,
/// and pass it into the inner function.
///
/// Your inner function should have the following type:
///
/// ```
/// fn attribute(
/// attr: proc_macro2::TokenStream,
/// structure: synstructure::Structure,
/// ) -> proc_macro2::TokenStream {
/// unimplemented!()
/// }
/// ```
///
/// # Usage
///
/// ```
/// fn attribute_interesting(
/// _attr: proc_macro2::TokenStream,
/// _structure: synstructure::Structure,
/// ) -> proc_macro2::TokenStream {
/// quote::quote! { ... }
/// }
///
/// # const _IGNORE: &'static str = stringify! {
/// decl_attribute!([interesting] => attribute_interesting);
/// # };
/// ```
///
/// *This macro is available if `synstructure` is built with the `"proc-macro"`
/// feature.*
#[cfg(all(
not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "wasi"))),
feature = "proc-macro"
))]
#[macro_export]
macro_rules! decl_attribute {
([$attribute:ident] => $(#[$($attrs:tt)*])* $inner:path) => {
#[proc_macro_attribute]
$(#[$($attrs)*])*
pub fn $attribute(
attr: $crate::macros::TokenStream,
i: $crate::macros::TokenStream,
) -> $crate::macros::TokenStream {
match $crate::macros::parse::<$crate::macros::DeriveInput>(i) {
::core::result::Result::Ok(p) => match $crate::Structure::try_new(&p) {
::core::result::Result::Ok(s) => {
$crate::MacroResult::into_stream(
$inner(::core::convert::Into::into(attr), s)
)
}
::core::result::Result::Err(e) => {
::core::convert::Into::into(e.to_compile_error())
}
},
::core::result::Result::Err(e) => {
::core::convert::Into::into(e.to_compile_error())
}
}
}
};
}
/// Run a test on a custom derive. This macro expands both the original struct
/// and the expansion to ensure that they compile correctly, and confirms that
/// feeding the original struct into the named derive will produce the written
/// output.
///
/// You can add `no_build` to the end of the macro invocation to disable
/// checking that the written code compiles. This is useful in contexts where
/// the procedural macro cannot depend on the crate where it is used during
/// tests.
///
/// # Usage
///
/// ```
/// fn test_derive_example(_s: synstructure::Structure)
/// -> Result<proc_macro2::TokenStream, syn::Error>
/// {
/// Ok(quote::quote! { const YOUR_OUTPUT: &'static str = "here"; })
/// }
///
/// fn main() {
/// synstructure::test_derive!{
/// test_derive_example {
/// struct A;
/// }
/// expands to {
/// const YOUR_OUTPUT: &'static str = "here";
/// }
/// }
/// }
/// ```
#[macro_export]
macro_rules! test_derive {
($name:path { $($i:tt)* } expands to { $($o:tt)* }) => {
{
#[allow(dead_code)]
fn ensure_compiles() {
$($i)*
$($o)*
}
$crate::test_derive!($name { $($i)* } expands to { $($o)* } no_build);
}
};
($name:path { $($i:tt)* } expands to { $($o:tt)* } no_build) => {
{
let i = $crate::macros::quote!( $($i)* );
let parsed = $crate::macros::parse2::<$crate::macros::DeriveInput>(i)
.expect(::core::concat!(
"Failed to parse input to `#[derive(",
::core::stringify!($name),
")]`",
));
let raw_res = $name($crate::Structure::new(&parsed));
let res = $crate::MacroResult::into_result(raw_res)
.expect(::core::concat!(
"Procedural macro failed for `#[derive(",
::core::stringify!($name),
")]`",
));
let expected_toks = $crate::macros::quote!( $($o)* );
if <$crate::macros::TokenStream2 as ::std::string::ToString>::to_string(&res)
!= <$crate::macros::TokenStream2 as ::std::string::ToString>::to_string(&expected_toks)
{
panic!("\
test_derive failed:
expected:
```
{}
```
got:
```
{}
```\n",
$crate::unpretty_print(&expected_toks),
$crate::unpretty_print(&res),
);
}
}
};
}