rustversion_detect/
macros.rs

1//! Defines the [`maybe_const_fn`] macro.
2
3/// Mark a function as `const` if a `cfg!(...)` attribute evaluates to true.
4///
5/// This has the same effect as using `#[rustversion::attr(version_test, const)]`,
6/// but is implemented as a declarative macro instead of a procedural one.
7///
8/// It is subject to some minor limitations (see below).
9///
10/// If these are unacceptable,
11/// please use the `rustversion` procedural macro.
12/// That is more robust and supports other markers like `unsafe` and `async`.
13///
14/// ## Example
15/// ```
16/// # use rustversion_detect::maybe_const_fn;
17///
18/// maybe_const_fn! {
19///     #[cfg_const(all())] // always true
20///     /// Example documentation
21///     #[inline] // Another attribute
22///     const fn example() -> u32 {
23///         3 + 7
24///     }
25///
26///     #[cfg_const(any())] // always false
27///     const fn not_const() -> Vec<u32> {
28///         vec![3, 7, 8]
29///     }
30/// }
31///
32/// const FOO: u32 = example();
33/// ```
34///
35/// ## Limitations
36/// Please remember to always place `const` before the `fn` declaration.
37/// Otherwise, the macro will give a value error.
38///
39/// The `#[cfg_const(...)]` marker must be the first attitude declaration.
40/// All other attributes and documentation comments must come after the `#[cfg_const(..)]` declartion.
41///
42/// The following two examples are **broken**:
43/// ```compile_fail
44/// # use rustversion_detect::maybe_const_fn;
45///
46/// maybe_const_fn! {
47///     /// doc comment first
48///     #[cfg_const(all())]
49///     /// Example documentation
50///     #[inline] // Another attribute
51///     const unsafe fn example() -> u32 {
52///         3 + 7
53///     }
54/// }
55/// ```
56///
57/// ```compile_fail
58/// # use rustversion_detect::maybe_const_fn;
59///
60/// maybe_const_fn! {
61///     #[inline] // attribute first
62///     #[cfg_const(all())]
63///     /// Example documentation
64///     #[inline] // Another attribute
65///     const unsafe fn example() -> u32 {
66///         3 + 7
67///     }
68/// }
69/// ```
70///
71/// ## Additional markers (`unsafe`, `async`, etc..)
72/// Additional markers like `async`, `unsafe`, and `extern "C"`
73/// must be surrounded by `{...}` due to macro limitations.
74///
75/// This is **correct**:
76/// ```
77/// # use rustversion_detect::maybe_const_fn;
78///
79/// maybe_const_fn! {
80///     #[cfg_const(all())] // always true
81///     /// Example documentation
82///     #[inline] // Another attribute
83///     const {unsafe} fn example() -> u32 {
84///         3 + 7
85///     }
86/// }
87///
88/// const FOO: u32 = unsafe { example() };
89/// ```
90///
91/// This is **broken**:
92/// ```compile_fail
93/// # use rustversion_detect::maybe_const_fn;
94///
95/// maybe_const_fn! {
96///     #[cfg_const(all())] // always true
97///     /// Example documentation
98///     #[inline] // Another attribute
99///     const unsafe fn example() -> u32 {
100///         3 + 7
101///     }
102/// }
103///
104/// const FOO: u32 = unsafe { example() };
105/// ```
106///
107/// ### Macro Forwarding
108/// When [forwarding a matched fragment] inside another macro,
109/// the outer macro cannot use fragment specifiers like `item`
110/// for the constant function declaration.
111/// As explained in the docs,
112/// using the `tt` and `ident` fragment specifiers are a special exception.
113///
114/// The following is **broken**:
115/// ```compile_fail
116/// # use rustversion_detect::maybe_const_fn;
117///
118/// macro_rules! item_spec {
119///     ($bad:item) => {
120///         maybe_const_fn! {
121///             #[cfg_const(all())] // always true
122///             $bad
123///         }
124///     }
125/// }
126///
127/// // will give the following error message:
128/// // > captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens
129/// item_spec! {
130///     const fn foo() {}
131/// }
132/// ```
133///
134/// [forwarding a matched fragment]: https://doc.rust-lang.org/reference/macros-by-example.html#forwarding-a-matched-fragment
135#[macro_export]
136macro_rules! maybe_const_fn {
137    ($(
138        #[cfg_const($cond:meta)]
139        $(#[$attr:meta])*
140        $visibility:vis const
141        // extra "specifiers" like `async` `unsafe` `extern "C"`
142        // needs to be surrounded with {...} due to macro limitations
143        //
144        // NOTE: Need to use $()* because $()? not supported on 1.31
145        $({$($extra_spec:tt)*})*
146        fn $name:ident ($($args:tt)*) $( -> $return_tp:ty)* $code:block
147    )*) => {$(
148        #[cfg($cond)]
149        $(#[$attr])*
150        $visibility const $($($extra_spec)*)* fn $name ( $($args)* ) $(-> $return_tp)* $code
151
152        #[cfg(not($cond))]
153        $(#[$attr])*
154        $visibility $($($extra_spec)*)* fn $name ( $($args)* ) $(-> $return_tp)* $code
155    )*};
156}