├── .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 | 
9 | [](https://crates.io/crates/variadics_please)
10 | [](https://crates.io/crates/variadics_please)
11 | [](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 |
--------------------------------------------------------------------------------