Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 59 additions & 54 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// FIXME(15321): solve CI failures, then replace with `#![expect()]`.
#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_cfg, rustdoc_internals))]

use proc_macro::TokenStream;
use proc_macro2::{Literal, Span as Span2, TokenStream as TokenStream2};
Expand All @@ -28,13 +28,6 @@ impl Parse for AllTuples {
let macro_ident = input.parse::<Ident>()?;
input.parse::<Comma>()?;
let start = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}

if start > 1 && fake_variadic {
return Err(Error::new(
Expand All @@ -43,6 +36,14 @@ impl Parse for AllTuples {
));
}

input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}

Ok(AllTuples {
fake_variadic,
macro_ident,
Expand Down Expand Up @@ -83,6 +84,7 @@ impl Parse for AllTuples {
/// };
/// }
///
/// // start from 0 element to 15 elements
/// all_tuples!(impl_wrapped_in_foo, 0, 15, T);
/// // impl_wrapped_in_foo!();
/// // impl_wrapped_in_foo!(T0);
Expand Down Expand Up @@ -119,12 +121,19 @@ impl Parse for AllTuples {
/// }
/// }
///
/// // start from 1 element to 15 elements
/// all_tuples!(impl_append, 1, 15, P, p);
/// // impl_append!((P0, p0));
/// // impl_append!((P0, p0), (P1, p1));
/// // impl_append!((P0, p0), (P1, p1), (P2, p2));
/// // ..
/// // impl_append!((P0, p0) .. (P14, p14));
///
/// // start from 16 elements to 20
/// all_tuples!(impl_append, 16, 20, P, p);
/// // impl_append!((P0, p0) .. (P15, p15));
/// // ..
/// // impl_append!((P0, p0) .. (P19, p19));
/// ```
///
/// **`#[doc(fake_variadic)]`**
Expand Down Expand Up @@ -164,8 +173,7 @@ impl Parse for AllTuples {
#[proc_macro]
pub fn all_tuples(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = 1 + input.end - input.start;
let ident_tuples = (0..=len)
let ident_tuples = (0..input.end)
.map(|i| {
let idents = input
.idents
Expand All @@ -175,13 +183,9 @@ pub fn all_tuples(input: TokenStream) -> TokenStream {
})
.collect::<Vec<_>>();
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i);
let attrs = if input.fake_variadic {
fake_variadic_attrs(len, i)
} else {
TokenStream2::default()
};
let invocations = (input.start..=input.end).map(|n| {
let ident_tuples = choose_ident_tuples(&input, &ident_tuples, n);
let attrs = attrs(&input, n);
quote! {
#macro_ident!(#attrs #ident_tuples);
}
Expand Down Expand Up @@ -239,24 +243,19 @@ pub fn all_tuples(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn all_tuples_enumerated(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = 1 + input.end - input.start;
let ident_tuples = (0..=len)
let ident_tuples = (0..input.end)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test case to make sure that if the input is 16 to 20, then it is not implemented for 0..=15. I have a suspicion.

Copy link
Contributor Author

@kingwingfly kingwingfly Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first add a test 3..15, then it conflicts with the original 1..15, so I believe 16..20 doesn’t impl for 1..15. 1..15and 15..20 are in the same block in doc, if 15..20 implemented for 1..15 again, it would not compile. Maybe current test is enough...

EDIT: I only added test in all_tuples and forgot others...

.map(|i| {
let idents = input
.idents
.iter()
.map(|ident| format_ident!("{}{}", ident, i));
to_ident_tuple_enumerated(idents, input.idents.len())
to_ident_tuple_enumerated(idents, i)
})
.collect::<Vec<_>>();
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = choose_ident_tuples_enumerated(&input, &ident_tuples, i);
let attrs = if input.fake_variadic {
fake_variadic_attrs(len, i)
} else {
TokenStream2::default()
};
let invocations = (input.start..=input.end).map(|n| {
let ident_tuples = choose_ident_tuples_enumerated(&input, &ident_tuples, n);
let attrs = attrs(&input, n);
quote! {
#macro_ident!(#attrs #ident_tuples);
}
Expand Down Expand Up @@ -392,15 +391,11 @@ pub fn all_tuples_with_size(input: TokenStream) -> TokenStream {
})
.collect::<Vec<_>>();
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = choose_ident_tuples(&input, &ident_tuples, i);
let attrs = if input.fake_variadic {
fake_variadic_attrs(len, i)
} else {
TokenStream2::default()
};
let invocations = (input.start..=input.end).map(|n| {
let ident_tuples = choose_ident_tuples(&input, &ident_tuples, n);
let attrs = attrs(&input, n);
quote! {
#macro_ident!(#i, #attrs #ident_tuples);
#macro_ident!(#n, #attrs #ident_tuples);
}
});
TokenStream::from(quote! {
Expand Down Expand Up @@ -436,30 +431,30 @@ fn parse_fake_variadic_attr(input: ParseStream) -> Result<bool> {
))
}

fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: usize) -> TokenStream2 {
fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], n: usize) -> TokenStream2 {
// `rustdoc` uses the first ident to generate nice
// idents with subscript numbers e.g. (F₁, F₂, …, Fₙ).
// We don't want two numbers, so we use the
// original, unnumbered idents for this case.
if input.fake_variadic && i == 1 {
if input.fake_variadic && n == 1 {
let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len());
quote! { #ident_tuple }
} else {
let ident_tuples = &ident_tuples[..i];
let ident_tuples = &ident_tuples[..n];
quote! { #(#ident_tuples),* }
}
}

fn choose_ident_tuples_enumerated(
input: &AllTuples,
ident_tuples: &[TokenStream2],
i: usize,
n: usize,
) -> TokenStream2 {
if input.fake_variadic && i == 1 {
if input.fake_variadic && n == 1 {
let ident_tuple = to_ident_tuple_enumerated(input.idents.iter().cloned(), 0);
quote! { #ident_tuple }
} else {
let ident_tuples = &ident_tuples[..i];
let ident_tuples = &ident_tuples[..n];
quote! { #(#ident_tuples),* }
}
}
Expand All @@ -478,23 +473,33 @@ fn to_ident_tuple_enumerated(idents: impl Iterator<Item = Ident>, idx: usize) ->
quote! { (#idx, #(#idents),*) }
}

fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 {
let cfg = quote! { any(docsrs, docsrs_dep) };
match i {
/// n: number of elements
fn attrs(input: &AllTuples, n: usize) -> TokenStream2 {
if !input.fake_variadic {
return TokenStream2::default();
}
match n {
// An empty tuple (i.e. the unit type) is still documented separately,
// so no `#[doc(hidden)]` here.
0 => TokenStream2::default(),
// The `#[doc(fake_variadic)]` attr has to be on the first impl block.
1 => {
let doc = LitStr::new(
&format!("This trait is implemented for tuples up to {len} items long."),
Span2::call_site(),
);
quote! {
#[cfg_attr(#cfg, doc(fake_variadic))]
#[cfg_attr(#cfg, doc = #doc)]
n => {
let cfg = quote! { any(docsrs, docsrs_dep) };
// The `#[doc(fake_variadic)]` attr has to be on the first impl block.
if n == 1 {
let doc = LitStr::new(
&format!(
"This trait is implemented for tuples down to {} up to {} items long.",
input.start, input.end
),
Span2::call_site(),
);
quote! {
#[cfg_attr(#cfg, doc(fake_variadic))]
#[cfg_attr(#cfg, doc = #doc)]
}
} else {
quote! { #[cfg_attr(#cfg, doc(hidden))] }
}
}
_ => quote! { #[cfg_attr(#cfg, doc(hidden))] },
}
}
Loading