├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── generate_tests.sh ├── main ├── Cargo.toml ├── build.rs └── src │ ├── lib.rs │ └── libs.rs ├── proc-macro ├── Cargo.toml ├── build.rs └── src │ ├── call.rs │ ├── call_handling.rs │ ├── call_handling │ └── forward.rs │ ├── const_generics_impl.rs │ ├── documentation.rs │ ├── extern_crate.rs │ ├── extern_crate │ └── impl_block.rs │ ├── helpers.rs │ ├── helpers │ └── attr.rs │ ├── lib.rs │ ├── pre_attr.rs │ ├── pre_attr │ └── expr_handling.rs │ ├── precondition.rs │ └── struct_impl.rs └── tests ├── Cargo.toml ├── build.rs ├── nightly ├── extern_crate │ ├── compile_fail │ │ ├── longer_path.rs │ │ └── longer_path.stderr │ └── pass │ │ └── simple_extern_crate.rs ├── function │ ├── compile_fail │ │ ├── extra_precondition.rs │ │ ├── extra_precondition.stderr │ │ ├── missing_assure.rs │ │ ├── missing_assure.stderr │ │ ├── missing_check.rs │ │ ├── missing_check.stderr │ │ ├── precondition_missing.rs │ │ ├── precondition_missing.stderr │ │ ├── undefined_precondition.rs │ │ └── undefined_precondition.stderr │ └── pass │ │ ├── multiple_preconditions.rs │ │ ├── nested_call.rs │ │ ├── nested_path_with_generics.rs │ │ ├── no_std.rs │ │ └── single_precondition.rs ├── misc │ ├── compile_fail │ │ ├── ambiguous_assure.rs │ │ ├── ambiguous_assure.stderr │ │ ├── cfg_attr_different_predicates.rs │ │ ├── cfg_attr_different_predicates.stderr │ │ ├── duplicate_forward.rs │ │ ├── duplicate_forward.stderr │ │ ├── forward_failed_replace.rs │ │ ├── forward_failed_replace.stderr │ │ ├── forward_non_path_fn.rs │ │ ├── forward_non_path_fn.stderr │ │ ├── hint_reason.rs │ │ ├── hint_reason.stderr │ │ ├── method_replace_forward.rs │ │ ├── method_replace_forward.stderr │ │ ├── missing_reason.rs │ │ ├── missing_reason.stderr │ │ ├── true_cfg_attr_checks_preconditions.rs │ │ └── true_cfg_attr_checks_preconditions.stderr │ └── pass │ │ ├── cfg_attr.rs │ │ ├── false_cfg_attr_doesnt_check_preconditions.rs │ │ ├── forward.rs │ │ └── no_debug_assert.rs ├── nightly-only │ └── pass │ │ ├── method.rs │ │ └── unnamed_fn.rs └── precondition_types │ ├── compile_fail │ ├── boolean_non_bool.rs │ ├── boolean_non_bool.stderr │ ├── boolean_unknown_var.rs │ ├── boolean_unknown_var.stderr │ ├── unknown_keyword.rs │ └── unknown_keyword.stderr │ └── pass │ └── all_types.rs ├── src └── lib.rs ├── stable ├── extern_crate │ ├── compile_fail │ │ ├── longer_path.rs │ │ └── longer_path.stderr │ └── pass │ │ └── simple_extern_crate.rs ├── function │ ├── compile_fail │ │ ├── extra_precondition.rs │ │ ├── extra_precondition.stderr │ │ ├── missing_assure.rs │ │ ├── missing_assure.stderr │ │ ├── missing_check.rs │ │ ├── missing_check.stderr │ │ ├── precondition_missing.rs │ │ ├── precondition_missing.stderr │ │ ├── undefined_precondition.rs │ │ └── undefined_precondition.stderr │ └── pass │ │ ├── multiple_preconditions.rs │ │ ├── nested_call.rs │ │ ├── nested_path_with_generics.rs │ │ ├── no_std.rs │ │ └── single_precondition.rs ├── misc │ ├── compile_fail │ │ ├── ambiguous_assure.rs │ │ ├── ambiguous_assure.stderr │ │ ├── cfg_attr_different_predicates.rs │ │ ├── cfg_attr_different_predicates.stderr │ │ ├── duplicate_forward.rs │ │ ├── duplicate_forward.stderr │ │ ├── forward_failed_replace.rs │ │ ├── forward_failed_replace.stderr │ │ ├── forward_non_path_fn.rs │ │ ├── forward_non_path_fn.stderr │ │ ├── hint_reason.rs │ │ ├── hint_reason.stderr │ │ ├── method_replace_forward.rs │ │ ├── method_replace_forward.stderr │ │ ├── missing_reason.rs │ │ ├── missing_reason.stderr │ │ ├── true_cfg_attr_checks_preconditions.rs │ │ └── true_cfg_attr_checks_preconditions.stderr │ └── pass │ │ ├── cfg_attr.rs │ │ ├── false_cfg_attr_doesnt_check_preconditions.rs │ │ ├── forward.rs │ │ └── no_debug_assert.rs ├── precondition_types │ ├── compile_fail │ │ ├── boolean_non_bool.rs │ │ ├── boolean_non_bool.stderr │ │ ├── boolean_unknown_var.rs │ │ ├── boolean_unknown_var.stderr │ │ ├── unknown_keyword.rs │ │ └── unknown_keyword.stderr │ └── pass │ │ └── all_types.rs └── stable-only │ └── compile_fail │ ├── method.rs │ ├── method.stderr │ ├── unnamed_fn.rs │ └── unnamed_fn.stderr └── templates ├── extern_crate ├── compile_fail │ └── longer_path.rs └── pass │ └── simple_extern_crate.rs ├── function ├── compile_fail │ ├── extra_precondition.rs │ ├── missing_assure.rs │ ├── missing_check.rs │ ├── precondition_missing.rs │ └── undefined_precondition.rs └── pass │ ├── multiple_preconditions.rs │ ├── nested_call.rs │ ├── nested_path_with_generics.rs │ ├── no_std.rs │ └── single_precondition.rs ├── misc ├── compile_fail │ ├── ambiguous_assure.rs │ ├── cfg_attr_different_predicates.rs │ ├── duplicate_forward.rs │ ├── forward_failed_replace.rs │ ├── forward_non_path_fn.rs │ ├── hint_reason.rs │ ├── method_replace_forward.rs │ ├── missing_reason.rs │ └── true_cfg_attr_checks_preconditions.rs └── pass │ ├── cfg_attr.rs │ ├── false_cfg_attr_doesnt_check_preconditions.rs │ ├── forward.rs │ └── no_debug_assert.rs ├── nightly-only └── pass │ ├── method.rs │ └── unnamed_fn.rs ├── precondition_types ├── compile_fail │ ├── boolean_non_bool.rs │ ├── boolean_unknown_var.rs │ └── unknown_keyword.rs └── pass │ └── all_types.rs └── stable-only └── compile_fail ├── method.rs └── unnamed_fn.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | stable_tests: 14 | name: Tests with stable compiler 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | - run: cd main && cargo test --no-default-features 24 | - run: cd proc-macro && cargo test 25 | - run: cd tests && cargo test 26 | 27 | nightly_tests: 28 | name: Tests with nightly compiler 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | profile: minimal 35 | toolchain: nightly 36 | override: true 37 | - run: cd main && cargo test --no-default-features 38 | - run: cd proc-macro && cargo test 39 | - run: cd tests && cargo test 40 | 41 | fmt: 42 | name: Rustfmt 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | profile: minimal 49 | toolchain: stable 50 | override: true 51 | - run: rustup component add rustfmt 52 | - uses: actions-rs/cargo@v1 53 | with: 54 | command: fmt 55 | args: --all -- --check 56 | 57 | clippy: 58 | name: Clippy 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - uses: actions-rs/toolchain@v1 63 | with: 64 | profile: minimal 65 | toolchain: stable 66 | override: true 67 | - run: rustup component add clippy 68 | - uses: actions-rs/cargo@v1 69 | with: 70 | command: clippy 71 | args: --workspace --tests -- -D warnings 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | However, since version 1.0.0 has not been reached, breaking changes between minor releases are 8 | possible, though if possible they are avoided. 9 | 10 | ## [0.2.1] - 2021-09-21 11 | 12 | ### Changed 13 | 14 | - Version `0.2.1` is compatible with `nightly-2021-09-19`. 15 | - Minor improvements were made to the generated documentation. 16 | 17 | ## [0.2.0] - 2020-07-26 18 | 19 | ### Added 20 | 21 | - Preconditions for the `unsafe` functions and methods in the following modules are now supported: 22 | - `core::slice`/`std::slice` 23 | - `alloc::vec`/`std::vec` 24 | - `alloc::string`/`std::string` 25 | - `core::str`/`alloc::str`/`std::str` 26 | - Preconditions for the methods on the following primitive types are now supported: 27 | - `*const T` (with `#[forward(impl pre::std::const_pointer)]`) 28 | - `*mut T` (with `#[forward(impl pre::std::mut_pointer)]`) 29 | - The `proper_align` precondition type was added. It allows specifying that a pointer has a proper 30 | alignment for the type its pointing to. 31 | - pre-related attributes behind a `cfg_attr` attribute are now supported. 32 | With [some limitations](https://github.com/aticu/pre#known-limitations). 33 | 34 | ### Changed 35 | 36 | - Functions that previously stated "`ptr` is proper aligned" now use the `proper_align` 37 | precondition type. **This is a breaking change.** 38 | - The main pre crate now depends on the exact same version of the proc-macro crate. A version 39 | mismatch between them could have caused things to misbehave. Version 0.1.0 was yanked because of 40 | that. 41 | 42 | ## [0.1.0] - 2020-07-14 [YANKED] 43 | 44 | Initial release 45 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "proc-macro", 4 | "main", 5 | "tests" 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://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 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Niclas Schwarzlose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /generate_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_scenarios="stable nightly" 4 | 5 | for file in $(find tests/templates -type f -name "*.rs"); do 6 | for scenario in $test_scenarios; do 7 | if (echo "$file" | grep -E '^tests/templates/.*-only/' >/dev/null) && ! (echo "$file" | grep -E "^tests/templates/$scenario-only/" >/dev/null); then 8 | continue 9 | fi 10 | target=$(echo "$file" | sed "s/^tests\/templates/tests\/$scenario/g") 11 | mkdir -p $(dirname "$target") && cp "$file" "$target" 12 | done 13 | done 14 | -------------------------------------------------------------------------------- /main/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pre" 3 | version = "0.2.1" 4 | authors = ["Niclas Schwarzlose <15schnic@gmail.com>"] 5 | license = "MIT OR Apache-2.0" 6 | description = "Compile-time assistance for working with unsafe code." 7 | repository = "https://github.com/aticu/pre" 8 | keywords = ["unsafe", "safety", "compile-time", "zero-cost", "no_std"] 9 | categories = ["development-tools", "no-std", "rust-patterns"] 10 | readme = "../README.md" 11 | edition = "2018" 12 | 13 | [features] 14 | default = ["std", "alloc", "core"] 15 | std = [] 16 | alloc = [] 17 | core = [] 18 | 19 | [dependencies] 20 | pre-proc-macro = { version = "=0.2.1", path = "../proc-macro" } 21 | cfg-if = "0.1" 22 | 23 | [build-dependencies] 24 | rustc_version = "0.2" 25 | -------------------------------------------------------------------------------- /main/build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | 6 | match version_meta() { 7 | Ok(version) if version.channel == Channel::Nightly => { 8 | println!("cargo:rustc-cfg=nightly"); 9 | } 10 | _ => (), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /proc-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pre-proc-macro" 3 | version = "0.2.1" 4 | authors = ["Niclas Schwarzlose <15schnic@gmail.com>"] 5 | license = "MIT OR Apache-2.0" 6 | description = "Procedural marco implementations for [pre](https://crates.io/crates/pre/)." 7 | repository = "https://github.com/aticu/pre" 8 | keywords = ["unsafe", "safety", "compile-time", "zero-cost", "no_std"] 9 | categories = ["development-tools", "no-std", "rust-patterns"] 10 | edition = "2018" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = "1.0" 17 | syn = { version = "1.0.23", features = ["full", "visit-mut", "extra-traits"] } 18 | quote = "1.0" 19 | proc-macro-error = "1.0" 20 | proc-macro-crate = "0.1.5" 21 | cfg-if = "0.1.6" 22 | lazy_static = "1.4" 23 | 24 | [build-dependencies] 25 | rustc_version = "0.2" 26 | -------------------------------------------------------------------------------- /proc-macro/build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | 6 | match version_meta() { 7 | Ok(version) if version.channel == Channel::Nightly => { 8 | println!("cargo:rustc-cfg=nightly"); 9 | } 10 | _ => (), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /proc-macro/src/call.rs: -------------------------------------------------------------------------------- 1 | //! Allows treating function and method call expressions the same. 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{quote, ToTokens, TokenStreamExt}; 5 | use std::convert::TryFrom; 6 | use syn::{punctuated::Punctuated, token::Comma, Expr, ExprCall, ExprMethodCall, ExprPath}; 7 | 8 | /// A call expression. 9 | #[derive(Clone)] 10 | pub(crate) enum Call { 11 | /// The call expression is a function call. 12 | Function(ExprCall), 13 | /// The call expression is a method call. 14 | Method(ExprMethodCall), 15 | } 16 | 17 | impl Call { 18 | /// Grants mutable access to the arguments of the call. 19 | pub(crate) fn args_mut(&mut self) -> &mut Punctuated { 20 | match self { 21 | Call::Function(call) => &mut call.args, 22 | Call::Method(call) => &mut call.args, 23 | } 24 | } 25 | 26 | /// The path to the function being called, if present. 27 | /// 28 | /// For non-function calls, this returns `None`. 29 | /// If the expression is a function call expression, but the expression that resolves to the 30 | /// function is not a path expression, this also returns `None`. 31 | #[allow(dead_code)] 32 | pub(crate) fn path(&self) -> Option { 33 | match self { 34 | Call::Function(call) => match &*call.func { 35 | Expr::Path(path) => Some(path.clone()), 36 | _ => None, 37 | }, 38 | Call::Method(_) => None, 39 | } 40 | } 41 | 42 | /// Checks if the call expression is a function call. 43 | #[allow(dead_code)] 44 | pub(crate) fn is_function(&self) -> bool { 45 | matches!(self, Call::Function(_)) 46 | } 47 | } 48 | 49 | impl From for Call { 50 | fn from(call: ExprCall) -> Self { 51 | Call::Function(call) 52 | } 53 | } 54 | 55 | impl From for Call { 56 | fn from(call: ExprMethodCall) -> Self { 57 | Call::Method(call) 58 | } 59 | } 60 | 61 | impl TryFrom for Call { 62 | type Error = Expr; 63 | 64 | fn try_from(value: Expr) -> Result { 65 | match value { 66 | Expr::Call(call) => Ok(call.into()), 67 | Expr::MethodCall(call) => Ok(call.into()), 68 | _ => Err(value), 69 | } 70 | } 71 | } 72 | 73 | impl From for Expr { 74 | fn from(call: Call) -> Self { 75 | match call { 76 | Call::Function(call) => Expr::Call(call), 77 | Call::Method(call) => Expr::MethodCall(call), 78 | } 79 | } 80 | } 81 | 82 | impl ToTokens for Call { 83 | fn to_tokens(&self, tokens: &mut TokenStream) { 84 | match self { 85 | Call::Function(call) => tokens.append_all(quote! { #call }), 86 | Call::Method(call) => tokens.append_all(quote! { #call }), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /proc-macro/src/call_handling.rs: -------------------------------------------------------------------------------- 1 | //! Functionality for parsing and visiting `assure` attributes. 2 | 3 | use proc_macro2::Span; 4 | use proc_macro_error::{emit_error, emit_warning}; 5 | use syn::{ 6 | parse::{Parse, ParseStream}, 7 | spanned::Spanned, 8 | Attribute, Expr, LitStr, Token, 9 | }; 10 | 11 | use self::forward::ForwardAttr; 12 | use crate::{ 13 | call::Call, 14 | helpers::{flatten_cfgs, visit_matching_attrs_parsed_mut, Attr, AttributeAction, HINT_REASON}, 15 | precondition::Precondition, 16 | render_assure, 17 | }; 18 | 19 | mod forward; 20 | 21 | /// The custom keywords used in the `assure` attribute. 22 | mod custom_keywords { 23 | use syn::custom_keyword; 24 | 25 | custom_keyword!(reason); 26 | } 27 | 28 | /// An attribute with an assurance that a precondition holds. 29 | pub(crate) enum AssureAttr { 30 | /// The statement had a reason attached to it. 31 | WithReason { 32 | /// The precondition that was stated. 33 | precondition: Precondition, 34 | /// The comma separating the precondition from the reason. 35 | _comma: Token![,], 36 | /// The reason that was stated. 37 | reason: Reason, 38 | }, 39 | /// The statement written without a reason. 40 | /// 41 | /// This is not permitted semantically. 42 | /// The only reason it is accepted syntactically is that it allows providing more relevant 43 | /// error messages. 44 | WithoutReason { 45 | /// The precondition that was stated. 46 | precondition: Precondition, 47 | }, 48 | } 49 | 50 | impl From for Precondition { 51 | fn from(holds_statement: AssureAttr) -> Precondition { 52 | match holds_statement { 53 | AssureAttr::WithoutReason { precondition } => precondition, 54 | AssureAttr::WithReason { precondition, .. } => precondition, 55 | } 56 | } 57 | } 58 | 59 | impl Spanned for AssureAttr { 60 | fn span(&self) -> Span { 61 | match self { 62 | AssureAttr::WithReason { 63 | precondition, 64 | reason, 65 | .. 66 | } => precondition 67 | .span() 68 | .join(reason.reason.span()) 69 | .unwrap_or_else(|| precondition.span()), 70 | AssureAttr::WithoutReason { precondition } => precondition.span(), 71 | } 72 | } 73 | } 74 | 75 | impl Parse for AssureAttr { 76 | fn parse(input: ParseStream) -> syn::Result { 77 | let precondition = input.parse()?; 78 | 79 | if input.is_empty() { 80 | Ok(AssureAttr::WithoutReason { precondition }) 81 | } else { 82 | let comma = input.parse()?; 83 | let reason = input.parse()?; 84 | 85 | Ok(AssureAttr::WithReason { 86 | precondition, 87 | _comma: comma, 88 | reason, 89 | }) 90 | } 91 | } 92 | } 93 | 94 | /// The reason why a precondition holds. 95 | pub(crate) struct Reason { 96 | /// The `reason` keyword. 97 | _reason_keyword: custom_keywords::reason, 98 | /// The `=` separating the `reason` keyword and the reason. 99 | _eq: Token![=], 100 | /// The reason the precondition holds. 101 | reason: LitStr, 102 | } 103 | 104 | impl Parse for Reason { 105 | fn parse(input: ParseStream) -> syn::Result { 106 | let reason_keyword = input.parse()?; 107 | let eq = input.parse()?; 108 | let reason = input.parse()?; 109 | 110 | Ok(Reason { 111 | _reason_keyword: reason_keyword, 112 | _eq: eq, 113 | reason, 114 | }) 115 | } 116 | } 117 | 118 | /// The attributes of a call expression. 119 | pub(crate) struct CallAttributes { 120 | /// The span best representing all the attributes. 121 | pub(crate) span: Span, 122 | /// The optional `forward` attribute. 123 | pub(crate) forward: Option>, 124 | /// The list of `assure` attributes. 125 | pub(crate) assure_attributes: Vec>, 126 | } 127 | 128 | /// Removes and returns all `pre`-related call-site attributes from the given attribute list. 129 | pub(crate) fn remove_call_attributes(attributes: &mut Vec) -> Option { 130 | flatten_cfgs(attributes); 131 | 132 | let mut forward = None; 133 | let mut assure_attributes = Vec::new(); 134 | 135 | let preconditions_span = visit_matching_attrs_parsed_mut(attributes, "assure", |attr| { 136 | assure_attributes.push(attr); 137 | 138 | AttributeAction::Remove 139 | }); 140 | 141 | let forward_span = visit_matching_attrs_parsed_mut(attributes, "forward", |attr| { 142 | let span = attr.span(); 143 | 144 | if let Some(old_forward) = forward.replace(attr) { 145 | emit_error!( 146 | span, 147 | "duplicate `forward` attribute"; 148 | help = old_forward.span() => "there can be just one location, try removing the wrong one" 149 | ); 150 | } 151 | 152 | AttributeAction::Remove 153 | }); 154 | 155 | let span = match (preconditions_span, forward_span) { 156 | (Some(preconditions_span), Some(forward_span)) => Some( 157 | preconditions_span 158 | .join(forward_span) 159 | .unwrap_or(preconditions_span), 160 | ), 161 | (Some(span), None) => Some(span), 162 | (None, Some(span)) => Some(span), 163 | (None, None) => None, 164 | }; 165 | 166 | span.map(|span| CallAttributes { 167 | span, 168 | forward, 169 | assure_attributes, 170 | }) 171 | } 172 | 173 | /// Renders the call using the found attributes for it. 174 | pub(crate) fn render_call( 175 | CallAttributes { 176 | span, 177 | forward, 178 | assure_attributes, 179 | }: CallAttributes, 180 | original_call: Call, 181 | ) -> Expr { 182 | check_reasons(&assure_attributes); 183 | 184 | let precondition = assure_attributes 185 | .into_iter() 186 | .map(|attr| attr.into()) 187 | .collect(); 188 | 189 | if let Some((forward, _, _)) = forward.map(|fwd| fwd.into_content()) { 190 | forward.update_call(original_call, |call| { 191 | render_assure(precondition, call, span) 192 | }) 193 | } else { 194 | let output = render_assure(precondition, original_call, span); 195 | 196 | output.into() 197 | } 198 | } 199 | 200 | /// Checks that all reasons exist and make sense. 201 | /// 202 | /// This function emits errors, if appropriate. 203 | fn check_reasons(assure_attributes: &[Attr]) { 204 | for assure_attribute in assure_attributes.iter() { 205 | match assure_attribute.content() { 206 | AssureAttr::WithReason { reason, .. } => { 207 | if let Some(reason) = unfinished_reason(&reason.reason) { 208 | emit_warning!( 209 | reason, 210 | "you should specify a different here"; 211 | help = "specifying a meaningful reason will help you and others understand why this is ok in the future" 212 | ) 213 | } else if reason.reason.value() == HINT_REASON { 214 | let todo_help_msg = if cfg!(nightly) { 215 | Some("using `TODO` here will emit a warning, reminding you to fix this later") 216 | } else { 217 | None 218 | }; 219 | 220 | emit_error!( 221 | reason.reason, 222 | "you need to specify a different reason here"; 223 | help = "specifying a meaningful reason will help you and others understand why this is ok in the future"; 224 | help =? todo_help_msg 225 | ) 226 | } 227 | } 228 | AssureAttr::WithoutReason { precondition } => emit_error!( 229 | precondition.span(), 230 | "you need to specify a reason why this precondition holds"; 231 | help = "add `, reason = {:?}`", HINT_REASON 232 | ), 233 | } 234 | } 235 | } 236 | 237 | /// Returns an unfinished reason declaration for the precondition if one exists. 238 | fn unfinished_reason(reason: &LitStr) -> Option<&LitStr> { 239 | let mut reason_val = reason.value(); 240 | 241 | reason_val.make_ascii_lowercase(); 242 | match &*reason_val { 243 | "todo" | "?" | "" => Some(reason), 244 | _ => None, 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /proc-macro/src/call_handling/forward.rs: -------------------------------------------------------------------------------- 1 | //! Handles forwarding function calls to a different location. 2 | //! 3 | //! # What the generated code looks like 4 | //! 5 | //! ```rust,ignore 6 | //! use std::ptr::read; 7 | //! 8 | //! #[pre::pre] 9 | //! fn main() { 10 | //! unsafe { 11 | //! #[forward(pre_std::ptr)] 12 | //! #[assure(valid_ptr(src, r), reason = "a reference is a valid pointer")] 13 | //! read(&42); 14 | //! } 15 | //! } 16 | //! ``` 17 | //! 18 | //! turns (roughly, if steps were not combined) into 19 | //! 20 | //! ```rust,ignore 21 | //! use std::ptr::read; 22 | //! 23 | //! #[pre::pre] 24 | //! fn main() { 25 | //! unsafe { 26 | //! if true { 27 | //! #[assure(valid_ptr(src, r), reason = "a reference is a valid pointer")] 28 | //! pre_std::ptr::read(&42) 29 | //! } else { 30 | //! // To silence the unused import warnings. 31 | //! // 32 | //! // This should have the same type inference as the other call. 33 | //! read(&42) 34 | //! }; 35 | //! } 36 | //! } 37 | //! ``` 38 | 39 | use proc_macro2::Span; 40 | use proc_macro_error::{abort, emit_error}; 41 | use quote::{quote, quote_spanned}; 42 | use syn::{ 43 | parse::{Parse, ParseStream}, 44 | parse2, 45 | punctuated::Pair, 46 | spanned::Spanned, 47 | Expr, ExprCall, ExprPath, Ident, Path, Token, 48 | }; 49 | 50 | use crate::{call::Call, extern_crate::impl_block_stub_name}; 51 | 52 | /// The content of a `forward` attribute. 53 | /// 54 | /// This specifies where the function the call should be forwarded to is located. 55 | pub(crate) enum ForwardAttr { 56 | /// The given path should be added before the already present path. 57 | /// 58 | /// For a method, this is equivalent to an `impl` forward attribute. 59 | Direct { 60 | /// The path that should be added. 61 | path: Path, 62 | }, 63 | /// The function or method to be called is located at the specified impl block. 64 | ImplBlock { 65 | /// The `impl` keyword that disambiguates this from a direct forward attribute. 66 | impl_keyword: Token![impl], 67 | /// The path to the impl block. 68 | path: Path, 69 | }, 70 | /// The function to be called is found by replacing `from` with `to` in the path. 71 | Replace { 72 | /// The prefix of the path that should be replaced. 73 | from: Path, 74 | /// The arrow token that marks the replacement. 75 | _arrow: Token![->], 76 | /// The path that should be prepended instead of the removed prefix. 77 | to: Path, 78 | }, 79 | } 80 | 81 | impl Parse for ForwardAttr { 82 | fn parse(input: ParseStream) -> syn::Result { 83 | let impl_keyword = if input.peek(Token![impl]) { 84 | Some(input.parse()?) 85 | } else { 86 | None 87 | }; 88 | 89 | let first_path = input.parse()?; 90 | 91 | Ok(if input.is_empty() { 92 | if let Some(impl_keyword) = impl_keyword { 93 | ForwardAttr::ImplBlock { 94 | impl_keyword, 95 | path: first_path, 96 | } 97 | } else { 98 | ForwardAttr::Direct { path: first_path } 99 | } 100 | } else { 101 | let arrow = input.parse()?; 102 | let second_path = input.parse()?; 103 | 104 | ForwardAttr::Replace { 105 | from: first_path, 106 | _arrow: arrow, 107 | to: second_path, 108 | } 109 | }) 110 | } 111 | } 112 | 113 | impl Spanned for ForwardAttr { 114 | fn span(&self) -> Span { 115 | match self { 116 | ForwardAttr::Direct { path } => path.span(), 117 | ForwardAttr::ImplBlock { impl_keyword, path } => impl_keyword 118 | .span 119 | .join(path.span()) 120 | .unwrap_or_else(|| path.span()), 121 | ForwardAttr::Replace { from, to, .. } => { 122 | from.span().join(to.span()).unwrap_or_else(|| to.span()) 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl ForwardAttr { 129 | /// Updates the call to use the forwarded location. 130 | pub(super) fn update_call(self, mut call: Call, render: impl FnOnce(Call) -> Call) -> Expr { 131 | let original_call = call.clone(); 132 | let span = self.span(); 133 | 134 | match &mut call { 135 | Call::Function(ref mut fn_call) => { 136 | let fn_path = if let Expr::Path(p) = *fn_call.func.clone() { 137 | p 138 | } else { 139 | emit_error!( 140 | fn_call.func, 141 | "unable to determine at compile time which function is being called"; 142 | help = "use a direct path to the function instead" 143 | ); 144 | 145 | return original_call.into(); 146 | }; 147 | 148 | parse2(match self { 149 | ForwardAttr::Direct { .. } | ForwardAttr::Replace { .. } => { 150 | *fn_call.func = Expr::Path(self.construct_new_path(&fn_path)); 151 | let call = render(call); 152 | 153 | quote_spanned! { span=> 154 | if true { 155 | #call 156 | } else { 157 | #original_call 158 | } 159 | } 160 | } 161 | ForwardAttr::ImplBlock { path, .. } => { 162 | let fn_name = if let Some(segment) = fn_path.path.segments.last() { 163 | &segment.ident 164 | } else { 165 | return original_call.into(); 166 | }; 167 | 168 | let rendered_call = render(create_empty_call(path, fn_name).into()); 169 | 170 | quote_spanned! { span=> 171 | if true { 172 | #original_call 173 | } else { 174 | #rendered_call; 175 | 176 | unreachable!() 177 | } 178 | } 179 | } 180 | }) 181 | .expect("valid expression") 182 | } 183 | Call::Method(method_call) => match self { 184 | ForwardAttr::ImplBlock { path, .. } | ForwardAttr::Direct { path, .. } => { 185 | let rendered_call = render(create_empty_call(path, &method_call.method).into()); 186 | 187 | parse2(quote_spanned! { span=> 188 | if true { 189 | #original_call 190 | } else { 191 | #rendered_call; 192 | 193 | unreachable!() 194 | } 195 | }) 196 | .expect("valid expression") 197 | } 198 | ForwardAttr::Replace { 199 | ref from, ref to, .. 200 | } => { 201 | emit_error!( 202 | call.span(), 203 | "a replacement `forward` attribute is not supported for method calls"; 204 | help = from.span().join(to.span()).unwrap_or_else(|| self.span()) => 205 | "try replacing it with a direct location, such as `{}`", quote! { #to }, 206 | ); 207 | 208 | original_call.into() 209 | } 210 | }, 211 | } 212 | } 213 | 214 | /// Constructs a new path correctly using addressing the forwarded function. 215 | pub(super) fn construct_new_path(self, fn_path: &ExprPath) -> ExprPath { 216 | let mut resulting_path = fn_path.clone(); 217 | 218 | match self { 219 | ForwardAttr::Direct { ref path, .. } => { 220 | for (i, segment) in path.segments.iter().enumerate() { 221 | resulting_path.path.segments.insert(i, segment.clone()); 222 | } 223 | } 224 | ForwardAttr::ImplBlock { .. } => { 225 | unreachable!("`construct_new_path` is never called for an `impl` forward attribute") 226 | } 227 | ForwardAttr::Replace { from, to, .. } => { 228 | if !check_prefix(&from, &fn_path.path) { 229 | return resulting_path; 230 | } 231 | 232 | resulting_path.path.segments = to 233 | .segments 234 | .into_pairs() 235 | .map(punctuate_end) // we don't want to have an `End` in the middle 236 | .chain( 237 | resulting_path 238 | .path 239 | .segments 240 | .into_pairs() 241 | .skip(from.segments.len()), 242 | ) 243 | .collect(); 244 | 245 | // Make sure that the path doesn't end with `::` 246 | if let Some(last_value) = resulting_path.path.segments.pop() { 247 | resulting_path.path.segments.push(last_value.into_value()); 248 | } 249 | } 250 | } 251 | 252 | resulting_path 253 | } 254 | } 255 | 256 | /// Creates an empty call to the given function. 257 | fn create_empty_call(mut path: Path, fn_name: &Ident) -> ExprCall { 258 | if let Some(segment_pair) = path.segments.pop() { 259 | path.segments 260 | .push(impl_block_stub_name(segment_pair.value(), fn_name, path.span()).into()); 261 | } else { 262 | abort!(path, "path must have at least one segment"); 263 | } 264 | 265 | ExprCall { 266 | attrs: Vec::new(), 267 | func: Box::new( 268 | ExprPath { 269 | attrs: Vec::new(), 270 | qself: None, 271 | path, 272 | } 273 | .into(), 274 | ), 275 | paren_token: Default::default(), 276 | args: Default::default(), 277 | } 278 | } 279 | 280 | /// Checks if the path is a prefix and emits errors, if it isn't. 281 | fn check_prefix(possible_prefix: &Path, path: &Path) -> bool { 282 | if possible_prefix.segments.len() > path.segments.len() { 283 | emit_error!( 284 | path, 285 | "cannot replace `{}` in this path", 286 | quote! { #possible_prefix }; 287 | help = possible_prefix.span()=> "try specifing a prefix of `{}` in the `forward` attribute", 288 | quote! { #path } 289 | ); 290 | return false; 291 | } 292 | 293 | for (prefix_segment, path_segment) in possible_prefix.segments.iter().zip(path.segments.iter()) 294 | { 295 | if prefix_segment != path_segment { 296 | emit_error!( 297 | path, 298 | "cannot replace `{}` in this path", 299 | quote! { #possible_prefix }; 300 | note = path_segment.span()=> "`{}` != `{}`", 301 | quote! { #prefix_segment }, 302 | quote! { #path_segment }; 303 | help = possible_prefix.span()=> "try specifing a prefix of `{}` in the `forward` attribute", 304 | quote! { #path } 305 | ); 306 | return false; 307 | } 308 | } 309 | 310 | true 311 | } 312 | 313 | /// Transforms `Pair::End` pairs to `Pair::Punctuated` ones. 314 | fn punctuate_end(pair: Pair) -> Pair { 315 | match pair { 316 | Pair::End(end) => Pair::Punctuated(end, Default::default()), 317 | Pair::Punctuated(elem, punct) => Pair::Punctuated(elem, punct), 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /proc-macro/src/const_generics_impl.rs: -------------------------------------------------------------------------------- 1 | //! Implements the procedural macros using a zero-sized const generics parameter. 2 | //! 3 | //! # Advantages of this approach 4 | //! - helpful error messages for typos 5 | //! - supports arbitrarily complex strings out of the box 6 | //! - quick to compute 7 | //! 8 | //! # Disadvantages of this approach 9 | //! - error messages for no invariants not very readable 10 | //! 11 | //! # What the generated code looks like 12 | //! 13 | //! ```rust,ignore 14 | //! #[pre::pre(some_val > 42.0)] 15 | //! fn has_preconditions(some_val: f32) -> f32 { 16 | //! assert!(some_val > 42.0); 17 | //! 18 | //! some_val 19 | //! } 20 | //! 21 | //! #[pre::pre] 22 | //! fn main() { 23 | //! #[assure(some_val > 42.0, reason = "43.0 > 42.0")] 24 | //! has_preconditions(43.0); 25 | //! } 26 | //! ``` 27 | //! 28 | //! turns into 29 | //! 30 | //! ```rust,ignore 31 | //! #[doc = "..."] 32 | //! fn has_preconditions( 33 | //! some_val: f32, 34 | //! #[cfg(not(doc))] 35 | //! _: (::pre::BooleanCondition<"some_val > 42.0">,), 36 | //! ) -> f32 { 37 | //! ::core::debug_assert!( 38 | //! some_val > 42.0 39 | //! "boolean precondition was wrongly assured: `{}`", 40 | //! ::core::stringify!(some_val > 42.0) 41 | //! ); 42 | //! assert!(some_val > 42.0); 43 | //! 44 | //! some_val 45 | //! } 46 | //! 47 | //! fn main() { 48 | //! has_preconditions( 49 | //! 43.0, 50 | //! (::pre::BooleanCondition<"some_val > 42.0">,), 51 | //! ); 52 | //! } 53 | //! ``` 54 | 55 | use proc_macro2::{Span, TokenStream}; 56 | use quote::{quote, quote_spanned, TokenStreamExt}; 57 | use syn::{parse2, spanned::Spanned, Ident, ItemFn, LitStr}; 58 | 59 | use crate::{ 60 | call::Call, 61 | helpers::{add_span_to_signature, combine_cfg, CRATE_NAME}, 62 | precondition::{CfgPrecondition, Precondition, ReadWrite}, 63 | }; 64 | 65 | /// Renders a precondition list to a token stream. 66 | fn render_condition_list(mut preconditions: Vec, span: Span) -> TokenStream { 67 | preconditions.sort_unstable(); 68 | 69 | let mut tokens = TokenStream::new(); 70 | let crate_name = Ident::new(&CRATE_NAME, span); 71 | 72 | for precondition in preconditions { 73 | match precondition.precondition() { 74 | Precondition::ValidPtr { 75 | ident, read_write, .. 76 | } => { 77 | let ident_lit = LitStr::new(&ident.to_string(), ident.span()); 78 | let rw_str = match read_write { 79 | ReadWrite::Read { .. } => LitStr::new("r", read_write.span()), 80 | ReadWrite::Write { .. } => LitStr::new("w", read_write.span()), 81 | ReadWrite::Both { .. } => LitStr::new("r+w", read_write.span()), 82 | }; 83 | tokens.append_all(quote_spanned! { precondition.span()=> 84 | ::#crate_name::ValidPtrCondition::<#ident_lit, #rw_str> 85 | }); 86 | } 87 | Precondition::ProperAlign { ident, .. } => { 88 | let ident_lit = LitStr::new(&ident.to_string(), ident.span()); 89 | tokens.append_all(quote_spanned! { precondition.span()=> 90 | ::#crate_name::ProperAlignCondition::<#ident_lit> 91 | }); 92 | } 93 | Precondition::Boolean(expr) => { 94 | let as_str = LitStr::new("e! { #expr }.to_string(), precondition.span()); 95 | 96 | tokens.append_all(quote_spanned! { precondition.span()=> 97 | ::#crate_name::BooleanCondition::<#as_str> 98 | }); 99 | } 100 | Precondition::Custom(string) => { 101 | tokens.append_all(quote_spanned! { precondition.span()=> 102 | ::#crate_name::CustomCondition::<#string> 103 | }); 104 | } 105 | } 106 | 107 | tokens.append_all(quote_spanned! { span=> 108 | , 109 | }); 110 | } 111 | 112 | tokens 113 | } 114 | 115 | /// Generates the code for the function with the precondition handling added. 116 | pub(crate) fn render_pre( 117 | preconditions: Vec, 118 | function: &mut ItemFn, 119 | span: Span, 120 | ) -> TokenStream { 121 | let combined_cfg = combine_cfg(&preconditions, span); 122 | let preconditions = render_condition_list(preconditions, span); 123 | 124 | // Include the precondition site into the span of the function. 125 | // This improves the error messages for the case where no preconditions are specified. 126 | add_span_to_signature(span, &mut function.sig); 127 | 128 | function.sig.inputs.push( 129 | parse2(quote_spanned! { span=> 130 | #[cfg(all(not(doc), #combined_cfg))] 131 | _: (#preconditions) 132 | }) 133 | .expect("parses as a function argument"), 134 | ); 135 | 136 | quote! { 137 | #function 138 | } 139 | } 140 | 141 | /// Generates the code for the call with the precondition handling added. 142 | pub(crate) fn render_assure( 143 | preconditions: Vec, 144 | mut call: Call, 145 | span: Span, 146 | ) -> Call { 147 | let combined_cfg = combine_cfg(&preconditions, span); 148 | let preconditions = render_condition_list(preconditions, span); 149 | 150 | call.args_mut().push( 151 | parse2(quote_spanned! { span=> 152 | #[cfg(all(not(doc), #combined_cfg))] 153 | (#preconditions) 154 | }) 155 | .expect("parses as an expression"), 156 | ); 157 | 158 | call 159 | } 160 | -------------------------------------------------------------------------------- /proc-macro/src/documentation.rs: -------------------------------------------------------------------------------- 1 | //! Provides functions to generate documentation about the preconditions. 2 | 3 | use proc_macro2::Span; 4 | use quote::{quote, quote_spanned}; 5 | use std::{env, fmt::Write}; 6 | use syn::{ 7 | spanned::Spanned, 8 | token::{Bracket, Pound}, 9 | AttrStyle, Attribute, Ident, LitStr, Path, PathArguments, Signature, 10 | }; 11 | 12 | use crate::{ 13 | extern_crate::{ImplBlock, Module}, 14 | helpers::HINT_REASON, 15 | precondition::{CfgPrecondition, Precondition}, 16 | }; 17 | 18 | /// Evaluates to the base URL of the documentation for the `pre` crate. 19 | macro_rules! docs_url { 20 | () => { 21 | concat!("https://docs.rs/pre/", env!("CARGO_PKG_VERSION"), "/pre",) 22 | }; 23 | } 24 | 25 | /// A link to the documentation of the `pre` attribute. 26 | const PRE_LINK: &str = concat!(docs_url!(), "/attr.pre.html"); 27 | 28 | /// A link to the documentation of the `assure` attribute. 29 | const ASSURE_LINK: &str = concat!(docs_url!(), "/attr.assure.html"); 30 | 31 | /// A link to the documentation of the `extern_crate` attribute. 32 | const EXTERN_CRATE_LINK: &str = concat!(docs_url!(), "/attr.extern_crate.html"); 33 | 34 | /// The required context for generating `impl` block documentation. 35 | pub(crate) struct ImplBlockContext<'a> { 36 | /// The `impl` block that the item belongs to. 37 | pub(crate) impl_block: &'a ImplBlock, 38 | /// The path to the `impl` block. 39 | pub(crate) path: &'a Path, 40 | /// The name of the top level module that the `impl` block is contained in. 41 | pub(crate) top_level_module: &'a Ident, 42 | } 43 | 44 | macro_rules! doc_inline { 45 | ($docs:expr) => { 46 | write!($docs).expect("string writes don't fail") 47 | }; 48 | ($docs:expr, $format_str:literal) => { 49 | write!($docs, $format_str).expect("string writes don't fail") 50 | }; 51 | ($docs:expr, $format_str:literal, $($args:expr),*) => { 52 | write!($docs, $format_str, $($args,)*).expect("string writes don't fail") 53 | }; 54 | } 55 | 56 | macro_rules! doc { 57 | ($docs:expr) => { 58 | writeln!($docs).expect("string writes don't fail") 59 | }; 60 | ($docs:expr, $format_str:literal) => { 61 | writeln!($docs, $format_str).expect("string writes don't fail") 62 | }; 63 | ($docs:expr, $format_str:literal, $($args:expr),*) => { 64 | writeln!($docs, $format_str, $($args,)*).expect("string writes don't fail") 65 | }; 66 | } 67 | 68 | /// Generates documentation of the preconditions for a function or method. 69 | pub(crate) fn generate_docs( 70 | function: &Signature, 71 | preconditions: &[CfgPrecondition], 72 | impl_block_context: Option, 73 | ) -> Attribute { 74 | let span = function.span(); 75 | let mut docs = String::new(); 76 | let plural = preconditions.len() != 1; 77 | 78 | if let Some(ctx) = &impl_block_context { 79 | let (path_str, path_str_no_generics) = if let Some(ty) = &ctx.impl_block.ty() { 80 | let mut path_str = String::new(); 81 | for segment in ctx.path.segments.iter() { 82 | doc_inline!(path_str, "{}::", segment.ident); 83 | } 84 | 85 | doc_inline!(path_str, "{}", ty.ident); 86 | 87 | let mut path_str_no_generics = path_str.clone(); 88 | 89 | match &ty.arguments { 90 | PathArguments::None => (), 91 | PathArguments::AngleBracketed(args) => { 92 | doc_inline!(path_str, "<"); 93 | for arg in &args.args { 94 | doc_inline!(path_str, "{}", quote! { #arg }); 95 | } 96 | doc_inline!(path_str, ">"); 97 | } 98 | PathArguments::Parenthesized(_) => unreachable!(), 99 | } 100 | 101 | let name = &function.ident; 102 | doc_inline!(path_str, "::{}", quote! { #name }); 103 | doc_inline!(path_str_no_generics, "::{}", quote! { #name }); 104 | 105 | (path_str, Some(path_str_no_generics)) 106 | } else { 107 | let path = &ctx.path; 108 | let ty = &ctx.impl_block.self_ty; 109 | let name = &function.ident; 110 | 111 | ( 112 | format!( 113 | "{}::{}::{}", 114 | quote! { #path }, 115 | quote! { #ty }, 116 | quote! { #name } 117 | ), 118 | None, 119 | ) 120 | }; 121 | 122 | // TODO: remove the nightly condition here, once rust paths are supported for documentation 123 | // links on stable 124 | match (cfg!(nightly), path_str_no_generics) { 125 | (true, Some(no_generics)) => doc!( 126 | docs, 127 | "A stub for the preconditions of the [`{}`](value@{}) function.", 128 | path_str, 129 | no_generics 130 | ), 131 | _ => doc!( 132 | docs, 133 | "A stub for the preconditions of the `{}` function.", 134 | path_str 135 | ), 136 | } 137 | 138 | doc!(docs); 139 | 140 | doc!(docs, "# What is this function?"); 141 | doc!(docs); 142 | 143 | doc!( 144 | docs, 145 | "This function was generated by an `impl` block inside a [`extern_crate` attribute]({}) that looked like this:", 146 | EXTERN_CRATE_LINK 147 | ); 148 | doc!(docs); 149 | 150 | doc!(docs, "```rust,ignore"); 151 | let ty = &ctx.impl_block.self_ty; 152 | let where_clause = &ctx.impl_block.generics.where_clause; 153 | let generics = if !ctx.impl_block.generics.params.is_empty() { 154 | Some(&ctx.impl_block.generics) 155 | } else { 156 | None 157 | }; 158 | 159 | doc!( 160 | docs, 161 | "impl{} {} {} {{", 162 | quote! { #generics }, 163 | quote! { #ty }, 164 | quote! { #where_clause } 165 | ); 166 | 167 | doc!(docs, " {};", quote! { #function }); 168 | if ctx.impl_block.items.len() > 1 { 169 | doc!(docs, " /* other items omitted */"); 170 | } 171 | 172 | doc!(docs, "}}"); 173 | doc!(docs, "```"); 174 | 175 | doc!(docs); 176 | doc!(docs, "Preconditions on external functions inside of an `impl` block are attached to empty functions like this one."); 177 | doc!(docs, "When the preconditions should be checked, a call to this function is inserted, which triggers checking the preconditions."); 178 | doc!(docs); 179 | } 180 | 181 | if !preconditions.is_empty() { 182 | doc!(docs, "# This function has preconditions"); 183 | doc!(docs); 184 | 185 | if plural { 186 | doc!(docs, "This function has the following preconditions generated by [`pre` attributes]({}):", PRE_LINK); 187 | } else { 188 | doc!(docs, "This function has the following precondition generated by the [`pre` attribute]({}):", PRE_LINK); 189 | } 190 | doc!(docs); 191 | 192 | for precondition in preconditions { 193 | match precondition.precondition() { 194 | Precondition::ValidPtr { 195 | ident, read_write, .. 196 | } => doc!( 197 | docs, 198 | "- the pointer `{}` must be valid for {}", 199 | ident.to_string(), 200 | read_write.doc_description() 201 | ), 202 | Precondition::ProperAlign { ident, .. } => doc!( 203 | docs, 204 | "- the pointer `{}` must have a proper alignment for its type", 205 | ident.to_string() 206 | ), 207 | Precondition::Boolean(expr) => doc!(docs, "- `{}`", quote! { #expr }), 208 | Precondition::Custom(text) => doc!(docs, "- {}", text.value()), 209 | } 210 | } 211 | 212 | doc!(docs); 213 | if plural { 214 | doc!( 215 | docs, 216 | "To call the function you need to [`assure`]({}) that the preconditions hold:", 217 | ASSURE_LINK 218 | ); 219 | } else { 220 | doc!( 221 | docs, 222 | "To call the function you need to [`assure`]({}) that the precondition holds:", 223 | ASSURE_LINK 224 | ); 225 | } 226 | doc!(docs); 227 | doc!(docs, "```rust,ignore"); 228 | 229 | if let Some(ctx) = &impl_block_context { 230 | let mut path_str = format!("{}", ctx.top_level_module); 231 | for segment in ctx.path.segments.iter().skip(1).chain(ctx.impl_block.ty()) { 232 | write!(path_str, "::{}", segment.ident).expect("string writes don't fail"); 233 | } 234 | 235 | if let Ok(name) = env::var("CARGO_PKG_NAME") { 236 | let mut name = name.replace('-', "_"); 237 | name.push_str("::"); 238 | path_str.insert_str(0, &name); 239 | } 240 | 241 | doc!(docs, "#[forward(impl {})]", path_str); 242 | } 243 | 244 | for precondition in preconditions { 245 | doc!(docs, "#[assure(",); 246 | doc!(docs, " {},", precondition.precondition()); 247 | doc!(docs, " reason = {:?}", HINT_REASON); 248 | doc!(docs, ")]"); 249 | } 250 | 251 | let receiver = if function.receiver().is_some() { 252 | "x." 253 | } else { 254 | "" 255 | }; 256 | let parameters = if function.inputs.is_empty() { 257 | "" 258 | } else { 259 | "/* parameters omitted */" 260 | }; 261 | doc!(docs, "{}{}({});", receiver, function.ident, parameters); 262 | 263 | doc!(docs, "```"); 264 | } 265 | 266 | let docs = LitStr::new(&docs, span); 267 | Attribute { 268 | pound_token: Pound { spans: [span] }, 269 | style: AttrStyle::Outer, 270 | bracket_token: Bracket { span }, 271 | path: Ident::new("doc", span).into(), 272 | tokens: quote_spanned! { span=> 273 | = #docs 274 | }, 275 | } 276 | } 277 | 278 | /// Generates documentation of the preconditions for a `extern_crate` module. 279 | pub(crate) fn generate_module_docs(module: &Module, path: &Path) -> Attribute { 280 | let span = module.span(); 281 | let mut docs = String::new(); 282 | 283 | let mut path_str = String::new(); 284 | for segment in path.segments.iter() { 285 | if !path_str.is_empty() { 286 | path_str.push_str("::"); 287 | } 288 | doc_inline!(path_str, "{}", segment.ident); 289 | } 290 | 291 | let item_name = if path.segments.len() == 1 { 292 | "crate" 293 | } else { 294 | "module" 295 | }; 296 | 297 | if cfg!(nightly) { 298 | doc!( 299 | docs, 300 | "[`pre` definitions]({}) for the [`{}`](module@{}) {}.", 301 | PRE_LINK, 302 | path_str, 303 | path_str, 304 | item_name 305 | ); 306 | } else { 307 | doc!( 308 | docs, 309 | "[`pre` definitions]({}) for the `{}` {}.", 310 | PRE_LINK, 311 | path_str, 312 | item_name 313 | ); 314 | } 315 | 316 | doc!(docs); 317 | doc!( 318 | docs, 319 | "This module was generated by a [`extern_crate` attribute]({}).", 320 | EXTERN_CRATE_LINK 321 | ); 322 | doc!( 323 | docs, 324 | "It acts as a drop-in replacement for the `{}` module.", 325 | path_str 326 | ); 327 | 328 | let docs = LitStr::new(&docs, span); 329 | Attribute { 330 | pound_token: Pound { spans: [span] }, 331 | style: AttrStyle::Outer, 332 | bracket_token: Bracket { span }, 333 | path: Ident::new("doc", span).into(), 334 | tokens: quote_spanned! { span=> 335 | = #docs 336 | }, 337 | } 338 | } 339 | 340 | /// Generates the start of the documentation for `extern_crate`-defined functions. 341 | pub(crate) fn generate_extern_crate_fn_docs( 342 | path: &Path, 343 | function: &Signature, 344 | span: Span, 345 | ) -> Attribute { 346 | let mut docs = String::new(); 347 | 348 | let mut path_str = String::new(); 349 | for segment in path.segments.iter() { 350 | doc_inline!(path_str, "{}::", segment.ident); 351 | } 352 | doc_inline!(path_str, "{}", function.ident); 353 | 354 | if cfg!(nightly) { 355 | doc!( 356 | docs, 357 | "[`{}`](value@{}) with preconditions.", 358 | path_str, 359 | path_str 360 | ); 361 | } else { 362 | doc!(docs, "`{}` with preconditions.", path_str); 363 | } 364 | doc!(docs); 365 | doc!( 366 | docs, 367 | "This function behaves exactly like `{}`, but also has preconditions checked by `pre`.", 368 | path_str 369 | ); 370 | if function.unsafety.is_some() { 371 | doc!(docs); 372 | if cfg!(nightly) { 373 | doc!( 374 | docs, 375 | "**You should also read the [Safety section on the documentation of `{}`](value@{}#safety).**", 376 | path_str, 377 | path_str 378 | ); 379 | } else { 380 | doc!( 381 | docs, 382 | "**You should also read the Safety section on the documentation of `{}`.**", 383 | path_str 384 | ); 385 | } 386 | } 387 | doc!(docs); 388 | 389 | let docs = LitStr::new(&docs, span); 390 | Attribute { 391 | pound_token: Pound { spans: [span] }, 392 | style: AttrStyle::Outer, 393 | bracket_token: Bracket { span }, 394 | path: Ident::new("doc", span).into(), 395 | tokens: quote_spanned! { span=> 396 | = #docs 397 | }, 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /proc-macro/src/extern_crate.rs: -------------------------------------------------------------------------------- 1 | //! Provides handling of `extern_crate` attributes. 2 | //! 3 | //! # What the generated code looks like 4 | //! 5 | //! ```rust,ignore 6 | //! #[pre::extern_crate(std)] 7 | //! mod pre_std { 8 | //! mod ptr { 9 | //! #[pre(valid_ptr(src, r))] 10 | //! unsafe fn read(src: *const T) -> T; 11 | //! 12 | //! impl NonNull { 13 | //! #[pre(!ptr.is_null())] 14 | //! const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 15 | //! } 16 | //! } 17 | //! } 18 | //! ``` 19 | //! 20 | //! turns into 21 | //! 22 | //! ```rust,ignore 23 | //! #[doc = "..."] 24 | //! mod pre_std { 25 | //! #[allow(unused_imports)] 26 | //! use pre::pre; 27 | //! #[allow(unused_imports)] 28 | //! #[doc(no_inline)] 29 | //! pub(crate) use std::*; 30 | //! 31 | //! #[doc = "..."] 32 | //! pub(crate) mod ptr { 33 | //! #[allow(unused_imports)] 34 | //! use pre::pre; 35 | //! #[allow(unused_imports)] 36 | //! #[doc(no_inline)] 37 | //! pub(crate) use std::ptr::*; 38 | //! 39 | //! #[doc = "..."] 40 | //! #[pre(!ptr.is_null())] 41 | //! #[pre(no_doc)] 42 | //! #[pre(no_debug_assert)] 43 | //! #[inline(always)] 44 | //! #[allow(non_snake_case)] 45 | //! pub(crate) fn NonNull__impl__new_unchecked__() {} 46 | //! 47 | //! #[pre(valid_ptr(src, r))] 48 | //! #[inline(always)] 49 | //! pub(crate) unsafe fn read(src: *const T) -> T { 50 | //! std::ptr::read(src) 51 | //! } 52 | //! } 53 | //! } 54 | //! ``` 55 | 56 | use proc_macro2::{Span, TokenStream}; 57 | use quote::{quote, quote_spanned, TokenStreamExt}; 58 | use std::fmt; 59 | use syn::{ 60 | braced, 61 | parse::{Parse, ParseStream}, 62 | spanned::Spanned, 63 | token::Brace, 64 | Attribute, FnArg, ForeignItemFn, Ident, ItemUse, Path, PathArguments, PathSegment, Token, 65 | Visibility, 66 | }; 67 | 68 | use crate::{ 69 | documentation::{generate_extern_crate_fn_docs, generate_module_docs}, 70 | helpers::{visit_matching_attrs_parsed_mut, AttributeAction, CRATE_NAME}, 71 | pre_attr::PreAttr, 72 | }; 73 | 74 | pub(crate) use impl_block::{impl_block_stub_name, ImplBlock}; 75 | 76 | mod impl_block; 77 | 78 | /// The parsed version of the `extern_crate` attribute content. 79 | pub(crate) struct ExternCrateAttr { 80 | /// The path of the crate/module to which function calls will be forwarded. 81 | path: Path, 82 | } 83 | 84 | impl fmt::Display for ExternCrateAttr { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | write!(f, "#[extern_crate(")?; 87 | 88 | if self.path.leading_colon.is_some() { 89 | write!(f, "::")?; 90 | } 91 | 92 | for segment in &self.path.segments { 93 | write!(f, "{}", segment.ident)?; 94 | } 95 | 96 | write!(f, ")]") 97 | } 98 | } 99 | 100 | impl Parse for ExternCrateAttr { 101 | fn parse(input: ParseStream) -> syn::Result { 102 | Ok(ExternCrateAttr { 103 | path: input.call(Path::parse_mod_style)?, 104 | }) 105 | } 106 | } 107 | 108 | /// A parsed `extern_crate` annotated module. 109 | pub(crate) struct Module { 110 | /// The attributes on the module. 111 | attrs: Vec, 112 | /// The visibility on the module. 113 | visibility: Visibility, 114 | /// The `mod` token. 115 | mod_token: Token![mod], 116 | /// The name of the module. 117 | ident: Ident, 118 | /// The braces surrounding the content. 119 | braces: Brace, 120 | /// The impl blocks contained in the module. 121 | impl_blocks: Vec, 122 | /// The imports contained in the module. 123 | imports: Vec, 124 | /// The functions contained in the module. 125 | functions: Vec, 126 | /// The submodules contained in the module. 127 | modules: Vec, 128 | } 129 | 130 | impl fmt::Display for Module { 131 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 132 | write!(f, "{}", self.original_token_stream()) 133 | } 134 | } 135 | 136 | impl Spanned for Module { 137 | fn span(&self) -> Span { 138 | self.visibility 139 | .span() 140 | .join(self.braces.span) 141 | .unwrap_or(self.braces.span) 142 | } 143 | } 144 | 145 | impl Parse for Module { 146 | fn parse(input: ParseStream) -> syn::Result { 147 | let attrs = input.call(Attribute::parse_outer)?; 148 | let visibility = input.parse()?; 149 | let mod_token = input.parse()?; 150 | let ident = input.parse()?; 151 | 152 | let content; 153 | let braces = braced!(content in input); 154 | 155 | let mut impl_blocks = Vec::new(); 156 | let mut imports = Vec::new(); 157 | let mut functions = Vec::new(); 158 | let mut modules = Vec::new(); 159 | 160 | while !content.is_empty() { 161 | if content.peek(Token![impl]) { 162 | impl_blocks.push(content.parse()?); 163 | } else if ::parse(&content.fork()).is_ok() { 164 | imports.push(content.parse()?); 165 | } else if ::parse(&content.fork()).is_ok() { 166 | functions.push(content.parse()?); 167 | } else { 168 | modules.push(content.parse().map_err(|err| { 169 | syn::Error::new( 170 | err.span(), 171 | "expected a module, a function signature, an impl block or a use statement", 172 | ) 173 | })?); 174 | } 175 | } 176 | 177 | Ok(Module { 178 | attrs, 179 | visibility, 180 | mod_token, 181 | ident, 182 | braces, 183 | impl_blocks, 184 | imports, 185 | functions, 186 | modules, 187 | }) 188 | } 189 | } 190 | 191 | impl Module { 192 | /// Renders this `extern_crate` annotated module to its final result. 193 | pub(crate) fn render(&self, attr: ExternCrateAttr) -> TokenStream { 194 | let mut tokens = TokenStream::new(); 195 | 196 | self.render_inner(attr.path, &mut tokens, None, &self.ident); 197 | 198 | tokens 199 | } 200 | 201 | /// A helper function to generate the final token stream. 202 | /// 203 | /// This allows passing the top level visibility and the updated path into recursive calls. 204 | fn render_inner( 205 | &self, 206 | mut path: Path, 207 | tokens: &mut TokenStream, 208 | visibility: Option<&TokenStream>, 209 | top_level_module: &Ident, 210 | ) { 211 | if visibility.is_some() { 212 | // Update the path only in recursive calls. 213 | path.segments.push(PathSegment { 214 | ident: self.ident.clone(), 215 | arguments: PathArguments::None, 216 | }); 217 | } 218 | 219 | let mut attrs = self.attrs.clone(); 220 | let mut render_docs = true; 221 | visit_matching_attrs_parsed_mut(&mut attrs, "pre", |attr| match attr.content() { 222 | PreAttr::NoDoc(_) => { 223 | render_docs = false; 224 | 225 | AttributeAction::Remove 226 | } 227 | _ => AttributeAction::Keep, 228 | }); 229 | 230 | if render_docs { 231 | let docs = generate_module_docs(self, &path); 232 | tokens.append_all(quote! { #docs }); 233 | } 234 | tokens.append_all(attrs); 235 | 236 | let visibility = if let Some(visibility) = visibility { 237 | // We're in a recursive call. 238 | // Use the visibility passed to us. 239 | tokens.append_all(quote! { #visibility }); 240 | 241 | visibility.clone() 242 | } else { 243 | // We're in the outermost call. 244 | // Use the original visibility and decide which visibility to use in recursive calls. 245 | let local_vis = &self.visibility; 246 | tokens.append_all(quote! { #local_vis }); 247 | 248 | if let Visibility::Public(pub_keyword) = local_vis { 249 | quote! { #pub_keyword } 250 | } else { 251 | let span = match local_vis { 252 | Visibility::Inherited => self.mod_token.span(), 253 | _ => local_vis.span(), 254 | }; 255 | quote_spanned! { span=> pub(crate) } 256 | } 257 | }; 258 | 259 | let mod_token = self.mod_token; 260 | tokens.append_all(quote! { #mod_token }); 261 | 262 | tokens.append(self.ident.clone()); 263 | 264 | let mut brace_content = TokenStream::new(); 265 | 266 | let crate_name = Ident::new(&CRATE_NAME, Span::call_site()); 267 | brace_content.append_all(quote! { 268 | #[allow(unused_imports)] 269 | #[doc(no_inline)] 270 | #visibility use #path::*; 271 | 272 | #[allow(unused_imports)] 273 | use #crate_name::pre; 274 | }); 275 | 276 | for impl_block in &self.impl_blocks { 277 | impl_block.render(&mut brace_content, &path, &visibility, top_level_module); 278 | } 279 | 280 | for import in &self.imports { 281 | brace_content.append_all(quote! { #import }); 282 | } 283 | 284 | for function in &self.functions { 285 | render_function(function, &mut brace_content, &path, &visibility); 286 | } 287 | 288 | for module in &self.modules { 289 | module.render_inner( 290 | path.clone(), 291 | &mut brace_content, 292 | Some(&visibility), 293 | top_level_module, 294 | ); 295 | } 296 | 297 | tokens.append_all(quote_spanned! { self.braces.span=> { #brace_content } }); 298 | } 299 | 300 | /// Generates a token stream that is semantically equivalent to the original token stream. 301 | /// 302 | /// This should only be used for debug purposes. 303 | fn original_token_stream(&self) -> TokenStream { 304 | let mut stream = TokenStream::new(); 305 | stream.append_all(&self.attrs); 306 | let vis = &self.visibility; 307 | stream.append_all(quote! { #vis }); 308 | stream.append_all(quote! { mod }); 309 | stream.append(self.ident.clone()); 310 | 311 | let mut content = TokenStream::new(); 312 | content.append_all( 313 | self.impl_blocks 314 | .iter() 315 | .map(|impl_block| impl_block.original_token_stream()), 316 | ); 317 | content.append_all(&self.imports); 318 | content.append_all(&self.functions); 319 | content.append_all(self.modules.iter().map(|m| m.original_token_stream())); 320 | 321 | stream.append_all(quote! { { #content } }); 322 | 323 | stream 324 | } 325 | } 326 | 327 | /// Generates the code for a function inside a `extern_crate` module. 328 | fn render_function( 329 | function: &ForeignItemFn, 330 | tokens: &mut TokenStream, 331 | path: &Path, 332 | visibility: &TokenStream, 333 | ) { 334 | tokens.append_all(&function.attrs); 335 | let doc_header = generate_extern_crate_fn_docs(path, &function.sig, function.span()); 336 | tokens.append_all(quote! { #doc_header }); 337 | tokens.append_all(quote_spanned! { function.span()=> #[inline(always)] }); 338 | tokens.append_all(visibility.clone().into_iter().map(|mut token| { 339 | token.set_span(function.span()); 340 | token 341 | })); 342 | let signature = &function.sig; 343 | tokens.append_all(quote! { #signature }); 344 | 345 | let mut path = path.clone(); 346 | 347 | path.segments.push(PathSegment { 348 | ident: function.sig.ident.clone(), 349 | arguments: PathArguments::None, 350 | }); 351 | 352 | // Update the spans of the `::` tokens to lie in the function 353 | for punct in path 354 | .segments 355 | .pairs_mut() 356 | .map(|p| p.into_tuple().1) 357 | .flatten() 358 | { 359 | punct.spans = [function.span(); 2]; 360 | } 361 | 362 | let mut args_list = TokenStream::new(); 363 | args_list.append_separated( 364 | function.sig.inputs.iter().map(|arg| match arg { 365 | FnArg::Receiver(_) => unreachable!("receiver is not valid in a function argument list"), 366 | FnArg::Typed(pat) => &pat.pat, 367 | }), 368 | quote_spanned! { function.span()=> , }, 369 | ); 370 | tokens.append_all(quote_spanned! { function.span()=> { #path(#args_list) } }); 371 | } 372 | -------------------------------------------------------------------------------- /proc-macro/src/extern_crate/impl_block.rs: -------------------------------------------------------------------------------- 1 | //! Handles impl blocks in `extern_crate` modules. 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use proc_macro_error::emit_error; 5 | use quote::{format_ident, quote, quote_spanned, TokenStreamExt}; 6 | use syn::{ 7 | braced, 8 | parse::{Parse, ParseStream}, 9 | spanned::Spanned, 10 | token::Brace, 11 | ForeignItemFn, Generics, Ident, Path, PathArguments, PathSegment, Token, Type, 12 | }; 13 | 14 | use crate::{ 15 | documentation::{generate_docs, ImplBlockContext}, 16 | helpers::visit_matching_attrs_parsed, 17 | pre_attr::PreAttr, 18 | precondition::CfgPrecondition, 19 | }; 20 | 21 | /// An impl block in a `extern_crate` module. 22 | pub(crate) struct ImplBlock { 23 | /// The impl keyword. 24 | impl_keyword: Token![impl], 25 | /// The generics for the impl block. 26 | pub(crate) generics: Generics, 27 | /// The type which the impl block is for. 28 | pub(crate) self_ty: Box, 29 | /// The brace of the block. 30 | brace: Brace, 31 | /// The functions which the block applies to. 32 | pub(crate) items: Vec, 33 | } 34 | 35 | impl Parse for ImplBlock { 36 | fn parse(input: ParseStream) -> syn::Result { 37 | let impl_keyword = input.parse()?; 38 | let generics = input.parse()?; 39 | let self_ty = input.parse()?; 40 | let where_clause = input.parse()?; 41 | let content; 42 | let brace = braced!(content in input); 43 | 44 | let mut items = Vec::new(); 45 | 46 | while !content.is_empty() { 47 | items.push(content.parse()?); 48 | } 49 | 50 | Ok(ImplBlock { 51 | impl_keyword, 52 | generics: Generics { 53 | where_clause, 54 | ..generics 55 | }, 56 | self_ty, 57 | brace, 58 | items, 59 | }) 60 | } 61 | } 62 | 63 | impl Spanned for ImplBlock { 64 | fn span(&self) -> Span { 65 | self.impl_keyword 66 | .span() 67 | .join(self.brace.span) 68 | .unwrap_or_else(|| self.impl_keyword.span()) 69 | } 70 | } 71 | 72 | impl ImplBlock { 73 | /// Generates a token stream that is semantically equivalent to the original token stream. 74 | /// 75 | /// This should only be used for debug purposes. 76 | pub(crate) fn original_token_stream(&self) -> TokenStream { 77 | let mut tokens = TokenStream::new(); 78 | 79 | let impl_keyword = &self.impl_keyword; 80 | tokens.append_all(quote! { #impl_keyword }); 81 | let generics = &self.generics; 82 | tokens.append_all(quote! { #generics }); 83 | let self_ty = &self.self_ty; 84 | tokens.append_all(quote! { #self_ty }); 85 | let where_clause = &generics.where_clause; 86 | tokens.append_all(quote! { #where_clause }); 87 | 88 | let mut items = TokenStream::new(); 89 | items.append_all(&self.items); 90 | tokens.append_all(quote! { { #items } }); 91 | 92 | tokens 93 | } 94 | 95 | /// Returns the type that this impl block is for. 96 | pub(crate) fn ty(&self) -> Option<&PathSegment> { 97 | if let Type::Path(path) = &*self.self_ty { 98 | if path.path.segments.len() != 1 { 99 | let mut path_str = String::new(); 100 | for i in 0..(path.path.segments.len() - 1) { 101 | let segment = &path.path.segments[i]; 102 | path_str.push_str("e! { #segment }.to_string()); 103 | 104 | if i != path.path.segments.len() - 2 { 105 | path_str.push_str("::"); 106 | } 107 | } 108 | 109 | let plural = if path.path.segments.len() > 2 { 110 | "submodules" 111 | } else { 112 | "a submodule" 113 | }; 114 | 115 | emit_error!( 116 | path, 117 | "only paths of length 1 are supported here"; 118 | help = "try adding `{}` as {} and put the `impl` block there", path_str, plural 119 | ); 120 | return None; 121 | } 122 | 123 | if let Some(qself) = &path.qself { 124 | emit_error!( 125 | qself 126 | .lt_token 127 | .span() 128 | .join(qself.gt_token.span()) 129 | .unwrap_or_else(|| qself.ty.span()), 130 | "qualified paths are not supported here" 131 | ); 132 | return None; 133 | } 134 | 135 | let ty = &path.path.segments[0]; 136 | 137 | if matches!(ty.arguments, PathArguments::Parenthesized(_)) { 138 | emit_error!( 139 | ty.arguments.span(), 140 | "parenthesized type arguments are not supported here" 141 | ); 142 | 143 | None 144 | } else { 145 | Some(ty) 146 | } 147 | } else { 148 | emit_error!( 149 | self.self_ty.span(), 150 | "`impl` block are only supported for structs, enums and unions in this context" 151 | ); 152 | 153 | None 154 | } 155 | } 156 | 157 | /// Generates the code for an impl block inside a `extern_crate` module. 158 | pub(crate) fn render( 159 | &self, 160 | tokens: &mut TokenStream, 161 | path: &Path, 162 | visibility: &TokenStream, 163 | top_level_module: &Ident, 164 | ) { 165 | let ty = if let Some(ty) = self.ty() { 166 | ty 167 | } else { 168 | return; 169 | }; 170 | 171 | for function in &self.items { 172 | let docs = { 173 | let mut render_docs = true; 174 | let mut preconditions = Vec::new(); 175 | 176 | visit_matching_attrs_parsed(&function.attrs, "pre", |attr| { 177 | match attr.into_content() { 178 | (PreAttr::NoDoc(_), _, _) => render_docs = false, 179 | (PreAttr::Precondition(precondition), cfg, span) => { 180 | preconditions.push(CfgPrecondition { 181 | precondition, 182 | cfg, 183 | span, 184 | }) 185 | } 186 | _ => (), 187 | } 188 | }); 189 | 190 | if render_docs { 191 | Some(generate_docs( 192 | &function.sig, 193 | &preconditions, 194 | Some(ImplBlockContext { 195 | impl_block: self, 196 | path, 197 | top_level_module, 198 | }), 199 | )) 200 | } else { 201 | None 202 | } 203 | }; 204 | 205 | let name = impl_block_stub_name(ty, &function.sig.ident, function.span()); 206 | tokens.append_all(quote! { #docs }); 207 | tokens.append_all(&function.attrs); 208 | tokens.append_all(quote_spanned! { function.sig.span()=> 209 | // The documentation for `impl` blocks is generated here instead of in the `pre` 210 | // attribute, to allow access to information about the `impl` block. 211 | // In order to prevent it from being generated twice, `pre(no_doc)` is applied 212 | // here. 213 | #[pre(no_doc)] 214 | // The debug assertions for the original method likely won't make sense here, since 215 | // they probably depend on local parameters, which aren't present in this empty 216 | // function. To prevent errors, we remove the debug assertions here. 217 | #[pre(no_debug_assert)] 218 | #[inline(always)] 219 | #[allow(non_snake_case)] 220 | #visibility fn #name() {} 221 | }); 222 | } 223 | } 224 | } 225 | 226 | /// Generates a name to use for an impl block stub function. 227 | pub(crate) fn impl_block_stub_name(ty: &PathSegment, fn_name: &Ident, span: Span) -> Ident { 228 | // Ideally this would start with `_` to reduce the chance for naming collisions with actual 229 | // functions. However this would silence any `dead_code` warnings, which the user may want to 230 | // be aware of. Instead this ends with `__` to reduce the chance for naming collisions. 231 | // 232 | // Note that hygiene would not help in reducing naming collisions, because the function needs 233 | // to be callable from an `assure` attribute that could possibly reside in a different hygenic 234 | // context. 235 | let mut ident = format_ident!("{}__impl__{}__", ty.ident, fn_name); 236 | ident.set_span(span); 237 | 238 | ident 239 | } 240 | -------------------------------------------------------------------------------- /proc-macro/src/helpers.rs: -------------------------------------------------------------------------------- 1 | //! Allows retrieving the name of the main crate. 2 | 3 | use lazy_static::lazy_static; 4 | use proc_macro2::{Span, TokenStream, TokenTree}; 5 | use proc_macro_error::{abort_call_site, emit_error}; 6 | use quote::quote_spanned; 7 | use std::env; 8 | use syn::{ 9 | parenthesized, 10 | parse::{Parse, ParseStream}, 11 | parse2, 12 | spanned::Spanned, 13 | token::Paren, 14 | Attribute, Expr, Signature, Token, 15 | }; 16 | 17 | use crate::precondition::CfgPrecondition; 18 | 19 | pub(crate) use attr::Attr; 20 | 21 | mod attr; 22 | 23 | /// The reason to display in examples on how to use reasons. 24 | pub(crate) const HINT_REASON: &str = ""; 25 | 26 | lazy_static! { 27 | /// Returns the name of the main `pre` crate. 28 | pub(crate) static ref CRATE_NAME: String = { 29 | match proc_macro_crate::crate_name("pre") { 30 | Ok(name) => name, 31 | Err(err) => match env::var("CARGO_PKG_NAME") { 32 | // This allows for writing documentation tests on the functions themselves. 33 | // 34 | // This *may* lead to false positives, if someone also names their crate `pre`, however 35 | // it will very likely fail to compile at a later stage then. 36 | Ok(val) if val == "pre" => "pre".into(), 37 | _ => abort_call_site!("crate `pre` must be imported: {}", err), 38 | }, 39 | } 40 | }; 41 | } 42 | 43 | /// Specifies what to do with a visited attribute. 44 | pub(crate) enum AttributeAction { 45 | /// Remove the attribute from the resulting code. 46 | Remove, 47 | /// Keep the attribute in resulting code. 48 | Keep, 49 | } 50 | 51 | /// Visits all pre attributes of name `attr_name` and performs the `AttributeAction` on them. 52 | pub(crate) fn visit_matching_attrs_parsed_mut( 53 | attributes: &mut Vec, 54 | attr_name: &str, 55 | mut visit: impl FnMut(Attr) -> AttributeAction, 56 | ) -> Option { 57 | let mut span_of_all: Option = None; 58 | 59 | attributes.retain(|attr| match Attr::from_inner(attr_name, attr) { 60 | Some(attr) => { 61 | let span = attr.span(); 62 | 63 | match visit(attr) { 64 | AttributeAction::Remove => { 65 | span_of_all = Some(match span_of_all.take() { 66 | Some(old_span) => old_span.join(span).unwrap_or(span), 67 | None => span, 68 | }); 69 | 70 | false 71 | } 72 | AttributeAction::Keep => true, 73 | } 74 | } 75 | None => true, 76 | }); 77 | 78 | span_of_all 79 | } 80 | 81 | /// Visits all pre attributes of name `attr_name`. 82 | pub(crate) fn visit_matching_attrs_parsed( 83 | attributes: &[Attribute], 84 | attr_name: &str, 85 | mut visit: impl FnMut(Attr), 86 | ) { 87 | for attr in attributes { 88 | if let Some(attr) = Attr::from_inner(attr_name, attr) { 89 | visit(attr); 90 | } 91 | } 92 | } 93 | 94 | /// Returns the attributes of the given expression. 95 | pub(crate) fn attributes_of_expression(expr: &mut Expr) -> Option<&mut Vec> { 96 | macro_rules! extract_attributes_from { 97 | ($expr:expr => $($variant:ident),*) => { 98 | match $expr { 99 | $( 100 | Expr::$variant(e) => Some(&mut e.attrs), 101 | )* 102 | _ => None, 103 | } 104 | } 105 | } 106 | 107 | extract_attributes_from!(expr => 108 | Array, Assign, AssignOp, Async, Await, Binary, Block, Box, Break, Call, Cast, 109 | Closure, Continue, Field, ForLoop, Group, If, Index, Let, Lit, Loop, Macro, Match, 110 | MethodCall, Paren, Path, Range, Reference, Repeat, Return, Struct, Try, TryBlock, Tuple, 111 | Type, Unary, Unsafe, While, Yield 112 | ) 113 | } 114 | 115 | /// Incorporates the given span into the signature. 116 | /// 117 | /// Ideally both are shown, when the function definition is shown. 118 | pub(crate) fn add_span_to_signature(span: Span, signature: &mut Signature) { 119 | signature.fn_token.span = signature.fn_token.span.join(span).unwrap_or(span); 120 | 121 | if let Some(token) = &mut signature.constness { 122 | token.span = token.span.join(span).unwrap_or(span); 123 | } 124 | 125 | if let Some(token) = &mut signature.asyncness { 126 | token.span = token.span.join(span).unwrap_or(span); 127 | } 128 | 129 | if let Some(token) = &mut signature.unsafety { 130 | token.span = token.span.join(span).unwrap_or(span); 131 | } 132 | 133 | if let Some(abi) = &mut signature.abi { 134 | abi.extern_token.span = abi.extern_token.span.join(span).unwrap_or(span); 135 | } 136 | } 137 | 138 | /// Combines the `cfg` of all preconditions if possible. 139 | pub(crate) fn combine_cfg(preconditions: &[CfgPrecondition], _span: Span) -> Option { 140 | const MISMATCHED_CFG: &str = "mismatched `cfg` predicates for preconditions"; 141 | const MISMATCHED_CFG_NOTE: &str = 142 | "all preconditions must have syntactically equal `cfg` predicates"; 143 | 144 | let render_cfg = |cfg: Option<&TokenStream>| cfg.map(|cfg| format!("{}", cfg)); 145 | 146 | let first_cfg = preconditions.first().and_then(|p| p.cfg.clone()); 147 | let first_cfg_rendered = render_cfg(first_cfg.as_ref()); 148 | 149 | for precondition in preconditions.iter().skip(1) { 150 | if first_cfg_rendered != render_cfg(precondition.cfg.as_ref()) { 151 | match (&first_cfg, &precondition.cfg) { 152 | (Some(first_cfg), Some(current_cfg)) => { 153 | emit_error!( 154 | current_cfg.span(), 155 | MISMATCHED_CFG; 156 | note = MISMATCHED_CFG_NOTE; 157 | note = first_cfg.span() => "`{}` != `{}`", first_cfg, current_cfg 158 | ); 159 | } 160 | (Some(cfg), None) | (None, Some(cfg)) => { 161 | emit_error!( 162 | cfg.span(), 163 | MISMATCHED_CFG; 164 | note = MISMATCHED_CFG_NOTE; 165 | note = "some preconditions have a `cfg` predicate and some do not" 166 | ); 167 | } 168 | (None, None) => unreachable!("two `None`s are equal to each other"), 169 | } 170 | } 171 | } 172 | 173 | first_cfg 174 | } 175 | 176 | /// A `TokenStream` surrounded by parentheses. 177 | struct Parenthesized { 178 | /// The parentheses surrounding the `TokenStream`. 179 | parentheses: Paren, 180 | /// The content that was surrounded by the parentheses. 181 | content: TokenStream, 182 | } 183 | 184 | impl Parse for Parenthesized { 185 | fn parse(input: ParseStream) -> syn::Result { 186 | let content; 187 | let parentheses = parenthesized!(content in input); 188 | 189 | Ok(Parenthesized { 190 | parentheses, 191 | content: content.parse()?, 192 | }) 193 | } 194 | } 195 | 196 | /// Parses the token stream to the next comma and returns the result as a new token stream. 197 | fn parse_to_comma(input: &mut TokenStream) -> (TokenStream, Option) { 198 | let mut to_comma = TokenStream::new(); 199 | let mut comma = None; 200 | 201 | let mut token_iter = input.clone().into_iter(); 202 | 203 | loop { 204 | match token_iter.next() { 205 | Some(TokenTree::Punct(p)) if p.as_char() == ',' => { 206 | comma = Some( 207 | parse2(TokenTree::from(p).into()).expect("`,` token tree is parsed as a comma"), 208 | ); 209 | 210 | break; 211 | } 212 | Some(token_tree) => to_comma.extend(std::iter::once(token_tree)), 213 | None => break, 214 | } 215 | } 216 | 217 | *input = TokenStream::new(); 218 | input.extend(token_iter); 219 | 220 | (to_comma, comma) 221 | } 222 | 223 | /// Transforms multiple attributes in a single `cfg_attr` into multiple `cfg_attr`. 224 | /// 225 | /// ```rust,ignore 226 | /// #[cfg_attr(some_cfg, attr_1, attr_2, /* ..., */ attr_n)] 227 | /// ``` 228 | /// 229 | /// becomes 230 | /// 231 | /// ```rust,ignore 232 | /// #[cfg_attr(some_cfg, attr_1)] 233 | /// #[cfg_attr(some_cfg, attr_2)] 234 | /// /* ... */ 235 | /// #[cfg_attr(some_cfg, attr_n)] 236 | /// ``` 237 | pub(crate) fn flatten_cfgs(attributes: &mut Vec) { 238 | let mut i = 0; 239 | while i < attributes.len() { 240 | if attributes[i].path.is_ident("cfg_attr") { 241 | let attribute = attributes.remove(i); 242 | 243 | let (parentheses, mut input) = if let Ok(Parenthesized { 244 | parentheses, 245 | content: input, 246 | }) = parse2(attribute.tokens.clone()) 247 | { 248 | (parentheses, input) 249 | } else { 250 | // Ignore invalid `cfg_attr` 251 | attributes.insert(i, attribute); 252 | i += 1; 253 | continue; 254 | }; 255 | 256 | let (cfg, comma) = parse_to_comma(&mut input); 257 | if comma.is_none() { 258 | // Ignore invalid `cfg_attr` 259 | attributes.insert(i, attribute); 260 | i += 1; 261 | continue; 262 | } 263 | 264 | // This ensures that the attributes are added in the correct order (the order they were 265 | // specified in). 266 | // 267 | // Note that i is kept at the same value to handle nested `cfg_attr` attributes (e.g. 268 | // `#[cfg_attr(abc, cfg_attr(def, ...))]`) 269 | loop { 270 | let (attr_tokens, comma) = parse_to_comma(&mut input); 271 | 272 | let new_attribute = Attribute { 273 | pound_token: attribute.pound_token, 274 | style: attribute.style, 275 | bracket_token: attribute.bracket_token, 276 | path: attribute.path.clone(), 277 | tokens: quote_spanned! { parentheses.span=> 278 | (#cfg, #attr_tokens) 279 | }, 280 | }; 281 | 282 | attributes.insert(i, new_attribute); 283 | i += 1; 284 | 285 | if comma.is_none() { 286 | // We found the last attribute 287 | break; 288 | } 289 | } 290 | } else { 291 | i += 1; 292 | } 293 | } 294 | } 295 | 296 | #[cfg(test)] 297 | mod tests { 298 | use super::*; 299 | 300 | #[test] 301 | fn basic_cfg_flattening() { 302 | let mut transformed_func: syn::ItemFn = syn::parse_quote! { 303 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), attr1, attr2, attr3)] 304 | #[cfg_attr(all(target_endian = "big", target_endian = "little"), attr4, attr5, attr6)] 305 | fn foo() {} 306 | }; 307 | 308 | flatten_cfgs(&mut transformed_func.attrs); 309 | 310 | let desired_result: syn::ItemFn = syn::parse_quote! { 311 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), attr1)] 312 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), attr2)] 313 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), attr3)] 314 | #[cfg_attr(all(target_endian = "big", target_endian = "little"), attr4)] 315 | #[cfg_attr(all(target_endian = "big", target_endian = "little"), attr5)] 316 | #[cfg_attr(all(target_endian = "big", target_endian = "little"), attr6)] 317 | fn foo() {} 318 | }; 319 | 320 | assert_eq!(transformed_func, desired_result); 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /proc-macro/src/helpers/attr.rs: -------------------------------------------------------------------------------- 1 | //! An abstraction for various types of attributes. 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use proc_macro_error::emit_error; 5 | use syn::{ 6 | parenthesized, 7 | parse::{Parse, ParseStream}, 8 | parse2, 9 | spanned::Spanned, 10 | token::Paren, 11 | Attribute, Path, Token, 12 | }; 13 | 14 | use super::{parse_to_comma, Parenthesized, CRATE_NAME}; 15 | use crate::precondition::{CfgPrecondition, Precondition}; 16 | 17 | /// Checks if the given attribute is an `attr_to_check` attribute of the main crate. 18 | fn is_attr(attr_to_check: &str, path: &Path) -> bool { 19 | if path.is_ident(attr_to_check) { 20 | true 21 | } else if path.segments.len() == 2 { 22 | // Note that `Path::leading_colon` is not checked here, so paths both with and without a 23 | // leading colon are accepted here 24 | path.segments[0].ident == *CRATE_NAME && path.segments[1].ident == attr_to_check 25 | } else { 26 | false 27 | } 28 | } 29 | 30 | /// A `Path` followed by parentheses surrounding a `TokenStream`. 31 | struct PathAndParenthesized { 32 | /// The path at the beginning of the construct. 33 | path: Path, 34 | /// The parentheses surrounding the `TokenStream`. 35 | parentheses: Paren, 36 | /// The content that was surrounded by the parentheses. 37 | content: TokenStream, 38 | } 39 | 40 | impl Parse for PathAndParenthesized { 41 | fn parse(input: ParseStream) -> syn::Result { 42 | let path = input.parse()?; 43 | let content; 44 | let parentheses = parenthesized!(content in input); 45 | 46 | Ok(PathAndParenthesized { 47 | path, 48 | parentheses, 49 | content: content.parse()?, 50 | }) 51 | } 52 | } 53 | 54 | /// Represents an attribute as seen by pre. 55 | pub(crate) enum Attr { 56 | /// The attribute is contained in an `cfg_attr`. 57 | /// 58 | /// This occurs when parsing inner `cfg_attr` wrapped attributes. 59 | /// 60 | /// Example of what the parser sees: `#[cfg_attr(some_condition, path(content))]` 61 | WithCfg { 62 | /// The `cfg_attr` keyword. 63 | /// 64 | /// ```text 65 | /// #[cfg_attr(some_condition, path(content))] 66 | /// ^^^^^^^^ 67 | /// ``` 68 | _cfg_attr_keyword: Path, 69 | /// The outer parentheses of the attribute. 70 | /// 71 | /// ```text 72 | /// #[cfg_attr(some_condition, path(content))] 73 | /// ^ ^ 74 | /// ``` 75 | _outer_parentheses: Paren, 76 | /// The configuration of the attribute. 77 | /// 78 | /// ```text 79 | /// #[cfg_attr(some_condition, path(content))] 80 | /// ^^^^^^^^^^^^^^ 81 | /// ``` 82 | cfg: TokenStream, 83 | /// The comma separating the configuration from the actual attribute. 84 | /// 85 | /// ```text 86 | /// #[cfg_attr(some_condition, path(content))] 87 | /// ^ 88 | /// ``` 89 | _comma: Token![,], 90 | /// The path of the actual attribute. 91 | /// 92 | /// ```text 93 | /// #[cfg_attr(some_condition, path(content))] 94 | /// ^^^^ 95 | /// ``` 96 | _path: Path, 97 | /// The parentheses of the actual attribute. 98 | /// 99 | /// ```text 100 | /// #[cfg_attr(some_condition, path(content))] 101 | /// ^ ^ 102 | /// ``` 103 | _inner_parentheses: Paren, 104 | /// The content of the attribute. 105 | /// 106 | /// ```text 107 | /// #[cfg_attr(some_condition, path(content))] 108 | /// ^^^^^^^ 109 | /// ``` 110 | content: Content, 111 | /// The span best representing the inner attribute. 112 | /// 113 | /// Ideally this is 114 | /// 115 | /// ```text 116 | /// #[cfg_attr(some_condition, path(content))] 117 | /// ^^^^^^^^^^^^^ 118 | /// ``` 119 | span: Span, 120 | }, 121 | /// The attribute is contained within parentheses. 122 | /// 123 | /// This occurs when parsing inner attributes. 124 | /// 125 | /// Example of what the parser sees: `#[path(content)]` 126 | WithParen { 127 | /// The path of the attribute. 128 | /// 129 | /// ```text 130 | /// #[path(content)] 131 | /// ^^^^ 132 | /// ``` 133 | _path: Path, 134 | /// The parentheses of the attribute. 135 | /// 136 | /// ```text 137 | /// #[path(content)] 138 | /// ^ ^ 139 | /// ``` 140 | _parentheses: Paren, 141 | /// The content of the attribute. 142 | /// 143 | /// ```text 144 | /// #[path(content)] 145 | /// ^^^^^^^ 146 | /// ``` 147 | content: Content, 148 | /// The span best representing the attribute. 149 | /// 150 | /// Ideally this is 151 | /// 152 | /// ```text 153 | /// #[path(content)] 154 | /// ^^^^^^^^^^^^^^^^ 155 | /// ``` 156 | span: Span, 157 | }, 158 | /// The attribute can be parsed directly. 159 | /// 160 | /// This occurs when parsing an attribute as a proc macro input. 161 | /// 162 | /// Example of what the parser sees: `content` 163 | Direct { 164 | /// The content of the attribute. 165 | content: Content, 166 | }, 167 | } 168 | 169 | impl Attr { 170 | /// Creates a parsed attribute from an attribute seen inside of a proc macro invocation. 171 | pub(crate) fn from_inner(target_attr: &str, attribute: &Attribute) -> Option> { 172 | if is_attr(target_attr, &attribute.path) { 173 | let Parenthesized { 174 | parentheses, 175 | content, 176 | } = parse2(attribute.tokens.clone()) 177 | .map_err(|err| emit_error!(err)) 178 | .ok()?; 179 | 180 | Some(Attr::WithParen { 181 | _path: attribute.path.clone(), 182 | _parentheses: parentheses, 183 | content: parse2(content).map_err(|err| emit_error!(err)).ok()?, 184 | span: attribute 185 | .pound_token 186 | .span 187 | .join(attribute.bracket_token.span) 188 | .unwrap_or(attribute.bracket_token.span), 189 | }) 190 | } else if attribute.path.is_ident("cfg_attr") { 191 | let Parenthesized { 192 | parentheses: outer_parentheses, 193 | content: mut cfg_attr_content, 194 | } = parse2(attribute.tokens.clone()) 195 | .map_err(|err| emit_error!(err)) 196 | .ok()?; 197 | 198 | let (cfg, comma) = parse_to_comma(&mut cfg_attr_content); 199 | let comma = comma?; 200 | 201 | let PathAndParenthesized { 202 | path, 203 | parentheses: inner_parentheses, 204 | content, 205 | } = parse2(cfg_attr_content) 206 | .map_err(|err| emit_error!(err)) 207 | .ok()?; 208 | 209 | if !is_attr(target_attr, &path) { 210 | return None; 211 | } 212 | 213 | let span = path 214 | .span() 215 | .join(inner_parentheses.span) 216 | .unwrap_or(inner_parentheses.span); 217 | 218 | Some(Attr::WithCfg { 219 | _cfg_attr_keyword: attribute.path.clone(), 220 | _outer_parentheses: outer_parentheses, 221 | cfg, 222 | _comma: comma, 223 | _path: path, 224 | _inner_parentheses: inner_parentheses, 225 | content: parse2(content).map_err(|err| emit_error!(err)).ok()?, 226 | span, 227 | }) 228 | } else { 229 | None 230 | } 231 | } 232 | 233 | /// Accesses the content of this attribute. 234 | pub(crate) fn content(&self) -> &Content { 235 | match self { 236 | Attr::WithCfg { content, .. } => content, 237 | Attr::WithParen { content, .. } => content, 238 | Attr::Direct { content } => content, 239 | } 240 | } 241 | 242 | /// Returns the pieces necessary to create a `CfgPrecondition` manually. 243 | pub(crate) fn into_content(self) -> (Content, Option, Span) { 244 | match self { 245 | Attr::WithCfg { 246 | content, cfg, span, .. 247 | } => (content, Some(cfg), span), 248 | Attr::WithParen { content, span, .. } => (content, None, span), 249 | Attr::Direct { content } => { 250 | let span = content.span(); 251 | 252 | (content, None, span) 253 | } 254 | } 255 | } 256 | } 257 | 258 | impl Spanned for Attr { 259 | fn span(&self) -> Span { 260 | match self { 261 | Attr::WithCfg { span, .. } => *span, 262 | Attr::WithParen { span, .. } => *span, 263 | Attr::Direct { content } => content.span(), 264 | } 265 | } 266 | } 267 | 268 | impl From for Attr { 269 | fn from(content: Content) -> Self { 270 | Attr::Direct { content } 271 | } 272 | } 273 | 274 | impl + Spanned> From> for CfgPrecondition { 275 | fn from(val: Attr) -> Self { 276 | match val { 277 | Attr::WithCfg { 278 | content, span, cfg, .. 279 | } => CfgPrecondition { 280 | precondition: content.into(), 281 | cfg: Some(cfg), 282 | span, 283 | }, 284 | Attr::WithParen { content, span, .. } => CfgPrecondition { 285 | precondition: content.into(), 286 | cfg: None, 287 | span, 288 | }, 289 | Attr::Direct { content } => { 290 | let span = content.span(); 291 | 292 | CfgPrecondition { 293 | precondition: content.into(), 294 | cfg: None, 295 | span, 296 | } 297 | } 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /proc-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains the implementation for attributes used in the `pre` crate. 2 | //! 3 | //! Refer to the documentation of the `pre` crate for more information. 4 | //! 5 | //! This crate is not designed to be used as a standalone crate and might not work, when used 6 | //! without the `pre` crate. 7 | 8 | #![forbid(unsafe_code)] 9 | 10 | use proc_macro::TokenStream; 11 | use proc_macro2::TokenStream as TokenStream2; 12 | use proc_macro_error::{abort_call_site, proc_macro_error}; 13 | use quote::quote; 14 | use syn::{parse_macro_input, visit_mut::VisitMut, File}; 15 | 16 | use crate::pre_attr::PreAttrVisitor; 17 | 18 | mod call; 19 | mod call_handling; 20 | mod documentation; 21 | mod extern_crate; 22 | mod helpers; 23 | mod pre_attr; 24 | mod precondition; 25 | 26 | cfg_if::cfg_if! { 27 | if #[cfg(nightly)] { 28 | mod const_generics_impl; 29 | pub(crate) use crate::const_generics_impl::{render_assure, render_pre}; 30 | } else { 31 | mod struct_impl; 32 | pub(crate) use crate::struct_impl::{render_assure, render_pre}; 33 | } 34 | } 35 | 36 | #[proc_macro_attribute] 37 | #[proc_macro_error] 38 | pub fn pre(attr: TokenStream, file: TokenStream) -> TokenStream { 39 | let dummy_file: TokenStream2 = file.clone().into(); 40 | proc_macro_error::set_dummy(quote! { 41 | #dummy_file 42 | }); 43 | 44 | let mut file = parse_macro_input!(file as File); 45 | 46 | PreAttrVisitor::new(attr.into()).visit_file_mut(&mut file); 47 | 48 | let output = quote! { 49 | #file 50 | }; 51 | 52 | // Reset the dummy here, in case errors were emitted while generating the code. 53 | // This will use the most up-to-date version of the generated code. 54 | proc_macro_error::set_dummy(quote! { 55 | #output 56 | }); 57 | 58 | output.into() 59 | } 60 | 61 | #[proc_macro_attribute] 62 | #[proc_macro_error] 63 | pub fn assure(_: TokenStream, _: TokenStream) -> TokenStream { 64 | // This macro currently only has two purposes: 65 | // - Exist as a place to put documentation for the actual `assure` attribute, which is 66 | // implemented inside the `pre` attribute. 67 | // - Emit an error with a more helpful message than "attribute not found", if the user uses 68 | // `assure` in the wrong place. 69 | abort_call_site!( 70 | "this attribute by itself is currently non-functional"; 71 | help = "use it on an expression in an item wrapped by a `pre` attribute" 72 | ) 73 | } 74 | 75 | #[proc_macro_attribute] 76 | #[proc_macro_error] 77 | pub fn forward(_: TokenStream, _: TokenStream) -> TokenStream { 78 | // This macro currently only has two purposes: 79 | // - Exist as a place to put documentation for the actual `forward` attribute, which is 80 | // implemented inside the `pre` attribute. 81 | // - Emit an error with a more helpful message than "attribute not found", if the user uses 82 | // `forward` in the wrong place. 83 | abort_call_site!( 84 | "this attribute by itself is currently non-functional"; 85 | help = "use it on an expression in an item wrapped by a `pre` attribute" 86 | ) 87 | } 88 | 89 | #[proc_macro_attribute] 90 | #[proc_macro_error] 91 | pub fn extern_crate(attr: TokenStream, module: TokenStream) -> TokenStream { 92 | let attr = parse_macro_input!(attr as extern_crate::ExternCrateAttr); 93 | let module = parse_macro_input!(module as extern_crate::Module); 94 | 95 | let output = module.render(attr); 96 | 97 | // Reset the dummy here, in case errors were emitted while generating the code. 98 | // This will use the most up-to-date version of the generated code. 99 | proc_macro_error::set_dummy(quote! { 100 | #output 101 | }); 102 | 103 | output.into() 104 | } 105 | -------------------------------------------------------------------------------- /proc-macro/src/pre_attr.rs: -------------------------------------------------------------------------------- 1 | //! Defines the `pre` attribute and how it is handled. 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use proc_macro_error::{emit_error, emit_warning}; 5 | use quote::{quote, quote_spanned}; 6 | use syn::{ 7 | parse::{Parse, ParseStream}, 8 | parse2, 9 | spanned::Spanned, 10 | visit_mut::{ 11 | visit_expr_mut, visit_file_mut, visit_item_fn_mut, visit_item_mut, visit_local_mut, 12 | VisitMut, 13 | }, 14 | Expr, File, Item, ItemFn, Local, 15 | }; 16 | 17 | use self::expr_handling::render_expr; 18 | use crate::{ 19 | call_handling::remove_call_attributes, 20 | documentation::generate_docs, 21 | helpers::{ 22 | attributes_of_expression, flatten_cfgs, visit_matching_attrs_parsed_mut, Attr, 23 | AttributeAction, 24 | }, 25 | precondition::{CfgPrecondition, Precondition}, 26 | render_pre, 27 | }; 28 | 29 | mod expr_handling; 30 | 31 | /// The custom keywords used for `pre` attributes. 32 | mod custom_keywords { 33 | use syn::custom_keyword; 34 | 35 | custom_keyword!(no_doc); 36 | custom_keyword!(no_debug_assert); 37 | } 38 | 39 | /// A `pre` attribute. 40 | pub(crate) enum PreAttr { 41 | /// An empty attribute to trigger checking for contained attributes. 42 | Empty, 43 | /// A request not to generate `pre`-related documentation for the contained item. 44 | NoDoc(custom_keywords::no_doc), 45 | /// A request not to generate `debug_assert` statements for boolean expressions. 46 | NoDebugAssert(custom_keywords::no_debug_assert), 47 | /// A precondition that needs to hold for the contained item. 48 | Precondition(Precondition), 49 | } 50 | 51 | impl Parse for PreAttr { 52 | fn parse(input: ParseStream) -> syn::Result { 53 | if input.is_empty() { 54 | Ok(PreAttr::Empty) 55 | } else if input.peek(custom_keywords::no_doc) { 56 | Ok(PreAttr::NoDoc(input.parse()?)) 57 | } else if input.peek(custom_keywords::no_debug_assert) { 58 | Ok(PreAttr::NoDebugAssert(input.parse()?)) 59 | } else { 60 | Ok(PreAttr::Precondition(input.parse()?)) 61 | } 62 | } 63 | } 64 | 65 | impl Spanned for PreAttr { 66 | fn span(&self) -> Span { 67 | match self { 68 | PreAttr::Empty => Span::call_site(), 69 | PreAttr::NoDoc(no_doc) => no_doc.span, 70 | PreAttr::NoDebugAssert(no_debug_assert) => no_debug_assert.span, 71 | PreAttr::Precondition(precondition) => precondition.span(), 72 | } 73 | } 74 | } 75 | 76 | /// Applies and removes all visited pre attributes. 77 | pub(crate) struct PreAttrVisitor { 78 | /// The original attribute that started the visitor. 79 | original_attr: Option, 80 | } 81 | 82 | impl PreAttrVisitor { 83 | /// Creates a new visitor for the syntax tree that `original_attr` was attached to. 84 | pub(crate) fn new(original_attr: TokenStream) -> PreAttrVisitor { 85 | let original_attr = if !original_attr.is_empty() { 86 | let span = original_attr.span(); 87 | 88 | match parse2(original_attr) { 89 | Ok(attr) => Some(attr), 90 | Err(err) => { 91 | emit_error!( 92 | span, 93 | "expected either nothing or a valid `pre` attribute here" 94 | ); 95 | emit_error!(err); 96 | 97 | None 98 | } 99 | } 100 | } else { 101 | None 102 | }; 103 | 104 | PreAttrVisitor { original_attr } 105 | } 106 | } 107 | 108 | impl VisitMut for PreAttrVisitor { 109 | fn visit_file_mut(&mut self, file: &mut File) { 110 | let original_attr = self.original_attr.take(); 111 | 112 | if let [Item::Fn(function)] = &mut file.items[..] { 113 | // Use `visit_item_fn_mut ` here, so that the function remains an `ItemFn` that can be 114 | // passed to `render_function`. Using `visit_item_mut` here would result in an 115 | // `Item::Verbatim` instead. 116 | visit_item_fn_mut(self, function); 117 | 118 | file.items[0] = Item::Verbatim(render_function(function, original_attr)); 119 | } else { 120 | visit_file_mut(self, file); 121 | 122 | if let Some(original_attr) = original_attr { 123 | if let Some(span) = match original_attr { 124 | PreAttr::Empty => None, 125 | PreAttr::NoDoc(no_doc) => Some(no_doc.span()), 126 | PreAttr::NoDebugAssert(no_debug_assert) => Some(no_debug_assert.span()), 127 | PreAttr::Precondition(precondition) => Some(precondition.span()), 128 | } { 129 | emit_warning!(span, "this is ignored in this context") 130 | } 131 | } 132 | } 133 | } 134 | 135 | fn visit_item_mut(&mut self, item: &mut Item) { 136 | visit_item_mut(self, item); 137 | 138 | if let Item::Fn(function) = item { 139 | let rendered_function = render_function(function, None); 140 | *item = Item::Verbatim(rendered_function); 141 | } 142 | } 143 | 144 | fn visit_expr_mut(&mut self, expr: &mut Expr) { 145 | visit_expr_mut(self, expr); 146 | 147 | if let Some(attrs) = attributes_of_expression(expr) { 148 | if let Some(call_attrs) = remove_call_attributes(attrs) { 149 | render_expr(expr, call_attrs); 150 | } 151 | } 152 | } 153 | 154 | fn visit_local_mut(&mut self, local: &mut Local) { 155 | visit_local_mut(self, local); 156 | 157 | if let Some((_, expr)) = &mut local.init { 158 | if let Some(call_attrs) = remove_call_attributes(&mut local.attrs) { 159 | render_expr(expr, call_attrs); 160 | } 161 | } 162 | } 163 | } 164 | 165 | /// Renders the given function and applies all `pre` attributes to it. 166 | fn render_function(function: &mut ItemFn, first_attr: Option) -> TokenStream { 167 | flatten_cfgs(&mut function.attrs); 168 | 169 | let first_attr_span = first_attr.as_ref().and_then(|attr| match attr { 170 | PreAttr::Empty => None, 171 | PreAttr::NoDoc(no_doc) => Some(no_doc.span()), 172 | PreAttr::NoDebugAssert(no_debug_assert) => Some(no_debug_assert.span()), 173 | PreAttr::Precondition(precondition) => Some(precondition.span()), 174 | }); 175 | 176 | let mut preconditions: Vec = Vec::new(); 177 | 178 | let mut render_docs = true; 179 | let mut debug_assert = true; 180 | 181 | let mut handle_attr = |attr: Attr| match attr.into_content() { 182 | (PreAttr::Empty, _, _) => (), 183 | (PreAttr::NoDoc(_), _, _) => render_docs = false, 184 | (PreAttr::NoDebugAssert(_), _, _) => debug_assert = false, 185 | (PreAttr::Precondition(precondition), cfg, span) => { 186 | if let Precondition::Boolean(boolean_expr) = &precondition { 187 | if let Expr::Path(p) = &**boolean_expr { 188 | if let (None, Some(ident)) = (&p.qself, p.path.get_ident()) { 189 | emit_error!( 190 | ident.span(), 191 | "keyword `{}` is not recognized by pre", ident; 192 | help = "if you wanted to use a boolean expression, try `{} == true`", 193 | ident 194 | ); 195 | } 196 | } 197 | } 198 | preconditions.push(CfgPrecondition { 199 | precondition, 200 | cfg, 201 | span, 202 | }) 203 | } 204 | }; 205 | 206 | if let Some(first_attr) = first_attr { 207 | handle_attr(first_attr.into()); 208 | } 209 | 210 | let attr_span = visit_matching_attrs_parsed_mut(&mut function.attrs, "pre", |attr| { 211 | handle_attr(attr); 212 | 213 | AttributeAction::Remove 214 | }); 215 | 216 | let span = match (attr_span, first_attr_span) { 217 | (Some(attr_span), Some(first_attr_span)) => { 218 | attr_span.join(first_attr_span).unwrap_or(attr_span) 219 | } 220 | (Some(span), None) => span, 221 | (None, Some(span)) => span, 222 | (None, None) => Span::call_site(), // Should never be the case for non-empty preconditions 223 | }; 224 | 225 | if !preconditions.is_empty() { 226 | if render_docs { 227 | function 228 | .attrs 229 | .push(generate_docs(&function.sig, &preconditions, None)); 230 | } 231 | 232 | if debug_assert { 233 | for condition in preconditions.iter() { 234 | if let Precondition::Boolean(expr) = condition.precondition() { 235 | function.block.stmts.insert( 236 | 0, 237 | parse2(quote_spanned! { expr.span()=> 238 | ::core::debug_assert!( 239 | #expr, 240 | "boolean precondition was wrongly assured: `{}`", 241 | ::core::stringify!(#expr) 242 | ); 243 | }) 244 | .expect("valid statement"), 245 | ); 246 | } 247 | } 248 | } 249 | 250 | render_pre(preconditions, function, span) 251 | } else { 252 | quote! { #function } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /proc-macro/src/pre_attr/expr_handling.rs: -------------------------------------------------------------------------------- 1 | //! Handles rendering of expressions and descending into nested expressions. 2 | 3 | use proc_macro2::Span; 4 | use proc_macro_error::emit_error; 5 | use std::convert::TryInto; 6 | use syn::{spanned::Spanned, Block, Expr, Local, Stmt}; 7 | 8 | use crate::call_handling::{render_call, CallAttributes}; 9 | 10 | /// Renders the contained call in the given expression. 11 | /// 12 | /// This only works, if the call can be unambiguosly determined. 13 | /// Otherwise errors are printed. 14 | pub(crate) fn render_expr(expr: &mut Expr, attrs: CallAttributes) { 15 | if let Some(expr) = extract_call_expr(expr) { 16 | let call = expr 17 | .clone() 18 | .try_into() 19 | .expect("`extract_call_expr` should only return call expressions"); 20 | 21 | *expr = render_call(attrs, call); 22 | } else { 23 | let emit_err = |span: Span| { 24 | emit_error!( 25 | span, 26 | "could not find an unambiguos call to apply this to"; 27 | help = "try moving it closer to the call it should apply to" 28 | ) 29 | }; 30 | 31 | if let Some(forward) = attrs.forward { 32 | emit_err(forward.span()); 33 | } 34 | 35 | for assure_attribute in attrs.assure_attributes { 36 | emit_err(assure_attribute.span()); 37 | } 38 | } 39 | } 40 | 41 | /// Extracts an expression that is a valid call from the given expression. 42 | /// 43 | /// This may descend into nested expressions, if it would be obvious which nested expression is 44 | /// meant. 45 | fn extract_call_expr(expr: &mut Expr) -> Option<&mut Expr> { 46 | fn extract_from_block(block: &mut Block) -> Option<&mut Expr> { 47 | if block.stmts.len() == 1 { 48 | match &mut block.stmts[0] { 49 | Stmt::Local(Local { 50 | init: Some((_, expr)), 51 | .. 52 | }) => extract_call_expr(expr), 53 | Stmt::Local(_) => None, 54 | Stmt::Item(_) => None, 55 | Stmt::Expr(expr) => extract_call_expr(expr), 56 | Stmt::Semi(expr, _) => extract_call_expr(expr), 57 | } 58 | } else { 59 | None 60 | } 61 | } 62 | 63 | macro_rules! find_subexpr { 64 | ($expr:expr; 65 | direct_return: 66 | $($direct_return:ident),*; 67 | subexpressions: 68 | $($simple_ty:ident . $simple_field:ident),*; 69 | binary_subexpressions: 70 | $($binary_ty:ident : $left:ident ^ $right:ident),*; 71 | optional_subexpressions: 72 | $($optional_ty:ident . $optional_syn_ty:ident ? $optional_field:ident),*; 73 | subblocks: 74 | $($block_ty:ident . $block_name:ident),*; 75 | manual: 76 | $($($manual_pat:pat)|+ $(if $manual_guard:expr)? => $manual_expr:expr),* $(;)? 77 | ) => { 78 | match $expr { 79 | // Direct return: 80 | // We found a call, so return it directly 81 | $( 82 | Expr::$direct_return(_) => Some($expr), 83 | )* 84 | // Subexpressions: 85 | // There is a single unambiguos subexpression that will be searched. 86 | $( 87 | Expr::$simple_ty(expr) => extract_call_expr(&mut expr.$simple_field), 88 | )* 89 | // Binary subexpressions: 90 | // There are always exactly two subexpressions. Search them both and return the 91 | // call if exactly one of them is an unambiguos call expression. 92 | $( 93 | Expr::$binary_ty(expr) => 94 | extract_call_expr(&mut expr.$left).xor(extract_call_expr(&mut expr.$right)), 95 | )* 96 | // Optional subexpressions: 97 | // There may or may not be a subexpression. If there is one, search it. 98 | $( 99 | Expr::$optional_ty(syn::$optional_syn_ty { expr: Some(expr), .. }) => 100 | extract_call_expr(expr), 101 | )* 102 | // Subblocks: 103 | // Search the contained block using the `extract_from_block`. 104 | $( 105 | Expr::$block_ty(expr) => extract_from_block(&mut expr.$block_name), 106 | )* 107 | // Manual: 108 | // Manually match on an expression pattern and handle it. 109 | $( 110 | $($manual_pat)|+ $(if $manual_guard)? => $manual_expr, 111 | )* 112 | // Otherwise: 113 | // Assume there is no contained call expression otherwise. 114 | _ => None, 115 | } 116 | } 117 | } 118 | 119 | find_subexpr! { expr; 120 | direct_return: 121 | Call, 122 | MethodCall; 123 | subexpressions: 124 | Await.base, 125 | Box.expr, 126 | Cast.expr, 127 | Closure.body, 128 | Field.base, 129 | Group.expr, 130 | Let.expr, 131 | Paren.expr, 132 | Reference.expr, 133 | Try.expr, 134 | Type.expr, 135 | Unary.expr; 136 | binary_subexpressions: 137 | Assign: left ^ right, 138 | AssignOp: left ^ right, 139 | Binary: left ^ right, 140 | Index: expr ^ index; 141 | optional_subexpressions: 142 | Break.ExprBreak ? expr, 143 | Return.ExprReturn ? expr, 144 | Yield.ExprYield ? expr; 145 | subblocks: 146 | Async.block, 147 | Block.block, 148 | Loop.body, 149 | TryBlock.block, 150 | Unsafe.block; 151 | manual: 152 | Expr::Tuple(expr) if expr.elems.len() == 1 => extract_call_expr(&mut expr.elems[0]); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /proc-macro/src/precondition.rs: -------------------------------------------------------------------------------- 1 | //! Defines the different kinds of preconditions. 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use quote::quote; 5 | use std::{cmp::Ordering, fmt}; 6 | use syn::{ 7 | ext::IdentExt, 8 | parenthesized, 9 | parse::{Parse, ParseStream}, 10 | spanned::Spanned, 11 | token::Paren, 12 | Error, Expr, Ident, LitStr, Token, 13 | }; 14 | 15 | /// The custom keywords used by the precondition kinds. 16 | mod custom_keywords { 17 | use syn::custom_keyword; 18 | 19 | custom_keyword!(valid_ptr); 20 | custom_keyword!(proper_align); 21 | custom_keyword!(r); 22 | custom_keyword!(w); 23 | } 24 | 25 | /// The different kinds of preconditions. 26 | #[derive(Clone)] 27 | pub(crate) enum Precondition { 28 | /// Requires that the given pointer is valid. 29 | ValidPtr { 30 | /// The `valid_ptr` keyword. 31 | valid_ptr_keyword: custom_keywords::valid_ptr, 32 | /// The parentheses following the `valid_ptr` keyword. 33 | parentheses: Paren, 34 | /// The identifier of the pointer. 35 | ident: Ident, 36 | /// The comma between the identifier and the read/write information. 37 | _comma: Token![,], 38 | /// Information on what accesses of the pointer must be valid. 39 | read_write: ReadWrite, 40 | }, 41 | ProperAlign { 42 | /// The `proper_align` keyword. 43 | proper_align_keyword: custom_keywords::proper_align, 44 | /// The parentheses following the `proper_align` keyword. 45 | parentheses: Paren, 46 | /// The identifier of the pointer. 47 | ident: Ident, 48 | }, 49 | /// An expression that should evaluate to a boolean value. 50 | Boolean(Box), 51 | /// A custom precondition that is spelled out in a string. 52 | Custom(LitStr), 53 | } 54 | 55 | impl fmt::Display for Precondition { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | match self { 58 | Precondition::ValidPtr { 59 | ident, read_write, .. 60 | } => write!(f, "valid_ptr({}, {})", ident.to_string(), read_write), 61 | Precondition::ProperAlign { ident, .. } => { 62 | write!(f, "proper_align({})", ident.to_string()) 63 | } 64 | Precondition::Boolean(expr) => write!(f, "{}", quote! { #expr }), 65 | Precondition::Custom(lit) => write!(f, "{:?}", lit.value()), 66 | } 67 | } 68 | } 69 | 70 | /// Parses an identifier that is valid for use in a precondition. 71 | fn parse_precondition_ident(input: ParseStream) -> syn::Result { 72 | let lookahead = input.lookahead1(); 73 | 74 | if lookahead.peek(Token![self]) { 75 | input.call(Ident::parse_any) 76 | } else if lookahead.peek(Ident) { 77 | input.parse() 78 | } else { 79 | Err(lookahead.error()) 80 | } 81 | } 82 | 83 | impl Parse for Precondition { 84 | fn parse(input: ParseStream) -> syn::Result { 85 | let start_span = input.span(); 86 | 87 | if input.peek(custom_keywords::valid_ptr) { 88 | let valid_ptr_keyword = input.parse()?; 89 | let content; 90 | let parentheses = parenthesized!(content in input); 91 | let ident = parse_precondition_ident(&content)?; 92 | let comma = content.parse()?; 93 | let read_write = content.parse()?; 94 | 95 | if content.is_empty() { 96 | Ok(Precondition::ValidPtr { 97 | valid_ptr_keyword, 98 | parentheses, 99 | ident, 100 | _comma: comma, 101 | read_write, 102 | }) 103 | } else { 104 | Err(content.error("unexpected token")) 105 | } 106 | } else if input.peek(custom_keywords::proper_align) { 107 | let proper_align_keyword = input.parse()?; 108 | let content; 109 | let parentheses = parenthesized!(content in input); 110 | let ident = parse_precondition_ident(&content)?; 111 | 112 | if content.is_empty() { 113 | Ok(Precondition::ProperAlign { 114 | proper_align_keyword, 115 | parentheses, 116 | ident, 117 | }) 118 | } else { 119 | Err(content.error("unexpected token")) 120 | } 121 | } else if input.peek(LitStr) { 122 | Ok(Precondition::Custom(input.parse()?)) 123 | } else { 124 | let expr = input.parse(); 125 | 126 | match expr { 127 | Ok(expr) => Ok(Precondition::Boolean(Box::new(expr))), 128 | Err(mut err) => { 129 | err.combine(Error::new( 130 | start_span, 131 | "expected `valid_ptr`, `proper_align`, a string literal or a boolean expression", 132 | )); 133 | 134 | Err(err) 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | impl Spanned for Precondition { 142 | fn span(&self) -> Span { 143 | match self { 144 | Precondition::ValidPtr { 145 | valid_ptr_keyword, 146 | parentheses, 147 | .. 148 | } => valid_ptr_keyword 149 | .span() 150 | .join(parentheses.span) 151 | .unwrap_or_else(|| valid_ptr_keyword.span()), 152 | Precondition::ProperAlign { 153 | proper_align_keyword, 154 | parentheses, 155 | .. 156 | } => proper_align_keyword 157 | .span() 158 | .join(parentheses.span) 159 | .unwrap_or_else(|| proper_align_keyword.span()), 160 | Precondition::Boolean(expr) => expr.span(), 161 | Precondition::Custom(lit) => lit.span(), 162 | } 163 | } 164 | } 165 | 166 | impl Precondition { 167 | /// Returns a unique id for each descriminant. 168 | fn descriminant_id(&self) -> usize { 169 | match self { 170 | Precondition::ValidPtr { .. } => 0, 171 | Precondition::ProperAlign { .. } => 1, 172 | Precondition::Boolean(_) => 2, 173 | Precondition::Custom(_) => 3, 174 | } 175 | } 176 | } 177 | 178 | // Define an order for the preconditions here. 179 | // 180 | // The exact ordering does not really matter, as long as it is deterministic. 181 | impl Ord for Precondition { 182 | fn cmp(&self, other: &Self) -> Ordering { 183 | match (self, other) { 184 | ( 185 | Precondition::ValidPtr { 186 | ident: ident_self, .. 187 | }, 188 | Precondition::ValidPtr { 189 | ident: ident_other, .. 190 | }, 191 | ) => ident_self.cmp(ident_other), 192 | ( 193 | Precondition::ProperAlign { 194 | ident: ident_self, .. 195 | }, 196 | Precondition::ProperAlign { 197 | ident: ident_other, .. 198 | }, 199 | ) => ident_self.cmp(ident_other), 200 | (Precondition::Boolean(expr_self), Precondition::Boolean(expr_other)) => { 201 | quote!(#expr_self) 202 | .to_string() 203 | .cmp("e!(#expr_other).to_string()) 204 | } 205 | (Precondition::Custom(lit_self), Precondition::Custom(lit_other)) => { 206 | lit_self.value().cmp(&lit_other.value()) 207 | } 208 | _ => { 209 | debug_assert_ne!(self.descriminant_id(), other.descriminant_id()); 210 | 211 | self.descriminant_id().cmp(&other.descriminant_id()) 212 | } 213 | } 214 | } 215 | } 216 | 217 | impl PartialOrd for Precondition { 218 | fn partial_cmp(&self, other: &Self) -> Option { 219 | Some(self.cmp(other)) 220 | } 221 | } 222 | 223 | impl PartialEq for Precondition { 224 | fn eq(&self, other: &Self) -> bool { 225 | self.cmp(other) == Ordering::Equal 226 | } 227 | } 228 | 229 | impl Eq for Precondition {} 230 | 231 | /// Whether something is readable, writable or both. 232 | #[derive(Clone)] 233 | pub(crate) enum ReadWrite { 234 | /// The described thing is only readable. 235 | Read { 236 | /// The `r` keyword, indicating readability. 237 | r_keyword: custom_keywords::r, 238 | }, 239 | /// The described thing is only writable. 240 | Write { 241 | /// The `w` keyword, indicating writability. 242 | w_keyword: custom_keywords::w, 243 | }, 244 | /// The described thing is both readable and writable. 245 | Both { 246 | /// The `r` keyword, indicating readability. 247 | r_keyword: custom_keywords::r, 248 | /// The `+` between the `r` and the `w`, if both are present. 249 | _plus: Token![+], 250 | /// The `w` keyword, indicating writability. 251 | w_keyword: custom_keywords::w, 252 | }, 253 | } 254 | 255 | impl ReadWrite { 256 | /// Generates a short description suitable for usage in generated documentation. 257 | /// 258 | /// The generated description should finish the sentence 259 | /// "The pointer must be valid for...". 260 | pub(crate) fn doc_description(&self) -> &str { 261 | match self { 262 | ReadWrite::Read { .. } => "reads", 263 | ReadWrite::Write { .. } => "writes", 264 | ReadWrite::Both { .. } => "reads and writes", 265 | } 266 | } 267 | } 268 | 269 | impl fmt::Display for ReadWrite { 270 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 271 | match self { 272 | ReadWrite::Read { .. } => write!(f, "r"), 273 | ReadWrite::Write { .. } => write!(f, "w"), 274 | ReadWrite::Both { .. } => write!(f, "r+w"), 275 | } 276 | } 277 | } 278 | 279 | impl Parse for ReadWrite { 280 | fn parse(input: ParseStream) -> syn::Result { 281 | let lookahead = input.lookahead1(); 282 | 283 | if lookahead.peek(custom_keywords::w) { 284 | Ok(ReadWrite::Write { 285 | w_keyword: input.parse()?, 286 | }) 287 | } else if lookahead.peek(custom_keywords::r) { 288 | let r_keyword = input.parse()?; 289 | 290 | if input.peek(Token![+]) { 291 | let plus = input.parse()?; 292 | let w_keyword = input.parse()?; 293 | 294 | Ok(ReadWrite::Both { 295 | r_keyword, 296 | _plus: plus, 297 | w_keyword, 298 | }) 299 | } else { 300 | Ok(ReadWrite::Read { r_keyword }) 301 | } 302 | } else { 303 | Err(lookahead.error()) 304 | } 305 | } 306 | } 307 | 308 | impl Spanned for ReadWrite { 309 | fn span(&self) -> Span { 310 | match self { 311 | ReadWrite::Read { r_keyword } => r_keyword.span, 312 | ReadWrite::Write { w_keyword } => w_keyword.span, 313 | ReadWrite::Both { 314 | r_keyword, 315 | w_keyword, 316 | .. 317 | } => r_keyword 318 | .span 319 | .join(w_keyword.span) 320 | .unwrap_or(r_keyword.span), 321 | } 322 | } 323 | } 324 | 325 | /// A precondition with an optional `cfg` applying to it. 326 | pub(crate) struct CfgPrecondition { 327 | /// The precondition with additional data. 328 | pub(crate) precondition: Precondition, 329 | /// The `cfg` applying to the precondition. 330 | #[allow(dead_code)] 331 | pub(crate) cfg: Option, 332 | /// The span best representing the precondition. 333 | pub(crate) span: Span, 334 | } 335 | 336 | impl CfgPrecondition { 337 | /// The raw precondition. 338 | pub(crate) fn precondition(&self) -> &Precondition { 339 | &self.precondition 340 | } 341 | } 342 | 343 | impl Spanned for CfgPrecondition { 344 | fn span(&self) -> Span { 345 | self.span 346 | } 347 | } 348 | 349 | impl PartialEq for CfgPrecondition { 350 | fn eq(&self, other: &Self) -> bool { 351 | matches!(self.cmp(other), Ordering::Equal) 352 | } 353 | } 354 | 355 | impl Eq for CfgPrecondition {} 356 | 357 | impl PartialOrd for CfgPrecondition { 358 | fn partial_cmp(&self, other: &Self) -> Option { 359 | Some(self.cmp(other)) 360 | } 361 | } 362 | 363 | impl Ord for CfgPrecondition { 364 | fn cmp(&self, other: &Self) -> Ordering { 365 | self.precondition.cmp(&other.precondition) 366 | } 367 | } 368 | 369 | #[cfg(test)] 370 | mod tests { 371 | use quote::quote; 372 | use syn::parse2; 373 | 374 | use super::*; 375 | 376 | #[test] 377 | fn parse_correct_custom() { 378 | let result: Result = parse2(quote! { 379 | "foo" 380 | }); 381 | assert!(result.is_ok()); 382 | } 383 | 384 | #[test] 385 | fn parse_correct_valid_ptr() { 386 | { 387 | let result: Result = parse2(quote! { 388 | valid_ptr(foo, r) 389 | }); 390 | assert!(result.is_ok()); 391 | } 392 | 393 | { 394 | let result: Result = parse2(quote! { 395 | valid_ptr(foo, r+w) 396 | }); 397 | assert!(result.is_ok()); 398 | } 399 | 400 | { 401 | let result: Result = parse2(quote! { 402 | valid_ptr(foo, w) 403 | }); 404 | assert!(result.is_ok()); 405 | } 406 | } 407 | 408 | #[test] 409 | fn parse_wrong_expr() { 410 | { 411 | let result: Result = parse2(quote! { 412 | a ++ b 413 | }); 414 | assert!(result.is_err()); 415 | } 416 | 417 | { 418 | let result: Result = parse2(quote! { 419 | 17 - + -- + [] 420 | }); 421 | assert!(result.is_err()); 422 | } 423 | } 424 | 425 | #[test] 426 | fn parse_extra_tokens() { 427 | { 428 | let result: Result = parse2(quote! { 429 | "foo" bar 430 | }); 431 | assert!(result.is_err()); 432 | } 433 | 434 | { 435 | let result: Result = parse2(quote! { 436 | valid_ptr(foo, r+w+x) 437 | }); 438 | assert!(result.is_err()); 439 | } 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /proc-macro/src/struct_impl.rs: -------------------------------------------------------------------------------- 1 | //! Implements the procedural macros using generated structs passed as an additional parameter. 2 | //! 3 | //! The struct has the same name as the function to avoid having to know how to import it. 4 | //! See [this 5 | //! description](https://github.com/dtolnay/case-studies/tree/master/unit-type-parameters/README.md) 6 | //! if you want to understand how it works. It describes solving the import issue for a different 7 | //! problem. 8 | //! 9 | //! # Advantages of this approach 10 | //! - uses only stable features 11 | //! - quick to compute 12 | //! 13 | //! # Disadvantages of this approach 14 | //! - possible name clashes, because the identifier namespace is limited 15 | //! - error messages not very readable 16 | //! - the struct must be defined somewhere, which is not possible for a method 17 | //! 18 | //! # What the generated code looks like 19 | //! 20 | //! ```rust,ignore 21 | //! #[pre::pre(some_val > 42.0)] 22 | //! fn has_preconditions(some_val: f32) -> f32 { 23 | //! assert!(some_val > 42.0); 24 | //! 25 | //! some_val 26 | //! } 27 | //! 28 | //! #[pre::pre] 29 | //! fn main() { 30 | //! #[assure(some_val > 42.0, reason = "43.0 > 42.0")] 31 | //! has_preconditions(43.0); 32 | //! } 33 | //! ``` 34 | //! 35 | //! turns into 36 | //! 37 | //! ```rust,ignore 38 | //! #[allow(non_camel_case_types)] 39 | //! #[allow(non_snake_case)] 40 | //! #[cfg(not(doc))] 41 | //! struct has_preconditions { 42 | //! _boolean_some__val_20_3e_2042_2e0: (), 43 | //! } 44 | //! 45 | //! #[doc = "..."] 46 | //! fn has_preconditions(some_val: f32, #[cfg(not(doc))] _: has_preconditions) -> f32 { 47 | //! ::core::debug_assert!( 48 | //! some_val > 42.0 49 | //! "boolean precondition was wrongly assured: `{}`", 50 | //! ::core::stringify!(some_val > 42.0) 51 | //! ); 52 | //! assert!(some_val > 42.0); 53 | //! 54 | //! some_val 55 | //! } 56 | //! 57 | //! fn main() { 58 | //! has_preconditions( 59 | //! 43.0, 60 | //! has_preconditions { 61 | //! _boolean_some__val_20_3e_2042_2e0: (), 62 | //! }, 63 | //! ); 64 | //! } 65 | //! ``` 66 | 67 | use proc_macro2::{Span, TokenStream}; 68 | use proc_macro_error::emit_error; 69 | use quote::{format_ident, quote, quote_spanned, TokenStreamExt}; 70 | use syn::{parse2, spanned::Spanned, Ident, ItemFn, PathArguments}; 71 | 72 | use crate::{ 73 | call::Call, 74 | helpers::{add_span_to_signature, combine_cfg}, 75 | precondition::{CfgPrecondition, Precondition, ReadWrite}, 76 | }; 77 | 78 | /// Renders a precondition as a `String` representing an identifier. 79 | pub(crate) fn render_as_ident(precondition: &CfgPrecondition) -> Ident { 80 | /// Escapes characters that are not valid in identifiers. 81 | fn escape_non_ident_chars(string: String) -> String { 82 | string 83 | .chars() 84 | .map(|c| match c { 85 | '0'..='9' | 'a'..='z' | 'A'..='Z' => c.to_string(), 86 | '_' => "__".to_string(), // escape `'_'` to prevent name clashes 87 | other => format!("_{:x}", other as u32), 88 | }) 89 | .collect() 90 | } 91 | 92 | let mut ident = match precondition.precondition() { 93 | Precondition::ValidPtr { 94 | ident, read_write, .. 95 | } => format_ident!( 96 | "_valid_ptr_{}_{}", 97 | ident, 98 | match read_write { 99 | ReadWrite::Read { .. } => "r", 100 | ReadWrite::Write { .. } => "w", 101 | ReadWrite::Both { .. } => "rw", 102 | } 103 | ), 104 | Precondition::ProperAlign { ident, .. } => format_ident!("_proper_align_{}", ident), 105 | Precondition::Boolean(expr) => format_ident!( 106 | "_boolean_{}", 107 | escape_non_ident_chars(quote! { #expr }.to_string()) 108 | ), 109 | Precondition::Custom(string) => { 110 | format_ident!("_custom_{}", escape_non_ident_chars(string.value())) 111 | } 112 | }; 113 | 114 | ident.set_span(precondition.span()); 115 | 116 | ident 117 | } 118 | 119 | /// Generates the code for the function with the precondition handling added. 120 | pub(crate) fn render_pre( 121 | preconditions: Vec, 122 | function: &mut ItemFn, 123 | span: Span, 124 | ) -> TokenStream { 125 | let combined_cfg = combine_cfg(&preconditions, span); 126 | if function.sig.receiver().is_some() { 127 | emit_error!( 128 | span, 129 | "preconditions are not supported for methods on the stable compiler" 130 | ); 131 | return quote! { #function }; 132 | } 133 | 134 | let vis = &function.vis; 135 | let mut preconditions_rendered = TokenStream::new(); 136 | preconditions_rendered.append_all( 137 | preconditions 138 | .iter() 139 | .map(render_as_ident) 140 | .map(|ident| quote_spanned! { span=> #vis #ident: (), }), 141 | ); 142 | 143 | let function_name = function.sig.ident.clone(); 144 | let struct_def = quote_spanned! { span=> 145 | #[allow(non_camel_case_types)] 146 | #[allow(non_snake_case)] 147 | #[cfg(all(not(doc), #combined_cfg))] 148 | #vis struct #function_name { 149 | #preconditions_rendered 150 | } 151 | }; 152 | 153 | // Include the precondition site into the span of the function. 154 | // This improves the error messages for the case where no preconditions are specified. 155 | add_span_to_signature(span, &mut function.sig); 156 | 157 | function.sig.inputs.push( 158 | parse2(quote_spanned! { span=> 159 | #[cfg(all(not(doc), #combined_cfg))] 160 | _: #function_name 161 | }) 162 | .expect("parses as valid function argument"), 163 | ); 164 | 165 | quote! { 166 | #struct_def 167 | #function 168 | } 169 | } 170 | 171 | /// Generates the code for the call with the precondition handling added. 172 | pub(crate) fn render_assure( 173 | preconditions: Vec, 174 | mut call: Call, 175 | span: Span, 176 | ) -> Call { 177 | let combined_cfg = combine_cfg(&preconditions, span); 178 | if !call.is_function() { 179 | emit_error!( 180 | call, 181 | "method calls are not supported by `pre` on the stable compiler" 182 | ); 183 | 184 | return call; 185 | } 186 | 187 | let mut path; 188 | 189 | if let Some(p) = call.path() { 190 | path = p; 191 | } else { 192 | match &call { 193 | Call::Function(call) => emit_error!( 194 | call.func, 195 | "unable to determine at compile time which function is being called"; 196 | help = "use a direct path to the function instead" 197 | ), 198 | _ => unreachable!("we already checked that it's a function"), 199 | } 200 | 201 | return call; 202 | } 203 | 204 | if let Some(last_path_segment) = path.path.segments.last_mut() { 205 | last_path_segment.arguments = PathArguments::None; 206 | 207 | // Use the precondition span somewhere in the path. 208 | // This should improve the error message when no preconditions are present at the 209 | // definition, but some were `assure`d. 210 | // It should show the preconditions in that case (possibly in addition to the call). 211 | last_path_segment.ident.set_span(span); 212 | } 213 | 214 | let mut preconditions_rendered = TokenStream::new(); 215 | preconditions_rendered.append_all( 216 | preconditions 217 | .iter() 218 | .map(render_as_ident) 219 | .map(|ident| quote_spanned! { span=> #ident: (), }), 220 | ); 221 | 222 | call.args_mut().push( 223 | parse2(quote_spanned! { span=> 224 | #[cfg(all(not(doc), #combined_cfg))] 225 | #path { 226 | #preconditions_rendered 227 | } 228 | }) 229 | .expect("parses as an expression"), 230 | ); 231 | 232 | call 233 | } 234 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pre-tests" 3 | version = "0.0.0" 4 | authors = ["Niclas Schwarzlose <15schnic@gmail.com>"] 5 | license = "MIT OR Apache-2.0" 6 | description = "Integration tests for `pre`." 7 | repository = "https://github.com/aticu/pre" 8 | keywords = ["unsafe", "safety", "compile-time", "zero-cost", "no_std"] 9 | categories = ["development-tools", "no-std", "rust-patterns"] 10 | readme = "../README.md" 11 | edition = "2018" 12 | publish = false 13 | 14 | [dev-dependencies] 15 | pre = { path = "../main" } 16 | trybuild = { version = "1.0", features = ["diff"] } 17 | 18 | [build-dependencies] 19 | rustc_version = "0.2" 20 | -------------------------------------------------------------------------------- /tests/build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | 6 | match version_meta() { 7 | Ok(version) if version.channel == Channel::Nightly => { 8 | println!("cargo:rustc-cfg=nightly"); 9 | } 10 | _ => (), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/nightly/extern_crate/compile_fail/longer_path.rs: -------------------------------------------------------------------------------- 1 | #[pre::extern_crate(std)] 2 | mod pre_std { 3 | impl ptr::NonNull { 4 | #[pre(!ptr.is_null())] 5 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 6 | } 7 | 8 | impl foo::bar::Baz {} 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/nightly/extern_crate/compile_fail/longer_path.stderr: -------------------------------------------------------------------------------- 1 | error: only paths of length 1 are supported here 2 | --> $DIR/longer_path.rs:3:13 3 | | 4 | 3 | impl ptr::NonNull { 5 | | ^^^^^^^^^^^^^^^ 6 | | 7 | = help: try adding `ptr` as a submodule and put the `impl` block there 8 | 9 | error: only paths of length 1 are supported here 10 | --> $DIR/longer_path.rs:8:10 11 | | 12 | 8 | impl foo::bar::Baz {} 13 | | ^^^^^^^^^^^^^ 14 | | 15 | = help: try adding `foo::bar` as submodules and put the `impl` block there 16 | -------------------------------------------------------------------------------- /tests/nightly/extern_crate/pass/simple_extern_crate.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre::extern_crate(std)] 4 | mod pre_std { 5 | mod ptr { 6 | #[pre(valid_ptr(src, r))] 7 | #[pre("`src` must point to a properly initialized value of type `T`")] 8 | unsafe fn read_unaligned(src: *const T) -> T; 9 | 10 | #[pre(valid_ptr(dst, w))] 11 | unsafe fn write_unaligned(dst: *mut T, src: T); 12 | 13 | impl NonNull { 14 | #[pre(!ptr.is_null())] 15 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 16 | } 17 | } 18 | } 19 | 20 | #[pre] 21 | fn main() { 22 | let mut val = 0; 23 | 24 | #[assure(valid_ptr(dst, w), reason = "`dst` is a reference")] 25 | unsafe { 26 | pre_std::ptr::write_unaligned(&mut val, 42) 27 | }; 28 | assert_eq!(val, 42); 29 | 30 | { 31 | use std::ptr::read_unaligned; 32 | 33 | #[forward(pre_std::ptr)] 34 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 35 | #[assure( 36 | "`src` must point to a properly initialized value of type `T`", 37 | reason = "`src` is a reference" 38 | )] 39 | let result = unsafe { read_unaligned(&mut val) }; 40 | 41 | assert_eq!(result, 42); 42 | } 43 | 44 | { 45 | #[forward(std -> pre_std)] 46 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 47 | #[assure( 48 | "`src` must point to a properly initialized value of type `T`", 49 | reason = "`src` is a reference" 50 | )] 51 | let result = unsafe { std::ptr::read_unaligned(&mut val) }; 52 | 53 | assert_eq!(result, 42); 54 | } 55 | 56 | #[forward(impl pre_std::ptr::NonNull)] 57 | #[assure(!ptr.is_null(), reason = "`ptr` is a reference")] 58 | let non_null = unsafe { pre_std::ptr::NonNull::new_unchecked(&mut val) }; 59 | 60 | let std_non_null = unsafe { std::ptr::NonNull::new_unchecked(&mut val) }; 61 | 62 | assert_eq!(non_null, std_non_null); 63 | } 64 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/extra_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is bar", reason = "foo is bar")] 9 | #[assure("is baz", reason = "foo is baz")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/extra_precondition.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/extra_precondition.rs:8:5 3 | | 4 | 8 | / #[assure("is bar", reason = "foo is bar")] 5 | 9 | | #[assure("is baz", reason = "foo is baz")] 6 | | |______________________________________________^ expected a tuple with 1 element, found one with 2 elements 7 | | 8 | = note: expected tuple `(CustomCondition<"is bar">,)` 9 | found tuple `(CustomCondition<"is bar">, CustomCondition<"is baz">)` 10 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/missing_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | unsafe fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | unsafe { foo() } 9 | } 10 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/missing_assure.stderr: -------------------------------------------------------------------------------- 1 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 2 | --> $DIR/missing_assure.rs:8:14 3 | | 4 | 8 | unsafe { foo() } 5 | | ^^^-- supplied 0 arguments 6 | | | 7 | | expected 1 argument 8 | | 9 | note: function defined here 10 | --> $DIR/missing_assure.rs:4:11 11 | | 12 | 3 | #[pre("is bar")] 13 | | _______- 14 | 4 | | unsafe fn foo() {} 15 | | |___________^^- 16 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/missing_check.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | fn main() { 7 | foo() 8 | } 9 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/missing_check.stderr: -------------------------------------------------------------------------------- 1 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 2 | --> $DIR/missing_check.rs:7:5 3 | | 4 | 7 | foo() 5 | | ^^^-- supplied 0 arguments 6 | | | 7 | | expected 1 argument 8 | | 9 | note: function defined here 10 | --> $DIR/missing_check.rs:4:4 11 | | 12 | 3 | #[pre("is bar")] 13 | | _______- 14 | 4 | | fn foo() {} 15 | | |____^^- 16 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/precondition_missing.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/precondition_missing.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/precondition_missing.rs:9:5 3 | | 4 | 9 | #[assure("is bar", reason = "foo is bar")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected a tuple with 2 elements, found one with 1 element 6 | | 7 | = note: expected tuple `(CustomCondition<"is bar">, CustomCondition<"is baz">)` 8 | found tuple `(CustomCondition<"is bar">,)` 9 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/undefined_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | #[assure("is bar", reason = "foo is bar")] 8 | foo() 9 | } 10 | -------------------------------------------------------------------------------- /tests/nightly/function/compile_fail/undefined_precondition.stderr: -------------------------------------------------------------------------------- 1 | error[E0061]: this function takes 0 arguments but 1 argument was supplied 2 | --> $DIR/undefined_precondition.rs:8:5 3 | | 4 | 7 | #[assure("is bar", reason = "foo is bar")] 5 | | ------------------------------------------ supplied 1 argument 6 | 8 | foo() 7 | | ^^^ expected 0 arguments 8 | | 9 | note: function defined here 10 | --> $DIR/undefined_precondition.rs:3:4 11 | | 12 | 3 | fn foo() {} 13 | | ^^^ 14 | -------------------------------------------------------------------------------- /tests/nightly/function/pass/multiple_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is also baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | #[assure("is also baz", reason = "foo is also baz")] 11 | foo() 12 | } 13 | -------------------------------------------------------------------------------- /tests/nightly/function/pass/nested_call.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("not called directly")] 4 | unsafe fn foo() -> Result { 5 | Ok(0) 6 | } 7 | 8 | #[pre] 9 | fn main() -> Result<(), ()> { 10 | let array = [1, 2, 3]; 11 | 12 | #[assure("not called directly", reason = "nested in multiple other expressions")] 13 | let one_ref = &unsafe { array[foo()?] }; 14 | 15 | assert_eq!(*one_ref, 1); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /tests/nightly/function/pass/nested_path_with_generics.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod b { 5 | pub(crate) mod c { 6 | use pre::pre; 7 | 8 | #[pre("is foo")] 9 | pub(crate) fn foo() {} 10 | } 11 | } 12 | } 13 | 14 | #[pre] 15 | fn main() { 16 | #[assure("is foo", reason = "foo is always foo")] 17 | a::b::c::foo::(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/nightly/function/pass/no_std.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use pre::pre; 4 | 5 | #[pre("foo")] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure("foo", reason = "is foo")] 11 | foo(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/nightly/function/pass/single_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo", reason = "bar is always foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/ambiguous_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("baz")] 4 | fn foo() -> [u8; 8] { 5 | [0; 8] 6 | } 7 | 8 | #[pre("baz")] 9 | fn bar() -> usize { 10 | 0 11 | } 12 | 13 | #[pre] 14 | fn main() { 15 | #[assure("baz", reason = "is baz")] 16 | foo()[bar()]; 17 | } 18 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/ambiguous_assure.stderr: -------------------------------------------------------------------------------- 1 | error: could not find an unambiguos call to apply this to 2 | --> $DIR/ambiguous_assure.rs:15:5 3 | | 4 | 15 | #[assure("baz", reason = "is baz")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: try moving it closer to the call it should apply to 8 | 9 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 10 | --> $DIR/ambiguous_assure.rs:16:5 11 | | 12 | 16 | foo()[bar()]; 13 | | ^^^-- supplied 0 arguments 14 | | | 15 | | expected 1 argument 16 | | 17 | note: function defined here 18 | --> $DIR/ambiguous_assure.rs:4:4 19 | | 20 | 3 | #[pre("baz")] 21 | | _______- 22 | 4 | | fn foo() -> [u8; 8] { 23 | | |____^^- 24 | 25 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 26 | --> $DIR/ambiguous_assure.rs:16:11 27 | | 28 | 16 | foo()[bar()]; 29 | | ^^^-- supplied 0 arguments 30 | | | 31 | | expected 1 argument 32 | | 33 | note: function defined here 34 | --> $DIR/ambiguous_assure.rs:9:4 35 | | 36 | 8 | #[pre("baz")] 37 | | _______- 38 | 9 | | fn bar() -> usize { 39 | | |____^^- 40 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/cfg_attr_different_predicates.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(target_endian = "big", pre("foo_big"))] 4 | #[cfg_attr(target_endian = "little", pre("foo_little"))] 5 | fn foo() {} 6 | 7 | #[pre("bar")] 8 | #[cfg_attr(any(target_endian = "big", target_endian = "little"), pre("baz"))] 9 | fn bar() {} 10 | 11 | #[pre] 12 | fn main() { 13 | #[cfg_attr(target_endian = "big", assure("foo_big", reason = "is foo_big"))] 14 | #[cfg_attr( 15 | target_endian = "little", 16 | assure("foo_little", reason = "is foo_little") 17 | )] 18 | foo(); 19 | 20 | #[assure("bar", reason = "is bar")] 21 | #[cfg_attr( 22 | any(target_endian = "big", target_endian = "little"), 23 | assure("baz", reason = "is baz") 24 | )] 25 | bar(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/cfg_attr_different_predicates.stderr: -------------------------------------------------------------------------------- 1 | error: mismatched `cfg` predicates for preconditions 2 | --> $DIR/cfg_attr_different_predicates.rs:15:9 3 | | 4 | 15 | target_endian = "little", 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: all preconditions must have syntactically equal `cfg` predicates 8 | note: `target_endian = "big"` != `target_endian = "little"` 9 | --> $DIR/cfg_attr_different_predicates.rs:13:16 10 | | 11 | 13 | #[cfg_attr(target_endian = "big", assure("foo_big", reason = "is foo_big"))] 12 | | ^^^^^^^^^^^^^^^^^^^^^ 13 | 14 | error: mismatched `cfg` predicates for preconditions 15 | --> $DIR/cfg_attr_different_predicates.rs:22:9 16 | | 17 | 22 | any(target_endian = "big", target_endian = "little"), 18 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | | 20 | = note: all preconditions must have syntactically equal `cfg` predicates 21 | = note: some preconditions have a `cfg` predicate and some do not 22 | 23 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 24 | --> $DIR/cfg_attr_different_predicates.rs:18:5 25 | | 26 | 18 | foo(); 27 | | ^^^-- supplied 0 arguments 28 | | | 29 | | expected 1 argument 30 | | 31 | note: function defined here 32 | --> $DIR/cfg_attr_different_predicates.rs:5:4 33 | | 34 | 4 | #[cfg_attr(target_endian = "little", pre("foo_little"))] 35 | | __________________________________________- 36 | 5 | | fn foo() {} 37 | | |____^^- 38 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/duplicate_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | mod nested { 6 | use pre::pre; 7 | 8 | #[pre("nested foo")] 9 | pub(super) fn foo() {} 10 | } 11 | 12 | mod other_nested { 13 | use pre::pre; 14 | 15 | #[pre("nested foo")] 16 | pub(super) fn foo() {} 17 | } 18 | 19 | #[pre] 20 | fn main() { 21 | #[forward(nested)] 22 | #[forward(other_nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/duplicate_forward.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate `forward` attribute 2 | --> $DIR/duplicate_forward.rs:22:5 3 | | 4 | 22 | #[forward(other_nested)] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | help: there can be just one location, try removing the wrong one 8 | --> $DIR/duplicate_forward.rs:21:5 9 | | 10 | 21 | #[forward(nested)] 11 | | ^^^^^^^^^^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/forward_failed_replace.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod d { 5 | pub(crate) fn foo() {} 6 | } 7 | } 8 | 9 | mod c { 10 | pub(crate) mod d { 11 | use pre::pre; 12 | 13 | #[pre("is foo")] 14 | pub(crate) fn foo() {} 15 | } 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | #[forward(b -> c)] 21 | #[assure("is foo", reason = "foo is always foo")] 22 | a::d::foo(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/forward_failed_replace.stderr: -------------------------------------------------------------------------------- 1 | error: cannot replace `b` in this path 2 | --> $DIR/forward_failed_replace.rs:22:5 3 | | 4 | 22 | a::d::foo(); 5 | | ^^^^^^^^^ 6 | | 7 | note: `b` != `a` 8 | --> $DIR/forward_failed_replace.rs:22:5 9 | | 10 | 22 | a::d::foo(); 11 | | ^ 12 | help: try specifing a prefix of `a :: d :: foo` in the `forward` attribute 13 | --> $DIR/forward_failed_replace.rs:20:15 14 | | 15 | 20 | #[forward(b -> c)] 16 | | ^ 17 | 18 | error[E0061]: this function takes 0 arguments but 1 argument was supplied 19 | --> $DIR/forward_failed_replace.rs:22:5 20 | | 21 | 20 | / #[forward(b -> c)] 22 | 21 | | #[assure("is foo", reason = "foo is always foo")] 23 | | |_____________________________________________________- supplied 1 argument 24 | 22 | a::d::foo(); 25 | | ^^^^^^^^^ expected 0 arguments 26 | | 27 | note: function defined here 28 | --> $DIR/forward_failed_replace.rs:5:23 29 | | 30 | 5 | pub(crate) fn foo() {} 31 | | ^^^ 32 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/forward_non_path_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | let array = [foo]; 8 | 9 | #[forward(foo)] 10 | array[0](); 11 | } 12 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/forward_non_path_fn.stderr: -------------------------------------------------------------------------------- 1 | error: unable to determine at compile time which function is being called 2 | --> $DIR/forward_non_path_fn.rs:10:5 3 | | 4 | 10 | array[0](); 5 | | ^^^^^^^^ 6 | | 7 | = help: use a direct path to the function instead 8 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/hint_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("some condition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure( 9 | "some condition", 10 | reason = "" 11 | )] 12 | foo() 13 | } 14 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/hint_reason.stderr: -------------------------------------------------------------------------------- 1 | error: you need to specify a different reason here 2 | --> $DIR/hint_reason.rs:10:18 3 | | 4 | 10 | reason = "" 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: specifying a meaningful reason will help you and others understand why this is ok in the future 8 | = help: using `TODO` here will emit a warning, reminding you to fix this later 9 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/method_replace_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | fn foo(&self) {} 7 | 8 | fn bar(&self) {} 9 | } 10 | 11 | #[pre] 12 | fn main() { 13 | #[forward(foo -> bar)] 14 | X.foo(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/method_replace_forward.stderr: -------------------------------------------------------------------------------- 1 | error: a replacement `forward` attribute is not supported for method calls 2 | --> $DIR/method_replace_forward.rs:14:5 3 | | 4 | 14 | X.foo(); 5 | | ^^^^^^^ 6 | | 7 | help: try replacing it with a direct location, such as `bar` 8 | --> $DIR/method_replace_forward.rs:13:15 9 | | 10 | 13 | #[forward(foo -> bar)] 11 | | ^^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/missing_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/missing_reason.stderr: -------------------------------------------------------------------------------- 1 | error: you need to specify a reason why this precondition holds 2 | --> $DIR/missing_reason.rs:8:14 3 | | 4 | 8 | #[assure("is foo")] 5 | | ^^^^^^^^ 6 | | 7 | = help: add `, reason = ""` 8 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/true_cfg_attr_checks_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/nightly/misc/compile_fail/true_cfg_attr_checks_preconditions.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/true_cfg_attr_checks_preconditions.rs:10:9 3 | | 4 | 10 | assure("fuu", reason = "is fuu") 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `"foo"`, found `"fuu"` 6 | | 7 | = note: expected struct `CustomCondition<"foo">` 8 | found struct `CustomCondition<"fuu">` 9 | -------------------------------------------------------------------------------- /tests/nightly/misc/pass/cfg_attr.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("foo", reason = "is foo") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/nightly/misc/pass/false_cfg_attr_doesnt_check_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | all(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/nightly/misc/pass/forward.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use pre::pre; 4 | 5 | fn foo() {} 6 | 7 | mod nested { 8 | use pre::pre; 9 | 10 | #[pre("nested foo")] 11 | pub(super) fn foo() {} 12 | } 13 | 14 | mod nested_no_pre { 15 | pub(super) fn foo() {} 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | foo(); 21 | 22 | #[forward(nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | 26 | #[forward(nested_no_pre -> nested)] 27 | #[assure("nested foo", reason = "corresponding forward present")] 28 | nested_no_pre::foo(); 29 | 30 | use nested_no_pre::foo as bar; 31 | 32 | #[forward(bar -> nested::foo)] 33 | #[assure("nested foo", reason = "corresponding forward present")] 34 | bar(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/nightly/misc/pass/no_debug_assert.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(no_debug_assert)] 4 | #[pre(abc > foo)] 5 | #[pre(27 + 25)] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure(abc > foo, reason = "unknown idents are not a problem, because of `no_debug_assert`")] 11 | #[assure(27 + 25, reason = "non-bool expr is not a problem, because of `no_debug_assert`")] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/nightly/nightly-only/pass/method.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | #[pre("precondition on method")] 7 | fn foo(&self) {} 8 | } 9 | 10 | #[pre] 11 | fn main() { 12 | #[assure("precondition on method", reason = "it is on a method")] 13 | X.foo(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/nightly/nightly-only/pass/unnamed_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("precondition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | let fn_array = [foo]; 9 | 10 | #[assure("precondition", reason = "precondition holds")] 11 | fn_array[0](); 12 | } 13 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/boolean_non_bool.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(14 + 20 + 8)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(14 + 20 + 8, reason = "math")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/boolean_non_bool.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/boolean_non_bool.rs:3:7 3 | | 4 | 3 | #[pre(14 + 20 + 8)] 5 | | ^^^^^^^^^^^ expected `bool`, found integer 6 | | 7 | = note: this error originates in the macro `::core::debug_assert` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/boolean_unknown_var.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(value > 0)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(value > 0, reason = "42 > 0")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/boolean_unknown_var.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `value` in this scope 2 | --> $DIR/boolean_unknown_var.rs:3:7 3 | | 4 | 3 | #[pre(value > 0)] 5 | | ^^^^^ help: a local variable with a similar name exists: `valu` 6 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/unknown_keyword.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(unknown_keyword)] 4 | fn foo(unknown_keyword: bool) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(unknown_keyword, reason = "`unknown_keyword` is `true`")] 9 | foo(true); 10 | } 11 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/compile_fail/unknown_keyword.stderr: -------------------------------------------------------------------------------- 1 | error: keyword `unknown_keyword` is not recognized by pre 2 | --> $DIR/unknown_keyword.rs:3:7 3 | | 4 | 3 | #[pre(unknown_keyword)] 5 | | ^^^^^^^^^^^^^^^ 6 | | 7 | = help: if you wanted to use a boolean expression, try `unknown_keyword == true` 8 | -------------------------------------------------------------------------------- /tests/nightly/precondition_types/pass/all_types.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("`some_ptr` is from a reference")] 4 | #[pre(valid_ptr(some_ptr, r))] 5 | #[pre(proper_align(some_ptr))] 6 | #[pre(!some_ptr.is_null())] 7 | fn foo(some_ptr: *const T) {} 8 | 9 | #[pre] 10 | fn main() { 11 | #[assure(valid_ptr(some_ptr, r), reason = "it is from a reference")] 12 | #[assure(!some_ptr.is_null(), reason = "it is from a reference")] 13 | #[assure("`some_ptr` is from a reference", reason = "it is")] 14 | #[assure(proper_align(some_ptr), reason = "it is from a reference")] 15 | foo(&42) 16 | } 17 | -------------------------------------------------------------------------------- /tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use trybuild::TestCases; 4 | 5 | macro_rules! add_category { 6 | ($test_cases:expr, $scenario:literal, $category:literal) => {{ 7 | $test_cases.pass(concat!($scenario, "/", $category, "/pass/*.rs")); 8 | $test_cases.compile_fail(concat!($scenario, "/", $category, "/compile_fail/*.rs")); 9 | }}; 10 | } 11 | 12 | macro_rules! add_testcases { 13 | ($test_cases:expr, $scenario:literal) => {{ 14 | add_category!($test_cases, $scenario, "function"); 15 | add_category!($test_cases, $scenario, "precondition_types"); 16 | add_category!($test_cases, $scenario, "extern_crate"); 17 | add_category!($test_cases, $scenario, "misc"); 18 | }}; 19 | } 20 | 21 | #[cfg(not(nightly))] 22 | #[test] 23 | fn stable_tests() { 24 | let test_cases = TestCases::new(); 25 | 26 | add_testcases!(test_cases, "stable"); 27 | 28 | add_category!(test_cases, "stable", "stable-only"); 29 | } 30 | 31 | #[cfg(nightly)] 32 | #[test] 33 | fn nightly_tests() { 34 | let test_cases = TestCases::new(); 35 | 36 | add_testcases!(test_cases, "nightly"); 37 | 38 | add_category!(test_cases, "nightly", "nightly-only"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/stable/extern_crate/compile_fail/longer_path.rs: -------------------------------------------------------------------------------- 1 | #[pre::extern_crate(std)] 2 | mod pre_std { 3 | impl ptr::NonNull { 4 | #[pre(!ptr.is_null())] 5 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 6 | } 7 | 8 | impl foo::bar::Baz {} 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/stable/extern_crate/compile_fail/longer_path.stderr: -------------------------------------------------------------------------------- 1 | error: only paths of length 1 are supported here 2 | 3 | = help: try adding `ptr` as a submodule and put the `impl` block there 4 | 5 | --> $DIR/longer_path.rs:3:13 6 | | 7 | 3 | impl ptr::NonNull { 8 | | ^^^^^^^^^^^^^^^ 9 | 10 | error: only paths of length 1 are supported here 11 | 12 | = help: try adding `foo::bar` as submodules and put the `impl` block there 13 | 14 | --> $DIR/longer_path.rs:8:10 15 | | 16 | 8 | impl foo::bar::Baz {} 17 | | ^^^^^^^^^^^^^ 18 | -------------------------------------------------------------------------------- /tests/stable/extern_crate/pass/simple_extern_crate.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre::extern_crate(std)] 4 | mod pre_std { 5 | mod ptr { 6 | #[pre(valid_ptr(src, r))] 7 | #[pre("`src` must point to a properly initialized value of type `T`")] 8 | unsafe fn read_unaligned(src: *const T) -> T; 9 | 10 | #[pre(valid_ptr(dst, w))] 11 | unsafe fn write_unaligned(dst: *mut T, src: T); 12 | 13 | impl NonNull { 14 | #[pre(!ptr.is_null())] 15 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 16 | } 17 | } 18 | } 19 | 20 | #[pre] 21 | fn main() { 22 | let mut val = 0; 23 | 24 | #[assure(valid_ptr(dst, w), reason = "`dst` is a reference")] 25 | unsafe { 26 | pre_std::ptr::write_unaligned(&mut val, 42) 27 | }; 28 | assert_eq!(val, 42); 29 | 30 | { 31 | use std::ptr::read_unaligned; 32 | 33 | #[forward(pre_std::ptr)] 34 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 35 | #[assure( 36 | "`src` must point to a properly initialized value of type `T`", 37 | reason = "`src` is a reference" 38 | )] 39 | let result = unsafe { read_unaligned(&mut val) }; 40 | 41 | assert_eq!(result, 42); 42 | } 43 | 44 | { 45 | #[forward(std -> pre_std)] 46 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 47 | #[assure( 48 | "`src` must point to a properly initialized value of type `T`", 49 | reason = "`src` is a reference" 50 | )] 51 | let result = unsafe { std::ptr::read_unaligned(&mut val) }; 52 | 53 | assert_eq!(result, 42); 54 | } 55 | 56 | #[forward(impl pre_std::ptr::NonNull)] 57 | #[assure(!ptr.is_null(), reason = "`ptr` is a reference")] 58 | let non_null = unsafe { pre_std::ptr::NonNull::new_unchecked(&mut val) }; 59 | 60 | let std_non_null = unsafe { std::ptr::NonNull::new_unchecked(&mut val) }; 61 | 62 | assert_eq!(non_null, std_non_null); 63 | } 64 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/extra_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is bar", reason = "foo is bar")] 9 | #[assure("is baz", reason = "foo is baz")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/extra_precondition.stderr: -------------------------------------------------------------------------------- 1 | error[E0560]: struct `foo` has no field named `_custom_is_20baz` 2 | --> $DIR/extra_precondition.rs:9:6 3 | | 4 | 9 | #[assure("is baz", reason = "foo is baz")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `foo` does not have this field 6 | | 7 | = note: available fields are: `_custom_is_20bar` 8 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/missing_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | unsafe fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | unsafe { foo() } 9 | } 10 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/missing_assure.stderr: -------------------------------------------------------------------------------- 1 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 2 | --> $DIR/missing_assure.rs:8:14 3 | | 4 | 8 | unsafe { foo() } 5 | | ^^^-- supplied 0 arguments 6 | | | 7 | | expected 1 argument 8 | | 9 | note: function defined here 10 | --> $DIR/missing_assure.rs:4:11 11 | | 12 | 3 | #[pre("is bar")] 13 | | _______- 14 | 4 | | unsafe fn foo() {} 15 | | |___________^^- 16 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/missing_check.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | fn main() { 7 | foo() 8 | } 9 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/missing_check.stderr: -------------------------------------------------------------------------------- 1 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 2 | --> $DIR/missing_check.rs:7:5 3 | | 4 | 7 | foo() 5 | | ^^^-- supplied 0 arguments 6 | | | 7 | | expected 1 argument 8 | | 9 | note: function defined here 10 | --> $DIR/missing_check.rs:4:4 11 | | 12 | 3 | #[pre("is bar")] 13 | | _______- 14 | 4 | | fn foo() {} 15 | | |____^^- 16 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/precondition_missing.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/precondition_missing.stderr: -------------------------------------------------------------------------------- 1 | error[E0063]: missing field `_custom_is_20baz` in initializer of `foo` 2 | --> $DIR/precondition_missing.rs:9:6 3 | | 4 | 9 | #[assure("is bar", reason = "foo is bar")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `_custom_is_20baz` 6 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/undefined_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | #[assure("is bar", reason = "foo is bar")] 8 | foo() 9 | } 10 | -------------------------------------------------------------------------------- /tests/stable/function/compile_fail/undefined_precondition.stderr: -------------------------------------------------------------------------------- 1 | error[E0574]: expected struct, variant or union type, found function `foo` 2 | --> $DIR/undefined_precondition.rs:7:6 3 | | 4 | 7 | #[assure("is bar", reason = "foo is bar")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a struct, variant or union type 6 | 7 | error[E0061]: this function takes 0 arguments but 1 argument was supplied 8 | --> $DIR/undefined_precondition.rs:8:5 9 | | 10 | 7 | #[assure("is bar", reason = "foo is bar")] 11 | | ----------------------------------------- supplied 1 argument 12 | 8 | foo() 13 | | ^^^ expected 0 arguments 14 | | 15 | note: function defined here 16 | --> $DIR/undefined_precondition.rs:3:4 17 | | 18 | 3 | fn foo() {} 19 | | ^^^ 20 | -------------------------------------------------------------------------------- /tests/stable/function/pass/multiple_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is also baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | #[assure("is also baz", reason = "foo is also baz")] 11 | foo() 12 | } 13 | -------------------------------------------------------------------------------- /tests/stable/function/pass/nested_call.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("not called directly")] 4 | unsafe fn foo() -> Result { 5 | Ok(0) 6 | } 7 | 8 | #[pre] 9 | fn main() -> Result<(), ()> { 10 | let array = [1, 2, 3]; 11 | 12 | #[assure("not called directly", reason = "nested in multiple other expressions")] 13 | let one_ref = &unsafe { array[foo()?] }; 14 | 15 | assert_eq!(*one_ref, 1); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /tests/stable/function/pass/nested_path_with_generics.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod b { 5 | pub(crate) mod c { 6 | use pre::pre; 7 | 8 | #[pre("is foo")] 9 | pub(crate) fn foo() {} 10 | } 11 | } 12 | } 13 | 14 | #[pre] 15 | fn main() { 16 | #[assure("is foo", reason = "foo is always foo")] 17 | a::b::c::foo::(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/stable/function/pass/no_std.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use pre::pre; 4 | 5 | #[pre("foo")] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure("foo", reason = "is foo")] 11 | foo(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/stable/function/pass/single_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo", reason = "bar is always foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/ambiguous_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("baz")] 4 | fn foo() -> [u8; 8] { 5 | [0; 8] 6 | } 7 | 8 | #[pre("baz")] 9 | fn bar() -> usize { 10 | 0 11 | } 12 | 13 | #[pre] 14 | fn main() { 15 | #[assure("baz", reason = "is baz")] 16 | foo()[bar()]; 17 | } 18 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/ambiguous_assure.stderr: -------------------------------------------------------------------------------- 1 | error: could not find an unambiguos call to apply this to 2 | 3 | = help: try moving it closer to the call it should apply to 4 | 5 | --> $DIR/ambiguous_assure.rs:15:6 6 | | 7 | 15 | #[assure("baz", reason = "is baz")] 8 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 9 | 10 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 11 | --> $DIR/ambiguous_assure.rs:16:5 12 | | 13 | 16 | foo()[bar()]; 14 | | ^^^-- supplied 0 arguments 15 | | | 16 | | expected 1 argument 17 | | 18 | note: function defined here 19 | --> $DIR/ambiguous_assure.rs:4:4 20 | | 21 | 3 | #[pre("baz")] 22 | | _______- 23 | 4 | | fn foo() -> [u8; 8] { 24 | | |____^^- 25 | 26 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 27 | --> $DIR/ambiguous_assure.rs:16:11 28 | | 29 | 16 | foo()[bar()]; 30 | | ^^^-- supplied 0 arguments 31 | | | 32 | | expected 1 argument 33 | | 34 | note: function defined here 35 | --> $DIR/ambiguous_assure.rs:9:4 36 | | 37 | 8 | #[pre("baz")] 38 | | _______- 39 | 9 | | fn bar() -> usize { 40 | | |____^^- 41 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/cfg_attr_different_predicates.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(target_endian = "big", pre("foo_big"))] 4 | #[cfg_attr(target_endian = "little", pre("foo_little"))] 5 | fn foo() {} 6 | 7 | #[pre("bar")] 8 | #[cfg_attr(any(target_endian = "big", target_endian = "little"), pre("baz"))] 9 | fn bar() {} 10 | 11 | #[pre] 12 | fn main() { 13 | #[cfg_attr(target_endian = "big", assure("foo_big", reason = "is foo_big"))] 14 | #[cfg_attr( 15 | target_endian = "little", 16 | assure("foo_little", reason = "is foo_little") 17 | )] 18 | foo(); 19 | 20 | #[assure("bar", reason = "is bar")] 21 | #[cfg_attr( 22 | any(target_endian = "big", target_endian = "little"), 23 | assure("baz", reason = "is baz") 24 | )] 25 | bar(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/cfg_attr_different_predicates.stderr: -------------------------------------------------------------------------------- 1 | error: mismatched `cfg` predicates for preconditions 2 | 3 | = note: all preconditions must have syntactically equal `cfg` predicates 4 | = note: `target_endian = "big"` != `target_endian = "little"` 5 | 6 | --> $DIR/cfg_attr_different_predicates.rs:15:9 7 | | 8 | 15 | target_endian = "little", 9 | | ^^^^^^^^^^^^^ 10 | 11 | error: mismatched `cfg` predicates for preconditions 12 | 13 | = note: all preconditions must have syntactically equal `cfg` predicates 14 | = note: some preconditions have a `cfg` predicate and some do not 15 | 16 | --> $DIR/cfg_attr_different_predicates.rs:22:9 17 | | 18 | 22 | any(target_endian = "big", target_endian = "little"), 19 | | ^^^ 20 | 21 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 22 | --> $DIR/cfg_attr_different_predicates.rs:18:5 23 | | 24 | 18 | foo(); 25 | | ^^^-- supplied 0 arguments 26 | | | 27 | | expected 1 argument 28 | | 29 | note: function defined here 30 | --> $DIR/cfg_attr_different_predicates.rs:5:4 31 | | 32 | 4 | #[cfg_attr(target_endian = "little", pre("foo_little"))] 33 | | __________________________________________- 34 | 5 | | fn foo() {} 35 | | |____^^- 36 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/duplicate_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | mod nested { 6 | use pre::pre; 7 | 8 | #[pre("nested foo")] 9 | pub(super) fn foo() {} 10 | } 11 | 12 | mod other_nested { 13 | use pre::pre; 14 | 15 | #[pre("nested foo")] 16 | pub(super) fn foo() {} 17 | } 18 | 19 | #[pre] 20 | fn main() { 21 | #[forward(nested)] 22 | #[forward(other_nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/duplicate_forward.stderr: -------------------------------------------------------------------------------- 1 | error: duplicate `forward` attribute 2 | 3 | = help: there can be just one location, try removing the wrong one 4 | 5 | --> $DIR/duplicate_forward.rs:22:6 6 | | 7 | 22 | #[forward(other_nested)] 8 | | ^^^^^^^^^^^^^^^^^^^^^^^ 9 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/forward_failed_replace.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod d { 5 | pub(crate) fn foo() {} 6 | } 7 | } 8 | 9 | mod c { 10 | pub(crate) mod d { 11 | use pre::pre; 12 | 13 | #[pre("is foo")] 14 | pub(crate) fn foo() {} 15 | } 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | #[forward(b -> c)] 21 | #[assure("is foo", reason = "foo is always foo")] 22 | a::d::foo(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/forward_failed_replace.stderr: -------------------------------------------------------------------------------- 1 | error: cannot replace `b` in this path 2 | 3 | = note: `b` != `a` 4 | = help: try specifing a prefix of `a :: d :: foo` in the `forward` attribute 5 | 6 | --> $DIR/forward_failed_replace.rs:22:5 7 | | 8 | 22 | a::d::foo(); 9 | | ^^^^^^^^^ 10 | 11 | error[E0574]: expected struct, variant or union type, found function `a::d::foo` 12 | --> $DIR/forward_failed_replace.rs:21:6 13 | | 14 | 21 | #[assure("is foo", reason = "foo is always foo")] 15 | | ______^ 16 | 22 | | a::d::foo(); 17 | | |_____^ not a struct, variant or union type 18 | | 19 | help: consider importing this struct instead 20 | | 21 | 1 | use crate::c::d::foo; 22 | | 23 | 24 | error[E0061]: this function takes 0 arguments but 1 argument was supplied 25 | --> $DIR/forward_failed_replace.rs:22:5 26 | | 27 | 21 | #[assure("is foo", reason = "foo is always foo")] 28 | | ______- 29 | 22 | | a::d::foo(); 30 | | | -^^^^^^^^ 31 | | | | 32 | | |_____expected 0 arguments 33 | | supplied 1 argument 34 | | 35 | note: function defined here 36 | --> $DIR/forward_failed_replace.rs:5:23 37 | | 38 | 5 | pub(crate) fn foo() {} 39 | | ^^^ 40 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/forward_non_path_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | let array = [foo]; 8 | 9 | #[forward(foo)] 10 | array[0](); 11 | } 12 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/forward_non_path_fn.stderr: -------------------------------------------------------------------------------- 1 | error: unable to determine at compile time which function is being called 2 | 3 | = help: use a direct path to the function instead 4 | 5 | --> $DIR/forward_non_path_fn.rs:10:5 6 | | 7 | 10 | array[0](); 8 | | ^^^^^^^^ 9 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/hint_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("some condition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure( 9 | "some condition", 10 | reason = "" 11 | )] 12 | foo() 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/hint_reason.stderr: -------------------------------------------------------------------------------- 1 | error: you need to specify a different reason here 2 | 3 | = help: specifying a meaningful reason will help you and others understand why this is ok in the future 4 | 5 | --> $DIR/hint_reason.rs:10:18 6 | | 7 | 10 | reason = "" 8 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 9 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/method_replace_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | fn foo(&self) {} 7 | 8 | fn bar(&self) {} 9 | } 10 | 11 | #[pre] 12 | fn main() { 13 | #[forward(foo -> bar)] 14 | X.foo(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/method_replace_forward.stderr: -------------------------------------------------------------------------------- 1 | error: a replacement `forward` attribute is not supported for method calls 2 | 3 | = help: try replacing it with a direct location, such as `bar` 4 | 5 | --> $DIR/method_replace_forward.rs:14:5 6 | | 7 | 14 | X.foo(); 8 | | ^ 9 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/missing_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/missing_reason.stderr: -------------------------------------------------------------------------------- 1 | error: you need to specify a reason why this precondition holds 2 | 3 | = help: add `, reason = ""` 4 | 5 | --> $DIR/missing_reason.rs:8:14 6 | | 7 | 8 | #[assure("is foo")] 8 | | ^^^^^^^^ 9 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/true_cfg_attr_checks_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable/misc/compile_fail/true_cfg_attr_checks_preconditions.stderr: -------------------------------------------------------------------------------- 1 | error[E0560]: struct `foo` has no field named `_custom_fuu` 2 | --> $DIR/true_cfg_attr_checks_preconditions.rs:10:15 3 | | 4 | 10 | assure("fuu", reason = "is fuu") 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: a field with a similar name exists: `_custom_foo` 6 | -------------------------------------------------------------------------------- /tests/stable/misc/pass/cfg_attr.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("foo", reason = "is foo") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable/misc/pass/false_cfg_attr_doesnt_check_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | all(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable/misc/pass/forward.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use pre::pre; 4 | 5 | fn foo() {} 6 | 7 | mod nested { 8 | use pre::pre; 9 | 10 | #[pre("nested foo")] 11 | pub(super) fn foo() {} 12 | } 13 | 14 | mod nested_no_pre { 15 | pub(super) fn foo() {} 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | foo(); 21 | 22 | #[forward(nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | 26 | #[forward(nested_no_pre -> nested)] 27 | #[assure("nested foo", reason = "corresponding forward present")] 28 | nested_no_pre::foo(); 29 | 30 | use nested_no_pre::foo as bar; 31 | 32 | #[forward(bar -> nested::foo)] 33 | #[assure("nested foo", reason = "corresponding forward present")] 34 | bar(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/stable/misc/pass/no_debug_assert.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(no_debug_assert)] 4 | #[pre(abc > foo)] 5 | #[pre(27 + 25)] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure(abc > foo, reason = "unknown idents are not a problem, because of `no_debug_assert`")] 11 | #[assure(27 + 25, reason = "non-bool expr is not a problem, because of `no_debug_assert`")] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/boolean_non_bool.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(14 + 20 + 8)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(14 + 20 + 8, reason = "math")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/boolean_non_bool.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/boolean_non_bool.rs:3:7 3 | | 4 | 3 | #[pre(14 + 20 + 8)] 5 | | ^^ expected `bool`, found integer 6 | | 7 | = note: this error originates in the macro `$crate::assert` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/boolean_unknown_var.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(value > 0)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(value > 0, reason = "42 > 0")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/boolean_unknown_var.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `value` in this scope 2 | --> $DIR/boolean_unknown_var.rs:3:7 3 | | 4 | 3 | #[pre(value > 0)] 5 | | ^^^^^ help: a local variable with a similar name exists: `valu` 6 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/unknown_keyword.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(unknown_keyword)] 4 | fn foo(unknown_keyword: bool) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(unknown_keyword, reason = "`unknown_keyword` is `true`")] 9 | foo(true); 10 | } 11 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/compile_fail/unknown_keyword.stderr: -------------------------------------------------------------------------------- 1 | error: keyword `unknown_keyword` is not recognized by pre 2 | 3 | = help: if you wanted to use a boolean expression, try `unknown_keyword == true` 4 | 5 | --> $DIR/unknown_keyword.rs:3:7 6 | | 7 | 3 | #[pre(unknown_keyword)] 8 | | ^^^^^^^^^^^^^^^ 9 | -------------------------------------------------------------------------------- /tests/stable/precondition_types/pass/all_types.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("`some_ptr` is from a reference")] 4 | #[pre(valid_ptr(some_ptr, r))] 5 | #[pre(proper_align(some_ptr))] 6 | #[pre(!some_ptr.is_null())] 7 | fn foo(some_ptr: *const T) {} 8 | 9 | #[pre] 10 | fn main() { 11 | #[assure(valid_ptr(some_ptr, r), reason = "it is from a reference")] 12 | #[assure(!some_ptr.is_null(), reason = "it is from a reference")] 13 | #[assure("`some_ptr` is from a reference", reason = "it is")] 14 | #[assure(proper_align(some_ptr), reason = "it is from a reference")] 15 | foo(&42) 16 | } 17 | -------------------------------------------------------------------------------- /tests/stable/stable-only/compile_fail/method.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | #[pre("precondition on method")] 7 | fn foo(&self) {} 8 | } 9 | 10 | #[pre] 11 | fn main() { 12 | #[assure("precondition on method", reason = "it is on a method")] 13 | X.foo(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/stable/stable-only/compile_fail/method.stderr: -------------------------------------------------------------------------------- 1 | error: preconditions are not supported for methods on the stable compiler 2 | --> $DIR/method.rs:6:11 3 | | 4 | 6 | #[pre("precondition on method")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | error: method calls are not supported by `pre` on the stable compiler 8 | --> $DIR/method.rs:13:5 9 | | 10 | 13 | X.foo(); 11 | | ^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/stable/stable-only/compile_fail/unnamed_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("precondition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | let fn_array = [foo]; 9 | 10 | #[assure("precondition", reason = "precondition holds")] 11 | fn_array[0](); 12 | } 13 | -------------------------------------------------------------------------------- /tests/stable/stable-only/compile_fail/unnamed_fn.stderr: -------------------------------------------------------------------------------- 1 | error: unable to determine at compile time which function is being called 2 | 3 | = help: use a direct path to the function instead 4 | 5 | --> $DIR/unnamed_fn.rs:11:5 6 | | 7 | 11 | fn_array[0](); 8 | | ^^^^^^^^^^^ 9 | 10 | error[E0061]: this function takes 1 argument but 0 arguments were supplied 11 | --> $DIR/unnamed_fn.rs:11:5 12 | | 13 | 11 | fn_array[0](); 14 | | ^^^^^^^^^^^-- supplied 0 arguments 15 | | | 16 | | expected 1 argument 17 | | 18 | note: function defined here 19 | --> $DIR/unnamed_fn.rs:4:4 20 | | 21 | 3 | #[pre("precondition")] 22 | | _______- 23 | 4 | | fn foo() {} 24 | | |____^^- 25 | -------------------------------------------------------------------------------- /tests/templates/extern_crate/compile_fail/longer_path.rs: -------------------------------------------------------------------------------- 1 | #[pre::extern_crate(std)] 2 | mod pre_std { 3 | impl ptr::NonNull { 4 | #[pre(!ptr.is_null())] 5 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 6 | } 7 | 8 | impl foo::bar::Baz {} 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/templates/extern_crate/pass/simple_extern_crate.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre::extern_crate(std)] 4 | mod pre_std { 5 | mod ptr { 6 | #[pre(valid_ptr(src, r))] 7 | #[pre("`src` must point to a properly initialized value of type `T`")] 8 | unsafe fn read_unaligned(src: *const T) -> T; 9 | 10 | #[pre(valid_ptr(dst, w))] 11 | unsafe fn write_unaligned(dst: *mut T, src: T); 12 | 13 | impl NonNull { 14 | #[pre(!ptr.is_null())] 15 | const unsafe fn new_unchecked(ptr: *mut T) -> NonNull; 16 | } 17 | } 18 | } 19 | 20 | #[pre] 21 | fn main() { 22 | let mut val = 0; 23 | 24 | #[assure(valid_ptr(dst, w), reason = "`dst` is a reference")] 25 | unsafe { 26 | pre_std::ptr::write_unaligned(&mut val, 42) 27 | }; 28 | assert_eq!(val, 42); 29 | 30 | { 31 | use std::ptr::read_unaligned; 32 | 33 | #[forward(pre_std::ptr)] 34 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 35 | #[assure( 36 | "`src` must point to a properly initialized value of type `T`", 37 | reason = "`src` is a reference" 38 | )] 39 | let result = unsafe { read_unaligned(&mut val) }; 40 | 41 | assert_eq!(result, 42); 42 | } 43 | 44 | { 45 | #[forward(std -> pre_std)] 46 | #[assure(valid_ptr(src, r), reason = "`src` is a reference")] 47 | #[assure( 48 | "`src` must point to a properly initialized value of type `T`", 49 | reason = "`src` is a reference" 50 | )] 51 | let result = unsafe { std::ptr::read_unaligned(&mut val) }; 52 | 53 | assert_eq!(result, 42); 54 | } 55 | 56 | #[forward(impl pre_std::ptr::NonNull)] 57 | #[assure(!ptr.is_null(), reason = "`ptr` is a reference")] 58 | let non_null = unsafe { pre_std::ptr::NonNull::new_unchecked(&mut val) }; 59 | 60 | let std_non_null = unsafe { std::ptr::NonNull::new_unchecked(&mut val) }; 61 | 62 | assert_eq!(non_null, std_non_null); 63 | } 64 | -------------------------------------------------------------------------------- /tests/templates/function/compile_fail/extra_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is bar", reason = "foo is bar")] 9 | #[assure("is baz", reason = "foo is baz")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/templates/function/compile_fail/missing_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | unsafe fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | unsafe { foo() } 9 | } 10 | -------------------------------------------------------------------------------- /tests/templates/function/compile_fail/missing_check.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | fn foo() {} 5 | 6 | fn main() { 7 | foo() 8 | } 9 | -------------------------------------------------------------------------------- /tests/templates/function/compile_fail/precondition_missing.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | foo() 11 | } 12 | -------------------------------------------------------------------------------- /tests/templates/function/compile_fail/undefined_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | #[assure("is bar", reason = "foo is bar")] 8 | foo() 9 | } 10 | -------------------------------------------------------------------------------- /tests/templates/function/pass/multiple_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is bar")] 4 | #[pre("is also baz")] 5 | fn foo() {} 6 | 7 | #[pre] 8 | fn main() { 9 | #[assure("is bar", reason = "foo is bar")] 10 | #[assure("is also baz", reason = "foo is also baz")] 11 | foo() 12 | } 13 | -------------------------------------------------------------------------------- /tests/templates/function/pass/nested_call.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("not called directly")] 4 | unsafe fn foo() -> Result { 5 | Ok(0) 6 | } 7 | 8 | #[pre] 9 | fn main() -> Result<(), ()> { 10 | let array = [1, 2, 3]; 11 | 12 | #[assure("not called directly", reason = "nested in multiple other expressions")] 13 | let one_ref = &unsafe { array[foo()?] }; 14 | 15 | assert_eq!(*one_ref, 1); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /tests/templates/function/pass/nested_path_with_generics.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod b { 5 | pub(crate) mod c { 6 | use pre::pre; 7 | 8 | #[pre("is foo")] 9 | pub(crate) fn foo() {} 10 | } 11 | } 12 | } 13 | 14 | #[pre] 15 | fn main() { 16 | #[assure("is foo", reason = "foo is always foo")] 17 | a::b::c::foo::(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/templates/function/pass/no_std.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use pre::pre; 4 | 5 | #[pre("foo")] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure("foo", reason = "is foo")] 11 | foo(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/templates/function/pass/single_precondition.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo", reason = "bar is always foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/ambiguous_assure.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("baz")] 4 | fn foo() -> [u8; 8] { 5 | [0; 8] 6 | } 7 | 8 | #[pre("baz")] 9 | fn bar() -> usize { 10 | 0 11 | } 12 | 13 | #[pre] 14 | fn main() { 15 | #[assure("baz", reason = "is baz")] 16 | foo()[bar()]; 17 | } 18 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/cfg_attr_different_predicates.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(target_endian = "big", pre("foo_big"))] 4 | #[cfg_attr(target_endian = "little", pre("foo_little"))] 5 | fn foo() {} 6 | 7 | #[pre("bar")] 8 | #[cfg_attr(any(target_endian = "big", target_endian = "little"), pre("baz"))] 9 | fn bar() {} 10 | 11 | #[pre] 12 | fn main() { 13 | #[cfg_attr(target_endian = "big", assure("foo_big", reason = "is foo_big"))] 14 | #[cfg_attr( 15 | target_endian = "little", 16 | assure("foo_little", reason = "is foo_little") 17 | )] 18 | foo(); 19 | 20 | #[assure("bar", reason = "is bar")] 21 | #[cfg_attr( 22 | any(target_endian = "big", target_endian = "little"), 23 | assure("baz", reason = "is baz") 24 | )] 25 | bar(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/duplicate_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | mod nested { 6 | use pre::pre; 7 | 8 | #[pre("nested foo")] 9 | pub(super) fn foo() {} 10 | } 11 | 12 | mod other_nested { 13 | use pre::pre; 14 | 15 | #[pre("nested foo")] 16 | pub(super) fn foo() {} 17 | } 18 | 19 | #[pre] 20 | fn main() { 21 | #[forward(nested)] 22 | #[forward(other_nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/forward_failed_replace.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | mod a { 4 | pub(crate) mod d { 5 | pub(crate) fn foo() {} 6 | } 7 | } 8 | 9 | mod c { 10 | pub(crate) mod d { 11 | use pre::pre; 12 | 13 | #[pre("is foo")] 14 | pub(crate) fn foo() {} 15 | } 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | #[forward(b -> c)] 21 | #[assure("is foo", reason = "foo is always foo")] 22 | a::d::foo(); 23 | } 24 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/forward_non_path_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | fn foo() {} 4 | 5 | #[pre] 6 | fn main() { 7 | let array = [foo]; 8 | 9 | #[forward(foo)] 10 | array[0](); 11 | } 12 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/hint_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("some condition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure( 9 | "some condition", 10 | reason = "" 11 | )] 12 | foo() 13 | } 14 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/method_replace_forward.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | fn foo(&self) {} 7 | 8 | fn bar(&self) {} 9 | } 10 | 11 | #[pre] 12 | fn main() { 13 | #[forward(foo -> bar)] 14 | X.foo(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/missing_reason.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("is foo")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure("is foo")] 9 | foo() 10 | } 11 | -------------------------------------------------------------------------------- /tests/templates/misc/compile_fail/true_cfg_attr_checks_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/templates/misc/pass/cfg_attr.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(any(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | any(target_endian = "little", target_endian = "big"), 10 | assure("foo", reason = "is foo") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/templates/misc/pass/false_cfg_attr_doesnt_check_preconditions.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[cfg_attr(all(target_endian = "little", target_endian = "big"), pre("foo"))] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[cfg_attr( 9 | all(target_endian = "little", target_endian = "big"), 10 | assure("fuu", reason = "is fuu") 11 | )] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/templates/misc/pass/forward.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use pre::pre; 4 | 5 | fn foo() {} 6 | 7 | mod nested { 8 | use pre::pre; 9 | 10 | #[pre("nested foo")] 11 | pub(super) fn foo() {} 12 | } 13 | 14 | mod nested_no_pre { 15 | pub(super) fn foo() {} 16 | } 17 | 18 | #[pre] 19 | fn main() { 20 | foo(); 21 | 22 | #[forward(nested)] 23 | #[assure("nested foo", reason = "corresponding forward present")] 24 | foo(); 25 | 26 | #[forward(nested_no_pre -> nested)] 27 | #[assure("nested foo", reason = "corresponding forward present")] 28 | nested_no_pre::foo(); 29 | 30 | use nested_no_pre::foo as bar; 31 | 32 | #[forward(bar -> nested::foo)] 33 | #[assure("nested foo", reason = "corresponding forward present")] 34 | bar(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/templates/misc/pass/no_debug_assert.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(no_debug_assert)] 4 | #[pre(abc > foo)] 5 | #[pre(27 + 25)] 6 | fn foo() {} 7 | 8 | #[pre] 9 | fn main() { 10 | #[assure(abc > foo, reason = "unknown idents are not a problem, because of `no_debug_assert`")] 11 | #[assure(27 + 25, reason = "non-bool expr is not a problem, because of `no_debug_assert`")] 12 | foo(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/templates/nightly-only/pass/method.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | #[pre("precondition on method")] 7 | fn foo(&self) {} 8 | } 9 | 10 | #[pre] 11 | fn main() { 12 | #[assure("precondition on method", reason = "it is on a method")] 13 | X.foo(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/templates/nightly-only/pass/unnamed_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("precondition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | let fn_array = [foo]; 9 | 10 | #[assure("precondition", reason = "precondition holds")] 11 | fn_array[0](); 12 | } 13 | -------------------------------------------------------------------------------- /tests/templates/precondition_types/compile_fail/boolean_non_bool.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(14 + 20 + 8)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(14 + 20 + 8, reason = "math")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/templates/precondition_types/compile_fail/boolean_unknown_var.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(value > 0)] 4 | fn foo(valu: i32) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(value > 0, reason = "42 > 0")] 9 | foo(42) 10 | } 11 | -------------------------------------------------------------------------------- /tests/templates/precondition_types/compile_fail/unknown_keyword.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre(unknown_keyword)] 4 | fn foo(unknown_keyword: bool) {} 5 | 6 | #[pre] 7 | fn main() { 8 | #[assure(unknown_keyword, reason = "`unknown_keyword` is `true`")] 9 | foo(true); 10 | } 11 | -------------------------------------------------------------------------------- /tests/templates/precondition_types/pass/all_types.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("`some_ptr` is from a reference")] 4 | #[pre(valid_ptr(some_ptr, r))] 5 | #[pre(proper_align(some_ptr))] 6 | #[pre(!some_ptr.is_null())] 7 | fn foo(some_ptr: *const T) {} 8 | 9 | #[pre] 10 | fn main() { 11 | #[assure(valid_ptr(some_ptr, r), reason = "it is from a reference")] 12 | #[assure(!some_ptr.is_null(), reason = "it is from a reference")] 13 | #[assure("`some_ptr` is from a reference", reason = "it is")] 14 | #[assure(proper_align(some_ptr), reason = "it is from a reference")] 15 | foo(&42) 16 | } 17 | -------------------------------------------------------------------------------- /tests/templates/stable-only/compile_fail/method.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | struct X; 4 | 5 | impl X { 6 | #[pre("precondition on method")] 7 | fn foo(&self) {} 8 | } 9 | 10 | #[pre] 11 | fn main() { 12 | #[assure("precondition on method", reason = "it is on a method")] 13 | X.foo(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/templates/stable-only/compile_fail/unnamed_fn.rs: -------------------------------------------------------------------------------- 1 | use pre::pre; 2 | 3 | #[pre("precondition")] 4 | fn foo() {} 5 | 6 | #[pre] 7 | fn main() { 8 | let fn_array = [foo]; 9 | 10 | #[assure("precondition", reason = "precondition holds")] 11 | fn_array[0](); 12 | } 13 | --------------------------------------------------------------------------------