bon/__/cfg_eval.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
/// This is all a big embarrassing workaround, please don't oversee 😳😳😳.
///
/// Anyway, if you are curious what the hell is going on here, then here is
/// an explanation 😸. So... where to start 🤔. Ah! The problem!
///
/// ## The problem
///
/// Proc macro attributes (like `#[builder]`) see all the `#[cfg(...)]` and `#[cfg_attr(...)]`
/// attributes unexpanded. For example, if you write smth like this:
///
/// ```
/// #[bon::builder]
/// fn func(
/// #[cfg(windows)]
/// windows_only_param: u32,
/// ) {}
///
/// ```
///
/// then the `#[builder]` macro will see the full `#[cfg(...)]` attribute with
/// the `windows_only_param` it is attached to verbatim. The `#[cfg(...)]` isn't
/// removed by the time the `#[builder]`'s macro expansion is invoked.
///
/// It is a problem because the `#[builder]` macro needs to know the exact list
/// of members it has to generate setters for. It doesn't know whether
/// the `windows` predicate evaluates to `true` or `false`, especially if this was
/// a more complex predicate. So it can't decide whether to generate a setter for
/// the `windows_only_param` or not.
///
/// ## The solution
///
/// This macro allows us to evaluate the `cfg` predicates by using a variation of
/// [the trick] shared by @recatek.
///
/// When the `#[builder]` macro finds any usage of `#[cfg(...)]` or `#[cfg_attr(...)]`
/// it generates a call to this macro with all `cfg` predicates collected from the
/// item it was placed on. The `#[builder]` macro deduplicates and sorts the `cfg`
/// predicates and passes them as `$pred` to this macro.
///
/// This macro then dispatches to `__eval_cfg_callback_true` or `__eval_cfg_callback_false`
/// by defining a conditional `use ...` statement for each predicate and collects the
/// results of the evaluation in the `$results` list.
///
/// For the last call to this macro (when no more `$pred` are left) the macro calls back
/// to the proc macro attribute that called it with the results of the evaluation and
/// the original parameters and the item which are passed through via the `$rest` macro variable.
///
/// [the trick]: https://users.rust-lang.org/t/supporting-or-evaluating-cfg-in-proc-macro-parameters/93240/2
#[macro_export]
#[doc(hidden)]
macro_rules! __eval_cfg_callback {
(
{ $($results:tt)* }
( $pred_id:ident: $($pred:tt)* )
$($rest:tt)*
) => {
// The `pred_id` is required to be a unique identifier for the current
// predicate evaluation so that we can use it in a `use` statement to define
// a new unique name for the macro to call.
#[cfg($($pred)*)]
#[doc(hidden)]
#[allow(deprecated)]
use $crate::__eval_cfg_callback_true as $pred_id;
#[cfg(not($($pred)*))]
#[doc(hidden)]
#[allow(deprecated)]
use $crate::__eval_cfg_callback_false as $pred_id;
// The trick here is that `$pred_id` now resolves either to
// `__eval_cfg_callback_true` or `__eval_cfg_callback_false`
// depending on the evaluation of the cfg predicate, so by
// invoking it as a macro, that macro internally pushes either
// `true` or `false` to the `$results` list.
$pred_id! {
{ $($results)* }
$($rest)*
}
};
// The terminal case for the recursion when there are no more predicates left.
// We have collected all the results of the cfg evaluations and now we can
// delegate them to the proc macro attribute that called this macro.
(
// The results of the cfg evaluation
{ $($results:tt)* }
// The proc macro attribute to invoke with the results
$final_macro:path,
// The number of times this macro was called recursively from the proc macro
$recursion_counter:literal,
// Parameters to pass to the proc macro attribute after the cfg results
( $($macro_params:tt)* )
// The item to attach the proc macro attribute to
$($item:tt)*
) => {
// The special `__cfgs(...)` prefix is parsed by the proc macro attribute
// to get the results of the cfg evaluations.
#[$final_macro(__cfgs($recursion_counter, $($results)*) $($macro_params)*)]
$($item)*
};
}
/// The `cfg` predicate evaluated to `true`, now push that information into
/// the `$results` list.
#[macro_export]
#[doc(hidden)]
macro_rules! __eval_cfg_callback_true {
(
{ $($results:tt)* }
$($tt:tt)*
) => {
$crate::__eval_cfg_callback! {
{ $($results)* true, }
$($tt)*
}
};
}
/// The `cfg` predicate evaluated to `false`, now push that information into
/// the `$results` list.
#[macro_export]
#[doc(hidden)]
macro_rules! __eval_cfg_callback_false {
(
{ $($results:tt)* }
$($tt:tt)*
) => {
$crate::__eval_cfg_callback! {
{ $($results)* false, }
$($tt)*
}
};
}