Skip to main content

hegel_macros/
lib.rs

1mod common;
2mod composite;
3mod enum_gen;
4mod explicit_test_case;
5mod hegel_main;
6mod hegel_test;
7mod pretty_printable;
8mod reproduce_failure;
9mod rewrite_draws;
10mod standalone_function;
11mod stateful;
12mod struct_gen;
13mod utils;
14
15use proc_macro::TokenStream;
16use syn::{Data, DeriveInput, ItemFn, ItemImpl, parse_macro_input};
17
18#[proc_macro_derive(DefaultGenerator)]
19pub fn derive_generator(input: TokenStream) -> TokenStream {
20    let input = parse_macro_input!(input as DeriveInput);
21
22    match &input.data {
23        Data::Struct(data) => struct_gen::derive_struct_generator(&input, data),
24        Data::Enum(data) => enum_gen::derive_enum_generator(&input, data),
25        Data::Union(_) => syn::Error::new_spanned(&input, "Generator cannot be derived for unions")
26            .to_compile_error()
27            .into(),
28    }
29}
30
31#[proc_macro_derive(PrettyPrintable, attributes(pretty))]
32pub fn derive_pretty_printable(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as DeriveInput);
34    pretty_printable::derive_pretty_printable(&input)
35        .unwrap_or_else(syn::Error::into_compile_error)
36        .into()
37}
38
39#[proc_macro_attribute]
40pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
41    hegel_test::expand_test(attr.into(), item.into()).into()
42}
43
44#[proc_macro_attribute]
45pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
46    hegel_main::expand_main(attr.into(), item.into()).into()
47}
48
49#[proc_macro_attribute]
50pub fn standalone_function(attr: TokenStream, item: TokenStream) -> TokenStream {
51    standalone_function::expand_standalone_function(attr.into(), item.into()).into()
52}
53
54#[proc_macro_attribute]
55pub fn composite(attr: TokenStream, item: TokenStream) -> TokenStream {
56    if !attr.is_empty() {
57        return syn::Error::new_spanned(
58            proc_macro2::TokenStream::from(attr),
59            "#[hegel::composite] takes no arguments",
60        )
61        .to_compile_error()
62        .into();
63    }
64    let input = parse_macro_input!(item as ItemFn);
65    composite::expand_composite(input).into()
66}
67
68/// Define an explicit test case to run before the property-based test.
69///
70/// Note: We are not currently 100% happy with the name of this attribute
71/// and expect that we might change it in future. The API should otherwise
72/// remain compatible, but you might have to rename some call-sites.
73///
74/// Must be placed **below** `#[hegel::test]`. Multiple attributes are allowed.
75///
76/// ```ignore
77/// #[hegel::test]
78/// #[hegel::explicit_test_case(x = 42, y = "hello")]
79/// fn my_test(tc: hegel::TestCase) {
80///     let x: i32 = tc.draw(hegel::generators::integers());
81///     let y: String = tc.draw(hegel::generators::text());
82///     // ...
83/// }
84/// ```
85///
86/// Arguments correspond to the names they would be printed with in a failing
87/// test case, so need suffixing if they're repeated. For example:
88///
89/// ```ignore
90/// #[hegel::test]
91/// #[hegel::explicit_test_case(x_1 = 1, x_2 = 2, x_3 = 4 )]
92/// fn my_test(tc: hegel::TestCase) {
93///     for _ in 0..3 {
94///         let x: i32 = tc.draw(hegel::generators::integers());
95///     }
96/// }
97/// ```
98#[proc_macro_attribute]
99pub fn explicit_test_case(attr: TokenStream, item: TokenStream) -> TokenStream {
100    explicit_test_case::expand_explicit_test_case(attr.into(), item.into()).into()
101}
102
103/// Reproduce a single failing example from a base64 failure blob.
104///
105/// Documentation lives in hegel's lib.rs for intra-doc links.
106#[proc_macro_attribute]
107pub fn reproduce_failure(attr: TokenStream, item: TokenStream) -> TokenStream {
108    reproduce_failure::expand_reproduce_failure(attr.into(), item.into()).into()
109}
110
111#[proc_macro_attribute]
112pub fn state_machine(attr: TokenStream, item: TokenStream) -> TokenStream {
113    if !attr.is_empty() {
114        return syn::Error::new_spanned(
115            proc_macro2::TokenStream::from(attr),
116            "#[hegel::state_machine] takes no arguments",
117        )
118        .to_compile_error()
119        .into();
120    }
121    let block = parse_macro_input!(item as ItemImpl);
122    stateful::expand_state_machine(block).into()
123}
124
125#[proc_macro_attribute]
126pub fn concurrent_state_machine(attr: TokenStream, item: TokenStream) -> TokenStream {
127    if !attr.is_empty() {
128        return syn::Error::new_spanned(
129            proc_macro2::TokenStream::from(attr),
130            "#[hegel::concurrent_state_machine] takes no arguments",
131        )
132        .to_compile_error()
133        .into();
134    }
135    let block = parse_macro_input!(item as ItemImpl);
136    stateful::expand_concurrent_state_machine(block).into()
137}
138
139/// Rewrite `tc.draw(gen)` calls inside a closure body to the named form used
140/// by `#[hegel::test]`, so failing-test output prints real variable names.
141///
142/// Intended for test infrastructure that constructs `Hegel::new(...)` by hand
143/// (e.g. when wrapping a test run with custom output capture) where the
144/// ordinary `#[hegel::test]` attribute isn't an option.
145///
146/// ```ignore
147/// let closure = hegel::rewrite_draws!(|tc: hegel::TestCase| {
148///     let x: i32 = tc.draw(hegel::generators::integers());
149///     assert!(x < 10);
150/// });
151/// ```
152#[proc_macro]
153pub fn rewrite_draws(input: TokenStream) -> TokenStream {
154    rewrite_draws::expand_rewrite_draws(input.into()).into()
155}