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}