diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..26e1f1f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: Continuous integration + +on: + push: + pull_request: + branches: [main] + +jobs: + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Test + run: cargo test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Format + run: cargo fmt --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Clippy + run: cargo clippy -- -D warnings diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f6577 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6aaa6e7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "variadics_please" +version = "1.0.0" +edition = "2021" +description = "Implement things as if rust had variadics" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/variadics_please" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "variadics", "docs"] +rust-version = "1.65.0" + +[features] +default = ["alloc"] +alloc = [] + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0" +quote = "1.0" +proc-macro2 = "1.0" + +[lints.clippy] +doc_markdown = "warn" +manual_let_else = "warn" +match_same_arms = "warn" +redundant_closure_for_method_calls = "warn" +redundant_else = "warn" +semicolon_if_nothing_returned = "warn" +type_complexity = "allow" +undocumented_unsafe_blocks = "warn" +unwrap_or_default = "warn" + +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +ref_as_ptr = "warn" + +# see: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366966219 +too_long_first_doc_paragraph = "allow" + +std_instead_of_core = "warn" +std_instead_of_alloc = "warn" +alloc_instead_of_core = "warn" + +[lints.rust] +missing_docs = "warn" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } +unsafe_code = "deny" +unsafe_op_in_unsafe_fn = "warn" +unused_qualifications = "warn" + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0790368 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +
+ +# `variadics_please` + +
+ +![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg) +[![Crates.io](https://img.shields.io/crates/v/variadics_please.svg)](https://crates.io/crates/variadics_please) +[![Downloads](https://img.shields.io/crates/d/variadics_please.svg)](https://crates.io/crates/variadics_please) +[![Docs](https://docs.rs/variadics_please/badge.svg)](https://docs.rs/variadics_please/latest/variadics_please/) + +Provides a macro for implementing traits on variadic types. + +## Contributing + +This crate is maintained by the Bevy organization, and is intended to be tiny, stable, zero-dependency, and broadly useful. +[Issues](https://github.com/bevyengine/variadics_please/issues) and [pull requests](https://github.com/bevyengine/variadics_please/pulls) are genuinely welcome! diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..eb817f7 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,6 @@ +# `variadics_please` Release Notes + +## Version 1.0.0 + +- initial release + - code was taken directly from `bevy_utils 0.15-rc2`, under a MIT + Apache dual license diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..11fa61c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,10 @@ +use_field_init_shorthand = true +newline_style = "Unix" + +# The following lines may be uncommented on nightly Rust. +# Once these features have stabilized, they should be added to the always-enabled options above. +# unstable_features = true +# imports_granularity = "Crate" +# wrap_comments = true +# comment_width = 100 +# normalize_comments = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d6b9cb1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,402 @@ +// FIXME(15321): solve CI failures, then replace with `#![expect()]`. +#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +use proc_macro::TokenStream; +use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + spanned::Spanned as _, + token::Comma, + Attribute, Error, Ident, LitInt, LitStr, Result, +}; +struct AllTuples { + fake_variadic: bool, + macro_ident: Ident, + start: usize, + end: usize, + idents: Vec, +} + +impl Parse for AllTuples { + fn parse(input: ParseStream) -> Result { + let fake_variadic = input.call(parse_fake_variadic_attr)?; + let macro_ident = input.parse::()?; + input.parse::()?; + let start = input.parse::()?.base10_parse()?; + input.parse::()?; + let end = input.parse::()?.base10_parse()?; + input.parse::()?; + let mut idents = vec![input.parse::()?]; + while input.parse::().is_ok() { + idents.push(input.parse::()?); + } + + if start > 1 && fake_variadic { + return Err(Error::new( + input.span(), + "#[doc(fake_variadic)] only works when the tuple with length one is included", + )); + } + + Ok(AllTuples { + fake_variadic, + macro_ident, + start, + end, + idents, + }) + } +} + +/// Helper macro to generate tuple pyramids. Useful to generate scaffolding to work around Rust +/// lacking variadics. Invoking `all_tuples!(impl_foo, start, end, P, Q, ..)` +/// invokes `impl_foo` providing ident tuples through arity `start..=end`. +/// If you require the length of the tuple, see [`all_tuples_with_size!`]. +/// +/// # Examples +/// +/// ## Single parameter +/// +/// ``` +/// # use core::marker::PhantomData; +/// # use bevy_utils_proc_macros::all_tuples; +/// # +/// struct Foo { +/// // .. +/// # _phantom: PhantomData +/// } +/// +/// trait WrappedInFoo { +/// type Tup; +/// } +/// +/// macro_rules! impl_wrapped_in_foo { +/// ($($T:ident),*) => { +/// impl<$($T),*> WrappedInFoo for ($($T,)*) { +/// type Tup = ($(Foo<$T>,)*); +/// } +/// }; +/// } +/// +/// all_tuples!(impl_wrapped_in_foo, 0, 15, T); +/// // impl_wrapped_in_foo!(); +/// // impl_wrapped_in_foo!(T0); +/// // impl_wrapped_in_foo!(T0, T1); +/// // .. +/// // impl_wrapped_in_foo!(T0 .. T14); +/// ``` +/// +/// # Multiple parameters +/// +/// ``` +/// # use bevy_utils_proc_macros::all_tuples; +/// # +/// trait Append { +/// type Out; +/// fn append(tup: Self, item: Item) -> Self::Out; +/// } +/// +/// impl Append for () { +/// type Out = (Item,); +/// fn append(_: Self, item: Item) -> Self::Out { +/// (item,) +/// } +/// } +/// +/// macro_rules! impl_append { +/// ($(($P:ident, $p:ident)),*) => { +/// impl<$($P),*> Append for ($($P,)*) { +/// type Out = ($($P),*, Item); +/// fn append(($($p,)*): Self, item: Item) -> Self::Out { +/// ($($p),*, item) +/// } +/// } +/// } +/// } +/// +/// 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)); +/// ``` +/// +/// **`#[doc(fake_variadic)]`** +/// +/// To improve the readability of your docs when implementing a trait for +/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker. +/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`. +/// +/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro +/// is that you have to accept attributes using `$(#[$meta:meta])*`. +/// +/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default. +/// Add the following to your lib.rs if not already present: +/// +/// ``` +/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +/// #![allow(internal_features)] +/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] +/// ``` +/// +/// ``` +/// # use bevy_utils_proc_macros::all_tuples; +/// # +/// trait Variadic {} +/// +/// impl Variadic for () {} +/// +/// macro_rules! impl_variadic { +/// ($(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { +/// $(#[$meta])* +/// impl<$($P),*> Variadic for ($($P,)*) {} +/// } +/// } +/// +/// all_tuples!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p); +/// ``` +#[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 mut ident_tuples = Vec::with_capacity(len); + for i in 0..=len { + let idents = input + .idents + .iter() + .map(|ident| format_ident!("{}{}", ident, i)); + ident_tuples.push(to_ident_tuple(idents, input.idents.len())); + } + + 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() + }; + quote! { + #macro_ident!(#attrs #ident_tuples); + } + }); + TokenStream::from(quote! { + #( + #invocations + )* + }) +} + +/// Helper macro to generate tuple pyramids with their length. Useful to generate scaffolding to +/// work around Rust lacking variadics. Invoking `all_tuples_with_size!(impl_foo, start, end, P, Q, ..)` +/// invokes `impl_foo` providing ident tuples through arity `start..=end` preceded by their length. +/// If you don't require the length of the tuple, see [`all_tuples!`]. +/// +/// # Examples +/// +/// ## Single parameter +/// +/// ``` +/// # use core::marker::PhantomData; +/// # use bevy_utils_proc_macros::all_tuples_with_size; +/// # +/// struct Foo { +/// // .. +/// # _phantom: PhantomData +/// } +/// +/// trait WrappedInFoo { +/// type Tup; +/// const LENGTH: usize; +/// } +/// +/// macro_rules! impl_wrapped_in_foo { +/// ($N:expr, $($T:ident),*) => { +/// impl<$($T),*> WrappedInFoo for ($($T,)*) { +/// type Tup = ($(Foo<$T>,)*); +/// const LENGTH: usize = $N; +/// } +/// }; +/// } +/// +/// all_tuples_with_size!(impl_wrapped_in_foo, 0, 15, T); +/// // impl_wrapped_in_foo!(0); +/// // impl_wrapped_in_foo!(1, T0); +/// // impl_wrapped_in_foo!(2, T0, T1); +/// // .. +/// // impl_wrapped_in_foo!(15, T0 .. T14); +/// ``` +/// +/// ## Multiple parameters +/// +/// ``` +/// # use bevy_utils_proc_macros::all_tuples_with_size; +/// # +/// trait Append { +/// type Out; +/// fn append(tup: Self, item: Item) -> Self::Out; +/// } +/// +/// impl Append for () { +/// type Out = (Item,); +/// fn append(_: Self, item: Item) -> Self::Out { +/// (item,) +/// } +/// } +/// +/// macro_rules! impl_append { +/// ($N:expr, $(($P:ident, $p:ident)),*) => { +/// impl<$($P),*> Append for ($($P,)*) { +/// type Out = ($($P),*, Item); +/// fn append(($($p,)*): Self, item: Item) -> Self::Out { +/// ($($p),*, item) +/// } +/// } +/// } +/// } +/// +/// all_tuples_with_size!(impl_append, 1, 15, P, p); +/// // impl_append!(1, (P0, p0)); +/// // impl_append!(2, (P0, p0), (P1, p1)); +/// // impl_append!(3, (P0, p0), (P1, p1), (P2, p2)); +/// // .. +/// // impl_append!(15, (P0, p0) .. (P14, p14)); +/// ``` +/// +/// **`#[doc(fake_variadic)]`** +/// +/// To improve the readability of your docs when implementing a trait for +/// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker. +/// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`. +/// +/// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro +/// is that you have to accept attributes using `$(#[$meta:meta])*`. +/// +/// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default. +/// Add the following to your lib.rs if not already present: +/// +/// ``` +/// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` +/// #![allow(internal_features)] +/// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] +/// ``` +/// +/// ``` +/// # use bevy_utils_proc_macros::all_tuples_with_size; +/// # +/// trait Variadic {} +/// +/// impl Variadic for () {} +/// +/// macro_rules! impl_variadic { +/// ($N:expr, $(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { +/// $(#[$meta])* +/// impl<$($P),*> Variadic for ($($P,)*) {} +/// } +/// } +/// +/// all_tuples_with_size!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p); +/// ``` +#[proc_macro] +pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as AllTuples); + let len = 1 + input.end - input.start; + let mut ident_tuples = Vec::with_capacity(len); + for i in 0..=len { + let idents = input + .idents + .iter() + .map(|ident| format_ident!("{}{}", ident, i)); + ident_tuples.push(to_ident_tuple(idents, input.idents.len())); + } + 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() + }; + quote! { + #macro_ident!(#i, #attrs #ident_tuples); + } + }); + TokenStream::from(quote! { + #( + #invocations + )* + }) +} + +/// Parses the attribute `#[doc(fake_variadic)]` +fn parse_fake_variadic_attr(input: ParseStream) -> Result { + let attribute = match input.call(Attribute::parse_outer)? { + attributes if attributes.is_empty() => return Ok(false), + attributes if attributes.len() == 1 => attributes[0].clone(), + attributes => { + return Err(Error::new( + input.span(), + format!("Expected exactly one attribute, got {}", attributes.len()), + )) + } + }; + + if attribute.path().is_ident("doc") { + let nested = attribute.parse_args::()?; + if nested == "fake_variadic" { + return Ok(true); + } + } + + Err(Error::new( + attribute.meta.span(), + "Unexpected attribute".to_string(), + )) +} + +fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], i: 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 { + let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len()); + quote! { #ident_tuple } + } else { + let ident_tuples = &ident_tuples[..i]; + quote! { #(#ident_tuples),* } + } +} + +fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStream2 { + if len < 2 { + quote! { #(#idents)* } + } else { + quote! { (#(#idents),*) } + } +} + +fn fake_variadic_attrs(len: usize, i: usize) -> TokenStream2 { + let cfg = quote! { any(docsrs, docsrs_dep) }; + match i { + // 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)] + } + } + _ => quote! { #[cfg_attr(#cfg, doc(hidden))] }, + } +}