├── .github ├── example-run │ └── all_tuples.ron ├── FUNDING.yml ├── linters │ └── .markdown-lint.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── docs_improvement.md │ ├── feature_request.md │ ├── bug_report.md │ └── performance_regression.md ├── workflows │ ├── action-on-PR-labeled.yml │ ├── welcome.yml │ ├── dependencies.yml │ ├── release.yml │ ├── post-release.yml │ ├── weekly.yml │ ├── ci-comment-failures.yml │ ├── validation-jobs.yml │ └── ci.yml └── pull_request_template.md ├── errors ├── V0001.md ├── README.md ├── src │ └── lib.rs └── Cargo.toml ├── tools ├── compile_fail_utils │ ├── tests │ │ ├── example_tests │ │ │ ├── import.rs │ │ │ ├── pass_test.rs │ │ │ ├── pass_test.stderr │ │ │ ├── basic_test.rs │ │ │ ├── import.stderr │ │ │ └── basic_test.stderr │ │ └── example.rs │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs └── publish.sh ├── .gitignore ├── RELEASES.md ├── rustfmt.toml ├── benches ├── Cargo.toml ├── benches │ └── dummy.rs └── README.md ├── .gitattributes ├── documentation ├── debugging.md └── linters.md ├── typos.toml ├── README.md ├── examples └── demonstrations │ └── all_tuples.rs ├── LICENSE-MIT ├── deny.toml ├── Cargo.toml ├── LICENSE-APACHE └── src └── lib.rs /.github/example-run/all_tuples.ron: -------------------------------------------------------------------------------- 1 | ( 2 | ) 3 | -------------------------------------------------------------------------------- /errors/V0001.md: -------------------------------------------------------------------------------- 1 | # V0001 2 | 3 | Placeholder 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://bevyengine.org/donate/ 2 | -------------------------------------------------------------------------------- /errors/README.md: -------------------------------------------------------------------------------- 1 | # Error Codes 2 | 3 | This crate lists and tests explanations and examples of the error codes. 4 | -------------------------------------------------------------------------------- /errors/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Definitions of `variadics_please`'s error codes that might occur at runtime. 2 | 3 | #[doc = include_str!("../V0001.md")] 4 | pub struct V0001; 5 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, 3 | "no-inline-html": { 4 | "allowed_elements": [ 5 | "details", 6 | "summary" 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/import.rs: -------------------------------------------------------------------------------- 1 | // You can import anything defined in the dependencies table of the crate. 2 | use ui_test::Config; 3 | 4 | fn wrong_type() { 5 | let _ = Config::this_function_does_not_exist(); 6 | //~^ E0599 7 | } 8 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/pass_test.rs: -------------------------------------------------------------------------------- 1 | //@check-pass 2 | 3 | // This code is expected to compile correctly. 4 | fn correct_borrowing() { 5 | let x = String::new(); 6 | let y = &x; 7 | 8 | println!("{x}"); 9 | println!("{y}"); 10 | } 11 | -------------------------------------------------------------------------------- /errors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "errors" 3 | edition = "2021" 4 | description = "Documentation and tests for error codes" 5 | publish = false 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | variadics_please = { path = ".." } 10 | 11 | [lints] 12 | workspace = true 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust build artifacts 2 | /target 3 | crates/*/target 4 | **/*.rs.bk 5 | /benches/target 6 | /tools/compile_fail_utils/target 7 | 8 | # Cargo 9 | Cargo.lock 10 | .cargo/config 11 | .cargo/config.toml 12 | 13 | # IDE files 14 | /.idea 15 | /.vscode 16 | .zed 17 | dxcompiler.dll 18 | dxil.dll 19 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # `variadics_please` Release Notes 2 | 3 | ## Version 1.1 4 | 5 | - added `all_tuples_enumerated`, which provides the index of each item in the tuple 6 | 7 | ## Version 1.0.0 8 | 9 | - initial release 10 | - code was taken directly from `bevy_utils 0.15-rc2`, under a MIT + Apache dual license 11 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/pass_test.stderr: -------------------------------------------------------------------------------- 1 | warning: function `correct_borrowing` is never used 2 | --> tests/example_tests/pass_test.rs:4:4 3 | | 4 | 4 | fn correct_borrowing() { 5 | | ^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: `#[warn(dead_code)]` on by default 8 | 9 | warning: 1 warning emitted 10 | 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | labels: 8 | - "C-Dependencies" 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: weekly 13 | labels: 14 | - "C-Dependencies" 15 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_field_init_shorthand = true 2 | newline_style = "Unix" 3 | 4 | # The following lines may be uncommented on nightly Rust. 5 | # Once these features have stabilized, they should be added to the always-enabled options above. 6 | # unstable_features = true 7 | # imports_granularity = "Crate" 8 | # wrap_comments = true 9 | # comment_width = 100 10 | # normalize_comments = true 11 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benches" 3 | edition = "2021" 4 | description = "Benchmarks that test performance" 5 | publish = false 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dev-dependencies] 9 | rand = "0.8" 10 | rand_chacha = "0.3" 11 | criterion = { version = "0.3", features = ["html_reports"] } 12 | 13 | [[bench]] 14 | name = "dummy" 15 | path = "benches/dummy.rs" 16 | harness = false 17 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compile_fail_utils" 3 | edition = "2021" 4 | description = "Utilities for compile tests" 5 | homepage = "https://github.com/bevyengine/variadics_please" 6 | repository = "https://github.com/bevyengine/variadics_please" 7 | license = "MIT OR Apache-2.0" 8 | publish = false 9 | 10 | [dependencies] 11 | ui_test = "0.23.0" 12 | 13 | [[test]] 14 | name = "example" 15 | harness = false 16 | -------------------------------------------------------------------------------- /benches/benches/dummy.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | fn foo(c: &mut Criterion) { 4 | c.bench_function("foo_1000", |b| { 5 | b.iter(|| { 6 | (0..1000).for_each(|test_case| { 7 | black_box(black_box(test_case * 2) * black_box(test_case * 2)); 8 | }) 9 | }); 10 | }); 11 | } 12 | 13 | criterion_group!(benches, foo); 14 | criterion_main!(benches); 15 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example.rs: -------------------------------------------------------------------------------- 1 | fn main() -> compile_test_utils::ui_test::Result<()> { 2 | // Run all tests in the tests/example_tests folder. 3 | // If we had more tests we could either call this function 4 | // on every single one or use test_multiple and past it an array 5 | // of paths. 6 | // 7 | // Don't forget that when running tests the working directory 8 | // is set to the crate root. 9 | compile_test_utils::test("tests/example_tests") 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Question 3 | url: https://github.com/bevyengine/variadics_please/discussions 4 | about: Questions about how to use or contribute belong in Github Discussions. 5 | You can use the search to check if someone already answered your question! 6 | 7 | - name: Community 8 | url: https://discord.gg/bevy 9 | about: If you'd like to converse with members of the `bevyengine` community, join us on discord. There is a channel for development of this crate. 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From: https://docs.github.com/en/github/getting-started-with-github/configuring-git-to-handle-line-endings 2 | # Set the default behavior, in case people don't have core.autocrlf set. 3 | * text=auto 4 | 5 | # Explicitly declare text files you want to always be normalized and converted 6 | # to native line endings on checkout. 7 | .gitattributes text eol=lf 8 | .gitignore text eol=lf 9 | *.json text eol=lf 10 | *.md text eol=lf 11 | *.rs text eol=lf 12 | *.sh text eol=lf 13 | *.toml text eol=lf 14 | *.yml text eol=lf 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs_improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation Improvement 3 | about: Help us write better documentation! 4 | title: '' 5 | labels: C-Documentation, S-Needs-Triage 6 | assignees: '' 7 | --- 8 | 9 | ## How can the documentation be improved? 10 | 11 | Provide a link to the documentation and describe how it could be improved. In what ways is it incomplete, incorrect, or misleading? 12 | 13 | If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and create a pull request instead. 14 | -------------------------------------------------------------------------------- /documentation/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | ## Macro Debugging 4 | 5 | - Print the final output of a macro using `cargo rustc --profile=check -- -Zunstable-options --pretty=expanded` 6 | - Alternatively you could install and use [cargo expand](https://github.com/dtolnay/cargo-expand) which adds syntax highlighting to the terminal output. 7 | - Additionally get pager by piping to `less` ( on Unix systems ): `cargo expand --color always | less -R` 8 | - Print output during macro compilation using `eprintln!("hi");` 9 | 10 | ## Preview Doc 11 | 12 | ```sh 13 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --example all_tuples --no-deps --open 14 | ``` 15 | -------------------------------------------------------------------------------- /tools/publish.sh: -------------------------------------------------------------------------------- 1 | # if crate A depends on crate B, B must come before A in this list 2 | crates=( 3 | ) 4 | 5 | if [ -n "$(git status --porcelain)" ]; then 6 | echo "You have local changes!" 7 | exit 1 8 | fi 9 | 10 | pushd crates 11 | 12 | for crate in "${crates[@]}"; do 13 | echo "Publishing ${crate}" 14 | cp ../LICENSE-MIT "$crate" 15 | cp ../LICENSE-APACHE "$crate" 16 | pushd "$crate" 17 | git add LICENSE-MIT LICENSE-APACHE 18 | cargo publish --no-verify --allow-dirty 19 | popd 20 | sleep 20 21 | done 22 | 23 | popd 24 | 25 | echo "Publishing root crate" 26 | cargo publish --allow-dirty 27 | 28 | echo "Cleaning local state" 29 | git reset HEAD --hard 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Propose a new feature! 4 | title: '' 5 | labels: C-Feature, S-Needs-Triage 6 | assignees: '' 7 | --- 8 | 9 | ## What problem does this solve or what need does it fill? 10 | 11 | A description of why this particular feature should be added. 12 | 13 | ## What solution would you like? 14 | 15 | The solution you propose for the problem presented. 16 | 17 | ## What alternative(s) have you considered? 18 | 19 | Other solutions to solve and/or work around the problem presented. 20 | 21 | ## Additional context 22 | 23 | Any other information you would like to add such as related previous work, 24 | screenshots, benchmarks, etc. 25 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | "*.patch", # Automatically generated files that should not be manually modified. 4 | "*.bin", # Binary files 5 | ".git/", # Version control files 6 | ] 7 | ignore-hidden = false 8 | 9 | # Corrections take the form of a key/value pair. The key is the incorrect word 10 | # and the value is the correct word. If the key and value are the same, the 11 | # word is treated as always correct. If the value is an empty string, the word 12 | # is treated as always incorrect. 13 | 14 | # Match Whole Word - Case Sensitive 15 | [default.extend-identifiers] 16 | 17 | # Match Inside a Word - Case Insensitive 18 | [default.extend-words] 19 | 20 | [default] 21 | locale = "en-us" 22 | extend-ignore-identifiers-re = [] 23 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/basic_test.rs: -------------------------------------------------------------------------------- 1 | // Compiler warnings also need to be annotated. 2 | // We don't want to annotate all the unused variables, so let's instruct the compiler to ignore them. 3 | #![allow(unused_variables)] 4 | 5 | fn bad_moves() { 6 | let x = String::new(); 7 | // Help diagnostics need to be annotated 8 | let y = x; 9 | //~^ HELP: consider cloning 10 | 11 | // We expect a failure on this line 12 | println!("{x}"); //~ ERROR: borrow 13 | 14 | 15 | let x = String::new(); 16 | // We expect the help message to mention cloning. 17 | //~v HELP: consider cloning 18 | let y = x; 19 | 20 | // Check error message using a regex 21 | println!("{x}"); 22 | //~^ ERROR: /(move)|(borrow)/ 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | # `variadics_please` 5 | 6 |
7 | 8 | ![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg) 9 | [![Crates.io](https://img.shields.io/crates/v/variadics_please.svg)](https://crates.io/crates/variadics_please) 10 | [![Downloads](https://img.shields.io/crates/d/variadics_please.svg)](https://crates.io/crates/variadics_please) 11 | [![Docs](https://docs.rs/variadics_please/badge.svg)](https://docs.rs/variadics_please/latest/variadics_please/) 12 | 13 | Provides macros for implementing traits on variadic types. 14 | 15 | ## Contributing 16 | 17 | This crate is maintained by the Bevy organization, and is intended to be tiny, stable, zero-dependency, and broadly useful. 18 | [Issues](https://github.com/bevyengine/variadics_please/issues) and [pull requests](https://github.com/bevyengine/variadics_please/pulls) are genuinely welcome! 19 | -------------------------------------------------------------------------------- /examples/demonstrations/all_tuples.rs: -------------------------------------------------------------------------------- 1 | //! An example of using `all_tuples!` 2 | 3 | #![cfg_attr(any(docsrs), feature(rustdoc_internals))] 4 | 5 | use variadics_please::all_tuples; 6 | 7 | fn main() {} 8 | 9 | /// For demonstration 10 | pub trait Foo { 11 | /// For demonstration 12 | const FOO_HARDER: bool; 13 | /// For demonstration 14 | fn foo() -> bool; 15 | } 16 | 17 | macro_rules! impl_tuple_foo { 18 | ($(#[$meta:meta])* $($name: ident),*) => { 19 | #[allow(unused_variables)] 20 | #[allow(non_snake_case)] 21 | #[allow(clippy::unused_unit)] 22 | $(#[$meta])* 23 | impl<$($name: Foo),*> Foo for ($($name,)*) { 24 | const FOO_HARDER: bool = true $(&& $name::FOO_HARDER)*; 25 | 26 | fn foo() -> bool { 27 | true 28 | } 29 | } 30 | }; 31 | } 32 | 33 | all_tuples!( 34 | #[doc(fake_variadic)] 35 | impl_tuple_foo, 36 | 0, 37 | 15, 38 | F 39 | ); 40 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | This is a crate with a collection of benchmarks, separate from the rest of the crates. 4 | 5 | ## Running the benchmarks 6 | 7 | 1. Setup everything you need to develop in rust. 8 | 2. `cd` into the `benches` directory (where this README is located). 9 | 10 | ```sh 11 | variadics_please $ cd benches 12 | ``` 13 | 14 | 3. Run the benchmarks with cargo (This will take a while) 15 | 16 | ```sh 17 | variadics_please/benches $ cargo bench 18 | ``` 19 | 20 | If you'd like to only compile the benchmarks (without running them), you can do that like this: 21 | 22 | ```sh 23 | variadics_please/benches $ cargo bench --no-run 24 | ``` 25 | 26 | ## Criterion 27 | 28 | The benchmarks use [Criterion](https://crates.io/crates/criterion). If you want to learn more about using Criterion for comparing performance against a baseline or generating detailed reports, you can read the [Criterion.rs documentation](https://bheisler.github.io/criterion.rs/book/criterion_rs.html). 29 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/import.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: no function or associated item named `this_function_does_not_exist` found for struct `Config` in the current scope 2 | --> tests/example_tests/import.rs:5:21 3 | | 4 | 5 | let _ = Config::this_function_does_not_exist(); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Config` 6 | | 7 | note: if you're trying to build a new `Config` consider using one of the following associated functions: 8 | Config::rustc 9 | Config::cargo 10 | --> $RUSTUP_HOME/.cargo/git/checkouts/ui_test-2b82183a391bb05c/680bb08/src/config.rs:63:5 11 | | 12 | 63 | pub fn rustc(root_dir: impl Into) -> Self { 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | ... 15 | 108 | pub fn cargo(root_dir: impl Into) -> Self { 16 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 17 | 18 | error: aborting due to 1 previous error 19 | 20 | For more information about this error, try `rustc --explain E0599`. 21 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/action-on-PR-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Action on PR labeled 2 | 3 | # This workflow has write permissions on the repo 4 | # It must not checkout a PR and run untrusted code 5 | 6 | on: 7 | pull_request_target: 8 | types: 9 | - labeled 10 | 11 | permissions: 12 | pull-requests: 'write' 13 | 14 | jobs: 15 | comment-on-breaking-change-label: 16 | runs-on: ubuntu-latest 17 | if: github.event.label.name == 'M-Needs-Migration-Guide' && !contains(github.event.pull_request.body, '## Migration Guide') 18 | steps: 19 | - uses: actions/github-script@v8 20 | with: 21 | script: | 22 | await github.rest.issues.createComment({ 23 | issue_number: context.issue.number, 24 | owner: context.repo.owner, 25 | repo: context.repo.repo, 26 | body: `It looks like your PR is a breaking change, but you didn't provide a migration guide. 27 | 28 | Could you add some context on what users should update when this change get released? 29 | Putting it after a \`## Migration Guide\` will help it get automatically picked up by our tooling.` 30 | }) 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug to help us improve! 4 | title: '' 5 | labels: C-Bug, S-Needs-Triage 6 | assignees: '' 7 | --- 8 | 9 | ## `variadics_please` version 10 | 11 | The release number or commit hash of the version you're using. 12 | 13 | ## Relevant system information 14 | 15 | This section is optional. Remove it if you know that the problem is not platform dependent. 16 | 17 | Rust version you're using: (`cargo --version`) 18 | 19 | ```text 20 | 21 | ``` 22 | 23 | > Notes: 24 | > 25 | > - Pay attention to the msrv (minimum supported rust version) of `variadics_please`. 26 | > - `nightly` should work, but sometimes there are regressions: please let us know! 27 | 28 | Operating system, including version: 29 | 30 | ```text 31 | 32 | ``` 33 | 34 | ## What you did 35 | 36 | Describe how you arrived at the problem. If you can, consider providing a code snippet or link. 37 | 38 | ## What went wrong 39 | 40 | If it's not clear, break this out into: 41 | 42 | - what were you expecting? 43 | - what actually happened? 44 | 45 | ## Additional information 46 | 47 | Other information that can be used to further reproduce or isolate the problem. 48 | This commonly includes: 49 | 50 | - screenshots 51 | - logs 52 | - theories about what might be going wrong 53 | - workarounds that you used 54 | - links to related bugs, PRs or discussions 55 | -------------------------------------------------------------------------------- /.github/workflows/welcome.yml: -------------------------------------------------------------------------------- 1 | name: Welcome new contributors 2 | 3 | # This workflow has write permissions on the repo 4 | # It must not checkout a PR and run untrusted code 5 | 6 | on: 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | jobs: 12 | welcome: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | steps: 17 | - uses: actions/github-script@v8 18 | with: 19 | script: | 20 | // Get a list of all issues created by the PR opener 21 | // See: https://octokit.github.io/rest.js/#pagination 22 | const creator = context.payload.sender.login 23 | const opts = github.rest.issues.listForRepo.endpoint.merge({ 24 | ...context.issue, 25 | creator, 26 | state: 'all' 27 | }) 28 | const issues = await github.paginate(opts) 29 | 30 | for (const issue of issues) { 31 | if (issue.number === context.issue.number) { 32 | continue 33 | } 34 | 35 | if (issue.pull_request) { 36 | return // Creator is already a contributor. 37 | } 38 | } 39 | 40 | await github.rest.issues.createComment({ 41 | issue_number: context.issue.number, 42 | owner: context.repo.owner, 43 | repo: context.repo.repo, 44 | body: `**Welcome**, new contributor! 45 | 46 | Please make sure you've read our [contributing guide](https://bevyengine.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨` 47 | }) 48 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/tests/example_tests/basic_test.stderr: -------------------------------------------------------------------------------- 1 | error[E0382]: borrow of moved value: `x` 2 | --> tests/example_tests/basic_test.rs:13:15 3 | | 4 | 7 | let x = String::new(); 5 | | - move occurs because `x` has type `String`, which does not implement the `Copy` trait 6 | 8 | // Help diagnostics need to be annotated 7 | 9 | let y = x; 8 | | - value moved here 9 | ... 10 | 13 | println!("{x}"); 11 | | ^^^ value borrowed here after move 12 | | 13 | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | help: consider cloning the value if the performance cost is acceptable 15 | | 16 | 9 | let y = x.clone(); 17 | | ++++++++ 18 | 19 | error[E0382]: borrow of moved value: `x` 20 | --> tests/example_tests/basic_test.rs:22:15 21 | | 22 | 16 | let x = String::new(); 23 | | - move occurs because `x` has type `String`, which does not implement the `Copy` trait 24 | ... 25 | 19 | let y = x; 26 | | - value moved here 27 | ... 28 | 22 | println!("{x}"); 29 | | ^^^ value borrowed here after move 30 | | 31 | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | help: consider cloning the value if the performance cost is acceptable 33 | | 34 | 19 | let y = x.clone(); 35 | | ++++++++ 36 | 37 | error: aborting due to 2 previous errors 38 | 39 | For more information about this error, try `rustc --explain E0382`. 40 | -------------------------------------------------------------------------------- /.github/workflows/dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Dependencies 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**/Cargo.toml' 7 | - 'deny.toml' 8 | push: 9 | paths: 10 | - '**/Cargo.toml' 11 | - 'deny.toml' 12 | branches: 13 | - main 14 | 15 | concurrency: 16 | group: ${{github.workflow}}-${{github.ref}} 17 | cancel-in-progress: ${{github.event_name == 'pull_request'}} 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | 22 | jobs: 23 | check-advisories: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v6 27 | - uses: dtolnay/rust-toolchain@stable 28 | - name: Install cargo-deny 29 | run: cargo install cargo-deny 30 | - name: Check for security advisories and unmaintained crates 31 | run: cargo deny check advisories 32 | 33 | check-bans: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v6 37 | - uses: dtolnay/rust-toolchain@stable 38 | - name: Install cargo-deny 39 | run: cargo install cargo-deny 40 | - name: Check for banned and duplicated dependencies 41 | run: cargo deny check bans 42 | 43 | check-licenses: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v6 47 | - uses: dtolnay/rust-toolchain@stable 48 | - name: Install cargo-deny 49 | run: cargo install cargo-deny 50 | - name: Check for unauthorized licenses 51 | run: cargo deny check licenses 52 | 53 | check-sources: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v6 57 | - uses: dtolnay/rust-toolchain@stable 58 | - name: Install cargo-deny 59 | run: cargo install cargo-deny 60 | - name: Checked for unauthorized crate sources 61 | run: cargo deny check sources 62 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Objective 2 | 3 | Describe the objective or issue this PR addresses. 4 | 5 | If you're fixing a specific issue, simply say "Fixes #X". 6 | 7 | ## Solution 8 | 9 | Describe the solution used to achieve the objective above. 10 | 11 | ## Testing 12 | 13 | - Did you test these changes? If so, how? 14 | - Are there any parts that need more testing? 15 | - How can other people (reviewers) test your changes? Is there anything specific they need to know? 16 | - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? 17 | 18 | ## Showcase 19 | 20 | This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section. 21 | 22 | - Help others understand the result of this PR by showcasing your awesome work! 23 | - If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action 24 | - If this PR includes a visual change, consider adding a screenshot, GIF, or video 25 | - If you want, you could even include a before/after comparison! 26 | - If the Migration Guide adequately covers the changes, you can delete this section 27 | 28 | While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases: 29 | 30 | ```rust 31 | println!("My super cool code."); 32 | ``` 33 | 34 | 35 | 36 | ## Migration Guide 37 | 38 | > This section is optional. If there are no breaking changes, you can delete this section. 39 | 40 | - If this PR is a breaking change (relative to the last release), describe how a user might need to migrate their code to support these changes 41 | - Simply adding new functionality is not a breaking change. 42 | - Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change. 43 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | all-features = true 3 | 4 | [advisories] 5 | version = 2 6 | ignore = [] 7 | 8 | [licenses] 9 | version = 2 10 | allow = [ 11 | "0BSD", 12 | "Apache-2.0", 13 | "Apache-2.0 WITH LLVM-exception", 14 | "BSD-2-Clause", 15 | "BSD-3-Clause", 16 | "BSL-1.0", 17 | "CC0-1.0", 18 | "ISC", 19 | "MIT", 20 | "MIT-0", 21 | "Unlicense", 22 | "Zlib", 23 | ] 24 | 25 | exceptions = [ 26 | { name = "unicode-ident", allow = [ 27 | "Unicode-DFS-2016", 28 | ] }, 29 | { name = "symphonia", allow = [ 30 | "MPL-2.0", 31 | ] }, 32 | { name = "symphonia-bundle-flac", allow = [ 33 | "MPL-2.0", 34 | ] }, 35 | { name = "symphonia-bundle-mp3", allow = [ 36 | "MPL-2.0", 37 | ] }, 38 | { name = "symphonia-codec-aac", allow = [ 39 | "MPL-2.0", 40 | ] }, 41 | { name = "symphonia-codec-adpcm", allow = [ 42 | "MPL-2.0", 43 | ] }, 44 | { name = "symphonia-codec-pcm", allow = [ 45 | "MPL-2.0", 46 | ] }, 47 | { name = "symphonia-codec-vorbis", allow = [ 48 | "MPL-2.0", 49 | ] }, 50 | { name = "symphonia-core", allow = [ 51 | "MPL-2.0", 52 | ] }, 53 | { name = "symphonia-format-isomp4", allow = [ 54 | "MPL-2.0", 55 | ] }, 56 | { name = "symphonia-format-riff", allow = [ 57 | "MPL-2.0", 58 | ] }, 59 | { name = "symphonia-metadata", allow = [ 60 | "MPL-2.0", 61 | ] }, 62 | { name = "symphonia-utils-xiph", allow = [ 63 | "MPL-2.0", 64 | ] }, 65 | ] 66 | 67 | [bans] 68 | multiple-versions = "warn" 69 | wildcards = "deny" 70 | # Certain crates that we don't want multiple versions of in the dependency tree 71 | deny = [ 72 | { name = "ahash", deny-multiple-versions = true }, 73 | { name = "android-activity", deny-multiple-versions = true }, 74 | { name = "glam", deny-multiple-versions = true }, 75 | { name = "raw-window-handle", deny-multiple-versions = true }, 76 | ] 77 | 78 | [sources] 79 | unknown-registry = "deny" 80 | unknown-git = "deny" 81 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 82 | allow-git = [] 83 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | # how to trigger: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow 4 | on: 5 | workflow_dispatch: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | ci: 12 | if: github.repository == 'bevyengine/variadics_please' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | 17 | - name: Install cargo-release 18 | run: cargo install cargo-release 19 | 20 | - name: Setup release 21 | run: | 22 | # Set the commit author to the github-actions bot. See discussion here for more information: 23 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212 24 | # https://github.community/t/github-actions-bot-email-address/17204/6 25 | git config user.name 'Auto Releaser' 26 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com' 27 | # release: remove the dev suffix, like going from 0.X.0-dev to 0.X.0 28 | # --workspace: updating all crates in the workspace 29 | # --no-publish: do not publish to crates.io 30 | # --execute: not a dry run 31 | # --no-tag: do not push tag for each new version 32 | # --no-push: do not push the update commits 33 | # --dependent-version upgrade: change 0.X.0-dev in internal dependencies to 0.X.0 34 | # --exclude: ignore those packages 35 | cargo release release \ 36 | --workspace \ 37 | --no-publish \ 38 | --execute \ 39 | --no-tag \ 40 | --no-confirm \ 41 | --no-push \ 42 | --dependent-version upgrade \ 43 | --exclude ci \ 44 | --exclude errors \ 45 | 46 | - name: Create PR 47 | uses: peter-evans/create-pull-request@v8 48 | with: 49 | delete-branch: true 50 | base: "main" 51 | title: "Preparing Next Release" 52 | body: | 53 | Preparing next release. This PR has been auto-generated. 54 | -------------------------------------------------------------------------------- /documentation/linters.md: -------------------------------------------------------------------------------- 1 | # Linters in this Repository 2 | 3 | ## Code Format Linting with [rustfmt](https://github.com/rust-lang/rustfmt) 4 | 5 | Can be automatically validated with [`cargo run -p ci`](../tools/ci) (which also runs other checks). Running this command will actually format the code: 6 | 7 | ```bash 8 | cargo fmt --all 9 | ``` 10 | 11 | ## Code Linting with [Clippy](https://github.com/rust-lang/rust-clippy) 12 | 13 | Can be automatically run with [`cargo run -p ci`](../tools/ci) (which also runs other checks) or manually with this command: 14 | 15 | ```bash 16 | cargo clippy --workspace --all-targets --all-features -- -D warnings 17 | ``` 18 | 19 | Explanation: 20 | 21 | * `-D warnings`: No warnings are allowed in the codebase. 22 | 23 | ## [super-linter](https://github.com/github/super-linter) 24 | 25 | `super-linter` provides easy access to many different Linters. 26 | 27 | ### [markdownlint](https://github.com/DavidAnson/markdownlint) 28 | 29 | `markdownlint` is provided by `super-linter` and is responsible for `.md` files. 30 | Its configuration is saved in the [.markdown-lint.yml](../.github/linters/.markdown-lint.yml) file. 31 | 32 | The provided rules are documented [here](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) and information about setting the config can be seen [here](https://github.com/DavidAnson/markdownlint#optionsconfig). 33 | 34 | #### Using [VS Code markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) 35 | 36 | If you want to use the VS Code Extension with the rules defined in [.markdown-lint.yml](../.github/linters/.markdown-lint.yml), then you need to create a local config file in the root of the project with the configuration below. 37 | Currently, this is not needed as the extension already disables the rule `MD013` by default. 38 | 39 | ```json 40 | { 41 | "extends": ".github/linters/.markdown-lint.yml" 42 | } 43 | ``` 44 | 45 | ### Other Linters provided by [super-linter](https://github.com/github/super-linter) 46 | 47 | All other linters not mentioned in the this file are not activated and can be seen [here](https://github.com/github/super-linter#supported-linters). 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/performance_regression.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Performance Regression 3 | about: Running slowly after upgrading? Report a performance regression. 4 | title: '' 5 | labels: C-Bug, C-Performance, P-Regression, S-Needs-Triage 6 | assignees: '' 7 | --- 8 | 9 | ## `variadics_please` version 10 | 11 | Original: `` 12 | 13 | Current: `` 14 | 15 | ## Relevant system information 16 | 17 | This section is optional. Remove it if you know that the problem is not platform dependent. 18 | 19 | Rust version you're using: (`cargo --version`) 20 | 21 | ```text 22 | 23 | ``` 24 | 25 | > Notes: 26 | > 27 | > - Pay attention to the msrv (minimum supported rust version) of `variadics_please`. 28 | > - `nightly` should work, but sometimes there are regressions: please let us know! 29 | 30 | Operating system, including version: 31 | 32 | ```text 33 | 34 | ``` 35 | 36 | ## What's performing poorly? 37 | 38 | Describe how you arrived at the problem. If you can, consider providing a code snippet or link 39 | to help reproduce the regression. 40 | 41 | If the exact scenario is not immediately reproducible on `cargo run`, please include a set list of steps to produce the correct setup. 42 | 43 | ## Before and After Traces 44 | 45 | To best help us investigate the regression, it's best to provide as much detailed profiling 46 | data as possible. 47 | 48 | If your app is running slowly, please show profiler traces before and after the change. 49 | For more information on how to get these traces, see 50 | . 51 | 52 | If this is about a compile-time regression, please provide the full output of `cargo build --timings`, 53 | for more information see . 54 | 55 | - Before: 56 | - After: 57 | 58 | ## Additional information 59 | 60 | Other information that can be used to further reproduce or isolate the problem. 61 | This commonly includes: 62 | 63 | - screenshots 64 | - logs 65 | - theories about what might be going wrong 66 | - workarounds that you used 67 | - links to related bugs, PRs or discussions 68 | -------------------------------------------------------------------------------- /.github/workflows/post-release.yml: -------------------------------------------------------------------------------- 1 | name: Post-release version bump 2 | 3 | # how to trigger: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow 4 | on: 5 | workflow_dispatch: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | ci: 12 | if: github.repository == 'bevyengine/variadics_please' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | 17 | - name: Install cargo-release 18 | run: cargo install cargo-release 19 | 20 | - name: Setup post-release version bump 21 | run: | 22 | # Set the commit author to the github-actions bot. See discussion here for more information: 23 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212 24 | # https://github.community/t/github-actions-bot-email-address/17204/6 25 | git config user.name 'Auto Releaser' 26 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com' 27 | # Read the current version from Cargo.toml 28 | current_version=$(cargo metadata --format-version 1 --no-deps | \ 29 | jq --raw-output '.packages | .[] | select(.name == "variadics_please").version') 30 | # Sanity check: current version should be 0.X.Y-dev 31 | if ! grep -q '^0\.[0-9]\+\.[0-9]\+-dev$' <<< "${current_version}"; then 32 | echo "Invalid version (not in 0.X.Y-dev format): ${current_version}" 33 | exit 1 34 | fi 35 | minor_version=$(sed 's/^0\.\([0-9]\+\).*/\1/' <<< "${current_version}") 36 | next_version=0.$((minor_version + 1)).0-dev 37 | echo "Bumping version to ${next_version}" 38 | # See release.yml for meaning of these arguments 39 | cargo release "${next_version}" \ 40 | --workspace \ 41 | --no-publish \ 42 | --execute \ 43 | --no-tag \ 44 | --no-confirm \ 45 | --no-push \ 46 | --exclude errors \ 47 | 48 | - name: Create PR 49 | uses: peter-evans/create-pull-request@v8 50 | with: 51 | delete-branch: true 52 | base: "main" 53 | title: "Bump Version after Release" 54 | body: | 55 | Bump version after release 56 | This PR has been auto-generated 57 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/README.md: -------------------------------------------------------------------------------- 1 | # Helpers for compile fail tests 2 | 3 | This crate contains everything needed to set up compile tests for this repository. It, like all compile test crates, is excluded from the workspace. This is done to not fail [`crater` tests](https://github.com/rust-lang/crater). The `CI` workflow executes these tests on the stable rust toolchain see ([tools/ci](../../tools/ci/src/main.rs)). 4 | 5 | ## Writing new test cases 6 | 7 | Test cases are annotated .rs files. These annotations define how the test case should be run and what we're expecting to fail. Please see for more information. 8 | 9 | Annotations can roughly be split into global annotations which are prefixed with `//@` and define how tests should be run and error annotations which are prefixed with `//~` and define where errors we expect to happen. From the global annotations, you're only likely to care about `//@check-pass` which will make any compile errors in the test trigger a test failure. 10 | 11 | The error annotations are composed of two parts. 12 | An optional location specifier: 13 | 14 | - `^` The error happens on the line above. 15 | - `v` The error happens on the line below. 16 | - `|` The error annotation is connected to another one. 17 | - If the location specifier is missing, the error is assumed to happen on the same line as the annotation. 18 | 19 | An error matcher: 20 | 21 | - `E####` The error we're expecting has the [`####` rustc error code](https://doc.rust-lang.org/error_codes/error-index.html), e.g `E0499` 22 | - `` The given [compiler lint](https://doc.rust-lang.org/rustc/lints/index.html) is triggered, e.g. `dead_code` 23 | - `LEVEL: ` A compiler error of the given level (valid levels are: `ERROR`, `HELP`, `WARN` or `NOTE`) will be raised and it will contain the `substring`. Substrings can contain spaces. 24 | - `LEVEL: //` Same as above but a regex is used to match the error message. 25 | 26 | An example of an error annotation would be `//~v ERROR: missing trait`. This error annotation will match any error occurring on the line below that contains the substring `missing trait`. 27 | 28 | ## A note about `.stderr` files 29 | 30 | We're capable of generating `.stderr` files for all our compile tests. These files contain the error output generated by the test. To create or regenerate them yourself, trigger the tests with the `BLESS` environment variable set to any value (e.g. `BLESS="some symbolic value"`). We currently have to ignore mismatches between these files and the actual stderr output from their corresponding test due to issues with file paths. We attempt to sanitize file paths but for proc-macros, the compiler error messages contain file paths to the current toolchain's copy of the standard library. If we knew of a way to construct a path to the current toolchains folder we could fix this. 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "variadics_please" 3 | version = "1.1.0" 4 | edition = "2021" 5 | description = "Implement things as if rust had variadics" 6 | homepage = "https://github.com/bevyengine/variadics_please" 7 | repository = "https://github.com/bevyengine/variadics_please" 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["bevy", "variadics", "docs"] 10 | rust-version = "1.81.0" 11 | categories = ["rust-patterns"] 12 | exclude = ["tools/", ".github/"] 13 | documentation = "https://docs.rs/variadics_please" 14 | 15 | [features] 16 | default = ["alloc"] 17 | alloc = [] 18 | 19 | [lib] 20 | proc-macro = true 21 | 22 | [dependencies] 23 | syn = "2.0" 24 | quote = "1.0" 25 | proc-macro2 = "1.0" 26 | 27 | [workspace] 28 | exclude = ["benches", "compile_fail", "tools/compile_fail_utils"] 29 | members = ["errors", ".", "benches"] 30 | 31 | [workspace.lints.clippy] 32 | doc_markdown = "warn" 33 | manual_let_else = "warn" 34 | match_same_arms = "warn" 35 | redundant_closure_for_method_calls = "warn" 36 | redundant_else = "warn" 37 | semicolon_if_nothing_returned = "warn" 38 | type_complexity = "allow" 39 | undocumented_unsafe_blocks = "warn" 40 | unwrap_or_default = "warn" 41 | ptr_as_ptr = "warn" 42 | ptr_cast_constness = "warn" 43 | ref_as_ptr = "warn" 44 | too_long_first_doc_paragraph = "allow" 45 | std_instead_of_core = "warn" 46 | std_instead_of_alloc = "warn" 47 | alloc_instead_of_core = "warn" 48 | 49 | [workspace.lints.rust] 50 | missing_docs = "warn" 51 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } 52 | unsafe_code = "deny" 53 | unsafe_op_in_unsafe_fn = "warn" 54 | unused_qualifications = "warn" 55 | internal_features = { level = "allow" } 56 | 57 | [lints.rust] 58 | missing_docs = "warn" 59 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] } 60 | unsafe_code = "deny" 61 | unsafe_op_in_unsafe_fn = "warn" 62 | unused_qualifications = "warn" 63 | internal_features = { level = "allow" } 64 | 65 | [profile.release] 66 | opt-level = 3 67 | lto = true 68 | 69 | [package.metadata.docs.rs] 70 | # This cfg is needed so that #[doc(fake_variadic)] is correctly propagated for 71 | # impls for re-exported traits. See https://github.com/rust-lang/cargo/issues/8811 72 | # for details on why this is needed. Since dependencies don't expect to be built 73 | # with `--cfg docsrs` (and thus fail to compile), we use a different cfg. 74 | rustc-args = ["--cfg", "docsrs_dep"] 75 | rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] 76 | all-features = true 77 | cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] 78 | 79 | # Examples 80 | 81 | [[example]] 82 | name = "all_tuples" 83 | path = "examples/demonstrations/all_tuples.rs" 84 | doc-scrape-examples = true 85 | 86 | [package.metadata.example.all_tuples] 87 | name = "all_tuples" 88 | description = "Demonstrates the functionality of `all_tuples!`." 89 | category = "Demonstration" 90 | -------------------------------------------------------------------------------- /.github/workflows/weekly.yml: -------------------------------------------------------------------------------- 1 | name: Weekly beta compile test 2 | 3 | on: 4 | schedule: 5 | # New versions of rust release on Thursdays. We test on Mondays to get at least 3 days of warning before all our CI breaks again. 6 | # https://forge.rust-lang.org/release/process.html#release-day-thursday 7 | - cron: '0 12 * * 1' 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | os: [windows-latest, ubuntu-latest, macos-latest] 18 | runs-on: ${{ matrix.os }} 19 | timeout-minutes: 30 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: dtolnay/rust-toolchain@beta 23 | - name: Install Linux dependencies 24 | run: cargo test --workspace --lib --bins --tests --benches 25 | env: 26 | CARGO_INCREMENTAL: 0 27 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 28 | 29 | lint: 30 | runs-on: ubuntu-latest 31 | timeout-minutes: 30 32 | steps: 33 | - uses: actions/checkout@v6 34 | - uses: dtolnay/rust-toolchain@beta 35 | with: 36 | components: rustfmt, clippy 37 | - name: Check formatting 38 | run: cargo fmt --all -- --check 39 | - name: Clippy 40 | run: cargo clippy --workspace --all-targets --all-features -- -Dwarnings 41 | 42 | check-compiles: 43 | runs-on: ubuntu-latest 44 | timeout-minutes: 30 45 | needs: test 46 | steps: 47 | - uses: actions/checkout@v6 48 | - uses: actions/cache@v5 49 | with: 50 | path: | 51 | ~/.cargo/bin/ 52 | ~/.cargo/registry/index/ 53 | ~/.cargo/registry/cache/ 54 | ~/.cargo/git/db/ 55 | target/ 56 | key: ${{ runner.os }}-cargo-check-compiles-${{ hashFiles('**/Cargo.toml') }} 57 | - uses: dtolnay/rust-toolchain@stable 58 | with: 59 | toolchain: stable 60 | - name: Check Compile 61 | run: | 62 | cargo test --workspace 63 | cargo check --benches --target-dir ../target --manifest-path ./benches/Cargo.toml 64 | cargo check --workspace --examples 65 | cargo check --workspace 66 | cargo check --workspace --tests 67 | 68 | check-doc: 69 | runs-on: ubuntu-latest 70 | timeout-minutes: 30 71 | steps: 72 | - uses: actions/checkout@v6 73 | - uses: dtolnay/rust-toolchain@beta 74 | - name: Build and check docs 75 | run: | 76 | cargo test --workspace --doc --no-fail-fast 77 | cargo doc --workspace --all-features --no-deps --document-private-items --keep-going 78 | env: 79 | CARGO_INCREMENTAL: 0 80 | RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep" 81 | 82 | open-issue: 83 | name: Warn that weekly CI fails 84 | runs-on: ubuntu-latest 85 | needs: [test, lint, check-compiles, check-doc] 86 | permissions: 87 | issues: write 88 | # Use always() so the job doesn't get canceled if any other jobs fail 89 | if: ${{ always() && contains(needs.*.result, 'failure') }} 90 | steps: 91 | - name: Create issue 92 | run: | 93 | previous_issue_number=$(gh issue list \ 94 | --search "$TITLE in:title" \ 95 | --json number \ 96 | --jq '.[0].number') 97 | if [[ -n $previous_issue_number ]]; then 98 | gh issue comment $previous_issue_number \ 99 | --body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" 100 | else 101 | gh issue create \ 102 | --title "$TITLE" \ 103 | --label "$LABELS" \ 104 | --body "$BODY" 105 | fi 106 | env: 107 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 108 | GH_REPO: ${{ github.repository }} 109 | TITLE: Main branch fails to compile on Rust beta. 110 | LABELS: C-Bug,S-Needs-Triage 111 | BODY: | 112 | ## Weekly CI run has failed. 113 | [The offending run.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) 114 | -------------------------------------------------------------------------------- /tools/compile_fail_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | // Re-export ui_test so all the tests use the same version. 7 | pub use ui_test; 8 | 9 | use ui_test::{ 10 | color_eyre::eyre::eyre, 11 | default_file_filter, default_per_file_config, 12 | dependencies::DependencyBuilder, 13 | run_tests_generic, 14 | spanned::Spanned, 15 | status_emitter::{Gha, StatusEmitter, Text}, 16 | Args, Config, OutputConflictHandling, 17 | }; 18 | 19 | /// Use this instead of hand rolling configs. 20 | /// 21 | /// `root_dir` is the directory your tests are contained in. Needs to be a path from crate root. 22 | /// This config will build dependencies and will assume that the cargo manifest is placed at the 23 | /// current working directory. 24 | fn basic_config(root_dir: impl Into, args: &Args) -> ui_test::Result { 25 | let root_dir = root_dir.into(); 26 | 27 | match root_dir.try_exists() { 28 | Ok(true) => { /* success */ } 29 | Ok(false) => { 30 | return Err(eyre!("path does not exist: {:?}", root_dir)); 31 | } 32 | Err(error) => { 33 | return Err(eyre!("failed to read path: {:?} ({:?})", root_dir, error)); 34 | } 35 | } 36 | 37 | let mut config = Config { 38 | bless_command: Some( 39 | "`cargo test` with the BLESS environment variable set to any non empty value" 40 | .to_string(), 41 | ), 42 | output_conflict_handling: if env::var_os("BLESS").is_some() { 43 | OutputConflictHandling::Bless 44 | } else { 45 | // stderr output changes between rust versions so we just rely on annotations 46 | OutputConflictHandling::Ignore 47 | }, 48 | ..Config::rustc(root_dir) 49 | }; 50 | 51 | config.with_args(args); 52 | 53 | let crate_root = ".."; 54 | 55 | // Don't leak contributor filesystem paths 56 | config.path_stderr_filter(Path::new(crate_root), b"$CRATE_ROOT"); 57 | config.path_stderr_filter(Path::new(env!("RUSTUP_HOME")), b"$RUSTUP_HOME"); 58 | 59 | // ui_test doesn't compile regex with perl character classes. 60 | // \pL = unicode class for letters, \pN = unicode class for numbers 61 | config.stderr_filter(r"\/home\/[\pL\pN_@#\-\. ]+", "$HOME"); 62 | // Paths in .stderr seem to always be normalized to use /. Handle both anyway. 63 | config.stderr_filter( 64 | r"[a-zA-Z]:(?:\\|\/)users(?:\\|\/)[\pL\pN_@#\-\. ]+", // NOTE: [\pL\pN_@#\-\. ] is a poor attempt at handling usernames 65 | "$HOME", 66 | ); 67 | 68 | // Manually insert @aux-build: comments into test files. This needs to 69 | // be done to build and link dependencies. Dependencies will be pulled from a 70 | // Cargo.toml file. 71 | config.comment_defaults.base().custom.insert( 72 | "dependencies", 73 | Spanned::dummy(vec![Box::new(DependencyBuilder::default())]), 74 | ); 75 | 76 | Ok(config) 77 | } 78 | 79 | /// Runs ui tests for a single directory. 80 | /// 81 | /// `root_dir` is the directory your tests are contained in. Needs to be a path from crate root. 82 | pub fn test(test_name: impl Into, test_root: impl Into) -> ui_test::Result<()> { 83 | test_multiple(test_name, [test_root]) 84 | } 85 | 86 | /// Run ui tests with the given config 87 | pub fn test_with_config(test_name: impl Into, config: Config) -> ui_test::Result<()> { 88 | test_with_multiple_configs(test_name, [Ok(config)]) 89 | } 90 | 91 | /// Runs ui tests for a multiple directories. 92 | /// 93 | /// `root_dirs` paths need to come from crate root. 94 | pub fn test_multiple( 95 | test_name: impl Into, 96 | test_roots: impl IntoIterator>, 97 | ) -> ui_test::Result<()> { 98 | let args = Args::test()?; 99 | 100 | let configs = test_roots.into_iter().map(|root| basic_config(root, &args)); 101 | 102 | test_with_multiple_configs(test_name, configs) 103 | } 104 | 105 | /// Run ui test with the given configs. 106 | /// 107 | /// Tests for configs are run in parallel. 108 | pub fn test_with_multiple_configs( 109 | test_name: impl Into, 110 | configs: impl IntoIterator>, 111 | ) -> ui_test::Result<()> { 112 | let configs = configs 113 | .into_iter() 114 | .collect::>>()?; 115 | 116 | let emitter: Box = if env::var_os("CI").is_some() { 117 | Box::new(( 118 | Text::verbose(), 119 | Gha:: { 120 | name: test_name.into(), 121 | }, 122 | )) 123 | } else { 124 | Box::new(Text::quiet()) 125 | }; 126 | 127 | run_tests_generic( 128 | configs, 129 | default_file_filter, 130 | default_per_file_config, 131 | emitter, 132 | ) 133 | } 134 | -------------------------------------------------------------------------------- /.github/workflows/ci-comment-failures.yml: -------------------------------------------------------------------------------- 1 | name: CI - PR Comments 2 | 3 | # This workflow has write permissions on the repo 4 | # It must not checkout a PR and run untrusted code 5 | 6 | # Also requesting write permissions on PR to be able to comment 7 | permissions: 8 | pull-requests: 'write' 9 | 10 | on: 11 | workflow_run: 12 | workflows: ["CI"] 13 | types: 14 | - completed 15 | 16 | jobs: 17 | 18 | missing-examples: 19 | runs-on: ubuntu-latest 20 | if: > 21 | github.event.workflow_run.event == 'pull_request' && 22 | github.event.workflow_run.conclusion == 'failure' 23 | steps: 24 | - name: 'Download artifact' 25 | id: find-artifact 26 | uses: actions/github-script@v8 27 | with: 28 | result-encoding: string 29 | script: | 30 | var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ 31 | owner: context.repo.owner, 32 | repo: context.repo.repo, 33 | run_id: ${{ github.event.workflow_run.id }}, 34 | }); 35 | var matchArtifacts = artifacts.data.artifacts.filter((artifact) => { 36 | return artifact.name == "missing-examples" 37 | }); 38 | if (matchArtifacts.length == 0) { return "false" } 39 | var matchArtifact = matchArtifacts[0]; 40 | var download = await github.rest.actions.downloadArtifact({ 41 | owner: context.repo.owner, 42 | repo: context.repo.repo, 43 | artifact_id: matchArtifact.id, 44 | archive_format: 'zip', 45 | }); 46 | var fs = require('fs'); 47 | fs.writeFileSync('${{github.workspace}}/missing-examples.zip', Buffer.from(download.data)); 48 | return "true" 49 | - run: unzip missing-examples.zip 50 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 51 | - name: 'Comment on PR' 52 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 53 | uses: actions/github-script@v8 54 | with: 55 | github-token: ${{ secrets.GITHUB_TOKEN }} 56 | script: | 57 | var fs = require('fs'); 58 | var issue_number = Number(fs.readFileSync('./NR')); 59 | if (fs.existsSync('./missing-metadata')) { 60 | await github.rest.issues.createComment({ 61 | owner: context.repo.owner, 62 | repo: context.repo.repo, 63 | issue_number: issue_number, 64 | body: 'You added a new example but didn\'t add metadata for it. Please update the root Cargo.toml file.' 65 | }); 66 | } 67 | 68 | missing-features: 69 | runs-on: ubuntu-latest 70 | if: > 71 | github.event.workflow_run.event == 'pull_request' && 72 | github.event.workflow_run.conclusion == 'failure' 73 | steps: 74 | - name: 'Download artifact' 75 | id: find-artifact 76 | uses: actions/github-script@v8 77 | with: 78 | result-encoding: string 79 | script: | 80 | var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ 81 | owner: context.repo.owner, 82 | repo: context.repo.repo, 83 | run_id: ${{ github.event.workflow_run.id }}, 84 | }); 85 | var matchArtifacts = artifacts.data.artifacts.filter((artifact) => { 86 | return artifact.name == "missing-features" 87 | }); 88 | if (matchArtifacts.length == 0) { return "false" } 89 | var matchArtifact = matchArtifacts[0]; 90 | var download = await github.rest.actions.downloadArtifact({ 91 | owner: context.repo.owner, 92 | repo: context.repo.repo, 93 | artifact_id: matchArtifact.id, 94 | archive_format: 'zip', 95 | }); 96 | var fs = require('fs'); 97 | fs.writeFileSync('${{github.workspace}}/missing-features.zip', Buffer.from(download.data)); 98 | return "true" 99 | - run: unzip missing-features.zip 100 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 101 | - name: 'Comment on PR' 102 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 103 | uses: actions/github-script@v8 104 | with: 105 | github-token: ${{ secrets.GITHUB_TOKEN }} 106 | script: | 107 | var fs = require('fs'); 108 | var issue_number = Number(fs.readFileSync('./NR')); 109 | if (fs.existsSync('./missing-features')) { 110 | await github.rest.issues.createComment({ 111 | owner: context.repo.owner, 112 | repo: context.repo.repo, 113 | issue_number: issue_number, 114 | body: 'You added a new feature but didn\'t add a description for it. Please update the root Cargo.toml file.' 115 | }); 116 | } 117 | 118 | msrv: 119 | runs-on: ubuntu-latest 120 | if: > 121 | github.event.workflow_run.event == 'pull_request' && 122 | github.event.workflow_run.conclusion == 'failure' 123 | steps: 124 | - name: 'Download artifact' 125 | id: find-artifact 126 | uses: actions/github-script@v8 127 | with: 128 | result-encoding: string 129 | script: | 130 | var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ 131 | owner: context.repo.owner, 132 | repo: context.repo.repo, 133 | run_id: ${{ github.event.workflow_run.id }}, 134 | }); 135 | var matchArtifacts = artifacts.data.artifacts.filter((artifact) => { 136 | return artifact.name == "msrv" 137 | }); 138 | if (matchArtifacts.length == 0) { return "false" } 139 | var matchArtifact = matchArtifacts[0]; 140 | var download = await github.rest.actions.downloadArtifact({ 141 | owner: context.repo.owner, 142 | repo: context.repo.repo, 143 | artifact_id: matchArtifact.id, 144 | archive_format: 'zip', 145 | }); 146 | var fs = require('fs'); 147 | fs.writeFileSync('${{github.workspace}}/msrv.zip', Buffer.from(download.data)); 148 | return "true" 149 | - run: unzip msrv.zip 150 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 151 | - name: 'Comment on PR' 152 | if: ${{ steps.find-artifact.outputs.result == 'true' }} 153 | uses: actions/github-script@v8 154 | with: 155 | github-token: ${{ secrets.GITHUB_TOKEN }} 156 | script: | 157 | var fs = require('fs'); 158 | var issue_number = Number(fs.readFileSync('./NR')); 159 | await github.rest.issues.createComment({ 160 | owner: context.repo.owner, 161 | repo: context.repo.repo, 162 | issue_number: issue_number, 163 | body: 'Your PR increases the Minimum Supported Rust Version (`msrv`). Please update the `rust-version` field in ``Cargo.toml.' 164 | }); 165 | -------------------------------------------------------------------------------- /.github/workflows/validation-jobs.yml: -------------------------------------------------------------------------------- 1 | name: validation jobs 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - release-* 10 | 11 | concurrency: 12 | group: ${{github.workflow}}-${{github.ref}} 13 | cancel-in-progress: ${{github.event_name == 'pull_request'}} 14 | 15 | env: 16 | CARGO_TERM_COLOR: always 17 | # If nightly is breaking CI, modify this variable to target a specific nightly version. 18 | NIGHTLY_TOOLCHAIN: nightly 19 | 20 | jobs: 21 | build-and-install-on-iOS: 22 | if: ${{ github.event_name == 'merge_group' }} 23 | runs-on: macos-latest 24 | timeout-minutes: 30 25 | steps: 26 | - uses: actions/checkout@v6 27 | - uses: dtolnay/rust-toolchain@stable 28 | - uses: actions/cache@v5 29 | with: 30 | path: | 31 | target 32 | key: ${{ runner.os }}-ios-install-${{ hashFiles('**/Cargo.lock') }} 33 | # TODO: remove x86 target once it always run on arm GitHub runners 34 | - name: Add iOS targets 35 | run: rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim 36 | - name: Build and install iOS app in iOS Simulator. 37 | run: cd examples/mobile && make install 38 | 39 | build-android: 40 | if: ${{ github.event_name == 'merge_group' }} 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 30 43 | steps: 44 | - uses: actions/checkout@v6 45 | - uses: dtolnay/rust-toolchain@stable 46 | - name: Set up JDK 17 47 | uses: actions/setup-java@v4 48 | with: 49 | java-version: '17' 50 | distribution: 'temurin' 51 | - uses: actions/cache@v5 52 | with: 53 | path: | 54 | ~/.cargo/bin/ 55 | ~/.cargo/registry/index/ 56 | ~/.cargo/registry/cache/ 57 | ~/.cargo/git/db/ 58 | target/ 59 | key: ${{ runner.os }}-cargo-build-android-${{ hashFiles('**/Cargo.toml') }} 60 | 61 | run-examples-linux: 62 | # also run when pushed to main to update reference screenshots 63 | if: ${{ github.event_name != 'pull_request' }} 64 | runs-on: ubuntu-22.04 65 | timeout-minutes: 30 66 | steps: 67 | - uses: actions/checkout@v6 68 | - name: Build 69 | # this uses the same command as when running the example to ensure build is reused 70 | run: | 71 | CI_TESTING_CONFIG=.github/example-run/all_tuples.ron cargo build --example all_tuples 72 | - name: Run examples 73 | run: | 74 | for example in .github/example-run/*.ron; do 75 | example_name=`basename $example .ron` 76 | echo -n $example_name > last_example_run 77 | echo "running $example_name - "`date` 78 | time CI_TESTING_CONFIG=$example cargo run --example $example_name 79 | sleep 10 80 | done 81 | - uses: actions/upload-artifact@v6 82 | if: ${{ failure() && github.event_name == 'pull_request' }} 83 | with: 84 | name: example-run-linux 85 | path: example-run/ 86 | 87 | run-examples-on-windows-dx12: 88 | if: ${{ github.event_name != 'pull_request' }} 89 | runs-on: windows-latest 90 | timeout-minutes: 30 91 | steps: 92 | - uses: actions/checkout@v6 93 | - uses: dtolnay/rust-toolchain@stable 94 | - name: Build 95 | shell: bash 96 | # this uses the same command as when running the example to ensure build is reused 97 | run: | 98 | CI_TESTING_CONFIG=.github/example-run/all_tuples.ron cargo build --example all_tuples 99 | - name: Run examples 100 | shell: bash 101 | run: | 102 | for example in .github/example-run/*.ron; do 103 | example_name=`basename $example .ron` 104 | echo -n $example_name > last_example_run 105 | echo "running $example_name - "`date` 106 | time CI_TESTING_CONFIG=$example cargo run --example $example_name 107 | sleep 10 108 | done 109 | - uses: actions/upload-artifact@v6 110 | if: ${{ failure() && github.event_name == 'pull_request' }} 111 | with: 112 | name: example-run-windows 113 | path: example-run/ 114 | 115 | run-examples-on-wasm: 116 | if: ${{ github.event_name == 'merge_group' }} 117 | runs-on: ubuntu-22.04 118 | timeout-minutes: 60 119 | steps: 120 | - uses: actions/checkout@v6 121 | 122 | - uses: dtolnay/rust-toolchain@stable 123 | with: 124 | target: wasm32-unknown-unknown 125 | 126 | - uses: actions/cache@v5 127 | with: 128 | path: | 129 | ~/.cargo/bin/ 130 | ~/.cargo/registry/index/ 131 | ~/.cargo/registry/cache/ 132 | ~/.cargo/git/db/ 133 | ~/.github/start-wasm-example/node_modules 134 | target/ 135 | key: ${{ runner.os }}-wasm-run-examples-${{ hashFiles('**/Cargo.toml') }} 136 | 137 | - name: install xvfb, llvmpipe and lavapipe 138 | run: | 139 | sudo apt-get update -y -qq 140 | sudo add-apt-repository ppa:kisak/turtle -y 141 | sudo apt-get update 142 | sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers 143 | 144 | - name: Install wasm-bindgen 145 | run: cargo install --force wasm-bindgen-cli 146 | 147 | - name: Setup playwright 148 | run: | 149 | cd .github/start-wasm-example 150 | npm install 151 | npx playwright install --with-deps 152 | cd ../.. 153 | 154 | - name: First Wasm build 155 | run: | 156 | cargo build --release --example testbed_ui --target wasm32-unknown-unknown 157 | 158 | - name: Run examples 159 | shell: bash 160 | run: | 161 | # start a webserver 162 | python3 -m http.server --directory examples/wasm & 163 | 164 | xvfb-run cargo run -p build-wasm-example -- --browsers chromium --browsers firefox --frames 25 --test 2d_shapes lighting text_debug breakout 165 | 166 | - name: Save screenshots 167 | uses: actions/upload-artifact@v6 168 | with: 169 | name: screenshots-wasm 170 | path: .github/start-wasm-example/screenshot-*.png 171 | 172 | build-without-default-features: 173 | if: ${{ github.event_name == 'merge_group' }} 174 | timeout-minutes: 30 175 | strategy: 176 | max-parallel: 1 177 | runs-on: ubuntu-latest 178 | steps: 179 | - uses: actions/checkout@v6 180 | - uses: dtolnay/rust-toolchain@stable 181 | - name: Build 182 | run: cargo build --no-default-features 183 | env: 184 | CARGO_INCREMENTAL: 0 185 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 186 | 187 | build-without-default-features-status: 188 | if: | 189 | always() && 190 | github.event_name == 'merge_group' 191 | needs: build-without-default-features 192 | runs-on: ubuntu-latest 193 | steps: 194 | - name: Successful 195 | if: ${{ !(contains(needs.*.result, 'failure')) }} 196 | run: exit 0 197 | - name: Failing 198 | if: ${{ contains(needs.*.result, 'failure') }} 199 | run: exit 1 200 | 201 | check-unused-dependencies: 202 | if: ${{ github.event_name == 'merge_group' }} 203 | runs-on: ubuntu-latest 204 | timeout-minutes: 30 205 | steps: 206 | - uses: actions/checkout@v6 207 | - uses: actions/cache@v5 208 | with: 209 | path: | 210 | ~/.cargo/bin/ 211 | ~/.cargo/registry/index/ 212 | ~/.cargo/registry/cache/ 213 | ~/.cargo/git/db/ 214 | target/ 215 | key: ${{ runner.os }}-cargo-check-unused-dependencies-${{ hashFiles('**/Cargo.toml') }} 216 | - uses: dtolnay/rust-toolchain@master 217 | with: 218 | toolchain: ${{ env.NIGHTLY_TOOLCHAIN }} 219 | - name: Installs cargo-udeps 220 | run: cargo install --force cargo-udeps 221 | - name: Run cargo udeps 222 | run: cargo udeps 223 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - release-* 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | # If nightly is breaking CI, modify this variable to target a specific nightly version. 14 | NIGHTLY_TOOLCHAIN: nightly 15 | 16 | concurrency: 17 | group: ${{github.workflow}}-${{github.ref}} 18 | cancel-in-progress: ${{github.event_name == 'pull_request'}} 19 | 20 | jobs: 21 | build: 22 | strategy: 23 | matrix: 24 | os: [windows-latest, ubuntu-latest, macos-latest] 25 | runs-on: ${{ matrix.os }} 26 | timeout-minutes: 30 27 | steps: 28 | - uses: actions/checkout@v6 29 | - uses: actions/cache@v5 30 | with: 31 | path: | 32 | ~/.cargo/bin/ 33 | ~/.cargo/registry/index/ 34 | ~/.cargo/registry/cache/ 35 | ~/.cargo/git/db/ 36 | target/ 37 | key: ${{ runner.os }}-cargo-build-stable-${{ hashFiles('**/Cargo.toml') }} 38 | - uses: dtolnay/rust-toolchain@stable 39 | - name: Build & run tests 40 | run: cargo test --workspace --lib --bins --tests --benches 41 | env: 42 | CARGO_INCREMENTAL: 0 43 | RUSTFLAGS: "-C debuginfo=0 -D warnings" 44 | 45 | lint: 46 | runs-on: ubuntu-latest 47 | timeout-minutes: 30 48 | steps: 49 | - uses: actions/checkout@v6 50 | - uses: actions/cache@v5 51 | with: 52 | path: | 53 | ~/.cargo/bin/ 54 | ~/.cargo/registry/index/ 55 | ~/.cargo/registry/cache/ 56 | ~/.cargo/git/db/ 57 | target/ 58 | key: ${{ runner.os }}-cargo-ci-${{ hashFiles('**/Cargo.toml') }} 59 | - uses: dtolnay/rust-toolchain@stable 60 | with: 61 | components: rustfmt, clippy 62 | - name: Check formatting 63 | run: cargo fmt --all -- --check 64 | - name: Clippy 65 | run: cargo clippy --workspace --all-targets --all-features -- -Dwarnings 66 | 67 | miri: 68 | # Explicitly use macOS 14 to take advantage of M1 chip. 69 | runs-on: macos-14 70 | timeout-minutes: 60 71 | steps: 72 | - uses: actions/checkout@v6 73 | - uses: actions/cache@v5 74 | with: 75 | path: | 76 | ~/.cargo/bin/ 77 | ~/.cargo/registry/index/ 78 | ~/.cargo/registry/cache/ 79 | ~/.cargo/git/db/ 80 | target/ 81 | key: ${{ runner.os }}-cargo-miri-${{ hashFiles('**/Cargo.toml') }} 82 | - uses: dtolnay/rust-toolchain@master 83 | with: 84 | toolchain: ${{ env.NIGHTLY_TOOLCHAIN }} 85 | components: miri 86 | - name: CI job 87 | run: cargo miri test 88 | env: 89 | RUSTFLAGS: -Zrandomize-layout 90 | 91 | check-compiles: 92 | runs-on: ubuntu-latest 93 | timeout-minutes: 30 94 | needs: lint 95 | steps: 96 | - uses: actions/checkout@v6 97 | - uses: actions/cache@v5 98 | with: 99 | path: | 100 | ~/.cargo/bin/ 101 | ~/.cargo/registry/index/ 102 | ~/.cargo/registry/cache/ 103 | ~/.cargo/git/db/ 104 | target/ 105 | key: ${{ runner.os }}-cargo-check-compiles-${{ hashFiles('**/Cargo.toml') }} 106 | - uses: dtolnay/rust-toolchain@stable 107 | with: 108 | toolchain: stable 109 | - name: Check Compile 110 | run: | 111 | cargo test --workspace 112 | cargo check --benches --target-dir ../target --manifest-path ./benches/Cargo.toml 113 | cargo check --workspace --examples 114 | cargo check --workspace 115 | cargo check --workspace --tests 116 | 117 | check-compiles-no-std: 118 | runs-on: ubuntu-latest 119 | timeout-minutes: 30 120 | needs: lint 121 | steps: 122 | - uses: actions/checkout@v6 123 | - uses: actions/cache@v5 124 | with: 125 | path: | 126 | ~/.cargo/bin/ 127 | ~/.cargo/registry/index/ 128 | ~/.cargo/registry/cache/ 129 | ~/.cargo/git/db/ 130 | target/ 131 | key: ${{ runner.os }}-cargo-check-compiles-no-std-${{ hashFiles('**/Cargo.toml') }} 132 | - uses: dtolnay/rust-toolchain@stable 133 | with: 134 | targets: x86_64-unknown-none 135 | - name: Check Compile 136 | run: cargo check --no-default-features 137 | 138 | build-wasm: 139 | runs-on: ubuntu-latest 140 | timeout-minutes: 30 141 | needs: build 142 | steps: 143 | - uses: actions/checkout@v6 144 | - uses: actions/cache@v5 145 | with: 146 | path: | 147 | ~/.cargo/bin/ 148 | ~/.cargo/registry/index/ 149 | ~/.cargo/registry/cache/ 150 | ~/.cargo/git/db/ 151 | target/ 152 | key: ubuntu-assets-cargo-build-wasm-stable-${{ hashFiles('**/Cargo.toml') }} 153 | - uses: dtolnay/rust-toolchain@stable 154 | with: 155 | target: wasm32-unknown-unknown 156 | - name: Check wasm 157 | run: cargo check --target wasm32-unknown-unknown 158 | 159 | build-wasm-atomics: 160 | runs-on: ubuntu-latest 161 | timeout-minutes: 30 162 | needs: build 163 | steps: 164 | - uses: actions/checkout@v6 165 | - uses: actions/cache@v5 166 | with: 167 | path: | 168 | ~/.cargo/bin/ 169 | ~/.cargo/registry/index/ 170 | ~/.cargo/registry/cache/ 171 | ~/.cargo/git/db/ 172 | target/ 173 | key: ubuntu-assets-cargo-build-wasm-nightly-${{ hashFiles('**/Cargo.toml') }} 174 | - uses: dtolnay/rust-toolchain@master 175 | with: 176 | toolchain: ${{ env.NIGHTLY_TOOLCHAIN }} 177 | targets: wasm32-unknown-unknown 178 | components: rust-src 179 | - name: Check wasm 180 | run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort 181 | env: 182 | RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory" 183 | 184 | markdownlint: 185 | runs-on: ubuntu-latest 186 | timeout-minutes: 30 187 | if: always() 188 | steps: 189 | - uses: actions/checkout@v6 190 | with: 191 | # Full git history is needed to get a proper list of changed files within `super-linter` 192 | fetch-depth: 0 193 | - name: Run Markdown Lint 194 | uses: docker://ghcr.io/github/super-linter:slim-v4 195 | env: 196 | MULTI_STATUS: false 197 | VALIDATE_ALL_CODEBASE: false 198 | VALIDATE_MARKDOWN: true 199 | DEFAULT_BRANCH: main 200 | 201 | toml: 202 | runs-on: ubuntu-latest 203 | timeout-minutes: 30 204 | steps: 205 | - uses: actions/checkout@v6 206 | - uses: dtolnay/rust-toolchain@stable 207 | - name: Install taplo 208 | run: cargo install taplo-cli --locked 209 | - name: Run Taplo 210 | id: taplo 211 | run: taplo fmt --check --diff 212 | - name: Taplo info 213 | if: failure() 214 | run: | 215 | echo 'To fix toml fmt, please run `taplo fmt`.' 216 | echo 'To check for a diff, run `taplo fmt --check --diff`.' 217 | echo 'You can find taplo here: https://taplo.tamasfe.dev/.' 218 | echo 'Also use the `Even Better Toml` extension.' 219 | echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml' 220 | 221 | typos: 222 | runs-on: ubuntu-latest 223 | timeout-minutes: 30 224 | steps: 225 | - uses: actions/checkout@v6 226 | - name: Check for typos 227 | uses: crate-ci/typos@v1.40.0 228 | - name: Typos info 229 | if: failure() 230 | run: | 231 | echo 'To fix typos, please run `typos -w`' 232 | echo 'To check for a diff, run `typos`' 233 | echo 'You can find typos here: https://crates.io/crates/typos' 234 | echo 'if you use VSCode, you can also install `Typos Spell Checker' 235 | echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode' 236 | 237 | run-examples-macos-metal: 238 | # Explicitly use macOS 14 to take advantage of M1 chip. 239 | runs-on: macos-14 240 | timeout-minutes: 30 241 | steps: 242 | - uses: actions/checkout@v6 243 | - uses: dtolnay/rust-toolchain@stable 244 | - name: Build 245 | # this uses the same command as when running the example to ensure build is reused 246 | run: | 247 | CI_TESTING_CONFIG=.github/example-run/all_tuples.ron cargo build --example all_tuples 248 | - name: Run examples 249 | run: | 250 | for example in .github/example-run/*.ron; do 251 | example_name=`basename $example .ron` 252 | echo -n $example_name > last_example_run 253 | echo "running $example_name - "`date` 254 | time CI_TESTING_CONFIG=$example cargo run --example $example_name 255 | sleep 10 256 | done 257 | 258 | check-doc: 259 | runs-on: ubuntu-latest 260 | timeout-minutes: 30 261 | steps: 262 | - uses: actions/checkout@v6 263 | - uses: actions/cache@v5 264 | with: 265 | path: | 266 | ~/.cargo/bin/ 267 | ~/.cargo/registry/index/ 268 | ~/.cargo/registry/cache/ 269 | ~/.cargo/git/db/ 270 | target/ 271 | key: ${{ runner.os }}-check-doc-${{ hashFiles('**/Cargo.toml') }} 272 | - uses: dtolnay/rust-toolchain@master 273 | with: 274 | toolchain: ${{ env.NIGHTLY_TOOLCHAIN }} 275 | - name: Build doc 276 | run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going 277 | env: 278 | CARGO_INCREMENTAL: 0 279 | RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep" 280 | - name: Check doc 281 | run: cargo test --workspace --doc 282 | env: 283 | CARGO_INCREMENTAL: 0 284 | RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep" 285 | - name: Installs cargo-deadlinks 286 | run: cargo install --force cargo-deadlinks 287 | - name: Checks dead links 288 | run: cargo deadlinks --dir target/documentation 289 | continue-on-error: true 290 | 291 | msrv: 292 | runs-on: ubuntu-latest 293 | timeout-minutes: 30 294 | needs: build 295 | steps: 296 | - uses: actions/checkout@v6 297 | - uses: actions/cache@v5 298 | with: 299 | path: | 300 | ~/.cargo/bin/ 301 | ~/.cargo/registry/index/ 302 | ~/.cargo/registry/cache/ 303 | ~/.cargo/git/db/ 304 | target/ 305 | key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.toml') }} 306 | - name: Get minimum supported rust version 307 | id: msrv 308 | run: | 309 | msrv=`cargo metadata --no-deps --format-version 1 | jq --raw-output '.packages[] | select(.name=="variadics_please") | .rust_version'` 310 | echo "msrv=$msrv" >> $GITHUB_OUTPUT 311 | - uses: dtolnay/rust-toolchain@master 312 | with: 313 | toolchain: ${{ steps.msrv.outputs.msrv }} 314 | - name: Run cargo check 315 | id: check 316 | run: | 317 | echo 'If this fails, run `cargo msrv` and update the msrv in `Cargo.toml`.' 318 | cargo check 319 | - name: Save PR number 320 | if: ${{ failure() && github.event_name == 'pull_request' && steps.check.conclusion == 'failure' }} 321 | run: | 322 | mkdir -p ./msrv 323 | echo ${{ github.event.number }} > ./msrv/NR 324 | - uses: actions/upload-artifact@v6 325 | if: ${{ failure() && github.event_name == 'pull_request' && steps.check.conclusion == 'failure' }} 326 | with: 327 | name: msrv 328 | path: msrv/ 329 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides macros for implementing traits on variadic types. 2 | 3 | // FIXME(15321): solve CI failures, then replace with `#![expect()]`. 4 | #![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")] 5 | #![cfg_attr(any(docsrs, docsrs_dep), feature(doc_cfg, rustdoc_internals))] 6 | 7 | use proc_macro::TokenStream; 8 | use proc_macro2::{Literal, Span as Span2, TokenStream as TokenStream2}; 9 | use quote::{format_ident, quote}; 10 | use syn::{ 11 | parse::{Parse, ParseStream}, 12 | parse_macro_input, 13 | spanned::Spanned as _, 14 | token::Comma, 15 | Attribute, Error, Ident, LitInt, LitStr, Result, 16 | }; 17 | struct AllTuples { 18 | fake_variadic: bool, 19 | macro_ident: Ident, 20 | start: usize, 21 | end: usize, 22 | idents: Vec, 23 | } 24 | 25 | impl Parse for AllTuples { 26 | fn parse(input: ParseStream) -> Result { 27 | let fake_variadic = input.call(parse_fake_variadic_attr)?; 28 | let macro_ident = input.parse::()?; 29 | input.parse::()?; 30 | let start = input.parse::()?.base10_parse()?; 31 | 32 | if start > 1 && fake_variadic { 33 | return Err(Error::new( 34 | input.span(), 35 | "#[doc(fake_variadic)] only works when the tuple with length one is included", 36 | )); 37 | } 38 | 39 | input.parse::()?; 40 | let end = input.parse::()?.base10_parse()?; 41 | input.parse::()?; 42 | let mut idents = vec![input.parse::()?]; 43 | while input.parse::().is_ok() { 44 | idents.push(input.parse::()?); 45 | } 46 | 47 | Ok(AllTuples { 48 | fake_variadic, 49 | macro_ident, 50 | start, 51 | end, 52 | idents, 53 | }) 54 | } 55 | } 56 | 57 | /// Helper macro to generate tuple pyramids. Useful to generate scaffolding to work around Rust 58 | /// lacking variadics. Invoking `all_tuples!(impl_foo, start, end, P, Q, ..)` 59 | /// invokes `impl_foo` providing ident tuples through arity `start..end`. 60 | /// If you require the length of the tuple, see [`all_tuples_with_size!`]. 61 | /// 62 | /// # Examples 63 | /// 64 | /// ## Single parameter 65 | /// 66 | /// ``` 67 | /// # use core::marker::PhantomData; 68 | /// # use variadics_please::all_tuples; 69 | /// # 70 | /// struct Foo { 71 | /// // .. 72 | /// # _phantom: PhantomData 73 | /// } 74 | /// 75 | /// trait WrappedInFoo { 76 | /// type Tup; 77 | /// } 78 | /// 79 | /// macro_rules! impl_wrapped_in_foo { 80 | /// ($($T:ident),*) => { 81 | /// impl<$($T),*> WrappedInFoo for ($($T,)*) { 82 | /// type Tup = ($(Foo<$T>,)*); 83 | /// } 84 | /// }; 85 | /// } 86 | /// 87 | /// // start from 0 element to 15 elements 88 | /// all_tuples!(impl_wrapped_in_foo, 0, 15, T); 89 | /// // impl_wrapped_in_foo!(); 90 | /// // impl_wrapped_in_foo!(T0); 91 | /// // impl_wrapped_in_foo!(T0, T1); 92 | /// // .. 93 | /// // impl_wrapped_in_foo!(T0 .. T14); 94 | /// ``` 95 | /// 96 | /// # Multiple parameters 97 | /// 98 | /// ``` 99 | /// # use variadics_please::all_tuples; 100 | /// # 101 | /// trait Append { 102 | /// type Out; 103 | /// fn append(tup: Self, item: Item) -> Self::Out; 104 | /// } 105 | /// 106 | /// impl Append for () { 107 | /// type Out = (Item,); 108 | /// fn append(_: Self, item: Item) -> Self::Out { 109 | /// (item,) 110 | /// } 111 | /// } 112 | /// 113 | /// macro_rules! impl_append { 114 | /// ($(($P:ident, $p:ident)),*) => { 115 | /// impl<$($P),*> Append for ($($P,)*) { 116 | /// type Out = ($($P),*, Item); 117 | /// fn append(($($p,)*): Self, item: Item) -> Self::Out { 118 | /// ($($p),*, item) 119 | /// } 120 | /// } 121 | /// } 122 | /// } 123 | /// 124 | /// // start from 1 element to 15 elements 125 | /// all_tuples!(impl_append, 1, 15, P, p); 126 | /// // impl_append!((P0, p0)); 127 | /// // impl_append!((P0, p0), (P1, p1)); 128 | /// // impl_append!((P0, p0), (P1, p1), (P2, p2)); 129 | /// // .. 130 | /// // impl_append!((P0, p0) .. (P14, p14)); 131 | /// 132 | /// // start from 16 elements to 20 133 | /// all_tuples!(impl_append, 16, 20, P, p); 134 | /// // impl_append!((P0, p0) .. (P15, p15)); 135 | /// // .. 136 | /// // impl_append!((P0, p0) .. (P19, p19)); 137 | /// ``` 138 | /// 139 | /// **`#[doc(fake_variadic)]`** 140 | /// 141 | /// To improve the readability of your docs when implementing a trait for 142 | /// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker. 143 | /// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`. 144 | /// 145 | /// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro 146 | /// is that you have to accept attributes using `$(#[$meta:meta])*`. 147 | /// 148 | /// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default. 149 | /// Add the following to your lib.rs if not already present: 150 | /// 151 | /// ``` 152 | /// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` 153 | /// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] 154 | /// ``` 155 | /// 156 | /// ``` 157 | /// # use variadics_please::all_tuples; 158 | /// # 159 | /// trait Variadic {} 160 | /// 161 | /// impl Variadic for () {} 162 | /// 163 | /// macro_rules! impl_variadic { 164 | /// ($(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { 165 | /// $(#[$meta])* 166 | /// impl<$($P),*> Variadic for ($($P,)*) {} 167 | /// } 168 | /// } 169 | /// 170 | /// all_tuples!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p); 171 | /// ``` 172 | #[proc_macro] 173 | pub fn all_tuples(input: TokenStream) -> TokenStream { 174 | let input = parse_macro_input!(input as AllTuples); 175 | let ident_tuples = (0..input.end) 176 | .map(|i| { 177 | let idents = input 178 | .idents 179 | .iter() 180 | .map(|ident| format_ident!("{}{}", ident, i)); 181 | to_ident_tuple(idents, input.idents.len()) 182 | }) 183 | .collect::>(); 184 | let macro_ident = &input.macro_ident; 185 | let invocations = (input.start..=input.end).map(|n| { 186 | let ident_tuples = choose_ident_tuples(&input, &ident_tuples, n); 187 | let attrs = attrs(&input, n); 188 | quote! { 189 | #macro_ident!(#attrs #ident_tuples); 190 | } 191 | }); 192 | TokenStream::from(quote! { 193 | #( 194 | #invocations 195 | )* 196 | }) 197 | } 198 | 199 | /// A variant of [`all_tuples!`] that enumerates its output. 200 | /// 201 | /// In particular, the tuples used by the inner macro will themselves be composed 202 | /// of tuples which contain the index. 203 | /// 204 | /// For example, with a single parameter: 205 | /// ``` 206 | /// # use variadics_please::all_tuples_enumerated; 207 | /// 208 | /// trait Squawk { 209 | /// fn squawk(&self); 210 | /// } 211 | /// 212 | /// // If every type in a tuple is `Squawk`, the tuple can squawk by having its 213 | /// // constituents squawk sequentially: 214 | /// macro_rules! impl_squawk { 215 | /// ($(($n:tt, $T:ident)),*) => { 216 | /// impl<$($T: Squawk),*> Squawk for ($($T,)*) { 217 | /// fn squawk(&self) { 218 | /// $( 219 | /// self.$n.squawk(); 220 | /// )* 221 | /// } 222 | /// } 223 | /// }; 224 | /// } 225 | /// 226 | /// all_tuples_enumerated!(impl_squawk, 1, 15, T); 227 | /// // impl_squawk!((0, T0)); 228 | /// // impl_squawk!((0, T0), (1, T1)); 229 | /// // .. 230 | /// // impl_squawk!((0, T0) .. (14, T14)); 231 | /// 232 | /// all_tuples_enumerated!(impl_squawk, 16, 20, T); 233 | /// // impl_append!((0, T0) .. (15, T15)); 234 | /// // .. 235 | /// // impl_append!((0, T0) .. (19, T19)); 236 | /// ``` 237 | /// 238 | /// With multiple parameters, the result is similar, but with the additional parameters 239 | /// included in each tuple; e.g.: 240 | /// ```ignore 241 | /// all_tuples_enumerated!(impl_squawk, 1, 15, P, p); 242 | /// // impl_squawk!((0, P0, p0)); 243 | /// // impl_squawk!((0, P0, p0), (1, P1, p1)); 244 | /// // .. 245 | /// // impl_squawk!((0, P0, p0) .. (14, P14, p14)); 246 | /// 247 | /// all_tuples_enumerated!(impl_append, 16, 20, P, p); 248 | /// // impl_append!((0, P0, p0) .. (15, P15, p15)); 249 | /// // .. 250 | /// // impl_append!((0, P0, p0) .. (19, P19, p19)); 251 | /// ``` 252 | #[proc_macro] 253 | pub fn all_tuples_enumerated(input: TokenStream) -> TokenStream { 254 | let input = parse_macro_input!(input as AllTuples); 255 | let ident_tuples = (0..input.end) 256 | .map(|i| { 257 | let idents = input 258 | .idents 259 | .iter() 260 | .map(|ident| format_ident!("{}{}", ident, i)); 261 | to_ident_tuple_enumerated(idents, i) 262 | }) 263 | .collect::>(); 264 | let macro_ident = &input.macro_ident; 265 | let invocations = (input.start..=input.end).map(|n| { 266 | let ident_tuples = choose_ident_tuples_enumerated(&input, &ident_tuples, n); 267 | let attrs = attrs(&input, n); 268 | quote! { 269 | #macro_ident!(#attrs #ident_tuples); 270 | } 271 | }); 272 | TokenStream::from(quote! { 273 | #( 274 | #invocations 275 | )* 276 | }) 277 | } 278 | 279 | /// Helper macro to generate tuple pyramids with their length. Useful to generate scaffolding to 280 | /// work around Rust lacking variadics. Invoking `all_tuples_with_size!(impl_foo, start, end, P, Q, ..)` 281 | /// invokes `impl_foo` providing ident tuples through arity `start..end` preceded by their length. 282 | /// If you don't require the length of the tuple, see [`all_tuples!`]. 283 | /// 284 | /// # Examples 285 | /// 286 | /// ## Single parameter 287 | /// 288 | /// ``` 289 | /// # use core::marker::PhantomData; 290 | /// # use variadics_please::all_tuples_with_size; 291 | /// # 292 | /// struct Foo { 293 | /// // .. 294 | /// # _phantom: PhantomData 295 | /// } 296 | /// 297 | /// trait WrappedInFoo { 298 | /// type Tup; 299 | /// const LENGTH: usize; 300 | /// } 301 | /// 302 | /// macro_rules! impl_wrapped_in_foo { 303 | /// ($N:expr, $($T:ident),*) => { 304 | /// impl<$($T),*> WrappedInFoo for ($($T,)*) { 305 | /// type Tup = ($(Foo<$T>,)*); 306 | /// const LENGTH: usize = $N; 307 | /// } 308 | /// }; 309 | /// } 310 | /// 311 | /// all_tuples_with_size!(impl_wrapped_in_foo, 0, 15, T); 312 | /// // impl_wrapped_in_foo!(0); 313 | /// // impl_wrapped_in_foo!(1, T0); 314 | /// // impl_wrapped_in_foo!(2, T0, T1); 315 | /// // .. 316 | /// // impl_wrapped_in_foo!(15, T0 .. T14); 317 | /// 318 | /// all_tuples_with_size!(impl_wrapped_in_foo, 16, 20, T); 319 | /// // impl_wrapped_in_foo!(16, T0 .. T15); 320 | /// // .. 321 | /// // impl_wrapped_in_foo!(20, T0 .. T19); 322 | /// ``` 323 | /// 324 | /// ## Multiple parameters 325 | /// 326 | /// ``` 327 | /// # use variadics_please::all_tuples_with_size; 328 | /// # 329 | /// trait Append { 330 | /// type Out; 331 | /// fn append(tup: Self, item: Item) -> Self::Out; 332 | /// } 333 | /// 334 | /// impl Append for () { 335 | /// type Out = (Item,); 336 | /// fn append(_: Self, item: Item) -> Self::Out { 337 | /// (item,) 338 | /// } 339 | /// } 340 | /// 341 | /// macro_rules! impl_append { 342 | /// ($N:expr, $(($P:ident, $p:ident)),*) => { 343 | /// impl<$($P),*> Append for ($($P,)*) { 344 | /// type Out = ($($P),*, Item); 345 | /// fn append(($($p,)*): Self, item: Item) -> Self::Out { 346 | /// ($($p),*, item) 347 | /// } 348 | /// } 349 | /// } 350 | /// } 351 | /// 352 | /// all_tuples_with_size!(impl_append, 1, 15, P, p); 353 | /// // impl_append!(1, (P0, p0)); 354 | /// // impl_append!(2, (P0, p0), (P1, p1)); 355 | /// // impl_append!(3, (P0, p0), (P1, p1), (P2, p2)); 356 | /// // .. 357 | /// // impl_append!(15, (P0, p0) .. (P14, p14)); 358 | /// 359 | /// all_tuples_with_size!(impl_append, 16, 20, P, p); 360 | /// // impl_append!(16, (P0, p0) .. (P15, p15)); 361 | /// // .. 362 | /// // impl_append!(20, (P0, p0) .. (P19, p19)); 363 | /// ``` 364 | /// 365 | /// **`#[doc(fake_variadic)]`** 366 | /// 367 | /// To improve the readability of your docs when implementing a trait for 368 | /// tuples or fn pointers of varying length you can use the rustdoc-internal `fake_variadic` marker. 369 | /// All your impls are collapsed and shown as a single `impl Trait for (F₁, F₂, …, Fₙ)`. 370 | /// 371 | /// The `all_tuples!` macro does most of the work for you, the only change to your implementation macro 372 | /// is that you have to accept attributes using `$(#[$meta:meta])*`. 373 | /// 374 | /// Since this feature requires a nightly compiler, it's only enabled on docs.rs by default. 375 | /// Add the following to your lib.rs if not already present: 376 | /// 377 | /// ``` 378 | /// // `rustdoc_internals` is needed for `#[doc(fake_variadics)]` 379 | /// #![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))] 380 | /// ``` 381 | /// 382 | /// ``` 383 | /// # use variadics_please::all_tuples_with_size; 384 | /// # 385 | /// trait Variadic {} 386 | /// 387 | /// impl Variadic for () {} 388 | /// 389 | /// macro_rules! impl_variadic { 390 | /// ($N:expr, $(#[$meta:meta])* $(($P:ident, $p:ident)),*) => { 391 | /// $(#[$meta])* 392 | /// impl<$($P),*> Variadic for ($($P,)*) {} 393 | /// } 394 | /// } 395 | /// 396 | /// all_tuples_with_size!(#[doc(fake_variadic)] impl_variadic, 1, 15, P, p); 397 | /// ``` 398 | #[proc_macro] 399 | pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { 400 | let input = parse_macro_input!(input as AllTuples); 401 | let ident_tuples = (0..input.end) 402 | .map(|i| { 403 | let idents = input 404 | .idents 405 | .iter() 406 | .map(|ident| format_ident!("{}{}", ident, i)); 407 | to_ident_tuple(idents, input.idents.len()) 408 | }) 409 | .collect::>(); 410 | let macro_ident = &input.macro_ident; 411 | let invocations = (input.start..=input.end).map(|n| { 412 | let ident_tuples = choose_ident_tuples(&input, &ident_tuples, n); 413 | let attrs = attrs(&input, n); 414 | quote! { 415 | #macro_ident!(#n, #attrs #ident_tuples); 416 | } 417 | }); 418 | TokenStream::from(quote! { 419 | #( 420 | #invocations 421 | )* 422 | }) 423 | } 424 | 425 | /// Parses the attribute `#[doc(fake_variadic)]` 426 | fn parse_fake_variadic_attr(input: ParseStream) -> Result { 427 | let attribute = match input.call(Attribute::parse_outer)? { 428 | attributes if attributes.is_empty() => return Ok(false), 429 | attributes if attributes.len() == 1 => attributes[0].clone(), 430 | attributes => { 431 | return Err(Error::new( 432 | input.span(), 433 | format!("Expected exactly one attribute, got {}", attributes.len()), 434 | )) 435 | } 436 | }; 437 | 438 | if attribute.path().is_ident("doc") { 439 | let nested = attribute.parse_args::()?; 440 | if nested == "fake_variadic" { 441 | return Ok(true); 442 | } 443 | } 444 | 445 | Err(Error::new( 446 | attribute.meta.span(), 447 | "Unexpected attribute".to_string(), 448 | )) 449 | } 450 | 451 | fn choose_ident_tuples(input: &AllTuples, ident_tuples: &[TokenStream2], n: usize) -> TokenStream2 { 452 | // `rustdoc` uses the first ident to generate nice 453 | // idents with subscript numbers e.g. (F₁, F₂, …, Fₙ). 454 | // We don't want two numbers, so we use the 455 | // original, unnumbered idents for this case. 456 | if input.fake_variadic && n == 1 { 457 | let ident_tuple = to_ident_tuple(input.idents.iter().cloned(), input.idents.len()); 458 | quote! { #ident_tuple } 459 | } else { 460 | let ident_tuples = &ident_tuples[..n]; 461 | quote! { #(#ident_tuples),* } 462 | } 463 | } 464 | 465 | fn choose_ident_tuples_enumerated( 466 | input: &AllTuples, 467 | ident_tuples: &[TokenStream2], 468 | n: usize, 469 | ) -> TokenStream2 { 470 | if input.fake_variadic && n == 1 { 471 | let ident_tuple = to_ident_tuple_enumerated(input.idents.iter().cloned(), 0); 472 | quote! { #ident_tuple } 473 | } else { 474 | let ident_tuples = &ident_tuples[..n]; 475 | quote! { #(#ident_tuples),* } 476 | } 477 | } 478 | 479 | fn to_ident_tuple(idents: impl Iterator, len: usize) -> TokenStream2 { 480 | if len < 2 { 481 | quote! { #(#idents)* } 482 | } else { 483 | quote! { (#(#idents),*) } 484 | } 485 | } 486 | 487 | /// Like `to_ident_tuple`, but it enumerates the identifiers 488 | fn to_ident_tuple_enumerated(idents: impl Iterator, idx: usize) -> TokenStream2 { 489 | let idx = Literal::usize_unsuffixed(idx); 490 | quote! { (#idx, #(#idents),*) } 491 | } 492 | 493 | /// n: number of elements 494 | fn attrs(input: &AllTuples, n: usize) -> TokenStream2 { 495 | if !input.fake_variadic { 496 | return TokenStream2::default(); 497 | } 498 | match n { 499 | // An empty tuple (i.e. the unit type) is still documented separately, 500 | // so no `#[doc(hidden)]` here. 501 | 0 => TokenStream2::default(), 502 | n => { 503 | let cfg = quote! { any(docsrs, docsrs_dep) }; 504 | // The `#[doc(fake_variadic)]` attr has to be on the first impl block. 505 | if n == 1 { 506 | let doc = LitStr::new( 507 | &format!( 508 | "This trait is implemented for tuples down to {} up to {} items long.", 509 | input.start, input.end 510 | ), 511 | Span2::call_site(), 512 | ); 513 | quote! { 514 | #[cfg_attr(#cfg, doc(fake_variadic))] 515 | #[cfg_attr(#cfg, doc = #doc)] 516 | } 517 | } else { 518 | quote! { #[cfg_attr(#cfg, doc(hidden))] } 519 | } 520 | } 521 | } 522 | } 523 | --------------------------------------------------------------------------------