├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── proc-macro-error-attr ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── lib.rs │ ├── parse.rs │ └── settings.rs ├── src ├── diagnostic.rs ├── dummy.rs ├── imp │ ├── delegate.rs │ └── fallback.rs ├── lib.rs ├── macros.rs └── sealed.rs ├── test-crate ├── .gitignore ├── Cargo.toml └── lib.rs └── tests ├── macro-errors.rs ├── ok.rs ├── runtime-errors.rs └── ui ├── abort.rs ├── abort.stderr ├── append_dummy.rs ├── append_dummy.stderr ├── children_messages.rs ├── children_messages.stderr ├── dummy.rs ├── dummy.stderr ├── emit.rs ├── emit.stderr ├── explicit_span_range.rs ├── explicit_span_range.stderr ├── misuse.rs ├── misuse.stderr ├── multiple_tokens.rs ├── multiple_tokens.stderr ├── not_proc_macro.rs ├── not_proc_macro.stderr ├── option_ext.rs ├── option_ext.stderr ├── result_ext.rs ├── result_ext.stderr ├── to_tokens_span.rs ├── to_tokens_span.stderr ├── unknown_setting.rs ├── unknown_setting.stderr ├── unrelated_panic.rs └── unrelated_panic.stderr /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | rust-version: ["stable", "beta", "nightly"] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - run: rustup toolchain install ${{ matrix.rust-version }} 15 | - run: cargo +${{ matrix.rust-version }} test --all 16 | 17 | msrv-check: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: rustup toolchain install 1.61 22 | - run: cargo +1.61 check --all 23 | 24 | ui-test: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - run: RUSTFLAGS='--cfg run_ui_tests' cargo +stable test ui 29 | 30 | test-fmt: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | - run: cargo fmt --check --all 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .fuse_hidden* 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.0.1 (2024-09-06) 2 | 3 | * Fixed a span location issue due to mistake in refactoring (#2) 4 | 5 | # v2.0.0 (2024-09-05) 6 | 7 | No changes, simply releasing pre-release as full release. 8 | 9 | # v2.0.0-pre.1 (2024-09-01) 10 | 11 | * __Crate has been renamed to `proc-macro-error2`, due to the old maintainer's inactivity.__ 12 | 13 | * `syn` has been upgraded to `2` 14 | * MSRV has been bumped to `1.61` 15 | * Warnings have been fixed, including `clippy::pedantic` lints 16 | * CI has been converted to GitHub actions, and testing infrastructure significantly simplified. 17 | * Automatic nightly detection has been removed, use the `nightly` feature for improved diagnostics at the cost of stability. 18 | 19 | # v1.0.4 (2020-7-31) 20 | 21 | * `SpanRange` facility is now public. 22 | * Docs have been improved. 23 | * Introduced the `syn-error` feature so you can opt-out from the `syn` dependency. 24 | 25 | # v1.0.3 (2020-6-26) 26 | 27 | * Corrected a few typos. 28 | * Fixed the `emit_call_site_warning` macro. 29 | 30 | # v1.0.2 (2020-4-9) 31 | 32 | * An obsolete note was removed from documentation. 33 | 34 | # v1.0.1 (2020-4-9) 35 | 36 | * `proc-macro-hack` is now well tested and supported. Not sure about `proc-macro-nested`, 37 | please fill a request if you need it. 38 | * Fixed `emit_call_site_error`. 39 | * Documentation improvements. 40 | 41 | # v1.0.0 (2020-3-25) 42 | 43 | I believe the API can be considered stable because it's been a few months without 44 | breaking changes, and I also don't think this crate will receive much further evolution. 45 | It's perfect, admit it. 46 | 47 | Hence, meet the new, stable release! 48 | 49 | ### Improvements 50 | 51 | * Supported nested `#[proc_macro_error]` attributes. Well, you aren't supposed to do that, 52 | but I caught myself doing it by accident on one occasion and the behavior was... surprising. 53 | Better to handle this smooth. 54 | 55 | # v0.4.12 (2020-3-23) 56 | 57 | * Error message on macros' misuse is now a bit more understandable. 58 | 59 | # v0.4.11 (2020-3-02) 60 | 61 | * `build.rs` no longer fails when `rustc` date could not be determined, 62 | (thanks to [`Fabian Möller`](https://gitlab.com/CreepySkeleton/proc-macro-error/issues/8) 63 | for noticing and to [`Igor Gnatenko`](https://gitlab.com/CreepySkeleton/proc-macro-error/-/merge_requests/25) 64 | for fixing). 65 | 66 | # v0.4.10 (2020-2-29) 67 | 68 | * `proc-macro-error` doesn't depend on syn\[full\] anymore, the compilation 69 | is \~30secs faster. 70 | 71 | # v0.4.9 (2020-2-13) 72 | 73 | * New function: `append_dummy`. 74 | 75 | # v0.4.8 (2020-2-01) 76 | 77 | * Support for children messages 78 | 79 | # v0.4.7 (2020-1-31) 80 | 81 | * Now any type that implements `quote::ToTokens` can be used instead of spans. 82 | This allows for high quality error messages. 83 | 84 | # v0.4.6 (2020-1-31) 85 | 86 | * `From` implementation doesn't lose span info anymore, see 87 | [#6](https://gitlab.com/CreepySkeleton/proc-macro-error/issues/6). 88 | 89 | # v0.4.5 (2020-1-20) 90 | Just a small intermediate release. 91 | 92 | * Fix some bugs. 93 | * Populate license files into subfolders. 94 | 95 | # v0.4.4 (2019-11-13) 96 | * Fix `abort_if_dirty` + warnings bug 97 | * Allow trailing commas in macros 98 | 99 | # v0.4.2 (2019-11-7) 100 | * FINALLY fixed `__pme__suggestions not found` bug 101 | 102 | # v0.4.1 (2019-11-7) YANKED 103 | * Fixed `__pme__suggestions not found` bug 104 | * Documentation improvements, links checked 105 | 106 | # v0.4.0 (2019-11-6) YANKED 107 | 108 | ## New features 109 | * "help" messages that can have their own span on nightly, they 110 | inherit parent span on stable. 111 | ```rust 112 | let cond_help = if condition { Some("some help message") else { None } }; 113 | abort!( 114 | span, // parent span 115 | "something's wrong, {} wrongs in total", 10; // main message 116 | help = "here's a help for you, {}", "take it"; // unconditional help message 117 | help =? cond_help; // conditional help message, must be Option 118 | note = note_span => "don't forget the note, {}", "would you?" // notes can have their own span but it's effective only on nightly 119 | ) 120 | ``` 121 | * Warnings via `emit_warning` and `emit_warning_call_site`. Nightly only, they're ignored on stable. 122 | * Now `proc-macro-error` delegates to `proc_macro::Diagnostic` on nightly. 123 | 124 | ## Breaking changes 125 | * `MacroError` is now replaced by `Diagnostic`. Its API resembles `proc_macro::Diagnostic`. 126 | * `Diagnostic` does not implement `From<&str/String>` so `Result::abort_or_exit()` 127 | won't work anymore (nobody used it anyway). 128 | * `macro_error!` macro is replaced with `diagnostic!`. 129 | 130 | ## Improvements 131 | * Now `proc-macro-error` renders notes exactly just like rustc does. 132 | * We don't parse a body of a function annotated with `#[proc_macro_error]` anymore, 133 | only looking at the signature. This should somewhat decrease expansion time for large functions. 134 | 135 | # v0.3.3 (2019-10-16) 136 | * Now you can use any word instead of "help", undocumented. 137 | 138 | # v0.3.2 (2019-10-16) 139 | * Introduced support for "help" messages, undocumented. 140 | 141 | # v0.3.0 (2019-10-8) 142 | 143 | ## The crate has been completely rewritten from scratch! 144 | 145 | ## Changes (most are breaking): 146 | * Renamed macros: 147 | * `span_error` => `abort` 148 | * `call_site_error` => `abort_call_site` 149 | * `filter_macro_errors` was replaced by `#[proc_macro_error]` attribute. 150 | * `set_dummy` now takes `TokenStream` instead of `Option` 151 | * Support for multiple errors via `emit_error` and `emit_call_site_error` 152 | * New `macro_error` macro for building errors in format=like style. 153 | * `MacroError` API had been reconsidered. It also now implements `quote::ToTokens`. 154 | 155 | # v0.2.6 (2019-09-02) 156 | * Introduce support for dummy implementations via `dummy::set_dummy` 157 | * `multi::*` is now deprecated, will be completely rewritten in v0.3 158 | 159 | # v0.2.0 (2019-08-15) 160 | 161 | ## Breaking changes 162 | * `trigger_error` replaced with `MacroError::trigger` and `filter_macro_error_panics` 163 | is hidden from docs. 164 | This is not quite a breaking change since users weren't supposed to use these functions directly anyway. 165 | * All dependencies are updated to `v1.*`. 166 | 167 | ## New features 168 | * Ability to stack multiple errors via `multi::MultiMacroErrors` and emit them at once. 169 | 170 | ## Improvements 171 | * Now `MacroError` implements `std::fmt::Display` instead of `std::string::ToString`. 172 | * `MacroError::span` inherent method. 173 | * `From for proc_macro/proc_macro2::TokenStream` implementations. 174 | * `AsRef/AsMut for MacroError` implementations. 175 | 176 | # v0.1.x (2019-07-XX) 177 | 178 | ## New features 179 | * An easy way to report errors inside within a proc-macro via `span_error`, 180 | `call_site_error` and `filter_macro_errors`. 181 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-error2" 3 | authors = [ 4 | "CreepySkeleton ", 5 | "GnomedDev ", 6 | ] 7 | version = "2.0.1" 8 | description = "Almost drop-in replacement to panics in proc-macros" 9 | repository = "https://github.com/GnomedDev/proc-macro-error-2" 10 | rust-version = "1.61" 11 | keywords = ["proc-macro", "error", "errors"] 12 | categories = ["development-tools::procedural-macro-helpers"] 13 | license = "MIT OR Apache-2.0" 14 | edition = "2021" 15 | 16 | [dependencies] 17 | quote = "1" 18 | proc-macro2 = "1" 19 | proc-macro-error-attr2 = { path = "./proc-macro-error-attr", version = "=2.0.0" } 20 | 21 | [dependencies.syn] 22 | version = "2" 23 | optional = true 24 | default-features = false 25 | 26 | [dev-dependencies] 27 | test-crate = { path = "./test-crate" } 28 | syn = { version = "2", features = ["full"] } 29 | trybuild = { version = "1.0.99", features = ["diff"] } 30 | 31 | [features] 32 | default = ["syn-error"] 33 | syn-error = ["dep:syn"] 34 | nightly = [] 35 | 36 | [lints.rust] 37 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(run_ui_tests)'] } 38 | 39 | [lints.clippy] 40 | pedantic = { level = "warn", priority = -1 } 41 | module_name_repetitions = { level = "allow" } 42 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 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 2019-2020 CreepySkeleton 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 | http://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 | MIT License 2 | 3 | Copyright (c) 2019-2020 CreepySkeleton 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Makes error reporting in procedural macros nice and easy 2 | 3 | [![docs.rs](https://docs.rs/proc-macro-error2/badge.svg)](https://docs.rs/proc-macro-error2) 4 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) 5 | 6 | This crate aims to make error reporting in proc-macros simple and easy to use. 7 | Migrate from `panic!`-based errors for as little effort as possible! 8 | 9 | Also, you can explicitly [append a dummy token stream][crate::dummy] to your errors. 10 | 11 | To achieve this, this crate serves as a tiny shim around `proc_macro::Diagnostic` and 12 | `compile_error!`. It detects the most preferable way to emit errors based on compiler's version. 13 | When the underlying diagnostic type is finally stabilized, this crate will be simply 14 | delegating to it, requiring no changes in your code! 15 | 16 | So you can just use this crate and have *both* some of `proc_macro::Diagnostic` functionality 17 | available on stable ahead of time and your error-reporting code future-proof. 18 | 19 | ```toml 20 | [dependencies] 21 | proc-macro-error2 = "2.0" 22 | ``` 23 | 24 | *Supports rustc 1.61 and up* 25 | 26 | [Documentation and guide][guide] 27 | 28 | ## Quick example 29 | 30 | Code: 31 | 32 | ```rust 33 | #[proc_macro] 34 | #[proc_macro_error] 35 | pub fn make_fn(input: TokenStream) -> TokenStream { 36 | let mut input = TokenStream2::from(input).into_iter(); 37 | let name = input.next().unwrap(); 38 | if let Some(second) = input.next() { 39 | abort! { second, 40 | "I don't like this part!"; 41 | note = "I see what you did there..."; 42 | help = "I need only one part, you know?"; 43 | } 44 | } 45 | 46 | quote!( fn #name() {} ).into() 47 | } 48 | ``` 49 | 50 | This is how the error is rendered in a terminal: 51 | 52 |

53 | 54 |

55 | 56 | And this is what your users will see in their IDE: 57 | 58 |

59 | 60 |

61 | 62 | ## Examples 63 | 64 | ### Panic-like usage 65 | 66 | ```rust 67 | use proc_macro_error2::{ 68 | proc_macro_error, 69 | abort, 70 | abort_call_site, 71 | ResultExt, 72 | OptionExt, 73 | }; 74 | use proc_macro::TokenStream; 75 | use syn::{DeriveInput, parse_macro_input}; 76 | use quote::quote; 77 | 78 | // This is your main entry point 79 | #[proc_macro] 80 | // This attribute *MUST* be placed on top of the #[proc_macro] function 81 | #[proc_macro_error] 82 | pub fn make_answer(input: TokenStream) -> TokenStream { 83 | let input = parse_macro_input!(input as DeriveInput); 84 | 85 | if let Err(err) = some_logic(&input) { 86 | // we've got a span to blame, let's use it 87 | // This immediately aborts the proc-macro and shows the error 88 | // 89 | // You can use `proc_macro::Span`, `proc_macro2::Span`, and 90 | // anything that implements `quote::ToTokens` (almost every type from 91 | // `syn` and `proc_macro2`) 92 | abort!(err, "You made an error, go fix it: {}", err.msg); 93 | } 94 | 95 | // `Result` has some handy shortcuts if your error type implements 96 | // `Into`. `Option` has one unconditionally. 97 | more_logic(&input).expect_or_abort("What a careless user, behave!"); 98 | 99 | if !more_logic_for_logic_god(&input) { 100 | // We don't have an exact location this time, 101 | // so just highlight the proc-macro invocation itself 102 | abort_call_site!( 103 | "Bad, bad user! Now go stand in the corner and think about what you did!"); 104 | } 105 | 106 | // Now all the processing is done, return `proc_macro::TokenStream` 107 | quote!(/* stuff */).into() 108 | } 109 | ``` 110 | 111 | ### `proc_macro::Diagnostic`-like usage 112 | 113 | ```rust 114 | use proc_macro_error2::*; 115 | use proc_macro::TokenStream; 116 | use syn::{spanned::Spanned, DeriveInput, ItemStruct, Fields, Attribute , parse_macro_input}; 117 | use quote::quote; 118 | 119 | fn process_attrs(attrs: &[Attribute]) -> Vec { 120 | attrs 121 | .iter() 122 | .filter_map(|attr| match process_attr(attr) { 123 | Ok(res) => Some(res), 124 | Err(msg) => { 125 | emit_error!(attr, "Invalid attribute: {}", msg); 126 | None 127 | } 128 | }) 129 | .collect() 130 | } 131 | 132 | fn process_fields(_attrs: &Fields) -> Vec { 133 | // processing fields in pretty much the same way as attributes 134 | unimplemented!() 135 | } 136 | 137 | #[proc_macro] 138 | #[proc_macro_error] 139 | pub fn make_answer(input: TokenStream) -> TokenStream { 140 | let input = parse_macro_input!(input as ItemStruct); 141 | let attrs = process_attrs(&input.attrs); 142 | 143 | // abort right now if some errors were encountered 144 | // at the attributes processing stage 145 | abort_if_dirty(); 146 | 147 | let fields = process_fields(&input.fields); 148 | 149 | // no need to think about emitted errors 150 | // #[proc_macro_error] will handle them for you 151 | // 152 | // just return a TokenStream as you normally would 153 | quote!(/* stuff */).into() 154 | } 155 | ``` 156 | 157 | ## Real world examples 158 | 159 | * [`structopt-derive`](https://github.com/TeXitoi/structopt/tree/master/structopt-derive) 160 | (abort-like usage) 161 | * [`auto-impl`](https://github.com/auto-impl-rs/auto_impl/) (emit-like usage) 162 | 163 | ## Limitations 164 | 165 | - Warnings are emitted only on nightly, they are ignored on stable. 166 | - "help" suggestions can't have their own span info on stable, 167 | (essentially inheriting the parent span). 168 | - If your macro happens to trigger a panic, no errors will be displayed. This is not a 169 | technical limitation but rather intentional design. `panic` is not for error reporting. 170 | 171 | ## MSRV policy 172 | 173 | The MSRV is currently `1.61`, and this is considered a breaking change to increase. 174 | 175 | However, if an existing dependency requires a higher MSRV without a semver breaking update, this may be raised. 176 | 177 | ## Motivation 178 | 179 | Error handling in proc-macros sucks. There's not much of a choice today: 180 | you either "bubble up" the error up to the top-level of the macro and convert it to 181 | a [`compile_error!`][compl_err] invocation or just use a good old panic. Both these ways suck: 182 | 183 | - Former sucks because it's quite redundant to unroll a proper error handling 184 | just for critical errors that will crash the macro anyway; so people mostly 185 | choose not to bother with it at all and use panic. Simple `.expect` is too tempting. 186 | 187 | Also, if you do decide to implement this `Result`-based architecture in your macro 188 | you're going to have to rewrite it entirely once [`proc_macro::Diagnostic`][] is finally 189 | stable. Not cool. 190 | 191 | - Later sucks because there's no way to carry out the span info via `panic!`. 192 | `rustc` will highlight the invocation itself but not some specific token inside it. 193 | 194 | Furthermore, panics aren't for error-reporting at all; panics are for bug-detecting 195 | (like unwrapping on `None` or out-of-range indexing) or for early development stages 196 | when you need a prototype ASAP so error handling can wait. Mixing these usages only 197 | messes things up. 198 | 199 | - There is [`proc_macro::Diagnostic`][] which is awesome but it has been experimental 200 | for more than a year and is unlikely to be stabilized any time soon. 201 | 202 | This crate's API is intentionally designed to be compatible with `proc_macro::Diagnostic` 203 | and delegates to it whenever possible. Once `Diagnostics` is stable this crate 204 | will **always** delegate to it, no code changes will be required on user side. 205 | 206 | That said, we need a solution, but this solution must meet these conditions: 207 | 208 | - It must be better than `panic!`. The main point: it must offer a way to carry the span information 209 | over to user. 210 | - It must take as little effort as possible to migrate from `panic!`. Ideally, a new 211 | macro with similar semantics plus ability to carry out span info. 212 | - It must maintain compatibility with [`proc_macro::Diagnostic`][] . 213 | - **It must be usable on stable**. 214 | 215 | This crate aims to provide such a mechanism. All you have to do is annotate your top-level 216 | `#[proc_macro]` function with `#[proc_macro_error]` attribute and change panics to 217 | [`abort!`]/[`abort_call_site!`] where appropriate, see [the Guide][guide]. 218 | 219 | ## Disclaimer 220 | Please note that **this crate is not intended to be used in any way other 221 | than error reporting in procedural macros**, use `Result` and `?` (possibly along with one of the 222 | many helpers out there) for anything else. 223 | 224 |
225 | 226 | #### License 227 | 228 | 229 | Licensed under either of Apache License, Version 230 | 2.0 or MIT license at your option. 231 | 232 | 233 |
234 | 235 | 236 | Unless you explicitly state otherwise, any contribution intentionally submitted 237 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 238 | be dual licensed as above, without any additional terms or conditions. 239 | 240 | 241 | 242 | [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html 243 | [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html 244 | 245 | [crate::dummy]: https://docs.rs/proc-macro-error2/1/proc_macro_error/dummy/index.html 246 | [crate::multi]: https://docs.rs/proc-macro-error2/1/proc_macro_error/multi/index.html 247 | 248 | [`abort_call_site!`]: https://docs.rs/proc-macro-error2/1/proc_macro_error/macro.abort_call_site.html 249 | [`abort!`]: https://docs.rs/proc-macro-error2/1/proc_macro_error/macro.abort.html 250 | [guide]: https://docs.rs/proc-macro-error2 251 | -------------------------------------------------------------------------------- /proc-macro-error-attr/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .fuse_hidden* 5 | -------------------------------------------------------------------------------- /proc-macro-error-attr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-error-attr2" 3 | version = "2.0.0" 4 | authors = [ 5 | "CreepySkeleton ", 6 | "GnomedDev ", 7 | ] 8 | edition = "2021" 9 | rust-version = "1.61" 10 | description = "Attribute macro for the proc-macro-error2 crate" 11 | license = "MIT OR Apache-2.0" 12 | repository = "https://github.com/GnomedDev/proc-macro-error-2" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | quote = "1" 19 | proc-macro2 = "1" 20 | 21 | [lints.clippy] 22 | pedantic = { level = "warn", priority = -1 } 23 | -------------------------------------------------------------------------------- /proc-macro-error-attr/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 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 2019-2020 CreepySkeleton 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 | http://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 | -------------------------------------------------------------------------------- /proc-macro-error-attr/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 CreepySkeleton 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 | -------------------------------------------------------------------------------- /proc-macro-error-attr/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is `#[proc_macro_error]` attribute to be used with 2 | //! [`proc-macro-error`](https://docs.rs/proc-macro-error2/). There you go. 3 | 4 | use crate::parse::parse_input; 5 | use crate::parse::Attribute; 6 | use proc_macro::TokenStream; 7 | use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree}; 8 | use quote::{quote, quote_spanned}; 9 | 10 | use crate::settings::{ 11 | parse_settings, 12 | Setting::{AllowNotMacro, AssertUnwindSafe, ProcMacroHack}, 13 | Settings, 14 | }; 15 | 16 | mod parse; 17 | mod settings; 18 | 19 | type Result = std::result::Result; 20 | 21 | struct Error { 22 | span: Span, 23 | message: String, 24 | } 25 | 26 | impl Error { 27 | fn new(span: Span, message: String) -> Self { 28 | Error { span, message } 29 | } 30 | 31 | fn into_compile_error(self) -> TokenStream2 { 32 | let mut message = Literal::string(&self.message); 33 | message.set_span(self.span); 34 | quote_spanned!(self.span=> compile_error!{#message}) 35 | } 36 | } 37 | 38 | #[proc_macro_attribute] 39 | pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream { 40 | match impl_proc_macro_error(attr.into(), input.clone().into()) { 41 | Ok(ts) => ts, 42 | Err(e) => { 43 | let error = e.into_compile_error(); 44 | let input = TokenStream2::from(input); 45 | 46 | quote!(#input #error).into() 47 | } 48 | } 49 | } 50 | 51 | fn impl_proc_macro_error(attr: TokenStream2, input: TokenStream2) -> Result { 52 | let (attrs, signature, body) = parse_input(input)?; 53 | let mut settings = parse_settings(attr)?; 54 | 55 | let is_proc_macro = is_proc_macro(&attrs); 56 | if is_proc_macro { 57 | settings.set(AssertUnwindSafe); 58 | } 59 | 60 | if detect_proc_macro_hack(&attrs) { 61 | settings.set(ProcMacroHack); 62 | } 63 | 64 | if settings.is_set(ProcMacroHack) { 65 | settings.set(AllowNotMacro); 66 | } 67 | 68 | if !(settings.is_set(AllowNotMacro) || is_proc_macro) { 69 | return Err(Error::new( 70 | Span::call_site(), 71 | "#[proc_macro_error] attribute can be used only with procedural macros\n\n \ 72 | = hint: if you are really sure that #[proc_macro_error] should be applied \ 73 | to this exact function, use #[proc_macro_error(allow_not_macro)]\n" 74 | .into(), 75 | )); 76 | } 77 | 78 | let body = gen_body(&body, &settings); 79 | 80 | let res = quote! { 81 | #(#attrs)* 82 | #(#signature)* 83 | { #body } 84 | }; 85 | Ok(res.into()) 86 | } 87 | 88 | fn gen_body(block: &TokenTree, settings: &Settings) -> proc_macro2::TokenStream { 89 | let is_proc_macro_hack = settings.is_set(ProcMacroHack); 90 | let closure = if settings.is_set(AssertUnwindSafe) { 91 | quote!(::std::panic::AssertUnwindSafe(|| #block )) 92 | } else { 93 | quote!(|| #block) 94 | }; 95 | 96 | quote!( ::proc_macro_error2::entry_point(#closure, #is_proc_macro_hack) ) 97 | } 98 | 99 | fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool { 100 | attrs 101 | .iter() 102 | .any(|attr| attr.path_is_ident("proc_macro_hack")) 103 | } 104 | 105 | fn is_proc_macro(attrs: &[Attribute]) -> bool { 106 | attrs.iter().any(|attr| { 107 | attr.path_is_ident("proc_macro") 108 | || attr.path_is_ident("proc_macro_derive") 109 | || attr.path_is_ident("proc_macro_attribute") 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /proc-macro-error-attr/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree}; 3 | use quote::ToTokens; 4 | use std::iter::Peekable; 5 | 6 | pub(crate) fn parse_input( 7 | input: TokenStream, 8 | ) -> Result<(Vec, Vec, TokenTree)> { 9 | let mut input = input.into_iter().peekable(); 10 | let mut attrs = Vec::new(); 11 | 12 | while let Some(attr) = parse_next_attr(&mut input)? { 13 | attrs.push(attr); 14 | } 15 | 16 | let sig = parse_signature(&mut input); 17 | let body = input.next().ok_or_else(|| { 18 | Error::new( 19 | Span::call_site(), 20 | "`#[proc_macro_error]` can be applied only to functions".to_string(), 21 | ) 22 | })?; 23 | 24 | Ok((attrs, sig, body)) 25 | } 26 | 27 | fn parse_next_attr( 28 | input: &mut Peekable>, 29 | ) -> Result> { 30 | let shebang = match input.peek() { 31 | Some(TokenTree::Punct(ref punct)) if punct.as_char() == '#' => input.next().unwrap(), 32 | _ => return Ok(None), 33 | }; 34 | 35 | let group = match input.peek() { 36 | Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Bracket => { 37 | let res = group.clone(); 38 | input.next(); 39 | res 40 | } 41 | other => { 42 | let span = other.map_or(Span::call_site(), TokenTree::span); 43 | return Err(Error::new(span, "expected `[`".to_string())); 44 | } 45 | }; 46 | 47 | let path = match group.stream().into_iter().next() { 48 | Some(TokenTree::Ident(ident)) => Some(ident), 49 | _ => None, 50 | }; 51 | 52 | Ok(Some(Attribute { 53 | shebang, 54 | group: TokenTree::Group(group), 55 | path, 56 | })) 57 | } 58 | 59 | fn parse_signature(input: &mut Peekable>) -> Vec { 60 | let mut sig = Vec::new(); 61 | loop { 62 | match input.peek() { 63 | Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Brace => { 64 | return sig; 65 | } 66 | None => return sig, 67 | _ => sig.push(input.next().unwrap()), 68 | } 69 | } 70 | } 71 | 72 | pub(crate) struct Attribute { 73 | pub(crate) shebang: TokenTree, 74 | pub(crate) group: TokenTree, 75 | pub(crate) path: Option, 76 | } 77 | 78 | impl Attribute { 79 | pub(crate) fn path_is_ident(&self, ident: &str) -> bool { 80 | self.path.as_ref().map_or(false, |p| *p == ident) 81 | } 82 | } 83 | 84 | impl ToTokens for Attribute { 85 | fn to_tokens(&self, ts: &mut TokenStream) { 86 | self.shebang.to_tokens(ts); 87 | self.group.to_tokens(ts); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /proc-macro-error-attr/src/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 3 | 4 | macro_rules! decl_settings { 5 | ($($val:expr => $variant:ident),+ $(,)*) => { 6 | #[derive(PartialEq, Clone, Copy)] 7 | pub(crate) enum Setting { 8 | $($variant),* 9 | } 10 | 11 | fn ident_to_setting(ident: Ident) -> Result { 12 | match &*ident.to_string() { 13 | $($val => Ok(Setting::$variant),)* 14 | _ => { 15 | let possible_vals = [$($val),*] 16 | .iter() 17 | .map(|v| format!("`{}`", v)) 18 | .collect::>() 19 | .join(", "); 20 | 21 | Err(Error::new( 22 | ident.span(), 23 | format!("unknown setting `{}`, expected one of {}", ident, possible_vals))) 24 | } 25 | } 26 | } 27 | }; 28 | } 29 | 30 | decl_settings! { 31 | "assert_unwind_safe" => AssertUnwindSafe, 32 | "allow_not_macro" => AllowNotMacro, 33 | "proc_macro_hack" => ProcMacroHack, 34 | } 35 | 36 | pub(crate) fn parse_settings(input: TokenStream) -> Result { 37 | let mut input = input.into_iter(); 38 | let mut res = Settings(Vec::new()); 39 | loop { 40 | match input.next() { 41 | Some(TokenTree::Ident(ident)) => { 42 | res.0.push(ident_to_setting(ident)?); 43 | } 44 | None => return Ok(res), 45 | other => { 46 | let span = other.map_or(Span::call_site(), |tt| tt.span()); 47 | return Err(Error::new(span, "expected identifier".to_string())); 48 | } 49 | } 50 | 51 | match input.next() { 52 | Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => {} 53 | None => return Ok(res), 54 | other => { 55 | let span = other.map_or(Span::call_site(), |tt| tt.span()); 56 | return Err(Error::new(span, "expected `,`".to_string())); 57 | } 58 | } 59 | } 60 | } 61 | 62 | pub(crate) struct Settings(Vec); 63 | 64 | impl Settings { 65 | pub(crate) fn is_set(&self, setting: Setting) -> bool { 66 | self.0.iter().any(|s| *s == setting) 67 | } 68 | 69 | pub(crate) fn set(&mut self, setting: Setting) { 70 | self.0.push(setting); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | use crate::{abort_now, check_correctness, sealed::Sealed, SpanRange}; 2 | use proc_macro2::Span; 3 | use proc_macro2::TokenStream; 4 | 5 | use quote::{quote_spanned, ToTokens}; 6 | 7 | /// Represents a diagnostic level 8 | /// 9 | /// # Warnings 10 | /// 11 | /// Warnings are ignored on stable/beta 12 | #[derive(Debug, PartialEq)] 13 | #[non_exhaustive] 14 | pub enum Level { 15 | Error, 16 | Warning, 17 | } 18 | 19 | /// Represents a single diagnostic message 20 | #[derive(Debug)] 21 | #[must_use = "A diagnostic does nothing unless emitted"] 22 | pub struct Diagnostic { 23 | pub(crate) level: Level, 24 | pub(crate) span_range: SpanRange, 25 | pub(crate) msg: String, 26 | pub(crate) suggestions: Vec<(SuggestionKind, String, Option)>, 27 | pub(crate) children: Vec<(SpanRange, String)>, 28 | } 29 | 30 | /// A collection of methods that do not exist in `proc_macro::Diagnostic` 31 | /// but still useful to have around. 32 | /// 33 | /// This trait is sealed and cannot be implemented outside of `proc_macro_error`. 34 | pub trait DiagnosticExt: Sealed { 35 | /// Create a new diagnostic message that points to the `span_range`. 36 | /// 37 | /// This function is the same as `Diagnostic::spanned` but produces considerably 38 | /// better error messages for multi-token spans on stable. 39 | fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self; 40 | 41 | /// Add another error message to self such that it will be emitted right after 42 | /// the main message. 43 | /// 44 | /// This function is the same as `Diagnostic::span_error` but produces considerably 45 | /// better error messages for multi-token spans on stable. 46 | #[must_use] 47 | fn span_range_error(self, span_range: SpanRange, msg: String) -> Self; 48 | 49 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. 50 | /// 51 | /// This function is the same as `Diagnostic::span_help` but produces considerably 52 | /// better error messages for multi-token spans on stable. 53 | /// 54 | /// # Span 55 | /// 56 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span 57 | #[must_use] 58 | fn span_range_help(self, span_range: SpanRange, msg: String) -> Self; 59 | 60 | /// Attach a note to your main message, the note will have it's own span on nightly. 61 | /// 62 | /// This function is the same as `Diagnostic::span_note` but produces considerably 63 | /// better error messages for multi-token spans on stable. 64 | /// 65 | /// # Span 66 | /// 67 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span 68 | #[must_use] 69 | fn span_range_note(self, span_range: SpanRange, msg: String) -> Self; 70 | } 71 | 72 | impl DiagnosticExt for Diagnostic { 73 | fn spanned_range(span_range: SpanRange, level: Level, message: String) -> Self { 74 | Diagnostic { 75 | level, 76 | span_range, 77 | msg: message, 78 | suggestions: vec![], 79 | children: vec![], 80 | } 81 | } 82 | 83 | fn span_range_error(mut self, span_range: SpanRange, msg: String) -> Self { 84 | self.children.push((span_range, msg)); 85 | self 86 | } 87 | 88 | fn span_range_help(mut self, span_range: SpanRange, msg: String) -> Self { 89 | self.suggestions 90 | .push((SuggestionKind::Help, msg, Some(span_range))); 91 | self 92 | } 93 | 94 | fn span_range_note(mut self, span_range: SpanRange, msg: String) -> Self { 95 | self.suggestions 96 | .push((SuggestionKind::Note, msg, Some(span_range))); 97 | self 98 | } 99 | } 100 | 101 | impl Diagnostic { 102 | /// Create a new diagnostic message that points to `Span::call_site()` 103 | pub fn new(level: Level, message: String) -> Self { 104 | Diagnostic::spanned(Span::call_site(), level, message) 105 | } 106 | 107 | /// Create a new diagnostic message that points to the `span` 108 | pub fn spanned(span: Span, level: Level, message: String) -> Self { 109 | Diagnostic::spanned_range( 110 | SpanRange { 111 | first: span, 112 | last: span, 113 | }, 114 | level, 115 | message, 116 | ) 117 | } 118 | 119 | /// Add another error message to self such that it will be emitted right after 120 | /// the main message. 121 | pub fn span_error(self, span: Span, msg: String) -> Self { 122 | self.span_range_error( 123 | SpanRange { 124 | first: span, 125 | last: span, 126 | }, 127 | msg, 128 | ) 129 | } 130 | 131 | /// Attach a "help" note to your main message, the note will have it's own span on nightly. 132 | /// 133 | /// # Span 134 | /// 135 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span 136 | pub fn span_help(self, span: Span, msg: String) -> Self { 137 | self.span_range_help( 138 | SpanRange { 139 | first: span, 140 | last: span, 141 | }, 142 | msg, 143 | ) 144 | } 145 | 146 | /// Attach a "help" note to your main message. 147 | pub fn help(mut self, msg: String) -> Self { 148 | self.suggestions.push((SuggestionKind::Help, msg, None)); 149 | self 150 | } 151 | 152 | /// Attach a note to your main message, the note will have it's own span on nightly. 153 | /// 154 | /// # Span 155 | /// 156 | /// The span is ignored on stable, the note effectively inherits its parent's (main message) span 157 | pub fn span_note(self, span: Span, msg: String) -> Self { 158 | self.span_range_note( 159 | SpanRange { 160 | first: span, 161 | last: span, 162 | }, 163 | msg, 164 | ) 165 | } 166 | 167 | /// Attach a note to your main message 168 | pub fn note(mut self, msg: String) -> Self { 169 | self.suggestions.push((SuggestionKind::Note, msg, None)); 170 | self 171 | } 172 | 173 | /// The message of main warning/error (no notes attached) 174 | #[must_use] 175 | pub fn message(&self) -> &str { 176 | &self.msg 177 | } 178 | 179 | /// Abort the proc-macro's execution and display the diagnostic. 180 | /// 181 | /// # Warnings 182 | /// 183 | /// Warnings are not emitted on stable and beta, but this function will abort anyway. 184 | pub fn abort(self) -> ! { 185 | self.emit(); 186 | abort_now() 187 | } 188 | 189 | /// Display the diagnostic while not aborting macro execution. 190 | /// 191 | /// # Warnings 192 | /// 193 | /// Warnings are ignored on stable/beta 194 | pub fn emit(self) { 195 | check_correctness(); 196 | crate::imp::emit_diagnostic(self); 197 | } 198 | } 199 | 200 | /// **NOT PUBLIC API! NOTHING TO SEE HERE!!!** 201 | #[doc(hidden)] 202 | impl Diagnostic { 203 | pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { 204 | match suggestion { 205 | "help" | "hint" => self.span_help(span, msg), 206 | _ => self.span_note(span, msg), 207 | } 208 | } 209 | 210 | pub fn suggestion(self, suggestion: &str, msg: String) -> Self { 211 | match suggestion { 212 | "help" | "hint" => self.help(msg), 213 | _ => self.note(msg), 214 | } 215 | } 216 | } 217 | 218 | impl ToTokens for Diagnostic { 219 | fn to_tokens(&self, ts: &mut TokenStream) { 220 | use std::borrow::Cow; 221 | 222 | fn ensure_lf(buf: &mut String, s: &str) { 223 | if s.ends_with('\n') { 224 | buf.push_str(s); 225 | } else { 226 | buf.push_str(s); 227 | buf.push('\n'); 228 | } 229 | } 230 | 231 | fn diag_to_tokens( 232 | span_range: SpanRange, 233 | level: &Level, 234 | msg: &str, 235 | suggestions: &[(SuggestionKind, String, Option)], 236 | ) -> TokenStream { 237 | if *level == Level::Warning { 238 | return TokenStream::new(); 239 | } 240 | 241 | let message = if suggestions.is_empty() { 242 | Cow::Borrowed(msg) 243 | } else { 244 | let mut message = String::new(); 245 | ensure_lf(&mut message, msg); 246 | message.push('\n'); 247 | 248 | for (kind, note, _span) in suggestions { 249 | message.push_str(" = "); 250 | message.push_str(kind.name()); 251 | message.push_str(": "); 252 | ensure_lf(&mut message, note); 253 | } 254 | message.push('\n'); 255 | 256 | Cow::Owned(message) 257 | }; 258 | 259 | let mut msg = proc_macro2::Literal::string(&message); 260 | msg.set_span(span_range.last); 261 | let group = quote_spanned!(span_range.last=> { #msg } ); 262 | quote_spanned!(span_range.first=> compile_error!#group) 263 | } 264 | 265 | ts.extend(diag_to_tokens( 266 | self.span_range, 267 | &self.level, 268 | &self.msg, 269 | &self.suggestions, 270 | )); 271 | ts.extend( 272 | self.children 273 | .iter() 274 | .map(|(span_range, msg)| diag_to_tokens(*span_range, &Level::Error, msg, &[])), 275 | ); 276 | } 277 | } 278 | 279 | #[derive(Debug)] 280 | pub(crate) enum SuggestionKind { 281 | Help, 282 | Note, 283 | } 284 | 285 | impl SuggestionKind { 286 | fn name(&self) -> &'static str { 287 | match self { 288 | SuggestionKind::Note => "note", 289 | SuggestionKind::Help => "help", 290 | } 291 | } 292 | } 293 | 294 | #[cfg(feature = "syn-error")] 295 | impl From for Diagnostic { 296 | fn from(err: syn::Error) -> Self { 297 | use proc_macro2::{Delimiter, TokenTree}; 298 | 299 | fn gut_error(ts: &mut impl Iterator) -> Option<(SpanRange, String)> { 300 | let start_span = ts.next()?.span(); 301 | ts.next().expect(":1"); 302 | ts.next().expect("core"); 303 | ts.next().expect(":2"); 304 | ts.next().expect(":3"); 305 | ts.next().expect("compile_error"); 306 | ts.next().expect("!"); 307 | 308 | let lit = match ts.next().unwrap() { 309 | TokenTree::Group(group) => { 310 | // Currently `syn` builds `compile_error!` invocations 311 | // exclusively in `ident{"..."}` (braced) form which is not 312 | // followed by `;` (semicolon). 313 | // 314 | // But if it changes to `ident("...");` (parenthesized) 315 | // or `ident["..."];` (bracketed) form, 316 | // we will need to skip the `;` as well. 317 | // Highly unlikely, but better safe than sorry. 318 | 319 | if group.delimiter() == Delimiter::Parenthesis 320 | || group.delimiter() == Delimiter::Bracket 321 | { 322 | ts.next().unwrap(); // ; 323 | } 324 | 325 | match group.stream().into_iter().next().unwrap() { 326 | TokenTree::Literal(lit) => lit, 327 | _ => unreachable!(""), 328 | } 329 | } 330 | _ => unreachable!(""), 331 | }; 332 | 333 | let last = lit.span(); 334 | let mut msg = lit.to_string(); 335 | 336 | // "abc" => abc 337 | msg.pop(); 338 | msg.remove(0); 339 | 340 | Some(( 341 | SpanRange { 342 | first: start_span, 343 | last, 344 | }, 345 | msg, 346 | )) 347 | } 348 | 349 | let mut ts = err.to_compile_error().into_iter(); 350 | 351 | let (span_range, msg) = gut_error(&mut ts).unwrap(); 352 | let mut res = Diagnostic::spanned_range(span_range, Level::Error, msg); 353 | 354 | while let Some((span_range, msg)) = gut_error(&mut ts) { 355 | res = res.span_range_error(span_range, msg); 356 | } 357 | 358 | res 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/dummy.rs: -------------------------------------------------------------------------------- 1 | //! Facility to emit dummy implementations (or whatever) in case 2 | //! an error happen. 3 | //! 4 | //! `compile_error!` does not abort a compilation right away. This means 5 | //! `rustc` doesn't just show you the error and abort, it carries on the 6 | //! compilation process looking for other errors to report. 7 | //! 8 | //! Let's consider an example: 9 | //! 10 | //! ```rust,ignore 11 | //! use proc_macro::TokenStream; 12 | //! use proc_macro_error2::*; 13 | //! 14 | //! trait MyTrait { 15 | //! fn do_thing(); 16 | //! } 17 | //! 18 | //! // this proc macro is supposed to generate MyTrait impl 19 | //! #[proc_macro_derive(MyTrait)] 20 | //! #[proc_macro_error] 21 | //! fn example(input: TokenStream) -> TokenStream { 22 | //! // somewhere deep inside 23 | //! abort!(span, "something's wrong"); 24 | //! 25 | //! // this implementation will be generated if no error happened 26 | //! quote! { 27 | //! impl MyTrait for #name { 28 | //! fn do_thing() {/* whatever */} 29 | //! } 30 | //! } 31 | //! } 32 | //! 33 | //! // ================ 34 | //! // in main.rs 35 | //! 36 | //! // this derive triggers an error 37 | //! #[derive(MyTrait)] // first BOOM! 38 | //! struct Foo; 39 | //! 40 | //! fn main() { 41 | //! Foo::do_thing(); // second BOOM! 42 | //! } 43 | //! ``` 44 | //! 45 | //! The problem is: the generated token stream contains only `compile_error!` 46 | //! invocation, the impl was not generated. That means user will see two compilation 47 | //! errors: 48 | //! 49 | //! ```text 50 | //! error: something's wrong 51 | //! --> $DIR/probe.rs:9:10 52 | //! | 53 | //! 9 |#[proc_macro_derive(MyTrait)] 54 | //! | ^^^^^^^ 55 | //! 56 | //! error[E0599]: no function or associated item named `do_thing` found for type `Foo` in the current scope 57 | //! --> src\main.rs:3:10 58 | //! | 59 | //! 1 | struct Foo; 60 | //! | ----------- function or associated item `do_thing` not found for this 61 | //! 2 | fn main() { 62 | //! 3 | Foo::do_thing(); // second BOOM! 63 | //! | ^^^^^^^^ function or associated item not found in `Foo` 64 | //! ``` 65 | //! 66 | //! But the second error is meaningless! We definitely need to fix this. 67 | //! 68 | //! Most used approach in cases like this is "dummy implementation" - 69 | //! omit `impl MyTrait for #name` and fill functions bodies with `unimplemented!()`. 70 | //! 71 | //! This is how you do it: 72 | //! 73 | //! ```rust,ignore 74 | //! use proc_macro::TokenStream; 75 | //! use proc_macro_error2::*; 76 | //! 77 | //! trait MyTrait { 78 | //! fn do_thing(); 79 | //! } 80 | //! 81 | //! // this proc macro is supposed to generate MyTrait impl 82 | //! #[proc_macro_derive(MyTrait)] 83 | //! #[proc_macro_error] 84 | //! fn example(input: TokenStream) -> TokenStream { 85 | //! // first of all - we set a dummy impl which will be appended to 86 | //! // `compile_error!` invocations in case a trigger does happen 87 | //! set_dummy(quote! { 88 | //! impl MyTrait for #name { 89 | //! fn do_thing() { unimplemented!() } 90 | //! } 91 | //! }); 92 | //! 93 | //! // somewhere deep inside 94 | //! abort!(span, "something's wrong"); 95 | //! 96 | //! // this implementation will be generated if no error happened 97 | //! quote! { 98 | //! impl MyTrait for #name { 99 | //! fn do_thing() {/* whatever */} 100 | //! } 101 | //! } 102 | //! } 103 | //! 104 | //! // ================ 105 | //! // in main.rs 106 | //! 107 | //! // this derive triggers an error 108 | //! #[derive(MyTrait)] // first BOOM! 109 | //! struct Foo; 110 | //! 111 | //! fn main() { 112 | //! Foo::do_thing(); // no more errors! 113 | //! } 114 | //! ``` 115 | 116 | use proc_macro2::TokenStream; 117 | use std::cell::RefCell; 118 | 119 | use crate::check_correctness; 120 | 121 | thread_local! { 122 | static DUMMY_IMPL: RefCell> = const { RefCell::new(None) }; 123 | } 124 | 125 | /// Sets dummy token stream which will be appended to `compile_error!(msg);...` 126 | /// invocations in case you'll emit any errors. 127 | /// 128 | /// See [guide](../index.html#guide). 129 | #[allow(clippy::must_use_candidate)] // Mutates thread local state 130 | pub fn set_dummy(dummy: TokenStream) -> Option { 131 | check_correctness(); 132 | DUMMY_IMPL.with(|old_dummy| old_dummy.replace(Some(dummy))) 133 | } 134 | 135 | /// Same as [`set_dummy`] but, instead of resetting, appends tokens to the 136 | /// existing dummy (if any). Behaves as `set_dummy` if no dummy is present. 137 | pub fn append_dummy(dummy: TokenStream) { 138 | check_correctness(); 139 | DUMMY_IMPL.with(|old_dummy| { 140 | let mut cell = old_dummy.borrow_mut(); 141 | if let Some(ts) = cell.as_mut() { 142 | ts.extend(dummy); 143 | } else { 144 | *cell = Some(dummy); 145 | } 146 | }); 147 | } 148 | 149 | pub(crate) fn cleanup() -> Option { 150 | DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None)) 151 | } 152 | -------------------------------------------------------------------------------- /src/imp/delegate.rs: -------------------------------------------------------------------------------- 1 | //! This implementation uses [`proc_macro::Diagnostic`], nightly only. 2 | 3 | use std::cell::Cell; 4 | 5 | use proc_macro::{Diagnostic as PDiag, Level as PLevel}; 6 | 7 | use crate::{ 8 | abort_now, check_correctness, 9 | diagnostic::{Diagnostic, Level, SuggestionKind}, 10 | }; 11 | 12 | pub fn abort_if_dirty() { 13 | check_correctness(); 14 | if IS_DIRTY.with(|c| c.get()) { 15 | abort_now() 16 | } 17 | } 18 | 19 | pub(crate) fn cleanup() -> Vec { 20 | IS_DIRTY.with(|c| c.set(false)); 21 | vec![] 22 | } 23 | 24 | pub(crate) fn emit_diagnostic(diag: Diagnostic) { 25 | let Diagnostic { 26 | level, 27 | span_range, 28 | msg, 29 | suggestions, 30 | children, 31 | } = diag; 32 | 33 | let span = span_range.collapse().unwrap(); 34 | 35 | let level = match level { 36 | Level::Warning => PLevel::Warning, 37 | Level::Error => { 38 | IS_DIRTY.with(|c| c.set(true)); 39 | PLevel::Error 40 | } 41 | }; 42 | 43 | let mut res = PDiag::spanned(span, level, msg); 44 | 45 | for (kind, msg, span) in suggestions { 46 | res = match (kind, span) { 47 | (SuggestionKind::Note, Some(span_range)) => { 48 | res.span_note(span_range.collapse().unwrap(), msg) 49 | } 50 | (SuggestionKind::Help, Some(span_range)) => { 51 | res.span_help(span_range.collapse().unwrap(), msg) 52 | } 53 | (SuggestionKind::Note, None) => res.note(msg), 54 | (SuggestionKind::Help, None) => res.help(msg), 55 | } 56 | } 57 | 58 | for (span_range, msg) in children { 59 | let span = span_range.collapse().unwrap(); 60 | res = res.span_error(span, msg); 61 | } 62 | 63 | res.emit() 64 | } 65 | 66 | thread_local! { 67 | static IS_DIRTY: Cell = Cell::new(false); 68 | } 69 | -------------------------------------------------------------------------------- /src/imp/fallback.rs: -------------------------------------------------------------------------------- 1 | //! This implementation uses self-written stable facilities. 2 | 3 | use crate::{ 4 | abort_now, check_correctness, 5 | diagnostic::{Diagnostic, Level}, 6 | }; 7 | use std::cell::RefCell; 8 | 9 | pub fn abort_if_dirty() { 10 | check_correctness(); 11 | ERR_STORAGE.with(|storage| { 12 | if !storage.borrow().is_empty() { 13 | abort_now() 14 | } 15 | }); 16 | } 17 | 18 | pub(crate) fn cleanup() -> Vec { 19 | ERR_STORAGE.with(|storage| storage.replace(Vec::new())) 20 | } 21 | 22 | pub(crate) fn emit_diagnostic(diag: Diagnostic) { 23 | if diag.level == Level::Error { 24 | ERR_STORAGE.with(|storage| storage.borrow_mut().push(diag)); 25 | } 26 | } 27 | 28 | thread_local! { 29 | static ERR_STORAGE: RefCell> = const { RefCell::new(Vec::new()) }; 30 | } 31 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # proc-macro-error2 2 | //! 3 | //! This crate aims to make error reporting in proc-macros simple and easy to use. 4 | //! Migrate from `panic!`-based errors for as little effort as possible! 5 | //! 6 | //! (Also, you can explicitly [append a dummy token stream](dummy/index.html) to your errors). 7 | //! 8 | //! To achieve his, this crate serves as a tiny shim around `proc_macro::Diagnostic` and 9 | //! `compile_error!`. It detects the best way of emitting available based on compiler's version. 10 | //! When the underlying diagnostic type is finally stabilized, this crate will simply be 11 | //! delegating to it requiring no changes in your code! 12 | //! 13 | //! So you can just use this crate and have *both* some of `proc_macro::Diagnostic` functionality 14 | //! available on stable ahead of time *and* your error-reporting code future-proof. 15 | //! 16 | //! ## Cargo features 17 | //! 18 | //! This crate provides *enabled by default* `syn-error` feature that gates 19 | //! `impl From for Diagnostic` conversion. If you don't use `syn` and want 20 | //! to cut off some of compilation time, you can disable it via 21 | //! 22 | //! ```toml 23 | //! [dependencies] 24 | //! proc-macro-error2 = { version = "2.0.0", default-features = false } 25 | //! ``` 26 | //! 27 | //! ***Please note that disabling this feature makes sense only if you don't depend on `syn` 28 | //! directly or indirectly, and you very likely do.** 29 | //! 30 | //! ## Real world examples 31 | //! 32 | //! * [`structopt-derive`](https://github.com/TeXitoi/structopt/tree/master/structopt-derive) 33 | //! (abort-like usage) 34 | //! * [`auto-impl`](https://github.com/auto-impl-rs/auto_impl/) (emit-like usage) 35 | //! 36 | //! ## Limitations 37 | //! 38 | //! - Warnings are emitted only on nightly, they are ignored on stable. 39 | //! - "help" suggestions can't have their own span info on stable, 40 | //! (essentially inheriting the parent span). 41 | //! - If a panic occurs somewhere in your macro no errors will be displayed. This is not a 42 | //! technical limitation but rather intentional design. `panic` is not for error reporting. 43 | //! 44 | //! ### `#[proc_macro_error]` attribute 45 | //! 46 | //! **This attribute MUST be present on the top level of your macro** (the function 47 | //! annotated with any of `#[proc_macro]`, `#[proc_macro_derive]`, `#[proc_macro_attribute]`). 48 | //! 49 | //! This attribute performs the setup and cleanup necessary to make things work. 50 | //! 51 | //! In most cases you'll need the simple `#[proc_macro_error]` form without any 52 | //! additional settings. Feel free to [skip the "Syntax" section](#macros). 53 | //! 54 | //! #### Syntax 55 | //! 56 | //! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...` 57 | //! is a comma-separated list of: 58 | //! 59 | //! - `proc_macro_hack`: 60 | //! 61 | //! In order to correctly cooperate with `#[proc_macro_hack]`, `#[proc_macro_error]` 62 | //! attribute must be placed *before* (above) it, like this: 63 | //! 64 | //! ```no_run 65 | //! # use proc_macro2::TokenStream; 66 | //! # const IGNORE: &str = " 67 | //! #[proc_macro_error] 68 | //! #[proc_macro_hack] 69 | //! #[proc_macro] 70 | //! # "; 71 | //! fn my_macro(input: TokenStream) -> TokenStream { 72 | //! unimplemented!() 73 | //! } 74 | //! ``` 75 | //! 76 | //! If, for some reason, you can't place it like that you can use 77 | //! `#[proc_macro_error(proc_macro_hack)]` instead. 78 | //! 79 | //! # Note 80 | //! 81 | //! If `proc-macro-hack` was detected (by any means) `allow_not_macro` 82 | //! and `assert_unwind_safe` will be applied automatically. 83 | //! 84 | //! - `allow_not_macro`: 85 | //! 86 | //! By default, the attribute checks that it's applied to a proc-macro. 87 | //! If none of `#[proc_macro]`, `#[proc_macro_derive]` nor `#[proc_macro_attribute]` are 88 | //! present it will panic. It's the intention - this crate is supposed to be used only with 89 | //! proc-macros. 90 | //! 91 | //! This setting is made to bypass the check, useful in certain circumstances. 92 | //! 93 | //! Pay attention: the function this attribute is applied to must return 94 | //! `proc_macro::TokenStream`. 95 | //! 96 | //! This setting is implied if `proc-macro-hack` was detected. 97 | //! 98 | //! - `assert_unwind_safe`: 99 | //! 100 | //! By default, your code must be [unwind safe]. If your code is not unwind safe, 101 | //! but you believe it's correct, you can use this setting to bypass the check. 102 | //! You would need this for code that uses `lazy_static` or `thread_local` with 103 | //! `Cell/RefCell` inside (and the like). 104 | //! 105 | //! This setting is implied if `#[proc_macro_error]` is applied to a function 106 | //! marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`. 107 | //! 108 | //! This setting is also implied if `proc-macro-hack` was detected. 109 | //! 110 | //! ## Macros 111 | //! 112 | //! Most of the time you want to use the macros. Syntax is described in the next section below. 113 | //! 114 | //! You'll need to decide how you want to emit errors: 115 | //! 116 | //! * Emit the error and abort. Very much panic-like usage. Served by [`abort!`] and 117 | //! [`abort_call_site!`]. 118 | //! * Emit the error but do not abort right away, looking for other errors to report. 119 | //! Served by [`emit_error!`] and [`emit_call_site_error!`]. 120 | //! 121 | //! You **can** mix these usages. 122 | //! 123 | //! `abort` and `emit_error` take a "source span" as the first argument. This source 124 | //! will be used to highlight the place the error originates from. It must be one of: 125 | //! 126 | //! * *Something* that implements [`ToTokens`] (most types in `syn` and `proc-macro2` do). 127 | //! This source is the preferable one since it doesn't lose span information on multi-token 128 | //! spans, see [this issue](https://gitlab.com/CreepySkeleton/proc-macro-error/-/issues/6) 129 | //! for details. 130 | //! * [`proc_macro::Span`] 131 | //! * [`proc-macro2::Span`] 132 | //! 133 | //! The rest is your message in format-like style. 134 | //! 135 | //! See [the next section](#syntax-1) for detailed syntax. 136 | //! 137 | //! - [`abort!`]: 138 | //! 139 | //! Very much panic-like usage - abort right away and show the error. 140 | //! Expands to [`!`] (never type). 141 | //! 142 | //! - [`abort_call_site!`]: 143 | //! 144 | //! Shortcut for `abort!(Span::call_site(), ...)`. Expands to [`!`] (never type). 145 | //! 146 | //! - [`emit_error!`]: 147 | //! 148 | //! [`proc_macro::Diagnostic`]-like usage - emit the error but keep going, 149 | //! looking for other errors to report. 150 | //! The compilation will fail nonetheless. Expands to [`()`] (unit type). 151 | //! 152 | //! - [`emit_call_site_error!`]: 153 | //! 154 | //! Shortcut for `emit_error!(Span::call_site(), ...)`. Expands to [`()`] (unit type). 155 | //! 156 | //! - [`emit_warning!`]: 157 | //! 158 | //! Like `emit_error!` but emit a warning instead of error. The compilation won't fail 159 | //! because of warnings. 160 | //! Expands to [`()`] (unit type). 161 | //! 162 | //! **Beware**: warnings are nightly only, they are completely ignored on stable. 163 | //! 164 | //! - [`emit_call_site_warning!`]: 165 | //! 166 | //! Shortcut for `emit_warning!(Span::call_site(), ...)`. Expands to [`()`] (unit type). 167 | //! 168 | //! - [`diagnostic`]: 169 | //! 170 | //! Build an instance of `Diagnostic` in format-like style. 171 | //! 172 | //! #### Syntax 173 | //! 174 | //! All the macros have pretty much the same syntax: 175 | //! 176 | //! 1. ```ignore 177 | //! abort!(single_expr) 178 | //! ``` 179 | //! Shortcut for `Diagnostic::from(expr).abort()`. 180 | //! 181 | //! 2. ```ignore 182 | //! abort!(span, message) 183 | //! ``` 184 | //! The first argument is an expression the span info should be taken from. 185 | //! 186 | //! The second argument is the error message, it must implement [`ToString`]. 187 | //! 188 | //! 3. ```ignore 189 | //! abort!(span, format_literal, format_args...) 190 | //! ``` 191 | //! 192 | //! This form is pretty much the same as 2, except `format!(format_literal, format_args...)` 193 | //! will be used to for the message instead of [`ToString`]. 194 | //! 195 | //! That's it. `abort!`, `emit_warning`, `emit_error` share this exact syntax. 196 | //! 197 | //! `abort_call_site!`, `emit_call_site_warning`, `emit_call_site_error` lack 1 form 198 | //! and do not take span in 2'th and 3'th forms. Those are essentially shortcuts for 199 | //! `macro!(Span::call_site(), args...)`. 200 | //! 201 | //! `diagnostic!` requires a [`Level`] instance between `span` and second argument 202 | //! (1'th form is the same). 203 | //! 204 | //! > **Important!** 205 | //! > 206 | //! > If you have some type from `proc_macro` or `syn` to point to, do not call `.span()` 207 | //! > on it but rather use it directly: 208 | //! > ```no_run 209 | //! > # use proc_macro_error2::abort; 210 | //! > # let input = proc_macro2::TokenStream::new(); 211 | //! > let ty: syn::Type = syn::parse2(input).unwrap(); 212 | //! > abort!(ty, "BOOM"); 213 | //! > // ^^ <-- avoid .span() 214 | //! > ``` 215 | //! > 216 | //! > `.span()` calls work too, but you may experience regressions in message quality. 217 | //! 218 | //! #### Note attachments 219 | //! 220 | //! 3. Every macro can have "note" attachments (only 2 and 3 form). 221 | //! ```ignore 222 | //! let opt_help = if have_some_info { Some("did you mean `this`?") } else { None }; 223 | //! 224 | //! abort!( 225 | //! span, message; // <--- attachments start with `;` (semicolon) 226 | //! 227 | //! help = "format {} {}", "arg1", "arg2"; // <--- every attachment ends with `;`, 228 | //! // maybe except the last one 229 | //! 230 | //! note = "to_string"; // <--- one arg uses `.to_string()` instead of `format!()` 231 | //! 232 | //! yay = "I see what {} did here", "you"; // <--- "help =" and "hint =" are mapped 233 | //! // to Diagnostic::help, 234 | //! // anything else is Diagnostic::note 235 | //! 236 | //! wow = note_span => "custom span"; // <--- attachments can have their own span 237 | //! // it takes effect only on nightly though 238 | //! 239 | //! hint =? opt_help; // <-- "optional" attachment, get displayed only if `Some` 240 | //! // must be single `Option` expression 241 | //! 242 | //! note =? note_span => opt_help // <-- optional attachments can have custom spans too 243 | //! ); 244 | //! ``` 245 | //! 246 | 247 | //! ### Diagnostic type 248 | //! 249 | //! [`Diagnostic`] type is intentionally designed to be API compatible with [`proc_macro::Diagnostic`]. 250 | //! Not all API is implemented, only the part that can be reasonably implemented on stable. 251 | //! 252 | //! 253 | //! [`abort!`]: macro.abort.html 254 | //! [`abort_call_site!`]: macro.abort_call_site.html 255 | //! [`emit_warning!`]: macro.emit_warning.html 256 | //! [`emit_error!`]: macro.emit_error.html 257 | //! [`emit_call_site_warning!`]: macro.emit_call_site_error.html 258 | //! [`emit_call_site_error!`]: macro.emit_call_site_warning.html 259 | //! [`diagnostic!`]: macro.diagnostic.html 260 | //! [`Diagnostic`]: struct.Diagnostic.html 261 | //! 262 | //! [`proc_macro::Span`]: https://doc.rust-lang.org/proc_macro/struct.Span.html 263 | //! [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html 264 | //! 265 | //! [unwind safe]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwind-safety 266 | //! [`!`]: https://doc.rust-lang.org/std/primitive.never.html 267 | //! [`()`]: https://doc.rust-lang.org/std/primitive.unit.html 268 | //! [`ToString`]: https://doc.rust-lang.org/std/string/trait.ToString.html 269 | //! 270 | //! [`proc-macro2::Span`]: https://docs.rs/proc-macro2/1.0.10/proc_macro2/struct.Span.html 271 | //! [`ToTokens`]: https://docs.rs/quote/1.0.3/quote/trait.ToTokens.html 272 | //! 273 | 274 | #![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))] 275 | #![forbid(unsafe_code)] 276 | 277 | extern crate proc_macro; 278 | 279 | pub use crate::{ 280 | diagnostic::{Diagnostic, DiagnosticExt, Level}, 281 | dummy::{append_dummy, set_dummy}, 282 | }; 283 | pub use proc_macro_error_attr2::proc_macro_error; 284 | 285 | use proc_macro2::Span; 286 | use quote::{quote, ToTokens}; 287 | 288 | use std::cell::Cell; 289 | use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; 290 | 291 | pub mod dummy; 292 | 293 | mod diagnostic; 294 | mod macros; 295 | mod sealed; 296 | 297 | #[cfg(not(feature = "nightly"))] 298 | #[path = "imp/fallback.rs"] 299 | mod imp; 300 | 301 | #[cfg(feature = "nightly")] 302 | #[path = "imp/delegate.rs"] 303 | mod imp; 304 | 305 | #[derive(Debug, Clone, Copy)] 306 | #[must_use = "A SpanRange does nothing unless used"] 307 | pub struct SpanRange { 308 | pub first: Span, 309 | pub last: Span, 310 | } 311 | 312 | impl SpanRange { 313 | /// Create a range with the `first` and `last` spans being the same. 314 | pub fn single_span(span: Span) -> Self { 315 | SpanRange { 316 | first: span, 317 | last: span, 318 | } 319 | } 320 | 321 | /// Create a `SpanRange` resolving at call site. 322 | pub fn call_site() -> Self { 323 | SpanRange::single_span(Span::call_site()) 324 | } 325 | 326 | /// Construct span range from a `TokenStream`. This method always preserves all the 327 | /// range. 328 | /// 329 | /// ### Note 330 | /// 331 | /// If the stream is empty, the result is `SpanRange::call_site()`. If the stream 332 | /// consists of only one `TokenTree`, the result is `SpanRange::single_span(tt.span())` 333 | /// that doesn't lose anything. 334 | pub fn from_tokens(ts: &dyn ToTokens) -> Self { 335 | let mut spans = ts.to_token_stream().into_iter().map(|tt| tt.span()); 336 | let first = spans.next().unwrap_or_else(Span::call_site); 337 | let last = spans.last().unwrap_or(first); 338 | 339 | SpanRange { first, last } 340 | } 341 | 342 | /// Join two span ranges. The resulting range will start at `self.first` and end at 343 | /// `other.last`. 344 | pub fn join_range(self, other: SpanRange) -> Self { 345 | SpanRange { 346 | first: self.first, 347 | last: other.last, 348 | } 349 | } 350 | 351 | /// Collapse the range into single span, preserving as much information as possible. 352 | #[must_use] 353 | pub fn collapse(self) -> Span { 354 | self.first.join(self.last).unwrap_or(self.first) 355 | } 356 | } 357 | 358 | /// This traits expands `Result>` with some handy shortcuts. 359 | pub trait ResultExt { 360 | type Ok; 361 | 362 | /// Behaves like `Result::unwrap`: if self is `Ok` yield the contained value, 363 | /// otherwise abort macro execution via `abort!`. 364 | fn unwrap_or_abort(self) -> Self::Ok; 365 | 366 | /// Behaves like `Result::expect`: if self is `Ok` yield the contained value, 367 | /// otherwise abort macro execution via `abort!`. 368 | /// If it aborts then resulting error message will be preceded with `message`. 369 | fn expect_or_abort(self, msg: &str) -> Self::Ok; 370 | } 371 | 372 | /// This traits expands `Option` with some handy shortcuts. 373 | pub trait OptionExt { 374 | type Some; 375 | 376 | /// Behaves like `Option::expect`: if self is `Some` yield the contained value, 377 | /// otherwise abort macro execution via `abort_call_site!`. 378 | /// If it aborts the `message` will be used for [`compile_error!`][compl_err] invocation. 379 | /// 380 | /// [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html 381 | fn expect_or_abort(self, msg: &str) -> Self::Some; 382 | } 383 | 384 | /// Abort macro execution and display all the emitted errors, if any. 385 | /// 386 | /// Does nothing if no errors were emitted (warnings do not count). 387 | pub fn abort_if_dirty() { 388 | imp::abort_if_dirty(); 389 | } 390 | 391 | impl> ResultExt for Result { 392 | type Ok = T; 393 | 394 | fn unwrap_or_abort(self) -> T { 395 | match self { 396 | Ok(res) => res, 397 | Err(e) => e.into().abort(), 398 | } 399 | } 400 | 401 | fn expect_or_abort(self, message: &str) -> T { 402 | match self { 403 | Ok(res) => res, 404 | Err(e) => { 405 | let mut e = e.into(); 406 | e.msg = format!("{}: {}", message, e.msg); 407 | e.abort() 408 | } 409 | } 410 | } 411 | } 412 | 413 | impl OptionExt for Option { 414 | type Some = T; 415 | 416 | fn expect_or_abort(self, message: &str) -> T { 417 | match self { 418 | Some(res) => res, 419 | None => abort_call_site!(message), 420 | } 421 | } 422 | } 423 | 424 | /// This is the entry point for a proc-macro. 425 | /// 426 | /// **NOT PUBLIC API, SUBJECT TO CHANGE WITHOUT ANY NOTICE** 427 | #[doc(hidden)] 428 | pub fn entry_point(f: F, proc_macro_hack: bool) -> proc_macro::TokenStream 429 | where 430 | F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, 431 | { 432 | ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() + 1)); 433 | let caught = catch_unwind(f); 434 | let dummy = dummy::cleanup(); 435 | let err_storage = imp::cleanup(); 436 | ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() - 1)); 437 | 438 | let gen_error = || { 439 | if proc_macro_hack { 440 | quote! {{ 441 | macro_rules! proc_macro_call { 442 | () => ( unimplemented!() ) 443 | } 444 | 445 | #(#err_storage)* 446 | #dummy 447 | 448 | unimplemented!() 449 | }} 450 | } else { 451 | quote!( #(#err_storage)* #dummy ) 452 | } 453 | }; 454 | 455 | match caught { 456 | Ok(ts) => { 457 | if err_storage.is_empty() { 458 | ts 459 | } else { 460 | gen_error().into() 461 | } 462 | } 463 | 464 | Err(boxed) => match boxed.downcast::() { 465 | Ok(_) => gen_error().into(), 466 | Err(boxed) => resume_unwind(boxed), 467 | }, 468 | } 469 | } 470 | 471 | fn abort_now() -> ! { 472 | check_correctness(); 473 | std::panic::panic_any(AbortNow) 474 | } 475 | 476 | thread_local! { 477 | static ENTERED_ENTRY_POINT: Cell = const { Cell::new(0) }; 478 | } 479 | 480 | struct AbortNow; 481 | 482 | fn check_correctness() { 483 | assert!( 484 | ENTERED_ENTRY_POINT.with(Cell::get) != 0, 485 | "proc-macro-error2 API cannot be used outside of `entry_point` invocation, \ 486 | perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]" 487 | ); 488 | } 489 | 490 | /// **ALL THE STUFF INSIDE IS NOT PUBLIC API!!!** 491 | #[doc(hidden)] 492 | pub mod __export { 493 | // reexports for use in macros 494 | pub use proc_macro; 495 | pub use proc_macro2; 496 | 497 | use proc_macro2::Span; 498 | use quote::ToTokens; 499 | 500 | use crate::SpanRange; 501 | 502 | // inspired by 503 | // https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md#simple-application 504 | 505 | pub trait SpanAsSpanRange { 506 | #[allow(non_snake_case)] 507 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; 508 | } 509 | 510 | pub trait Span2AsSpanRange { 511 | #[allow(non_snake_case)] 512 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; 513 | } 514 | 515 | pub trait ToTokensAsSpanRange { 516 | #[allow(non_snake_case)] 517 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; 518 | } 519 | 520 | pub trait SpanRangeAsSpanRange { 521 | #[allow(non_snake_case)] 522 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; 523 | } 524 | 525 | impl ToTokensAsSpanRange for &T { 526 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { 527 | let mut ts = self.to_token_stream().into_iter(); 528 | let first = match ts.next() { 529 | Some(t) => t.span(), 530 | None => Span::call_site(), 531 | }; 532 | 533 | let last = match ts.last() { 534 | Some(t) => t.span(), 535 | None => first, 536 | }; 537 | 538 | SpanRange { first, last } 539 | } 540 | } 541 | 542 | impl Span2AsSpanRange for Span { 543 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { 544 | SpanRange { 545 | first: *self, 546 | last: *self, 547 | } 548 | } 549 | } 550 | 551 | impl SpanAsSpanRange for proc_macro::Span { 552 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { 553 | SpanRange { 554 | first: (*self).into(), 555 | last: (*self).into(), 556 | } 557 | } 558 | } 559 | 560 | impl SpanRangeAsSpanRange for SpanRange { 561 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { 562 | *self 563 | } 564 | } 565 | } 566 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // FIXME: this can be greatly simplified via $()? 2 | // as soon as MRSV hits 1.32 3 | 4 | /// Build [`Diagnostic`](struct.Diagnostic.html) instance from provided arguments. 5 | /// 6 | /// # Syntax 7 | /// 8 | /// See [the guide](index.html#guide). 9 | /// 10 | #[macro_export] 11 | macro_rules! diagnostic { 12 | // from alias 13 | ($err:expr) => { $crate::Diagnostic::from($err) }; 14 | 15 | // span, message, help 16 | ($span:expr, $level:expr, $fmt:expr, $($args:expr),+ ; $($rest:tt)+) => {{ 17 | #[allow(unused_imports)] 18 | use $crate::__export::{ 19 | ToTokensAsSpanRange, 20 | Span2AsSpanRange, 21 | SpanAsSpanRange, 22 | SpanRangeAsSpanRange 23 | }; 24 | use $crate::DiagnosticExt; 25 | let span_range = (&$span).FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(); 26 | 27 | let diag = $crate::Diagnostic::spanned_range( 28 | span_range, 29 | $level, 30 | format!($fmt, $($args),*) 31 | ); 32 | $crate::__pme__suggestions!(diag $($rest)*); 33 | diag 34 | }}; 35 | 36 | ($span:expr, $level:expr, $msg:expr ; $($rest:tt)+) => {{ 37 | #[allow(unused_imports)] 38 | use $crate::__export::{ 39 | ToTokensAsSpanRange, 40 | Span2AsSpanRange, 41 | SpanAsSpanRange, 42 | SpanRangeAsSpanRange 43 | }; 44 | use $crate::DiagnosticExt; 45 | let span_range = (&$span).FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(); 46 | 47 | let diag = $crate::Diagnostic::spanned_range(span_range, $level, $msg.to_string()); 48 | $crate::__pme__suggestions!(diag $($rest)*); 49 | diag 50 | }}; 51 | 52 | // span, message, no help 53 | ($span:expr, $level:expr, $fmt:expr, $($args:expr),+) => {{ 54 | #[allow(unused_imports)] 55 | use $crate::__export::{ 56 | ToTokensAsSpanRange, 57 | Span2AsSpanRange, 58 | SpanAsSpanRange, 59 | SpanRangeAsSpanRange 60 | }; 61 | use $crate::DiagnosticExt; 62 | let span_range = (&$span).FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(); 63 | 64 | $crate::Diagnostic::spanned_range( 65 | span_range, 66 | $level, 67 | format!($fmt, $($args),*) 68 | ) 69 | }}; 70 | 71 | ($span:expr, $level:expr, $msg:expr) => {{ 72 | #[allow(unused_imports)] 73 | use $crate::__export::{ 74 | ToTokensAsSpanRange, 75 | Span2AsSpanRange, 76 | SpanAsSpanRange, 77 | SpanRangeAsSpanRange 78 | }; 79 | use $crate::DiagnosticExt; 80 | let span_range = (&$span).FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(); 81 | 82 | $crate::Diagnostic::spanned_range(span_range, $level, $msg.to_string()) 83 | }}; 84 | 85 | 86 | // trailing commas 87 | 88 | ($span:expr, $level:expr, $fmt:expr, $($args:expr),+, ; $($rest:tt)+) => { 89 | $crate::diagnostic!($span, $level, $fmt, $($args),* ; $($rest)*) 90 | }; 91 | ($span:expr, $level:expr, $msg:expr, ; $($rest:tt)+) => { 92 | $crate::diagnostic!($span, $level, $msg ; $($rest)*) 93 | }; 94 | ($span:expr, $level:expr, $fmt:expr, $($args:expr),+,) => { 95 | $crate::diagnostic!($span, $level, $fmt, $($args),*) 96 | }; 97 | ($span:expr, $level:expr, $msg:expr,) => { 98 | $crate::diagnostic!($span, $level, $msg) 99 | }; 100 | // ($err:expr,) => { $crate::diagnostic!($err) }; 101 | } 102 | 103 | /// Abort proc-macro execution right now and display the error. 104 | /// 105 | /// # Syntax 106 | /// 107 | /// See [the guide](index.html#guide). 108 | #[macro_export] 109 | macro_rules! abort { 110 | ($err:expr) => { 111 | $crate::diagnostic!($err).abort() 112 | }; 113 | 114 | ($span:expr, $($tts:tt)*) => { 115 | $crate::diagnostic!($span, $crate::Level::Error, $($tts)*).abort() 116 | }; 117 | } 118 | 119 | /// Shortcut for `abort!(Span::call_site(), msg...)`. This macro 120 | /// is still preferable over plain panic, panics are not for error reporting. 121 | /// 122 | /// # Syntax 123 | /// 124 | /// See [the guide](index.html#guide). 125 | /// 126 | #[macro_export] 127 | macro_rules! abort_call_site { 128 | ($($tts:tt)*) => { 129 | $crate::abort!($crate::__export::proc_macro2::Span::call_site(), $($tts)*) 130 | }; 131 | } 132 | 133 | /// Emit an error while not aborting the proc-macro right away. 134 | /// 135 | /// # Syntax 136 | /// 137 | /// See [the guide](index.html#guide). 138 | /// 139 | #[macro_export] 140 | macro_rules! emit_error { 141 | ($err:expr) => { 142 | $crate::diagnostic!($err).emit() 143 | }; 144 | 145 | ($span:expr, $($tts:tt)*) => {{ 146 | let level = $crate::Level::Error; 147 | $crate::diagnostic!($span, level, $($tts)*).emit() 148 | }}; 149 | } 150 | 151 | /// Shortcut for `emit_error!(Span::call_site(), ...)`. This macro 152 | /// is still preferable over plain panic, panics are not for error reporting.. 153 | /// 154 | /// # Syntax 155 | /// 156 | /// See [the guide](index.html#guide). 157 | /// 158 | #[macro_export] 159 | macro_rules! emit_call_site_error { 160 | ($($tts:tt)*) => { 161 | $crate::emit_error!($crate::__export::proc_macro2::Span::call_site(), $($tts)*) 162 | }; 163 | } 164 | 165 | /// Emit a warning. Warnings are not errors and compilation won't fail because of them. 166 | /// 167 | /// **Does nothing on stable** 168 | /// 169 | /// # Syntax 170 | /// 171 | /// See [the guide](index.html#guide). 172 | /// 173 | #[macro_export] 174 | macro_rules! emit_warning { 175 | ($span:expr, $($tts:tt)*) => { 176 | $crate::diagnostic!($span, $crate::Level::Warning, $($tts)*).emit() 177 | }; 178 | } 179 | 180 | /// Shortcut for `emit_warning!(Span::call_site(), ...)`. 181 | /// 182 | /// **Does nothing on stable** 183 | /// 184 | /// # Syntax 185 | /// 186 | /// See [the guide](index.html#guide). 187 | /// 188 | #[macro_export] 189 | macro_rules! emit_call_site_warning { 190 | ($($tts:tt)*) => {{ 191 | $crate::emit_warning!($crate::__export::proc_macro2::Span::call_site(), $($tts)*) 192 | }}; 193 | } 194 | 195 | #[doc(hidden)] 196 | #[macro_export] 197 | macro_rules! __pme__suggestions { 198 | ($var:ident) => (); 199 | 200 | ($var:ident $help:ident =? $msg:expr) => { 201 | let $var = if let Some(msg) = $msg { 202 | $var.suggestion(stringify!($help), msg.to_string()) 203 | } else { 204 | $var 205 | }; 206 | }; 207 | ($var:ident $help:ident =? $span:expr => $msg:expr) => { 208 | let $var = if let Some(msg) = $msg { 209 | $var.span_suggestion($span.into(), stringify!($help), msg.to_string()) 210 | } else { 211 | $var 212 | }; 213 | }; 214 | 215 | ($var:ident $help:ident =? $msg:expr ; $($rest:tt)*) => { 216 | $crate::__pme__suggestions!($var $help =? $msg); 217 | $crate::__pme__suggestions!($var $($rest)*); 218 | }; 219 | ($var:ident $help:ident =? $span:expr => $msg:expr ; $($rest:tt)*) => { 220 | $crate::__pme__suggestions!($var $help =? $span => $msg); 221 | $crate::__pme__suggestions!($var $($rest)*); 222 | }; 223 | 224 | 225 | ($var:ident $help:ident = $msg:expr) => { 226 | let $var = $var.suggestion(stringify!($help), $msg.to_string()); 227 | }; 228 | ($var:ident $help:ident = $fmt:expr, $($args:expr),+) => { 229 | let $var = $var.suggestion( 230 | stringify!($help), 231 | format!($fmt, $($args),*) 232 | ); 233 | }; 234 | ($var:ident $help:ident = $span:expr => $msg:expr) => { 235 | let $var = $var.span_suggestion($span.into(), stringify!($help), $msg.to_string()); 236 | }; 237 | ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+) => { 238 | let $var = $var.span_suggestion( 239 | $span.into(), 240 | stringify!($help), 241 | format!($fmt, $($args),*) 242 | ); 243 | }; 244 | 245 | ($var:ident $help:ident = $msg:expr ; $($rest:tt)*) => { 246 | $crate::__pme__suggestions!($var $help = $msg); 247 | $crate::__pme__suggestions!($var $($rest)*); 248 | }; 249 | ($var:ident $help:ident = $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { 250 | $crate::__pme__suggestions!($var $help = $fmt, $($args),*); 251 | $crate::__pme__suggestions!($var $($rest)*); 252 | }; 253 | ($var:ident $help:ident = $span:expr => $msg:expr ; $($rest:tt)*) => { 254 | $crate::__pme__suggestions!($var $help = $span => $msg); 255 | $crate::__pme__suggestions!($var $($rest)*); 256 | }; 257 | ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { 258 | $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*); 259 | $crate::__pme__suggestions!($var $($rest)*); 260 | }; 261 | 262 | // trailing commas 263 | 264 | ($var:ident $help:ident = $msg:expr,) => { 265 | $crate::__pme__suggestions!($var $help = $msg) 266 | }; 267 | ($var:ident $help:ident = $fmt:expr, $($args:expr),+,) => { 268 | $crate::__pme__suggestions!($var $help = $fmt, $($args)*) 269 | }; 270 | ($var:ident $help:ident = $span:expr => $msg:expr,) => { 271 | $crate::__pme__suggestions!($var $help = $span => $msg) 272 | }; 273 | ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),*,) => { 274 | $crate::__pme__suggestions!($var $help = $span => $fmt, $($args)*) 275 | }; 276 | ($var:ident $help:ident = $msg:expr, ; $($rest:tt)*) => { 277 | $crate::__pme__suggestions!($var $help = $msg; $($rest)*) 278 | }; 279 | ($var:ident $help:ident = $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { 280 | $crate::__pme__suggestions!($var $help = $fmt, $($args),*; $($rest)*) 281 | }; 282 | ($var:ident $help:ident = $span:expr => $msg:expr, ; $($rest:tt)*) => { 283 | $crate::__pme__suggestions!($var $help = $span => $msg; $($rest)*) 284 | }; 285 | ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { 286 | $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*; $($rest)*) 287 | }; 288 | } 289 | -------------------------------------------------------------------------------- /src/sealed.rs: -------------------------------------------------------------------------------- 1 | pub trait Sealed {} 2 | 3 | impl Sealed for crate::Diagnostic {} 4 | -------------------------------------------------------------------------------- /test-crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .fuse_hidden* 5 | -------------------------------------------------------------------------------- /test-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-crate" 3 | version = "0.0.0" 4 | authors = [ 5 | "CreepySkeleton ", 6 | "GnomedDev ", 7 | ] 8 | edition = "2021" 9 | publish = false 10 | 11 | [lib] 12 | path = "lib.rs" 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro-error2 = { path = "../" } 17 | quote = "1" 18 | proc-macro2 = "1" 19 | 20 | [dependencies.syn] 21 | version = "2" 22 | features = ["full"] 23 | 24 | [lints.clippy] 25 | pedantic = { level = "warn", priority = -1 } 26 | module_name_repetitions = { level = "allow" } 27 | -------------------------------------------------------------------------------- /test-crate/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use proc_macro_error2::{ 3 | abort, abort_call_site, diagnostic, emit_call_site_error, emit_call_site_warning, emit_error, 4 | emit_warning, proc_macro_error, set_dummy, Diagnostic, Level, OptionExt, ResultExt, SpanRange, 5 | }; 6 | 7 | use quote::quote; 8 | use syn::{parse_macro_input, spanned::Spanned}; 9 | 10 | // Macros and Diagnostic 11 | 12 | #[proc_macro] 13 | #[proc_macro_error] 14 | pub fn abort_from(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 15 | let span = input.into_iter().next().unwrap().span(); 16 | abort!( 17 | span, 18 | syn::Error::new(Span::call_site(), "abort!(span, from) test") 19 | ) 20 | } 21 | 22 | #[proc_macro] 23 | #[proc_macro_error] 24 | pub fn abort_to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 25 | let span = input.into_iter().next().unwrap().span(); 26 | abort!(span, "abort!(span, single_expr) test") 27 | } 28 | 29 | #[proc_macro] 30 | #[proc_macro_error] 31 | pub fn abort_format(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 32 | let span = input.into_iter().next().unwrap().span(); 33 | abort!(span, "abort!(span, expr1, {}) test", "expr2") 34 | } 35 | 36 | #[proc_macro] 37 | #[proc_macro_error] 38 | pub fn abort_call_site_test(_: proc_macro::TokenStream) -> proc_macro::TokenStream { 39 | abort_call_site!("abort_call_site! test") 40 | } 41 | 42 | #[proc_macro] 43 | #[proc_macro_error] 44 | pub fn direct_abort(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 45 | let span = input.into_iter().next().unwrap().span(); 46 | Diagnostic::spanned(span.into(), Level::Error, "Diagnostic::abort() test".into()).abort() 47 | } 48 | 49 | #[proc_macro] 50 | #[proc_macro_error] 51 | pub fn emit(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 52 | let mut spans = input.into_iter().step_by(2).map(|t| t.span()); 53 | emit_error!( 54 | spans.next().unwrap(), 55 | syn::Error::new(Span::call_site(), "emit!(span, from) test") 56 | ); 57 | emit_error!( 58 | spans.next().unwrap(), 59 | "emit!(span, expr1, {}) test", 60 | "expr2" 61 | ); 62 | emit_error!(spans.next().unwrap(), "emit!(span, single_expr) test"); 63 | Diagnostic::spanned( 64 | spans.next().unwrap().into(), 65 | Level::Error, 66 | "Diagnostic::emit() test".into(), 67 | ) 68 | .emit(); 69 | 70 | emit_call_site_error!("emit_call_site_error!(expr) test"); 71 | 72 | // NOOP on stable, just checking that the macros themselves compile. 73 | emit_warning!(spans.next().unwrap(), "emit_warning! test"); 74 | emit_call_site_warning!("emit_call_site_warning! test"); 75 | 76 | quote!().into() 77 | } 78 | 79 | // Notes 80 | 81 | #[proc_macro] 82 | #[proc_macro_error] 83 | pub fn abort_notes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 84 | let mut spans = input.into_iter().map(|s| s.span()); 85 | let span = spans.next().unwrap(); 86 | let span2 = spans.next().unwrap(); 87 | 88 | let some_note = Some("Some note"); 89 | let none_note: Option<&'static str> = None; 90 | 91 | abort! { 92 | span, "This is {} error", "an"; 93 | 94 | note = "simple note"; 95 | help = "simple help"; 96 | hint = "simple hint"; 97 | yay = "simple yay"; 98 | 99 | note = "format {}", "note"; 100 | 101 | note =? some_note; 102 | note =? none_note; 103 | 104 | note = span2 => "spanned simple note"; 105 | note = span2 => "spanned format {}", "note"; 106 | note =? span2 => some_note; 107 | note =? span2 => none_note; 108 | } 109 | } 110 | 111 | #[proc_macro] 112 | #[proc_macro_error] 113 | pub fn emit_notes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 114 | let mut spans = input.into_iter().step_by(2).map(|s| s.span()); 115 | let span = spans.next().unwrap(); 116 | let span2 = spans.next().unwrap(); 117 | 118 | let some_note = Some("Some note"); 119 | let none_note: Option<&'static str> = None; 120 | 121 | abort! { 122 | span, "This is {} error", "an"; 123 | 124 | note = "simple note"; 125 | help = "simple help"; 126 | hint = "simple hint"; 127 | yay = "simple yay"; 128 | 129 | note = "format {}", "note"; 130 | 131 | note =? some_note; 132 | note =? none_note; 133 | 134 | note = span2 => "spanned simple note"; 135 | note = span2 => "spanned format {}", "note"; 136 | note =? span2 => some_note; 137 | note =? span2 => none_note; 138 | } 139 | } 140 | 141 | // Extension traits 142 | 143 | #[proc_macro] 144 | #[proc_macro_error] 145 | pub fn option_ext(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { 146 | let none: Option<()> = None; 147 | none.expect_or_abort("Option::expect_or_abort() test"); 148 | quote!().into() 149 | } 150 | 151 | #[proc_macro] 152 | #[proc_macro_error] 153 | pub fn result_unwrap_or_abort(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 154 | let span = input.into_iter().next().unwrap().span(); 155 | let err = Diagnostic::spanned( 156 | span.into(), 157 | Level::Error, 158 | "Result::unwrap_or_abort() test".to_string(), 159 | ); 160 | let res: Result<(), _> = Err(err); 161 | res.unwrap_or_abort(); 162 | quote!().into() 163 | } 164 | 165 | #[proc_macro] 166 | #[proc_macro_error] 167 | pub fn result_expect_or_abort(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 168 | let span = input.into_iter().next().unwrap().span(); 169 | let err = Diagnostic::spanned( 170 | span.into(), 171 | Level::Error, 172 | "Result::expect_or_abort() test".to_string(), 173 | ); 174 | let res: Result<(), _> = Err(err); 175 | res.expect_or_abort("BOOM"); 176 | quote!().into() 177 | } 178 | 179 | // Dummy 180 | 181 | #[proc_macro] 182 | #[proc_macro_error] 183 | pub fn dummy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 184 | let span = input.into_iter().next().unwrap().span(); 185 | set_dummy(quote! { 186 | impl Default for NeedDefault { 187 | fn default() -> Self { NeedDefault::A } 188 | } 189 | }); 190 | 191 | abort!(span, "set_dummy test"); 192 | } 193 | 194 | #[proc_macro] 195 | #[proc_macro_error] 196 | pub fn append_dummy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 197 | let span = input.into_iter().next().unwrap().span(); 198 | set_dummy(quote! { 199 | impl Default for NeedDefault 200 | }); 201 | 202 | proc_macro_error2::append_dummy(quote!({ 203 | fn default() -> Self { 204 | NeedDefault::A 205 | } 206 | })); 207 | 208 | abort!(span, "append_dummy test"); 209 | } 210 | 211 | // Panic 212 | 213 | #[proc_macro] 214 | #[proc_macro_error] 215 | pub fn unrelated_panic(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { 216 | panic!("unrelated panic test") 217 | } 218 | 219 | // Success 220 | 221 | #[proc_macro] 222 | #[proc_macro_error] 223 | pub fn ok(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 224 | let input = TokenStream::from(input); 225 | quote!(fn #input() {}).into() 226 | } 227 | 228 | // Multiple tokens 229 | 230 | #[proc_macro_attribute] 231 | #[proc_macro_error] 232 | pub fn multiple_tokens( 233 | _: proc_macro::TokenStream, 234 | input: proc_macro::TokenStream, 235 | ) -> proc_macro::TokenStream { 236 | let input = proc_macro2::TokenStream::from(input); 237 | abort!(input, "..."); 238 | } 239 | 240 | #[proc_macro] 241 | #[proc_macro_error] 242 | pub fn to_tokens_span(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 243 | let ty = parse_macro_input!(input as syn::Type); 244 | emit_error!(ty, "whole type"); 245 | emit_error!(ty.span(), "explicit .span()"); 246 | quote!().into() 247 | } 248 | 249 | #[proc_macro] 250 | #[proc_macro_error] 251 | pub fn explicit_span_range(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 252 | let mut spans = input.into_iter().step_by(2).map(|s| s.span()); 253 | let first = Span::from(spans.next().unwrap()); 254 | let last = Span::from(spans.nth(1).unwrap()); 255 | abort!(SpanRange { first, last }, "explicit SpanRange") 256 | } 257 | 258 | // Children messages 259 | 260 | #[proc_macro] 261 | #[proc_macro_error] 262 | pub fn children_messages(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 263 | let mut spans = input.into_iter().step_by(2).map(|s| s.span()); 264 | diagnostic!(spans.next().unwrap(), Level::Error, "main macro message") 265 | .span_error(spans.next().unwrap().into(), "child message".into()) 266 | .emit(); 267 | 268 | let mut main = syn::Error::new(spans.next().unwrap().into(), "main syn::Error"); 269 | let child = syn::Error::new(spans.next().unwrap().into(), "child syn::Error"); 270 | main.combine(child); 271 | Diagnostic::from(main).abort() 272 | } 273 | -------------------------------------------------------------------------------- /tests/macro-errors.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[cfg(run_ui_tests)] 3 | fn ui() { 4 | let t = trybuild::TestCases::new(); 5 | t.compile_fail("tests/ui/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/ok.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | ok!(it_works); 4 | 5 | #[test] 6 | fn check_it_works() { 7 | it_works(); 8 | } 9 | -------------------------------------------------------------------------------- /tests/runtime-errors.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error2::*; 2 | 3 | #[test] 4 | #[should_panic = "proc-macro-error2 API cannot be used outside of"] 5 | fn missing_attr_emit() { 6 | emit_call_site_error!("You won't see me"); 7 | } 8 | 9 | #[test] 10 | #[should_panic = "proc-macro-error2 API cannot be used outside of"] 11 | fn missing_attr_abort() { 12 | abort_call_site!("You won't see me"); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/abort.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | abort_from!(one, two); 4 | abort_to_string!(one, two); 5 | abort_format!(one, two); 6 | direct_abort!(one, two); 7 | abort_notes!(one, two); 8 | abort_call_site_test!(one, two); 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/abort.stderr: -------------------------------------------------------------------------------- 1 | error: abort!(span, from) test 2 | --> tests/ui/abort.rs:3:13 3 | | 4 | 3 | abort_from!(one, two); 5 | | ^^^ 6 | 7 | error: abort!(span, single_expr) test 8 | --> tests/ui/abort.rs:4:18 9 | | 10 | 4 | abort_to_string!(one, two); 11 | | ^^^ 12 | 13 | error: abort!(span, expr1, expr2) test 14 | --> tests/ui/abort.rs:5:15 15 | | 16 | 5 | abort_format!(one, two); 17 | | ^^^ 18 | 19 | error: Diagnostic::abort() test 20 | --> tests/ui/abort.rs:6:15 21 | | 22 | 6 | direct_abort!(one, two); 23 | | ^^^ 24 | 25 | error: This is an error 26 | 27 | = note: simple note 28 | = help: simple help 29 | = help: simple hint 30 | = note: simple yay 31 | = note: format note 32 | = note: Some note 33 | = note: spanned simple note 34 | = note: spanned format note 35 | = note: Some note 36 | 37 | --> tests/ui/abort.rs:7:14 38 | | 39 | 7 | abort_notes!(one, two); 40 | | ^^^ 41 | 42 | error: abort_call_site! test 43 | --> tests/ui/abort.rs:8:1 44 | | 45 | 8 | abort_call_site_test!(one, two); 46 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 47 | | 48 | = note: this error originates in the macro `abort_call_site_test` (in Nightly builds, run with -Z macro-backtrace for more info) 49 | -------------------------------------------------------------------------------- /tests/ui/append_dummy.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | enum NeedDefault { 4 | A, 5 | B, 6 | } 7 | 8 | append_dummy!(need_default); 9 | 10 | fn main() { 11 | let _ = NeedDefault::default(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/append_dummy.stderr: -------------------------------------------------------------------------------- 1 | error: append_dummy test 2 | --> tests/ui/append_dummy.rs:8:15 3 | | 4 | 8 | append_dummy!(need_default); 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/children_messages.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | children_messages!(one, two, three, four); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/children_messages.stderr: -------------------------------------------------------------------------------- 1 | error: main macro message 2 | --> tests/ui/children_messages.rs:3:20 3 | | 4 | 3 | children_messages!(one, two, three, four); 5 | | ^^^ 6 | 7 | error: child message 8 | --> tests/ui/children_messages.rs:3:25 9 | | 10 | 3 | children_messages!(one, two, three, four); 11 | | ^^^ 12 | 13 | error: main syn::Error 14 | --> tests/ui/children_messages.rs:3:30 15 | | 16 | 3 | children_messages!(one, two, three, four); 17 | | ^^^^^ 18 | 19 | error: child syn::Error 20 | --> tests/ui/children_messages.rs:3:37 21 | | 22 | 3 | children_messages!(one, two, three, four); 23 | | ^^^^ 24 | -------------------------------------------------------------------------------- /tests/ui/dummy.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | enum NeedDefault { 4 | A, 5 | B, 6 | } 7 | 8 | dummy!(need_default); 9 | 10 | fn main() { 11 | let _ = NeedDefault::default(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/dummy.stderr: -------------------------------------------------------------------------------- 1 | error: set_dummy test 2 | --> tests/ui/dummy.rs:8:8 3 | | 4 | 8 | dummy!(need_default); 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/emit.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | emit!(one, two, three, four, five); 4 | emit_notes!(one, two); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/ui/emit.stderr: -------------------------------------------------------------------------------- 1 | error: emit!(span, from) test 2 | --> tests/ui/emit.rs:3:7 3 | | 4 | 3 | emit!(one, two, three, four, five); 5 | | ^^^ 6 | 7 | error: emit!(span, expr1, expr2) test 8 | --> tests/ui/emit.rs:3:12 9 | | 10 | 3 | emit!(one, two, three, four, five); 11 | | ^^^ 12 | 13 | error: emit!(span, single_expr) test 14 | --> tests/ui/emit.rs:3:17 15 | | 16 | 3 | emit!(one, two, three, four, five); 17 | | ^^^^^ 18 | 19 | error: Diagnostic::emit() test 20 | --> tests/ui/emit.rs:3:24 21 | | 22 | 3 | emit!(one, two, three, four, five); 23 | | ^^^^ 24 | 25 | error: emit_call_site_error!(expr) test 26 | --> tests/ui/emit.rs:3:1 27 | | 28 | 3 | emit!(one, two, three, four, five); 29 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in the macro `emit` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | 33 | error: This is an error 34 | 35 | = note: simple note 36 | = help: simple help 37 | = help: simple hint 38 | = note: simple yay 39 | = note: format note 40 | = note: Some note 41 | = note: spanned simple note 42 | = note: spanned format note 43 | = note: Some note 44 | 45 | --> tests/ui/emit.rs:4:13 46 | | 47 | 4 | emit_notes!(one, two); 48 | | ^^^ 49 | -------------------------------------------------------------------------------- /tests/ui/explicit_span_range.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | explicit_span_range!(one, two, three, four); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/explicit_span_range.stderr: -------------------------------------------------------------------------------- 1 | error: explicit SpanRange 2 | --> tests/ui/explicit_span_range.rs:3:22 3 | | 4 | 3 | explicit_span_range!(one, two, three, four); 5 | | ^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/misuse.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error2::abort; 2 | 3 | struct Foo; 4 | 5 | #[allow(unused)] 6 | fn foo() { 7 | abort!(Foo, "BOOM"); 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /tests/ui/misuse.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: the method `FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange` exists for reference `&Foo`, but its trait bounds were not satisfied 2 | --> tests/ui/misuse.rs:7:5 3 | | 4 | 3 | struct Foo; 5 | | ---------- doesn't satisfy `Foo: quote::to_tokens::ToTokens` 6 | ... 7 | 7 | abort!(Foo, "BOOM"); 8 | | ^^^^^^^^^^^^^^^^^^^ method cannot be called on `&Foo` due to unsatisfied trait bounds 9 | | 10 | = note: the following trait bounds were not satisfied: 11 | `Foo: quote::to_tokens::ToTokens` 12 | which is required by `&Foo: ToTokensAsSpanRange` 13 | note: the trait `quote::to_tokens::ToTokens` must be implemented 14 | --> $CARGO/quote-1.0.37/src/to_tokens.rs 15 | | 16 | | pub trait ToTokens { 17 | | ^^^^^^^^^^^^^^^^^^ 18 | = help: items from traits can only be used if the trait is implemented and in scope 19 | = note: the following traits define an item `FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange`, perhaps you need to implement one of them: 20 | candidate #1: `Span2AsSpanRange` 21 | candidate #2: `SpanAsSpanRange` 22 | candidate #3: `SpanRangeAsSpanRange` 23 | candidate #4: `ToTokensAsSpanRange` 24 | = note: this error originates in the macro `$crate::diagnostic` which comes from the expansion of the macro `abort` (in Nightly builds, run with -Z macro-backtrace for more info) 25 | -------------------------------------------------------------------------------- /tests/ui/multiple_tokens.rs: -------------------------------------------------------------------------------- 1 | #[test_crate::multiple_tokens] 2 | type T = (); 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/multiple_tokens.stderr: -------------------------------------------------------------------------------- 1 | error: ... 2 | --> tests/ui/multiple_tokens.rs:2:1 3 | | 4 | 2 | type T = (); 5 | | ^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/not_proc_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error2::proc_macro_error; 2 | 3 | #[proc_macro_error] 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/not_proc_macro.stderr: -------------------------------------------------------------------------------- 1 | error: #[proc_macro_error] attribute can be used only with procedural macros 2 | 3 | = hint: if you are really sure that #[proc_macro_error] should be applied to this exact function, use #[proc_macro_error(allow_not_macro)] 4 | --> tests/ui/not_proc_macro.rs:3:1 5 | | 6 | 3 | #[proc_macro_error] 7 | | ^^^^^^^^^^^^^^^^^^^ 8 | | 9 | = note: this error originates in the attribute macro `proc_macro_error` (in Nightly builds, run with -Z macro-backtrace for more info) 10 | -------------------------------------------------------------------------------- /tests/ui/option_ext.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | option_ext!(one, two); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/option_ext.stderr: -------------------------------------------------------------------------------- 1 | error: Option::expect_or_abort() test 2 | --> tests/ui/option_ext.rs:3:1 3 | | 4 | 3 | option_ext!(one, two); 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the macro `option_ext` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/ui/result_ext.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | result_unwrap_or_abort!(one, two); 4 | result_expect_or_abort!(one, two); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/ui/result_ext.stderr: -------------------------------------------------------------------------------- 1 | error: Result::unwrap_or_abort() test 2 | --> tests/ui/result_ext.rs:3:25 3 | | 4 | 3 | result_unwrap_or_abort!(one, two); 5 | | ^^^ 6 | 7 | error: BOOM: Result::expect_or_abort() test 8 | --> tests/ui/result_ext.rs:4:25 9 | | 10 | 4 | result_expect_or_abort!(one, two); 11 | | ^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/to_tokens_span.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | to_tokens_span!(std::option::Option); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/to_tokens_span.stderr: -------------------------------------------------------------------------------- 1 | error: whole type 2 | --> tests/ui/to_tokens_span.rs:3:17 3 | | 4 | 3 | to_tokens_span!(std::option::Option); 5 | | ^^^^^^^^^^^^^^^^^^^ 6 | 7 | error: explicit .span() 8 | --> tests/ui/to_tokens_span.rs:3:17 9 | | 10 | 3 | to_tokens_span!(std::option::Option); 11 | | ^^^ 12 | -------------------------------------------------------------------------------- /tests/ui/unknown_setting.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_error2::proc_macro_error; 2 | 3 | #[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/unknown_setting.stderr: -------------------------------------------------------------------------------- 1 | error: unknown setting `trololo`, expected one of `assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack` 2 | --> tests/ui/unknown_setting.rs:3:57 3 | | 4 | 3 | #[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/unrelated_panic.rs: -------------------------------------------------------------------------------- 1 | use test_crate::*; 2 | 3 | unrelated_panic!(); 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/ui/unrelated_panic.stderr: -------------------------------------------------------------------------------- 1 | error: proc macro panicked 2 | --> tests/ui/unrelated_panic.rs:3:1 3 | | 4 | 3 | unrelated_panic!(); 5 | | ^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: unrelated panic test 8 | --------------------------------------------------------------------------------