├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build ├── build.rs └── rustc.rs ├── src ├── attr.rs ├── bound.rs ├── constfn.rs ├── date.rs ├── error.rs ├── expand.rs ├── expr.rs ├── iter.rs ├── lib.rs ├── release.rs ├── time.rs ├── token.rs └── version.rs └── tests ├── compiletest.rs ├── test_const.rs ├── test_eval.rs ├── test_parse.rs └── ui ├── bad-bound.rs ├── bad-bound.stderr ├── bad-date.rs ├── bad-date.stderr ├── bad-not.rs ├── bad-not.stderr ├── bad-version.rs ├── bad-version.stderr ├── const-not-fn.rs └── const-not-fn.stderr /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable, 1.56.0] 28 | timeout-minutes: 45 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: ${{matrix.rust}} 34 | - name: Enable type layout randomization 35 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 36 | if: matrix.rust == 'nightly' 37 | - run: cargo test 38 | - name: RUSTFLAGS=-Zfmt-debug=none cargo test 39 | run: RUSTFLAGS=${RUSTFLAGS}\ -Zfmt-debug=none cargo test 40 | if: matrix.rust == 'nightly' 41 | - uses: actions/upload-artifact@v4 42 | if: matrix.rust == 'nightly' && always() 43 | with: 44 | name: Cargo.lock 45 | path: Cargo.lock 46 | continue-on-error: true 47 | 48 | windows: 49 | name: Windows 50 | needs: pre_ci 51 | if: needs.pre_ci.outputs.continue 52 | runs-on: windows-latest 53 | timeout-minutes: 45 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: dtolnay/rust-toolchain@stable 57 | - run: cargo test 58 | 59 | msrv: 60 | name: Rust 1.31.0 61 | needs: pre_ci 62 | if: needs.pre_ci.outputs.continue 63 | runs-on: ubuntu-latest 64 | timeout-minutes: 45 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: dtolnay/rust-toolchain@1.31.0 68 | - run: cargo check 69 | 70 | doc: 71 | name: Documentation 72 | needs: pre_ci 73 | if: needs.pre_ci.outputs.continue 74 | runs-on: ubuntu-latest 75 | timeout-minutes: 45 76 | env: 77 | RUSTDOCFLAGS: -Dwarnings 78 | steps: 79 | - uses: actions/checkout@v4 80 | - uses: dtolnay/rust-toolchain@nightly 81 | - uses: dtolnay/install@cargo-docs-rs 82 | - run: cargo docs-rs 83 | 84 | clippy: 85 | name: Clippy 86 | runs-on: ubuntu-latest 87 | if: github.event_name != 'pull_request' 88 | timeout-minutes: 45 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: dtolnay/rust-toolchain@clippy 92 | - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic 93 | 94 | miri: 95 | name: Miri 96 | needs: pre_ci 97 | if: needs.pre_ci.outputs.continue 98 | runs-on: ubuntu-latest 99 | timeout-minutes: 45 100 | steps: 101 | - uses: actions/checkout@v4 102 | - uses: dtolnay/rust-toolchain@master 103 | with: 104 | toolchain: nightly-2025-05-16 # https://github.com/rust-lang/miri/issues/4323 105 | components: miri, rust-src 106 | - run: cargo miri setup 107 | - run: cargo miri test 108 | env: 109 | MIRIFLAGS: -Zmiri-strict-provenance 110 | 111 | outdated: 112 | name: Outdated 113 | runs-on: ubuntu-latest 114 | if: github.event_name != 'pull_request' 115 | timeout-minutes: 45 116 | steps: 117 | - uses: actions/checkout@v4 118 | - uses: dtolnay/rust-toolchain@stable 119 | - uses: dtolnay/install@cargo-outdated 120 | - run: cargo outdated --workspace --exit-code 1 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustversion" 3 | version = "1.0.21" 4 | authors = ["David Tolnay "] 5 | build = "build/build.rs" 6 | categories = ["development-tools::build-utils", "no-std", "no-std::no-alloc"] 7 | description = "Conditional compilation according to rustc compiler version" 8 | documentation = "https://docs.rs/rustversion" 9 | edition = "2018" 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/dtolnay/rustversion" 12 | rust-version = "1.31" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dev-dependencies] 18 | trybuild = { version = "1.0.49", features = ["diff"] } 19 | 20 | [package.metadata.docs.rs] 21 | targets = ["x86_64-unknown-linux-gnu"] 22 | rustdoc-args = [ 23 | "--generate-link-to-definition", 24 | "--extern-html-root-url=core=https://doc.rust-lang.org", 25 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 26 | "--extern-html-root-url=std=https://doc.rust-lang.org", 27 | "--extern-html-root-url=proc_macro=https://doc.rust-lang.org", 28 | ] 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Compiler version cfg 2 | ==================== 3 | 4 | [github](https://github.com/dtolnay/rustversion) 5 | [crates.io](https://crates.io/crates/rustversion) 6 | [docs.rs](https://docs.rs/rustversion) 7 | [build status](https://github.com/dtolnay/rustversion/actions?query=branch%3Amaster) 8 | 9 | This crate provides macros for conditional compilation according to rustc 10 | compiler version, analogous to [`#[cfg(...)]`][cfg] and 11 | [`#[cfg_attr(...)]`][cfg_attr]. 12 | 13 | [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute 14 | [cfg_attr]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute 15 | 16 | ```toml 17 | [dependencies] 18 | rustversion = "1.0" 19 | ``` 20 | 21 |
22 | 23 | ## Selectors 24 | 25 | - `#[rustversion::stable]` 26 | —
27 | True on any stable compiler. 28 | 29 | - `#[rustversion::stable(1.34)]` 30 | —
31 | True on exactly the specified stable compiler. 32 | 33 | - `#[rustversion::beta]` 34 | —
35 | True on any beta compiler. 36 | 37 | - `#[rustversion::nightly]` 38 | —
39 | True on any nightly compiler or dev build. 40 | 41 | - `#[rustversion::nightly(2025-01-01)]` 42 | —
43 | True on exactly one nightly. 44 | 45 | - `#[rustversion::since(1.34)]` 46 | —
47 | True on that stable release and any later compiler, including beta and 48 | nightly. 49 | 50 | - `#[rustversion::since(2025-01-01)]` 51 | —
52 | True on that nightly and all newer ones. 53 | 54 | - `#[rustversion::before(`version or date`)]` 55 | —
56 | Negative of *#[rustversion::since(...)]*. 57 | 58 | - `#[rustversion::not(`selector`)]` 59 | —
60 | Negative of any selector; for example *#[rustversion::not(nightly)]*. 61 | 62 | - `#[rustversion::any(`selectors...`)]` 63 | —
64 | True if any of the comma-separated selectors is true; for example 65 | *#[rustversion::any(stable, beta)]*. 66 | 67 | - `#[rustversion::all(`selectors...`)]` 68 | —
69 | True if all of the comma-separated selectors are true; for example 70 | *#[rustversion::all(since(1.31), before(1.34))]*. 71 | 72 | - `#[rustversion::attr(`selector`, `attribute`)]` 73 | —
74 | For conditional inclusion of attributes; analogous to `cfg_attr`. 75 | 76 | - `rustversion::cfg!(`selector`)` 77 | —
78 | An expression form of any of the above attributes; for example 79 | *if rustversion::cfg!(any(stable, beta)) { ... }*. 80 | 81 |
82 | 83 | ## Use cases 84 | 85 | Providing additional trait impls as types are stabilized in the standard library 86 | without breaking compatibility with older compilers; in this case Pin\ 87 | stabilized in [Rust 1.33][pin]: 88 | 89 | [pin]: https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html#pinning 90 | 91 | ```rust 92 | #[rustversion::since(1.33)] 93 | use std::pin::Pin; 94 | 95 | #[rustversion::since(1.33)] 96 | impl MyTrait for Pin

