variadics_please/
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::{Literal, 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 variadics_please::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 variadics_please::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 variadics_please::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/// A variant of [`all_tuples!`] that enumerates its output.
195///
196/// In particular, the tuples used by the inner macro will themselves be composed
197/// of tuples which contain the index.
198///
199/// For example, with a single parameter:
200/// ```
201/// # use variadics_please::all_tuples_enumerated;
202///
203/// trait Squawk {
204///     fn squawk(&self);
205/// }
206///
207/// // If every type in a tuple is `Squawk`, the tuple can squawk by having its
208/// // constituents squawk sequentially:
209/// macro_rules! impl_squawk {
210///     ($(($n:tt, $T:ident)),*) => {
211///         impl<$($T: Squawk),*> Squawk for ($($T,)*) {
212///             fn squawk(&self) {
213///                 $(
214///                     self.$n.squawk();
215///                 )*
216///             }
217///         }
218///     };
219/// }
220///
221/// all_tuples_enumerated!(impl_squawk, 1, 15, T);
222/// // impl_squawk!((0, T0));
223/// // impl_squawk!((0, T0), (1, T1));
224/// // ..
225/// // impl_squawk!((0, T0) .. (14, T14));
226/// ```
227///
228/// With multiple parameters, the result is similar, but with the additional parameters
229/// included in each tuple; e.g.:
230/// ```ignore
231/// all_tuples_enumerated!(impl_squawk, 1, 15, P, p);
232/// // impl_squawk!((0, P0, p0));
233/// // impl_squawk!((0, P0, p0), (1, P1, p1));
234/// // ..
235/// // impl_squawk!((0, P0, p0) .. (14, P14, p14));
236/// ```
237#[proc_macro]
238pub fn all_tuples_enumerated(input: TokenStream) -> TokenStream {
239    let input = parse_macro_input!(input as AllTuples);
240    let len = 1 + input.end - input.start;
241    let mut ident_tuples = Vec::with_capacity(len);
242    for i in 0..=len {
243        let idents = input
244            .idents
245            .iter()
246            .map(|ident| format_ident!("{}{}", ident, i));
247        ident_tuples.push(to_ident_tuple_enumerated(idents, i));
248    }
249
250    let macro_ident = &input.macro_ident;
251    let invocations = (input.start..=input.end).map(|i| {
252        let ident_tuples = choose_ident_tuples_enumerated(&input, &ident_tuples, i);
253        let attrs = if input.fake_variadic {
254            fake_variadic_attrs(len, i)
255        } else {
256            TokenStream2::default()
257        };
258        quote! {
259            #macro_ident!(#attrs #ident_tuples);
260        }
261    });
262    TokenStream::from(quote! {
263        #(
264            #invocations
265        )*
266    })
267}
268
269/// Helper macro to generate tuple pyramids with their length. Useful to generate scaffolding to
270/// work around Rust lacking variadics. Invoking `all_tuples_with_size!(impl_foo, start, end, P, Q, ..)`
271/// invokes `impl_foo` providing ident tuples through arity `start..end` preceded by their length.
272/// If you don't require the length of the tuple, see [`all_tuples!`].
273///
274/// # Examples
275///
276/// ## Single parameter
277///
278/// ```
279/// # use core::marker::PhantomData;
280/// # use variadics_please::all_tuples_with_size;
281/// #
282/// struct Foo<T> {
283///     // ..
284/// #    _phantom: PhantomData<T>
285/// }
286///
287/// trait WrappedInFoo {
288///     type Tup;
289///     const LENGTH: usize;
290/// }
291///
292/// macro_rules! impl_wrapped_in_foo {
293///     ($N:expr, $($T:ident),*) => {
294///         impl<$($T),*> WrappedInFoo for ($($T,)*) {
295///             type Tup = ($(Foo<$T>,)*);
296///             const LENGTH: usize = $N;
297///         }
298///     };
299/// }
300///
301/// all_tuples_with_size!(impl_wrapped_in_foo, 0, 15, T);
302/// // impl_wrapped_in_foo!(0);
303/// // impl_wrapped_in_foo!(1, T0);
304/// // impl_wrapped_in_foo!(2, T0, T1);
305/// // ..
306/// // impl_wrapped_in_foo!(15, T0 .. T14);
307/// ```
308///
309/// ## Multiple parameters
310///
311/// ```
312/// # use variadics_please::all_tuples_with_size;
313/// #
314/// trait Append {
315///     type Out<Item>;
316///     fn append<Item>(tup: Self, item: Item) -> Self::Out<Item>;
317/// }
318///
319/// impl Append for () {
320///     type Out<Item> = (Item,);
321///     fn append<Item>(_: Self, item: Item) -> Self::Out<Item> {
322///         (item,)
323///     }
324/// }
325///
326/// macro_rules! impl_append {
327///     ($N:expr, $(($P:ident, $p:ident)),*) => {
328///         impl<$($P),*> Append for ($($P,)*) {
329///             type Out<Item> = ($($P),*, Item);
330///             fn append<Item>(($($p,)*): Self, item: Item) -> Self::Out<Item> {
331///                 ($($p),*, item)
332///             }
333///         }
334///     }
335/// }
336///
337/// all_tuples_with_size!(impl_append, 1, 15, P, p);
338/// // impl_append!(1, (P0, p0));
339/// // impl_append!(2, (P0, p0), (P1, p1));
340/// // impl_append!(3, (P0, p0), (P1, p1), (P2, p2));
341/// // ..
342/// // impl_append!(15, (P0, p0) .. (P14, p14));
343/// ```
344///
345/// **`#[doc(fake_variadic)]`**
346///
347/// To improve the readability of your docs when implementing a trait for
348/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker.
349/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`.
350///
351/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro
352/// is that you have to accept attributes using `$(#[$meta:meta])*`.
353///
354/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default.
355/// Add the following to your lib.rs if not already present:
356///
357/// ```
358/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]`
359/// #![allow(internal_features)]
360/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
361/// ```
362///
363/// ```
364/// # use variadics_please::all_tuples_with_size;
365/// #
366/// trait Variadic {}
367///
368/// impl Variadic for () {}
369///
370/// macro_rules! impl_variadic {
371///     ($N:expr, $(#[$meta:meta])* $(($P:ident, $p:ident)),*) => {
372///         $(#[$meta])*
373///         impl<$($P),*> Variadic for ($($P,)*) {}
374///     }
375/// }
376///
377/// all_tuples_with_size!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p);
378/// ```
379#[proc_macro]
380pub fn all_tuples_with_size(input: TokenStream) -> TokenStream {
381    let input = parse_macro_input!(input as AllTuples);
382    let len = 1 + input.end - input.start;
383    let mut ident_tuples = Vec::with_capacity(len);
384    for i in 0..=len {
385        let idents = input
386            .idents
387            .iter()
388            .map(|ident| format_ident!("{}{}", ident, i));
389        ident_tuples.push(to_ident_tuple(idents, input.idents.len()));
390    }
391    let macro_ident = &input.macro_ident;
392    let invocations = (input.start..=input.end).map(|i| {
393        let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i);
394        let attrs = if input.fake_variadic {
395            fake_variadic_attrs(len, i)
396        } else {
397            TokenStream2::default()
398        };
399        quote! {
400            #macro_ident!(#i, #attrs #ident_tuples);
401        }
402    });
403    TokenStream::from(quote! {
404        #(
405            #invocations
406        )*
407    })
408}
409
410/// Parses the attribute `#[doc(fake_variadic)]`
411fn parse_fake_variadic_attr(input: ParseStream) -> Result<bool> {
412    let attribute = match input.call(Attribute::parse_outer)? {
413        attributes if attributes.is_empty() => return Ok(false),
414        attributes if attributes.len() == 1 => attributes[0].clone(),
415        attributes => {
416            return Err(Error::new(
417                input.span(),
418                format!("Expected exactly one attribute, got {}", attributes.len()),
419            ))
420        }
421    };
422
423    if attribute.path().is_ident("doc") {
424        let nested = attribute.parse_args::<Ident>()?;
425        if nested == "fake_variadic" {
426            return Ok(true);
427        }
428    }
429
430    Err(Error::new(
431        attribute.meta.span(),
432        "Unexpected attribute".to_string(),
433    ))
434}
435
436fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usize) -> TokenStream2 {
437    // `rustdoc` uses the first ident to generate nice
438    // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ).
439    // We don't want two numbers, so we use the
440    // original, unnumbered idents for this case.
441    if input.fake_variadic && i == 1 {
442        let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len());
443        quote! { #ident_tuple }
444    } else {
445        let ident_tuples = &ident_tuples[..i];
446        quote! { #(#ident_tuples),* }
447    }
448}
449
450fn choose_ident_tuples_enumerated(
451    input: &AllTuples,
452    ident_tuples: &[TokenStream2],
453    i: usize,
454) -> TokenStream2 {
455    if input.fake_variadic && i == 1 {
456        let ident_tuple = to_ident_tuple_enumerated(input.idents.iter().cloned(), 0);
457        quote! { #ident_tuple }
458    } else {
459        let ident_tuples = &ident_tuples[..i];
460        quote! { #(#ident_tuples),* }
461    }
462}
463
464fn to_ident_tuple(idents: impl Iterator<Item = Ident>, len: usize) -> TokenStream2 {
465    if len < 2 {
466        quote! { #(#idents)* }
467    } else {
468        quote! { (#(#idents),*) }
469    }
470}
471
472/// Like `to_ident_tuple`, but it enumerates the identifiers
473fn to_ident_tuple_enumerated(idents: impl Iterator<Item = Ident>, idx: usize) -> TokenStream2 {
474    let idx = Literal::usize_unsuffixed(idx);
475    quote! { (#idx, #(#idents),*) }
476}
477
478fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 {
479    let cfg = quote! { any(docsrs, docsrs_dep) };
480    match i {
481        // An empty tuple (i.e. the unit type) is still documented separately,
482        // so no `#[doc(hidden)]` here.
483        0 => TokenStream2::default(),
484        // The `#[doc(fake_variadic)]` attr has to be on the first impl block.
485        1 => {
486            let doc = LitStr::new(
487                &format!("This trait is implemented for tuples up to {len} items long."),
488                Span2::call_site(),
489            );
490            quote! {
491                #[cfg_attr(#cfg, doc(fake_variadic))]
492                #[cfg_attr(#cfg, doc = #doc)]
493            }
494        }
495        _ => quote! { #[cfg_attr(#cfg, doc(hidden))] },
496    }
497}