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}