{ 97 | /* ... */ 98 | } 99 | ``` 100 | 101 | Similar but for language features; the ability to control alignment greater than 102 | 1 of packed structs was stabilized in [Rust 1.33][packed]. 103 | 104 | [packed]: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1330-2019-02-28 105 | 106 | ```rust 107 | #[rustversion::attr(before(1.33), repr(packed))] 108 | #[rustversion::attr(since(1.33), repr(packed(2)))] 109 | struct Six(i16, i32); 110 | 111 | fn main() { 112 | println!("{}", std::mem::align_of::()); 113 | } 114 | ``` 115 | 116 | Augmenting code with `const` as const impls are stabilized in the standard 117 | library. This use of `const` as an attribute is recognized as a special case by 118 | the rustversion::attr macro. 119 | 120 | ```rust 121 | use std::time::Duration; 122 | 123 | #[rustversion::attr(since(1.32), const)] 124 | fn duration_as_days(dur: Duration) -> u64 { 125 | dur.as_secs() / 60 / 60 / 24 126 | } 127 | ``` 128 | 129 | Emitting Cargo cfg directives from a build script. Note that this requires 130 | listing `rustversion` under `[build-dependencies]` in Cargo.toml, not 131 | `[dependencies]`. 132 | 133 | ```rust 134 | // build.rs 135 | 136 | fn main() { 137 | if rustversion::cfg!(since(1.36)) { 138 | println!("cargo:rustc-cfg=no_std"); 139 | } 140 | } 141 | ``` 142 | 143 | ```rust 144 | // src/lib.rs 145 | 146 | #![cfg_attr(no_std, no_std)] 147 | 148 | #[cfg(no_std)] 149 | extern crate alloc; 150 | ``` 151 | 152 |
153 | 154 | #### License 155 | 156 | 157 | Licensed under either of Apache License, Version 158 | 2.0 or MIT license at your option. 159 | 160 | 161 |
162 | 163 | 164 | Unless you explicitly state otherwise, any contribution intentionally submitted 165 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 166 | be dual licensed as above, without any additional terms or conditions. 167 | 168 | -------------------------------------------------------------------------------- /build/build.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::elidable_lifetime_names, 3 | clippy::enum_glob_use, 4 | clippy::must_use_candidate, 5 | clippy::single_match_else 6 | )] 7 | 8 | mod rustc; 9 | 10 | use std::env; 11 | use std::ffi::OsString; 12 | use std::fmt::{self, Debug, Display}; 13 | use std::fs; 14 | use std::iter; 15 | use std::path::Path; 16 | use std::process::{self, Command}; 17 | 18 | fn main() { 19 | println!("cargo:rerun-if-changed=build/build.rs"); 20 | 21 | let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); 22 | let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty()); 23 | let wrapped_rustc = rustc_wrapper.iter().chain(iter::once(&rustc)); 24 | 25 | let mut is_clippy_driver = false; 26 | let mut is_mirai = false; 27 | let version = loop { 28 | let mut command; 29 | if is_mirai { 30 | command = Command::new(&rustc); 31 | } else { 32 | let mut wrapped_rustc = wrapped_rustc.clone(); 33 | command = Command::new(wrapped_rustc.next().unwrap()); 34 | command.args(wrapped_rustc); 35 | } 36 | if is_clippy_driver { 37 | command.arg("--rustc"); 38 | } 39 | command.arg("--version"); 40 | 41 | let output = match command.output() { 42 | Ok(output) => output, 43 | Err(e) => { 44 | let rustc = rustc.to_string_lossy(); 45 | eprintln!("Error: failed to run `{} --version`: {}", rustc, e); 46 | process::exit(1); 47 | } 48 | }; 49 | 50 | let string = match String::from_utf8(output.stdout) { 51 | Ok(string) => string, 52 | Err(e) => { 53 | let rustc = rustc.to_string_lossy(); 54 | eprintln!( 55 | "Error: failed to parse output of `{} --version`: {}", 56 | rustc, e, 57 | ); 58 | process::exit(1); 59 | } 60 | }; 61 | 62 | break match rustc::parse(&string) { 63 | rustc::ParseResult::Success(version) => version, 64 | rustc::ParseResult::OopsClippy if !is_clippy_driver => { 65 | is_clippy_driver = true; 66 | continue; 67 | } 68 | rustc::ParseResult::OopsMirai if !is_mirai && rustc_wrapper.is_some() => { 69 | is_mirai = true; 70 | continue; 71 | } 72 | rustc::ParseResult::Unrecognized 73 | | rustc::ParseResult::OopsClippy 74 | | rustc::ParseResult::OopsMirai => { 75 | eprintln!( 76 | "Error: unexpected output from `rustc --version`: {:?}\n\n\ 77 | Please file an issue in https://github.com/dtolnay/rustversion", 78 | string 79 | ); 80 | process::exit(1); 81 | } 82 | }; 83 | }; 84 | 85 | if version.minor < 38 { 86 | // Prior to 1.38, a #[proc_macro] is not allowed to be named `cfg`. 87 | println!("cargo:rustc-cfg=cfg_macro_not_allowed"); 88 | } 89 | 90 | if version.minor >= 80 { 91 | println!("cargo:rustc-check-cfg=cfg(cfg_macro_not_allowed)"); 92 | println!("cargo:rustc-check-cfg=cfg(host_os, values(\"windows\"))"); 93 | } 94 | 95 | let version = format!("{:#}\n", Render(&version)); 96 | let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set"); 97 | let out_file = Path::new(&out_dir).join("version.expr"); 98 | fs::write(out_file, version).expect("failed to write version.expr"); 99 | 100 | let host = env::var_os("HOST").expect("HOST not set"); 101 | if let Some("windows") = host.to_str().unwrap().split('-').nth(2) { 102 | println!("cargo:rustc-cfg=host_os=\"windows\""); 103 | } 104 | } 105 | 106 | // Shim Version's {:?} format into a {} format, because {:?} is unusable in 107 | // format strings when building with `-Zfmt-debug=none`. 108 | struct Render<'a>(&'a rustc::Version); 109 | 110 | impl<'a> Display for Render<'a> { 111 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 112 | Debug::fmt(self.0, formatter) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /build/rustc.rs: -------------------------------------------------------------------------------- 1 | use self::Channel::*; 2 | use std::fmt::{self, Debug}; 3 | 4 | pub enum ParseResult { 5 | Success(Version), 6 | OopsClippy, 7 | OopsMirai, 8 | Unrecognized, 9 | } 10 | 11 | #[cfg_attr(test, derive(PartialEq))] 12 | pub struct Version { 13 | pub minor: u16, 14 | pub patch: u16, 15 | pub channel: Channel, 16 | } 17 | 18 | #[cfg_attr(test, derive(PartialEq))] 19 | pub enum Channel { 20 | Stable, 21 | Beta, 22 | Nightly(Date), 23 | Dev, 24 | } 25 | 26 | #[cfg_attr(test, derive(PartialEq))] 27 | pub struct Date { 28 | pub year: u16, 29 | pub month: u8, 30 | pub day: u8, 31 | } 32 | 33 | pub fn parse(string: &str) -> ParseResult { 34 | let last_line = string.lines().last().unwrap_or(string); 35 | let mut words = last_line.trim().split(' '); 36 | 37 | match words.next() { 38 | Some("rustc") => {} 39 | Some(word) if word.starts_with("clippy") => return ParseResult::OopsClippy, 40 | Some("mirai") => return ParseResult::OopsMirai, 41 | Some(_) | None => return ParseResult::Unrecognized, 42 | } 43 | 44 | parse_words(&mut words).map_or(ParseResult::Unrecognized, ParseResult::Success) 45 | } 46 | 47 | fn parse_words(words: &mut dyn Iterator) -> Option { 48 | let mut version_channel = words.next()?.split('-'); 49 | let version = version_channel.next()?; 50 | let channel = version_channel.next(); 51 | 52 | let mut digits = version.split('.'); 53 | let major = digits.next()?; 54 | if major != "1" { 55 | return None; 56 | } 57 | let minor = digits.next()?.parse().ok()?; 58 | let patch = digits.next().unwrap_or("0").parse().ok()?; 59 | 60 | let channel = match channel { 61 | None => Stable, 62 | Some("dev") => Dev, 63 | Some(channel) if channel.starts_with("beta") => Beta, 64 | Some("nightly") => match words.next() { 65 | Some(hash) if hash.starts_with('(') => match words.next() { 66 | None if hash.ends_with(')') => Dev, 67 | Some(date) if date.ends_with(')') => { 68 | let mut date = date[..date.len() - 1].split('-'); 69 | let year = date.next()?.parse().ok()?; 70 | let month = date.next()?.parse().ok()?; 71 | let day = date.next()?.parse().ok()?; 72 | match date.next() { 73 | None => Nightly(Date { year, month, day }), 74 | Some(_) => return None, 75 | } 76 | } 77 | None | Some(_) => return None, 78 | }, 79 | Some(_) => return None, 80 | None => Dev, 81 | }, 82 | Some(_) => return None, 83 | }; 84 | 85 | Some(Version { 86 | minor, 87 | patch, 88 | channel, 89 | }) 90 | } 91 | 92 | impl Debug for Version { 93 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 94 | formatter 95 | .debug_struct("crate::version::Version") 96 | .field("minor", &self.minor) 97 | .field("patch", &self.patch) 98 | .field("channel", &self.channel) 99 | .finish() 100 | } 101 | } 102 | 103 | impl Debug for Channel { 104 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 105 | match self { 106 | Channel::Stable => formatter.write_str("crate::version::Channel::Stable"), 107 | Channel::Beta => formatter.write_str("crate::version::Channel::Beta"), 108 | Channel::Nightly(date) => formatter 109 | .debug_tuple("crate::version::Channel::Nightly") 110 | .field(date) 111 | .finish(), 112 | Channel::Dev => formatter.write_str("crate::version::Channel::Dev"), 113 | } 114 | } 115 | } 116 | 117 | impl Debug for Date { 118 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 119 | formatter 120 | .debug_struct("crate::date::Date") 121 | .field("year", &self.year) 122 | .field("month", &self.month) 123 | .field("day", &self.day) 124 | .finish() 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/attr.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::expr::{self, Expr}; 3 | use crate::{iter, token}; 4 | use proc_macro::{Span, TokenStream}; 5 | 6 | pub struct Args { 7 | pub condition: Expr, 8 | pub then: Then, 9 | } 10 | 11 | pub enum Then { 12 | Const(Span), 13 | Attribute(TokenStream), 14 | } 15 | 16 | pub fn parse(input: TokenStream) -> Result { 17 | let ref mut input = iter::new(input); 18 | let condition = expr::parse(input)?; 19 | 20 | token::parse_punct(input, ',')?; 21 | if input.peek().is_none() { 22 | return Err(Error::new(Span::call_site(), "expected one or more attrs")); 23 | } 24 | 25 | let const_span = token::parse_optional_keyword(input, "const"); 26 | let then = if let Some(const_span) = const_span { 27 | token::parse_optional_punct(input, ','); 28 | token::parse_end(input)?; 29 | Then::Const(const_span) 30 | } else { 31 | Then::Attribute(input.collect()) 32 | }; 33 | 34 | Ok(Args { condition, then }) 35 | } 36 | -------------------------------------------------------------------------------- /src/bound.rs: -------------------------------------------------------------------------------- 1 | use crate::date::{self, Date}; 2 | use crate::error::{Error, Result}; 3 | use crate::iter::Iter; 4 | use crate::release::{self, Release}; 5 | use crate::time; 6 | use crate::version::{Channel::*, Version}; 7 | use proc_macro::{Group, TokenTree}; 8 | use std::cmp::Ordering; 9 | 10 | pub enum Bound { 11 | Nightly(Date), 12 | Stable(Release), 13 | } 14 | 15 | pub fn parse(paren: Group, iter: Iter) -> Result { 16 | if let Some(TokenTree::Literal(literal)) = iter.peek() { 17 | let repr = literal.to_string(); 18 | if repr.starts_with(|ch: char| ch.is_ascii_digit()) { 19 | if repr.contains('.') { 20 | return release::parse(paren, iter).map(Bound::Stable); 21 | } else { 22 | return date::parse(paren, iter).map(Bound::Nightly); 23 | } 24 | } 25 | } 26 | let msg = format!( 27 | "expected rustc release number like 1.85, or nightly date like {}", 28 | time::today(), 29 | ); 30 | Err(Error::group(paren, msg)) 31 | } 32 | 33 | impl PartialEq for Version { 34 | fn eq(&self, rhs: &Bound) -> bool { 35 | match rhs { 36 | Bound::Nightly(date) => match self.channel { 37 | Stable | Beta | Dev => false, 38 | Nightly(nightly) => nightly == *date, 39 | }, 40 | Bound::Stable(release) => { 41 | self.minor == release.minor 42 | && release.patch.map_or(true, |patch| self.patch == patch) 43 | } 44 | } 45 | } 46 | } 47 | 48 | impl PartialOrd for Version { 49 | fn partial_cmp(&self, rhs: &Bound) -> Option { 50 | match rhs { 51 | Bound::Nightly(date) => match self.channel { 52 | Stable | Beta => Some(Ordering::Less), 53 | Nightly(nightly) => Some(nightly.cmp(date)), 54 | Dev => Some(Ordering::Greater), 55 | }, 56 | Bound::Stable(release) => { 57 | let version = (self.minor, self.patch); 58 | let bound = (release.minor, release.patch.unwrap_or(0)); 59 | Some(version.cmp(&bound)) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/constfn.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use proc_macro::{Ident, Span, TokenStream, TokenTree}; 3 | use std::iter; 4 | 5 | #[derive(PartialOrd, PartialEq)] 6 | enum Qualifiers { 7 | None, 8 | Async, 9 | Unsafe, 10 | Extern, 11 | Abi, 12 | } 13 | 14 | impl Qualifiers { 15 | fn from_ident(ident: &Ident) -> Self { 16 | match ident.to_string().as_str() { 17 | "async" => Qualifiers::Async, 18 | "unsafe" => Qualifiers::Unsafe, 19 | "extern" => Qualifiers::Extern, 20 | _ => Qualifiers::None, 21 | } 22 | } 23 | } 24 | 25 | pub(crate) fn insert_const(input: TokenStream, const_span: Span) -> Result { 26 | let ref mut input = crate::iter::new(input); 27 | let mut out = TokenStream::new(); 28 | let mut qualifiers = Qualifiers::None; 29 | let mut pending = Vec::new(); 30 | 31 | while let Some(token) = input.next() { 32 | match token { 33 | TokenTree::Ident(ref ident) if ident.to_string() == "fn" => { 34 | let const_ident = Ident::new("const", const_span); 35 | out.extend(iter::once(TokenTree::Ident(const_ident))); 36 | out.extend(pending); 37 | out.extend(iter::once(token)); 38 | out.extend(input); 39 | return Ok(out); 40 | } 41 | TokenTree::Ident(ref ident) if Qualifiers::from_ident(ident) > qualifiers => { 42 | qualifiers = Qualifiers::from_ident(ident); 43 | pending.push(token); 44 | } 45 | TokenTree::Literal(_) if qualifiers == Qualifiers::Extern => { 46 | qualifiers = Qualifiers::Abi; 47 | pending.push(token); 48 | } 49 | _ => { 50 | qualifiers = Qualifiers::None; 51 | out.extend(pending.drain(..)); 52 | out.extend(iter::once(token)); 53 | } 54 | } 55 | } 56 | 57 | Err(Error::new(const_span, "only allowed on a fn item")) 58 | } 59 | -------------------------------------------------------------------------------- /src/date.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::iter::Iter; 3 | use crate::{time, token}; 4 | use proc_macro::Group; 5 | use std::fmt::{self, Display}; 6 | 7 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 8 | pub struct Date { 9 | pub year: u16, 10 | pub month: u8, 11 | pub day: u8, 12 | } 13 | 14 | impl Display for Date { 15 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 16 | write!( 17 | formatter, 18 | "{:04}-{:02}-{:02}", 19 | self.year, self.month, self.day, 20 | ) 21 | } 22 | } 23 | 24 | pub fn parse(paren: Group, iter: Iter) -> Result { 25 | try_parse(iter).map_err(|()| { 26 | let msg = format!("expected nightly date, like {}", time::today()); 27 | Error::group(paren, msg) 28 | }) 29 | } 30 | 31 | fn try_parse(iter: Iter) -> Result { 32 | let year = token::parse_literal(iter).map_err(drop)?; 33 | token::parse_punct(iter, '-').map_err(drop)?; 34 | let month = token::parse_literal(iter).map_err(drop)?; 35 | token::parse_punct(iter, '-').map_err(drop)?; 36 | let day = token::parse_literal(iter).map_err(drop)?; 37 | 38 | let year = year.to_string().parse::().map_err(drop)?; 39 | let month = month.to_string().parse::().map_err(drop)?; 40 | let day = day.to_string().parse::().map_err(drop)?; 41 | if year >= 3000 || month > 12 || day > 31 { 42 | return Err(()); 43 | } 44 | 45 | Ok(Date { 46 | year: year as u16, 47 | month: month as u8, 48 | day: day as u8, 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 2 | use std::fmt::Display; 3 | use std::iter::FromIterator; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | pub struct Error { 8 | begin: Span, 9 | end: Span, 10 | msg: String, 11 | } 12 | 13 | impl Error { 14 | pub fn new(span: Span, msg: impl Display) -> Self { 15 | Self::new2(span, span, msg) 16 | } 17 | 18 | pub fn new2(begin: Span, end: Span, msg: impl Display) -> Self { 19 | Error { 20 | begin, 21 | end, 22 | msg: msg.to_string(), 23 | } 24 | } 25 | 26 | pub fn group(group: Group, msg: impl Display) -> Self { 27 | let mut iter = group.stream().into_iter(); 28 | let delimiter = group.span(); 29 | let begin = iter.next().map_or(delimiter, |t| t.span()); 30 | let end = iter.last().map_or(begin, |t| t.span()); 31 | Self::new2(begin, end, msg) 32 | } 33 | 34 | pub fn into_compile_error(self) -> TokenStream { 35 | // compile_error! { $msg } 36 | TokenStream::from_iter(vec![ 37 | TokenTree::Ident(Ident::new("compile_error", self.begin)), 38 | TokenTree::Punct({ 39 | let mut punct = Punct::new('!', Spacing::Alone); 40 | punct.set_span(self.begin); 41 | punct 42 | }), 43 | TokenTree::Group({ 44 | let mut group = Group::new(Delimiter::Brace, { 45 | TokenStream::from_iter(vec![TokenTree::Literal({ 46 | let mut string = Literal::string(&self.msg); 47 | string.set_span(self.end); 48 | string 49 | })]) 50 | }); 51 | group.set_span(self.end); 52 | group 53 | }), 54 | ]) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/expand.rs: -------------------------------------------------------------------------------- 1 | use crate::attr::{self, Then}; 2 | use crate::error::{Error, Result}; 3 | use crate::{constfn, expr, iter, token}; 4 | use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; 5 | use std::iter::FromIterator; 6 | 7 | pub fn cfg(introducer: &str, args: TokenStream, input: TokenStream) -> TokenStream { 8 | try_cfg(introducer, args, input).unwrap_or_else(Error::into_compile_error) 9 | } 10 | 11 | fn try_cfg(introducer: &str, args: TokenStream, input: TokenStream) -> Result { 12 | let introducer = Ident::new(introducer, Span::call_site()); 13 | 14 | let mut full_args = TokenStream::from(TokenTree::Ident(introducer)); 15 | if !args.is_empty() { 16 | full_args.extend(std::iter::once(TokenTree::Group(Group::new( 17 | Delimiter::Parenthesis, 18 | args, 19 | )))); 20 | } 21 | 22 | let ref mut full_args = iter::new(full_args); 23 | let expr = expr::parse(full_args)?; 24 | token::parse_end(full_args)?; 25 | 26 | if expr.eval(crate::RUSTVERSION) { 27 | Ok(input) 28 | } else { 29 | Ok(TokenStream::new()) 30 | } 31 | } 32 | 33 | pub fn try_attr(args: attr::Args, input: TokenStream) -> Result { 34 | if !args.condition.eval(crate::RUSTVERSION) { 35 | return Ok(input); 36 | } 37 | 38 | match args.then { 39 | Then::Const(const_token) => constfn::insert_const(input, const_token), 40 | Then::Attribute(then) => { 41 | // #[cfg_attr(all(), #then)] 42 | Ok(TokenStream::from_iter( 43 | vec![ 44 | TokenTree::Punct(Punct::new('#', Spacing::Alone)), 45 | TokenTree::Group(Group::new( 46 | Delimiter::Bracket, 47 | TokenStream::from_iter(vec![ 48 | TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())), 49 | TokenTree::Group(Group::new( 50 | Delimiter::Parenthesis, 51 | TokenStream::from_iter( 52 | vec![ 53 | TokenTree::Ident(Ident::new("all", Span::call_site())), 54 | TokenTree::Group(Group::new( 55 | Delimiter::Parenthesis, 56 | TokenStream::new(), 57 | )), 58 | TokenTree::Punct(Punct::new(',', Spacing::Alone)), 59 | ] 60 | .into_iter() 61 | .chain(then), 62 | ), 63 | )), 64 | ]), 65 | )), 66 | ] 67 | .into_iter() 68 | .chain(input), 69 | )) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::bound::{self, Bound}; 2 | use crate::date::{self, Date}; 3 | use crate::error::{Error, Result}; 4 | use crate::iter::{self, Iter}; 5 | use crate::release::{self, Release}; 6 | use crate::token; 7 | use crate::version::{Channel, Version}; 8 | use proc_macro::{Ident, Span, TokenTree}; 9 | 10 | pub enum Expr { 11 | Stable, 12 | Beta, 13 | Nightly, 14 | Date(Date), 15 | Since(Bound), 16 | Before(Bound), 17 | Release(Release), 18 | Not(Box), 19 | Any(Vec), 20 | All(Vec), 21 | } 22 | 23 | impl Expr { 24 | pub fn eval(&self, rustc: Version) -> bool { 25 | use self::Expr::*; 26 | 27 | match self { 28 | Stable => rustc.channel == Channel::Stable, 29 | Beta => rustc.channel == Channel::Beta, 30 | Nightly => match rustc.channel { 31 | Channel::Nightly(_) | Channel::Dev => true, 32 | Channel::Stable | Channel::Beta => false, 33 | }, 34 | Date(date) => match rustc.channel { 35 | Channel::Nightly(rustc) => rustc == *date, 36 | Channel::Stable | Channel::Beta | Channel::Dev => false, 37 | }, 38 | Since(bound) => rustc >= *bound, 39 | Before(bound) => rustc < *bound, 40 | Release(release) => { 41 | rustc.channel == Channel::Stable 42 | && rustc.minor == release.minor 43 | && release.patch.map_or(true, |patch| rustc.patch == patch) 44 | } 45 | Not(expr) => !expr.eval(rustc), 46 | Any(exprs) => exprs.iter().any(|e| e.eval(rustc)), 47 | All(exprs) => exprs.iter().all(|e| e.eval(rustc)), 48 | } 49 | } 50 | } 51 | 52 | pub fn parse(iter: Iter) -> Result { 53 | match &iter.next() { 54 | Some(TokenTree::Ident(i)) if i.to_string() == "stable" => parse_stable(iter), 55 | Some(TokenTree::Ident(i)) if i.to_string() == "beta" => Ok(Expr::Beta), 56 | Some(TokenTree::Ident(i)) if i.to_string() == "nightly" => parse_nightly(iter), 57 | Some(TokenTree::Ident(i)) if i.to_string() == "since" => parse_since(i, iter), 58 | Some(TokenTree::Ident(i)) if i.to_string() == "before" => parse_before(i, iter), 59 | Some(TokenTree::Ident(i)) if i.to_string() == "not" => parse_not(i, iter), 60 | Some(TokenTree::Ident(i)) if i.to_string() == "any" => parse_any(i, iter), 61 | Some(TokenTree::Ident(i)) if i.to_string() == "all" => parse_all(i, iter), 62 | unexpected => { 63 | let span = unexpected 64 | .as_ref() 65 | .map_or_else(Span::call_site, TokenTree::span); 66 | Err(Error::new(span, "expected one of `stable`, `beta`, `nightly`, `since`, `before`, `not`, `any`, `all`")) 67 | } 68 | } 69 | } 70 | 71 | fn parse_nightly(iter: Iter) -> Result { 72 | let paren = match token::parse_optional_paren(iter) { 73 | Some(group) => group, 74 | None => return Ok(Expr::Nightly), 75 | }; 76 | 77 | let ref mut inner = iter::new(paren.stream()); 78 | let date = date::parse(paren, inner)?; 79 | token::parse_optional_punct(inner, ','); 80 | token::parse_end(inner)?; 81 | 82 | Ok(Expr::Date(date)) 83 | } 84 | 85 | fn parse_stable(iter: Iter) -> Result { 86 | let paren = match token::parse_optional_paren(iter) { 87 | Some(group) => group, 88 | None => return Ok(Expr::Stable), 89 | }; 90 | 91 | let ref mut inner = iter::new(paren.stream()); 92 | let release = release::parse(paren, inner)?; 93 | token::parse_optional_punct(inner, ','); 94 | token::parse_end(inner)?; 95 | 96 | Ok(Expr::Release(release)) 97 | } 98 | 99 | fn parse_since(introducer: &Ident, iter: Iter) -> Result { 100 | let paren = token::parse_paren(introducer, iter)?; 101 | 102 | let ref mut inner = iter::new(paren.stream()); 103 | let bound = bound::parse(paren, inner)?; 104 | token::parse_optional_punct(inner, ','); 105 | token::parse_end(inner)?; 106 | 107 | Ok(Expr::Since(bound)) 108 | } 109 | 110 | fn parse_before(introducer: &Ident, iter: Iter) -> Result { 111 | let paren = token::parse_paren(introducer, iter)?; 112 | 113 | let ref mut inner = iter::new(paren.stream()); 114 | let bound = bound::parse(paren, inner)?; 115 | token::parse_optional_punct(inner, ','); 116 | token::parse_end(inner)?; 117 | 118 | Ok(Expr::Before(bound)) 119 | } 120 | 121 | fn parse_not(introducer: &Ident, iter: Iter) -> Result { 122 | let paren = token::parse_paren(introducer, iter)?; 123 | 124 | let ref mut inner = iter::new(paren.stream()); 125 | let expr = self::parse(inner)?; 126 | token::parse_optional_punct(inner, ','); 127 | token::parse_end(inner)?; 128 | 129 | Ok(Expr::Not(Box::new(expr))) 130 | } 131 | 132 | fn parse_any(introducer: &Ident, iter: Iter) -> Result { 133 | let paren = token::parse_paren(introducer, iter)?; 134 | 135 | let ref mut inner = iter::new(paren.stream()); 136 | let exprs = parse_comma_separated(inner)?; 137 | 138 | Ok(Expr::Any(exprs.into_iter().collect())) 139 | } 140 | 141 | fn parse_all(introducer: &Ident, iter: Iter) -> Result { 142 | let paren = token::parse_paren(introducer, iter)?; 143 | 144 | let ref mut inner = iter::new(paren.stream()); 145 | let exprs = parse_comma_separated(inner)?; 146 | 147 | Ok(Expr::All(exprs.into_iter().collect())) 148 | } 149 | 150 | fn parse_comma_separated(iter: Iter) -> Result> { 151 | let mut exprs = Vec::new(); 152 | 153 | while iter.peek().is_some() { 154 | let expr = self::parse(iter)?; 155 | exprs.push(expr); 156 | if iter.peek().is_none() { 157 | break; 158 | } 159 | token::parse_punct(iter, ',')?; 160 | } 161 | 162 | Ok(exprs) 163 | } 164 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{token_stream, Delimiter, TokenStream, TokenTree}; 2 | 3 | pub type Iter<'a> = &'a mut IterImpl; 4 | 5 | pub struct IterImpl { 6 | stack: Vec, 7 | peeked: Option, 8 | } 9 | 10 | pub fn new(tokens: TokenStream) -> IterImpl { 11 | IterImpl { 12 | stack: vec![tokens.into_iter()], 13 | peeked: None, 14 | } 15 | } 16 | 17 | impl IterImpl { 18 | pub fn peek(&mut self) -> Option<&TokenTree> { 19 | self.peeked = self.next(); 20 | self.peeked.as_ref() 21 | } 22 | } 23 | 24 | impl Iterator for IterImpl { 25 | type Item = TokenTree; 26 | 27 | fn next(&mut self) -> Option { 28 | if let Some(tt) = self.peeked.take() { 29 | return Some(tt); 30 | } 31 | loop { 32 | let top = self.stack.last_mut()?; 33 | match top.next() { 34 | None => drop(self.stack.pop()), 35 | Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::None => { 36 | self.stack.push(group.stream().into_iter()); 37 | } 38 | Some(tt) => return Some(tt), 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/rustversion) [![crates-io]](https://crates.io/crates/rustversion) [![docs-rs]](https://docs.rs/rustversion) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! This crate provides macros for conditional compilation according to rustc 10 | //! compiler version, analogous to [`#[cfg(...)]`][cfg] and 11 | //! [`#[cfg_attr(...)]`][cfg_attr]. 12 | //! 13 | //! [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute 14 | //! [cfg_attr]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute 15 | //! 16 | //!
17 | //! 18 | //! # Selectors 19 | //! 20 | //! -

21 | //! #[rustversion::stable] 22 | //! —
23 | //! True on any stable compiler. 24 | //!

25 | //! 26 | //! -

27 | //! #[rustversion::stable(1.34)] 28 | //! —
29 | //! True on exactly the specified stable compiler. 30 | //!

31 | //! 32 | //! -

33 | //! #[rustversion::beta] 34 | //! —
35 | //! True on any beta compiler. 36 | //!

37 | //! 38 | //! -

39 | //! #[rustversion::nightly] 40 | //! —
41 | //! True on any nightly compiler or dev build. 42 | //!

43 | //! 44 | //! -

45 | //! #[rustversion::nightly(2025-01-01)] 46 | //! —
47 | //! True on exactly one nightly. 48 | //!

49 | //! 50 | //! -

51 | //! #[rustversion::since(1.34)] 52 | //! —
53 | //! True on that stable release and any later compiler, including beta and 54 | //! nightly. 55 | //!

56 | //! 57 | //! -

58 | //! #[rustversion::since(2025-01-01)] 59 | //! —
60 | //! True on that nightly and all newer ones. 61 | //!

62 | //! 63 | //! -

64 | //! #[rustversion::before(version or date)] 65 | //! —
66 | //! Negative of #[rustversion::since(...)]. 67 | //!

68 | //! 69 | //! -

70 | //! #[rustversion::not(selector)] 71 | //! —
72 | //! Negative of any selector; for example #[rustversion::not(nightly)]. 73 | //!

74 | //! 75 | //! -

76 | //! #[rustversion::any(selectors...)] 77 | //! —
78 | //! True if any of the comma-separated selectors is true; for example 79 | //! #[rustversion::any(stable, beta)]. 80 | //!

81 | //! 82 | //! -

83 | //! #[rustversion::all(selectors...)] 84 | //! —
85 | //! True if all of the comma-separated selectors are true; for example 86 | //! #[rustversion::all(since(1.31), before(1.34))]. 87 | //!

88 | //! 89 | //! -

90 | //! #[rustversion::attr(selector, attribute)] 91 | //! —
92 | //! For conditional inclusion of attributes; analogous to 93 | //! cfg_attr. 94 | //!

95 | //! 96 | //! -

97 | //! rustversion::cfg!(selector) 98 | //! —
99 | //! An expression form of any of the above attributes; for example 100 | //! if rustversion::cfg!(any(stable, beta)) { ... }. 101 | //!

102 | //! 103 | //!
104 | //! 105 | //! # Use cases 106 | //! 107 | //! Providing additional trait impls as types are stabilized in the standard library 108 | //! without breaking compatibility with older compilers; in this case Pin\ 109 | //! stabilized in [Rust 1.33][pin]: 110 | //! 111 | //! [pin]: https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html#pinning 112 | //! 113 | //! ``` 114 | //! # trait MyTrait {} 115 | //! # 116 | //! #[rustversion::since(1.33)] 117 | //! use std::pin::Pin; 118 | //! 119 | //! #[rustversion::since(1.33)] 120 | //! impl MyTrait for Pin

{ 121 | //! /* ... */ 122 | //! } 123 | //! ``` 124 | //! 125 | //! Similar but for language features; the ability to control alignment greater than 126 | //! 1 of packed structs was stabilized in [Rust 1.33][packed]. 127 | //! 128 | //! [packed]: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1330-2019-02-28 129 | //! 130 | //! ``` 131 | //! #[rustversion::attr(before(1.33), repr(packed))] 132 | //! #[rustversion::attr(since(1.33), repr(packed(2)))] 133 | //! struct Six(i16, i32); 134 | //! 135 | //! fn main() { 136 | //! println!("{}", std::mem::align_of::()); 137 | //! } 138 | //! ``` 139 | //! 140 | //! Augmenting code with `const` as const impls are stabilized in the standard 141 | //! library. This use of `const` as an attribute is recognized as a special case 142 | //! by the rustversion::attr macro. 143 | //! 144 | //! ``` 145 | //! use std::time::Duration; 146 | //! 147 | //! #[rustversion::attr(since(1.32), const)] 148 | //! fn duration_as_days(dur: Duration) -> u64 { 149 | //! dur.as_secs() / 60 / 60 / 24 150 | //! } 151 | //! ``` 152 | //! 153 | //! Emitting Cargo cfg directives from a build script. Note that this requires 154 | //! listing `rustversion` under `[build-dependencies]` in Cargo.toml, not 155 | //! `[dependencies]`. 156 | //! 157 | //! ``` 158 | //! // build.rs 159 | //! 160 | //! fn main() { 161 | //! if rustversion::cfg!(since(1.36)) { 162 | //! println!("cargo:rustc-cfg=no_std"); 163 | //! } 164 | //! } 165 | //! ``` 166 | //! 167 | //! ``` 168 | //! // src/lib.rs 169 | //! 170 | //! #![cfg_attr(no_std, no_std)] 171 | //! 172 | //! #[cfg(no_std)] 173 | //! extern crate alloc; 174 | //! ``` 175 | //! 176 | //!
177 | 178 | #![doc(html_root_url = "https://docs.rs/rustversion/1.0.21")] 179 | #![allow( 180 | clippy::cast_lossless, 181 | clippy::cast_possible_truncation, 182 | clippy::derive_partial_eq_without_eq, 183 | clippy::doc_markdown, 184 | clippy::enum_glob_use, 185 | clippy::from_iter_instead_of_collect, 186 | // https://github.com/rust-lang/rust-clippy/issues/8539 187 | clippy::iter_with_drain, 188 | clippy::module_name_repetitions, 189 | clippy::must_use_candidate, 190 | clippy::needless_doctest_main, 191 | clippy::needless_pass_by_value, 192 | clippy::redundant_else, 193 | clippy::toplevel_ref_arg, 194 | clippy::unreadable_literal 195 | )] 196 | 197 | extern crate proc_macro; 198 | 199 | mod attr; 200 | mod bound; 201 | mod constfn; 202 | mod date; 203 | mod error; 204 | mod expand; 205 | mod expr; 206 | mod iter; 207 | mod release; 208 | mod time; 209 | mod token; 210 | mod version; 211 | 212 | use crate::error::Error; 213 | use crate::version::Version; 214 | use proc_macro::TokenStream; 215 | 216 | #[cfg(not(host_os = "windows"))] 217 | const RUSTVERSION: Version = include!(concat!(env!("OUT_DIR"), "/version.expr")); 218 | 219 | #[cfg(host_os = "windows")] 220 | const RUSTVERSION: Version = include!(concat!(env!("OUT_DIR"), "\\version.expr")); 221 | 222 | #[proc_macro_attribute] 223 | pub fn stable(args: TokenStream, input: TokenStream) -> TokenStream { 224 | expand::cfg("stable", args, input) 225 | } 226 | 227 | #[proc_macro_attribute] 228 | pub fn beta(args: TokenStream, input: TokenStream) -> TokenStream { 229 | expand::cfg("beta", args, input) 230 | } 231 | 232 | #[proc_macro_attribute] 233 | pub fn nightly(args: TokenStream, input: TokenStream) -> TokenStream { 234 | expand::cfg("nightly", args, input) 235 | } 236 | 237 | #[proc_macro_attribute] 238 | pub fn since(args: TokenStream, input: TokenStream) -> TokenStream { 239 | expand::cfg("since", args, input) 240 | } 241 | 242 | #[proc_macro_attribute] 243 | pub fn before(args: TokenStream, input: TokenStream) -> TokenStream { 244 | expand::cfg("before", args, input) 245 | } 246 | 247 | #[proc_macro_attribute] 248 | pub fn not(args: TokenStream, input: TokenStream) -> TokenStream { 249 | expand::cfg("not", args, input) 250 | } 251 | 252 | #[proc_macro_attribute] 253 | pub fn any(args: TokenStream, input: TokenStream) -> TokenStream { 254 | expand::cfg("any", args, input) 255 | } 256 | 257 | #[proc_macro_attribute] 258 | pub fn all(args: TokenStream, input: TokenStream) -> TokenStream { 259 | expand::cfg("all", args, input) 260 | } 261 | 262 | #[proc_macro_attribute] 263 | pub fn attr(args: TokenStream, input: TokenStream) -> TokenStream { 264 | attr::parse(args) 265 | .and_then(|args| expand::try_attr(args, input)) 266 | .unwrap_or_else(Error::into_compile_error) 267 | } 268 | 269 | #[cfg(not(cfg_macro_not_allowed))] 270 | #[proc_macro] 271 | pub fn cfg(input: TokenStream) -> TokenStream { 272 | use proc_macro::{Ident, Span, TokenTree}; 273 | (|| { 274 | let ref mut args = iter::new(input); 275 | let expr = expr::parse(args)?; 276 | token::parse_end(args)?; 277 | let boolean = expr.eval(RUSTVERSION); 278 | let ident = Ident::new(&boolean.to_string(), Span::call_site()); 279 | Ok(TokenStream::from(TokenTree::Ident(ident))) 280 | })() 281 | .unwrap_or_else(Error::into_compile_error) 282 | } 283 | -------------------------------------------------------------------------------- /src/release.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::iter::Iter; 3 | use crate::token; 4 | use proc_macro::Group; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 7 | pub struct Release { 8 | pub minor: u16, 9 | pub patch: Option, 10 | } 11 | 12 | pub fn parse(paren: Group, iter: Iter) -> Result { 13 | try_parse(iter).map_err(|()| Error::group(paren, "expected rustc release number, like 1.31")) 14 | } 15 | 16 | fn try_parse(iter: Iter) -> Result { 17 | let major_minor = token::parse_literal(iter).map_err(drop)?; 18 | let string = major_minor.to_string(); 19 | 20 | if !string.starts_with("1.") { 21 | return Err(()); 22 | } 23 | 24 | let minor: u16 = string[2..].parse().map_err(drop)?; 25 | 26 | let patch = if token::parse_optional_punct(iter, '.').is_some() { 27 | let int = token::parse_literal(iter).map_err(drop)?; 28 | Some(int.to_string().parse().map_err(drop)?) 29 | } else { 30 | None 31 | }; 32 | 33 | Ok(Release { minor, patch }) 34 | } 35 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | use crate::date::Date; 2 | use std::env; 3 | use std::time::{SystemTime, UNIX_EPOCH}; 4 | 5 | // Timestamp of 2016-03-01 00:00:00 in UTC. 6 | const BASE: u64 = 1456790400; 7 | const BASE_YEAR: u16 = 2016; 8 | const BASE_MONTH: u8 = 3; 9 | 10 | // Days between leap days. 11 | const CYCLE: u64 = 365 * 4 + 1; 12 | 13 | const DAYS_BY_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 14 | 15 | pub fn today() -> Date { 16 | let default = Date { 17 | year: 2025, 18 | month: 2, 19 | day: 25, 20 | }; 21 | try_today().unwrap_or(default) 22 | } 23 | 24 | fn try_today() -> Option { 25 | if let Some(pkg_name) = env::var_os("CARGO_PKG_NAME") { 26 | if pkg_name.to_str() == Some("rustversion-tests") { 27 | return None; // Stable date for ui testing. 28 | } 29 | } 30 | 31 | let now = SystemTime::now(); 32 | let since_epoch = now.duration_since(UNIX_EPOCH).ok()?; 33 | let secs = since_epoch.as_secs(); 34 | 35 | let approx_days = secs.checked_sub(BASE)? / 60 / 60 / 24; 36 | let cycle = approx_days / CYCLE; 37 | let mut rem = approx_days % CYCLE; 38 | 39 | let mut year = BASE_YEAR + cycle as u16 * 4; 40 | let mut month = BASE_MONTH; 41 | loop { 42 | let days_in_month = DAYS_BY_MONTH[month as usize - 1]; 43 | if rem < days_in_month as u64 { 44 | let day = rem as u8 + 1; 45 | return Some(Date { year, month, day }); 46 | } 47 | rem -= days_in_month as u64; 48 | year += (month == 12) as u16; 49 | month = month % 12 + 1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::iter::Iter; 3 | use proc_macro::{Delimiter, Group, Ident, Literal, Span, TokenTree}; 4 | 5 | pub fn parse_punct(iter: Iter, ch: char) -> Result<()> { 6 | match iter.next() { 7 | Some(TokenTree::Punct(ref punct)) if punct.as_char() == ch => Ok(()), 8 | unexpected => { 9 | let span = unexpected 10 | .as_ref() 11 | .map_or_else(Span::call_site, TokenTree::span); 12 | Err(Error::new(span, format!("expected `{}`", ch))) 13 | } 14 | } 15 | } 16 | 17 | pub fn parse_optional_punct(iter: Iter, ch: char) -> Option<()> { 18 | match iter.peek() { 19 | Some(TokenTree::Punct(punct)) if punct.as_char() == ch => iter.next().map(drop), 20 | _ => None, 21 | } 22 | } 23 | 24 | pub fn parse_optional_keyword(iter: Iter, keyword: &str) -> Option { 25 | match iter.peek() { 26 | Some(TokenTree::Ident(ident)) if ident.to_string() == keyword => { 27 | Some(iter.next().unwrap().span()) 28 | } 29 | _ => None, 30 | } 31 | } 32 | 33 | pub fn parse_literal(iter: Iter) -> Result { 34 | match iter.next() { 35 | Some(TokenTree::Literal(literal)) => Ok(literal), 36 | unexpected => { 37 | let span = unexpected 38 | .as_ref() 39 | .map_or_else(Span::call_site, TokenTree::span); 40 | Err(Error::new(span, "expected literal")) 41 | } 42 | } 43 | } 44 | 45 | pub fn parse_paren(introducer: &Ident, iter: Iter) -> Result { 46 | match iter.peek() { 47 | Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => { 48 | match iter.next() { 49 | Some(TokenTree::Group(group)) => Ok(group), 50 | _ => unreachable!(), 51 | } 52 | } 53 | Some(unexpected) => Err(Error::new(unexpected.span(), "expected `(`")), 54 | None => Err(Error::new( 55 | introducer.span(), 56 | format!("expected `(` after `{}`", introducer), 57 | )), 58 | } 59 | } 60 | 61 | pub fn parse_optional_paren(iter: Iter) -> Option { 62 | match iter.peek() { 63 | Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => { 64 | match iter.next() { 65 | Some(TokenTree::Group(group)) => Some(group), 66 | _ => unreachable!(), 67 | } 68 | } 69 | _ => None, 70 | } 71 | } 72 | 73 | pub fn parse_end(iter: Iter) -> Result<()> { 74 | match iter.next() { 75 | None => Ok(()), 76 | Some(unexpected) => Err(Error::new(unexpected.span(), "unexpected token")), 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::date::Date; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq)] 6 | pub struct Version { 7 | pub minor: u16, 8 | pub patch: u16, 9 | pub channel: Channel, 10 | } 11 | 12 | #[derive(Copy, Clone, Debug, PartialEq)] 13 | pub enum Channel { 14 | Stable, 15 | Beta, 16 | Nightly(Date), 17 | Dev, 18 | } 19 | -------------------------------------------------------------------------------- /tests/compiletest.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(not(nightly), ignore = "requires nightly")] 2 | #[cfg_attr(miri, ignore = "incompatible with miri")] 3 | #[test] 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/test_const.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::semicolon_if_nothing_returned, // https://github.com/rust-lang/rust-clippy/issues/7324 3 | clippy::used_underscore_items, 4 | )] 5 | 6 | #[rustversion::attr(all(), const)] 7 | fn _basic() {} 8 | const _BASIC: () = _basic(); 9 | 10 | #[rustversion::attr(all(), const)] 11 | unsafe fn _unsafe() {} 12 | const _UNSAFE: () = unsafe { _unsafe() }; 13 | 14 | macro_rules! item { 15 | ($i:item) => { 16 | #[rustversion::attr(all(), const)] 17 | $i 18 | }; 19 | } 20 | 21 | item! {fn _item() {}} 22 | const _ITEM: () = _item(); 23 | 24 | macro_rules! ident { 25 | ($fn:ident) => { 26 | #[rustversion::attr(all(), const)] 27 | $fn _ident() {} 28 | }; 29 | } 30 | 31 | ident! {fn} 32 | const _IDENT: () = _ident(); 33 | 34 | #[rustversion::attr(all(), const)] 35 | /// doc 36 | fn _doc_below() {} 37 | const _DOC_BELOW: () = _doc_below(); 38 | 39 | /// doc 40 | #[rustversion::attr(all(), const)] 41 | fn _doc_above() {} 42 | const _DOC_ABOVE: () = _doc_above(); 43 | -------------------------------------------------------------------------------- /tests/test_eval.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::any( 2 | stable, 3 | stable(1.34), 4 | stable(1.34.0), 5 | beta, 6 | nightly, 7 | nightly(2020-02-25), 8 | since(1.34), 9 | since(2020-02-25), 10 | before(1.34), 11 | before(2020-02-25), 12 | not(nightly), 13 | all(stable, beta, nightly), 14 | )] 15 | fn success() {} 16 | 17 | #[test] 18 | fn test() { 19 | success(); 20 | } 21 | -------------------------------------------------------------------------------- /tests/test_parse.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::derive_partial_eq_without_eq, 3 | clippy::enum_glob_use, 4 | clippy::must_use_candidate 5 | )] 6 | 7 | include!("../build/rustc.rs"); 8 | 9 | #[test] 10 | fn test_parse() { 11 | let cases = &[ 12 | ( 13 | "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)", 14 | Version { 15 | minor: 0, 16 | patch: 0, 17 | channel: Stable, 18 | }, 19 | ), 20 | ( 21 | "rustc 1.18.0", 22 | Version { 23 | minor: 18, 24 | patch: 0, 25 | channel: Stable, 26 | }, 27 | ), 28 | ( 29 | "rustc 1.24.1 (d3ae9a9e0 2018-02-27)", 30 | Version { 31 | minor: 24, 32 | patch: 1, 33 | channel: Stable, 34 | }, 35 | ), 36 | ( 37 | "rustc 1.35.0-beta.3 (c13114dc8 2019-04-27)", 38 | Version { 39 | minor: 35, 40 | patch: 0, 41 | channel: Beta, 42 | }, 43 | ), 44 | ( 45 | "rustc 1.36.0-nightly (938d4ffe1 2019-04-27)", 46 | Version { 47 | minor: 36, 48 | patch: 0, 49 | channel: Nightly(Date { 50 | year: 2019, 51 | month: 4, 52 | day: 27, 53 | }), 54 | }, 55 | ), 56 | ( 57 | "rustc 1.36.0-dev", 58 | Version { 59 | minor: 36, 60 | patch: 0, 61 | channel: Dev, 62 | }, 63 | ), 64 | ( 65 | "rustc 1.36.0-nightly", 66 | Version { 67 | minor: 36, 68 | patch: 0, 69 | channel: Dev, 70 | }, 71 | ), 72 | ( 73 | "warning: invalid logging spec 'warning', ignoring it 74 | rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)", 75 | Version { 76 | minor: 30, 77 | patch: 0, 78 | channel: Nightly(Date { 79 | year: 2018, 80 | month: 9, 81 | day: 20, 82 | }), 83 | }, 84 | ), 85 | ( 86 | "rustc 1.52.1-nightly (gentoo)", 87 | Version { 88 | minor: 52, 89 | patch: 1, 90 | channel: Dev, 91 | }, 92 | ), 93 | ]; 94 | 95 | for (string, expected) in cases { 96 | match parse(string) { 97 | ParseResult::Success(version) => assert_eq!(version, *expected), 98 | ParseResult::OopsClippy | ParseResult::OopsMirai | ParseResult::Unrecognized => { 99 | panic!("unrecognized: {:?}", string); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests/ui/bad-bound.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::since(stable)] 2 | struct S; 3 | 4 | #[rustversion::any(since(stable))] 5 | struct S; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/bad-bound.stderr: -------------------------------------------------------------------------------- 1 | error: expected rustc release number like 1.85, or nightly date like 2025-02-25 2 | --> tests/ui/bad-bound.rs:1:22 3 | | 4 | 1 | #[rustversion::since(stable)] 5 | | ^^^^^^ 6 | 7 | error: expected rustc release number like 1.85, or nightly date like 2025-02-25 8 | --> tests/ui/bad-bound.rs:4:26 9 | | 10 | 4 | #[rustversion::any(since(stable))] 11 | | ^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/bad-date.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::nightly(stable)] 2 | struct S; 3 | 4 | #[rustversion::any(nightly(stable))] 5 | struct S; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/bad-date.stderr: -------------------------------------------------------------------------------- 1 | error: expected nightly date, like 2025-02-25 2 | --> tests/ui/bad-date.rs:1:24 3 | | 4 | 1 | #[rustversion::nightly(stable)] 5 | | ^^^^^^ 6 | 7 | error: expected nightly date, like 2025-02-25 8 | --> tests/ui/bad-date.rs:4:28 9 | | 10 | 4 | #[rustversion::any(nightly(stable))] 11 | | ^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/bad-not.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::any(not)] 2 | struct S; 3 | 4 | #[rustversion::any(not, not)] 5 | struct S; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/bad-not.stderr: -------------------------------------------------------------------------------- 1 | error: expected `(` after `not` 2 | --> tests/ui/bad-not.rs:1:20 3 | | 4 | 1 | #[rustversion::any(not)] 5 | | ^^^ 6 | 7 | error: expected `(` 8 | --> tests/ui/bad-not.rs:4:23 9 | | 10 | 4 | #[rustversion::any(not, not)] 11 | | ^ 12 | -------------------------------------------------------------------------------- /tests/ui/bad-version.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::stable(nightly)] 2 | struct S; 3 | 4 | #[rustversion::any(stable(nightly))] 5 | struct S; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/bad-version.stderr: -------------------------------------------------------------------------------- 1 | error: expected rustc release number, like 1.31 2 | --> tests/ui/bad-version.rs:1:23 3 | | 4 | 1 | #[rustversion::stable(nightly)] 5 | | ^^^^^^^ 6 | 7 | error: expected rustc release number, like 1.31 8 | --> tests/ui/bad-version.rs:4:27 9 | | 10 | 4 | #[rustversion::any(stable(nightly))] 11 | | ^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/const-not-fn.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(all(), const)] 2 | pub struct S; 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/const-not-fn.stderr: -------------------------------------------------------------------------------- 1 | error: only allowed on a fn item 2 | --> tests/ui/const-not-fn.rs:1:28 3 | | 4 | 1 | #[rustversion::attr(all(), const)] 5 | | ^^^^^ 6 | --------------------------------------------------------------------------------