bevy_utils_proc_macros/
lib.rs

1// FIXME(15321): solve CI failures, then replace with `#![expect()]`.
2#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4
5use proc_macro::TokenStream;
6use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
7use quote::{format_ident, quote};
8use syn::{
9    parse::{Parse, ParseStream},
10    parse_macro_input,
11    spanned::Spanned as _,
12    token::Comma,
13    Attribute, Error, Ident, LitInt, LitStr, Result,
14};
15struct AllTuples {
16    fake_variadic: bool,
17    macro_ident: Ident,
18    start: usize,
19    end: usize,
20    idents: Vec<Ident>,
21}
22
23impl Parse for AllTuples {
24    fn parse(input: ParseStream) -> Result<Self> {
25        let fake_variadic = input.call(parse_fake_variadic_attr)?;
26        let macro_ident = input.parse::<Ident>()?;
27        input.parse::<Comma>()?;
28        let start = input.parse::<LitInt>()?.base10_parse()?;
29        input.parse::<Comma>()?;
30        let end = input.parse::<LitInt>()?.base10_parse()?;
31        input.parse::<Comma>()?;
32        let mut idents = vec![input.parse::<Ident>()?];
33        while input.parse::<Comma>().is_ok() {
34            idents.push(input.parse::<Ident>()?);
35        }
36
37        if start > 1 && fake_variadic {
38            return Err(Error::new(
39                input.span(),
40                "#[doc(fake_variadic)] only works when the tuple with length one is included",
41            ));
42        }
43
44        Ok(AllTuples {
45            fake_variadic,
46            macro_ident,
47            start,
48            end,
49            idents,
50        })
51    }
52}
53
54/// Helper macro to generate tuple pyramids. Useful to generate scaffolding to work around Rust
55/// lacking variadics. Invoking `all_tuples!(impl_foo, start, end, P, Q, ..)`
56/// invokes `impl_foo` providing ident tuples through arity `start..=end`.
57/// If you require the length of the tuple, see [`all_tuples_with_size!`].
58///
59/// # Examples
60///
61/// ## Single parameter
62///
63/// ```
64/// # use core::marker::PhantomData;
65/// # use bevy_utils_proc_macros::all_tuples;
66/// #
67/// struct Foo<T> {
68///     // ..
69/// #    _phantom: PhantomData<T>
70/// }
71///
72/// trait WrappedInFoo {
73///     type Tup;
74/// }
75///
76/// macro_rules! impl_wrapped_in_foo {
77///     ($($T:ident),*) => {
78///         impl<$($T),*> WrappedInFoo for ($($T,)*) {
79///             type Tup = ($(Foo<$T>,)*);
80///         }
81///     };
82/// }
83///
84/// all_tuples!(impl_wrapped_in_foo, 0, 15, T);
85/// // impl_wrapped_in_foo!();
86/// // impl_wrapped_in_foo!(T0);
87/// // impl_wrapped_in_foo!(T0, T1);
88/// // ..
89/// // impl_wrapped_in_foo!(T0 .. T14);
90/// ```
91///
92/// # Multiple parameters
93///
94/// ```
95/// # use bevy_utils_proc_macros::all_tuples;
96/// #
97/// trait Append {
98///     type Out<Item>;
99///     fn append<Item>(tup: Self, item: Item) -> Self::Out<Item>;
100/// }
101///
102/// impl Append for () {
103///     type Out<Item> = (Item,);
104///     fn append<Item>(_: Self, item: Item) -> Self::Out<Item> {
105///         (item,)
106///     }
107/// }
108///
109/// macro_rules! impl_append {
110///     ($(($P:ident, $p:ident)),*) => {
111///         impl<$($P),*> Append for ($($P,)*) {
112///             type Out<Item> = ($($P),*, Item);
113///             fn append<Item>(($($p,)*): Self, item: Item) -> Self::Out<Item> {
114///                 ($($p),*, item)
115///             }
116///         }
117///     }
118/// }
119///
120/// all_tuples!(impl_append, 1, 15, P, p);
121/// // impl_append!((P0, p0));
122/// // impl_append!((P0, p0), (P1, p1));
123/// // impl_append!((P0, p0), (P1, p1), (P2, p2));
124/// // ..
125/// // impl_append!((P0, p0) .. (P14, p14));
126/// ```
127///
128/// **`#[doc(fake_variadic)]`**
129///
130/// To improve the readability of your docs when implementing a trait for
131/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker.
132/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`.
133///
134/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro
135/// is that you have to accept attributes using `$(#[$meta:meta])*`.
136///
137/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default.
138/// Add the following to your lib.rs if not already present:
139///
140/// ```
141/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]`
142/// #![allow(internal_features)]
143/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
144/// ```
145///
146/// ```
147/// # use bevy_utils_proc_macros::all_tuples;
148/// #
149/// trait Variadic {}
150///
151/// impl Variadic for () {}
152///
153/// macro_rules! impl_variadic {
154///     ($(#[$meta:meta])* $(($P:ident, $p:ident)),*) => {
155///         $(#[$meta])*
156///         impl<$($P),*> Variadic for ($($P,)*) {}
157///     }
158/// }
159///
160/// all_tuples!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p);
161/// ```
162#[proc_macro]
163pub fn all_tuples(input: TokenStream) -> TokenStream {
164    let input = parse_macro_input!(input as AllTuples);
165    let len = 1 + input.end - input.start;
166    let mut ident_tuples = Vec::with_capacity(len);
167    for i in 0..=len {
168        let idents = input
169            .idents
170            .iter()
171            .map(|ident| format_ident!("{}{}", ident, i));
172        ident_tuples.push(to_ident_tuple(idents, input.idents.len()));
173    }
174
175    let macro_ident = &input.macro_ident;
176    let invocations = (input.start..=input.end).map(|i| {
177        let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i);
178        let attrs = if input.fake_variadic {
179            fake_variadic_attrs(len, i)
180        } else {
181            TokenStream2::default()
182        };
183        quote! {
184            #macro_ident!(#attrs #ident_tuples);
185        }
186    });
187    TokenStream::from(quote! {
188        #(
189            #invocations
190        )*
191    })
192}
193
194/// Helper macro to generate tuple pyramids with their length. Useful to generate scaffolding to
195/// work around Rust lacking variadics. Invoking `all_tuples_with_size!(impl_foo, start, end, P, Q, ..)`
196/// invokes `impl_foo` providing ident tuples through arity `start..=end` preceded by their length.
197/// If you don't require the length of the tuple, see [`all_tuples!`].
198///
199/// # Examples
200///
201/// ## Single parameter
202///
203/// ```
204/// # use core::marker::PhantomData;
205/// # use bevy_utils_proc_macros::all_tuples_with_size;
206/// #
207/// struct Foo<T> {
208///     // ..
209/// #    _phantom: PhantomData<T>
210/// }
211///
212/// trait WrappedInFoo {
213///     type Tup;
214///     const LENGTH: usize;
215/// }
216///
217/// macro_rules! impl_wrapped_in_foo {
218///     ($N:expr, $($T:ident),*) => {
219///         impl<$($T),*> WrappedInFoo for ($($T,)*) {
220///             type Tup = ($(Foo<$T>,)*);
221///             const LENGTH: usize = $N;
222///         }
223///     };
224/// }
225///
226/// all_tuples_with_size!(impl_wrapped_in_foo, 0, 15, T);
227/// // impl_wrapped_in_foo!(0);
228/// // impl_wrapped_in_foo!(1, T0);
229/// // impl_wrapped_in_foo!(2, T0, T1);
230/// // ..
231/// // impl_wrapped_in_foo!(15, T0 .. T14);
232/// ```
233///
234/// ## Multiple parameters
235///
236/// ```
237/// # use bevy_utils_proc_macros::all_tuples_with_size;
238/// #
239/// trait Append {
240///     type Out<Item>;
241///     fn append<Item>(tup: Self, item: Item) -> Self::Out<Item>;
242/// }
243///
244/// impl Append for () {
245///     type Out<Item> = (Item,);
246///     fn append<Item>(_: Self, item: Item) -> Self::Out<Item> {
247///         (item,)
248///     }
249/// }
250///
251/// macro_rules! impl_append {
252///     ($N:expr, $(($P:ident, $p:ident)),*) => {
253///         impl<$($P),*> Append for ($($P,)*) {
254///             type Out<Item> = ($($P),*, Item);
255///             fn append<Item>(($($p,)*): Self, item: Item) -> Self::Out<Item> {
256///                 ($($p),*, item)
257///             }
258///         }
259///     }
260/// }
261///
262/// all_tuples_with_size!(impl_append, 1, 15, P, p);
263/// // impl_append!(1, (P0, p0));
264/// // impl_append!(2, (P0, p0), (P1, p1));
265/// // impl_append!(3, (P0, p0), (P1, p1), (P2, p2));
266/// // ..
267/// // impl_append!(15, (P0, p0) .. (P14, p14));
268/// ```
269///
270/// **`#[doc(fake_variadic)]`**
271///
272/// To improve the readability of your docs when implementing a trait for
273/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker.
274/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`.
275///
276/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro
277/// is that you have to accept attributes using `$(#[$meta:meta])*`.
278///
279/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default.
280/// Add the following to your lib.rs if not already present:
281///
282/// ```
283/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]`
284/// #![allow(internal_features)]
285/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
286/// ```
287///
288/// ```
289/// # use bevy_utils_proc_macros::all_tuples_with_size;
290/// #
291/// trait Variadic {}
292///
293/// impl Variadic for () {}
294///
295/// macro_rules! impl_variadic {
296///     ($N:expr, $(#[$meta:meta])* $(($P:ident, $p:ident)),*) => {
297///         $(#[$meta])*
298///         impl<$($P),*> Variadic for ($($P,)*) {}
299///     }
300/// }
301///
302/// all_tuples_with_size!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p);
303/// ```
304#[proc_macro]
305pub fn all_tuples_with_size(input: TokenStream) -> TokenStream {
306    let input = parse_macro_input!(input as AllTuples);
307    let len = 1 + input.end - input.start;
308    let mut ident_tuples = Vec::with_capacity(len);
309    for i in 0..=len {
310        let idents = input
311            .idents
312            .iter()
313            .map(|ident| format_ident!("{}{}", ident, i));
314        ident_tuples.push(to_ident_tuple(idents, input.idents.len()));
315    }
316    let macro_ident = &input.macro_ident;
317    let invocations = (input.start..=input.end).map(|i| {
318        let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i);
319        let attrs = if input.fake_variadic {
320            fake_variadic_attrs(len, i)
321        } else {
322            TokenStream2::default()
323        };
324        quote! {
325            #macro_ident!(#i, #attrs #ident_tuples);
326        }
327    });
328    TokenStream::from(quote! {
329        #(
330            #invocations
331        )*
332    })
333}
334
335/// Parses the attribute `#[doc(fake_variadic)]`
336fn parse_fake_variadic_attr(input: ParseStream) -> Result<bool> {
337    let attribute = match input.call(Attribute::parse_outer)? {
338        attributes if attributes.is_empty() => return Ok(false),
339        attributes if attributes.len() == 1 => attributes[0].clone(),
340        attributes => {
341            return Err(Error::new(
342                input.span(),
343                format!("Expected exactly one attribute, got {}", attributes.len()),
344            ))
345        }
346    };
347
348    if attribute.path().is_ident("doc") {
349        let nested = attribute.parse_args::<Ident>()?;
350        if nested == "fake_variadic" {
351            return Ok(true);
352        }
353    }
354
355    Err(Error::new(
356        attribute.meta.span(),
357        "Unexpected attribute".to_string(),
358    ))
359}
360
361fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usize) -> TokenStream2 {
362    // `rustdoc` uses the first ident to generate nice
363    // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ).
364    // We don't want two numbers, so we use the
365    // original, unnumbered idents for this case.
366    if input.fake_variadic && i == 1 {
367        let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len());
368        quote! { #ident_tuple }
369    } else {
370        let ident_tuples = &ident_tuples[..i];
371        quote! { #(#ident_tuples),* }
372    }
373}
374
375fn to_ident_tuple(idents: impl Iterator<Item = Ident>, len: usize) -> TokenStream2 {
376    if len < 2 {
377        quote! { #(#idents)* }
378    } else {
379        quote! { (#(#idents),*) }
380    }
381}
382
383fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 {
384    let cfg = quote! { any(docsrs, docsrs_dep) };
385    match i {
386        // An empty tuple (i.e. the unit type) is still documented separately,
387        // so no `#[doc(hidden)]` here.
388        0 => TokenStream2::default(),
389        // The `#[doc(fake_variadic)]` attr has to be on the first impl block.
390        1 => {
391            let doc = LitStr::new(
392                &format!("This trait is implemented for tuples up to {len} items long."),
393                Span2::call_site(),
394            );
395            quote! {
396                #[cfg_attr(#cfg, doc(fake_variadic))]
397                #[cfg_attr(#cfg, doc = #doc)]
398            }
399        }
400        _ => quote! { #[cfg_attr(#cfg, doc(hidden))] },
401    }
402}