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}