diff options
Diffstat (limited to 'proc-macro-error')
43 files changed, 2120 insertions, 0 deletions
diff --git a/proc-macro-error/.gitignore b/proc-macro-error/.gitignore new file mode 100644 index 0000000..9d538f2 --- /dev/null +++ b/proc-macro-error/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +.fuse_hidden*
\ No newline at end of file diff --git a/proc-macro-error/.gitlab-ci.yml b/proc-macro-error/.gitlab-ci.yml new file mode 100644 index 0000000..e36a714 --- /dev/null +++ b/proc-macro-error/.gitlab-ci.yml @@ -0,0 +1,54 @@ +stages: + - test + + +.setup_template: &setup_template + stage: test + image: debian:stable-slim + before_script: + - export CARGO_HOME="$CI_PROJECT_DIR/.cargo" + - export PATH="$PATH:$CARGO_HOME/bin" + - export RUST_BACKTRACE=full + - apt-get update > /dev/null + - apt-get install -y curl build-essential > /dev/null + - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_VERSION + - rustup --version + - rustc --version + - cargo --version + +.test_all_template: &test_all_template + <<: *setup_template + script: + - cargo test --all + + +test-stable: + <<: *test_all_template + variables: + RUST_VERSION: stable + +test-beta: + <<: *test_all_template + variables: + RUST_VERSION: beta + +test-nightly: + <<: *test_all_template + variables: + RUST_VERSION: nightly + + +test-1.31.0: + <<: *setup_template + script: + - cargo test --tests # skip doctests + variables: + RUST_VERSION: 1.31.0 + +test-fmt: + <<: *setup_template + script: + - cargo fmt --all -- --check + - RUTSFLAGS='--cfg nightly_fmt' cargo fmt --all -- --check + variables: + RUST_VERSION: stable diff --git a/proc-macro-error/.travis.yml b/proc-macro-error/.travis.yml new file mode 100644 index 0000000..362003f --- /dev/null +++ b/proc-macro-error/.travis.yml @@ -0,0 +1,19 @@ +language: rust +rust: + - stable + - beta + - nightly +script: + - cargo test --all +matrix: + include: + - rust: 1.31.0 + script: cargo test --tests # skip doctests + allow_failures: + - rust: nightly + fast_finish: true + + +notifications: + email: + on_success: never diff --git a/proc-macro-error/CHANGELOG.md b/proc-macro-error/CHANGELOG.md new file mode 100644 index 0000000..29043b5 --- /dev/null +++ b/proc-macro-error/CHANGELOG.md @@ -0,0 +1,86 @@ +# v0.4.4 (2019-11-13) +* Fix `abort_if_dirty` + warnings bug +* Allow trailing commas in macros + +# v0.4.2 (2019-11-7) +* FINALLY fixed `__pme__suggestions not found` bug + +# v0.4.1 (2019-11-7) YANKED +* Fixed `__pme__suggestions not found` bug +* Documentation improvements, links checked + +# v0.4.0 (2019-11-6) YANKED + +## New features +* "help" messages that can have their own span on nightly, they + inherit parent span on stable. + ```rust + let cond_help = if condition { Some("some help message") else { None } }; + abort!( + span, // parent span + "something's wrong, {} wrongs in total", 10; // main message + help = "here's a help for you, {}", "take it"; // unconditional help message + help =? cond_help; // conditional help message, must be Option + note = note_span => "don't forget the note, {}", "would you?" // notes can have their own span but it's effective only on nightly + ) + ``` +* Warnings via `emit_warning` and `emit_warning_call_site`. Nightly only, they're ignored on stable. +* Now `proc-macro-error` delegates to `proc_macro::Diagnostic` on nightly. + +## Breaking changes +* `MacroError` is now replaced by `Diagnostic`. Its API resembles `proc_macro::Diagnostic`. +* `Diagnostic` does not implement `From<&str/String>` so `Result<T, &str/String>::abort_or_exit()` + won't work anymore (nobody used it anyway). +* `macro_error!` macro is replaced with `diagnostic!`. + +## Improvements +* Now `proc-macro-error` renders notes exactly just like rustc does. +* We don't parse a body of a function annotated with `#[proc_macro_error]` anymore, + only looking at the signature. This should somewhat decrease expansion time for large functions. + +# v0.3.3 (2019-10-16) +* Now you can use any word instead of "help", undocumented. + +# v0.3.2 (2019-10-16) +* Introduced support for "help" messages, undocumented. + +# v0.3.0 (2019-10-8) + +## The crate has been completely rewritten from scratch! + +## Changes (most are breaking): +* Renamed macros: + * `span_error` => `abort` + * `call_site_error` => `abort_call_site` +* `filter_macro_errors` was replaced by `#[proc_macro_error]` attribute. +* `set_dummy` now takes `TokenStream` instead of `Option<TokenStream>` +* Support for multiple errors via `emit_error` and `emit_call_site_error` +* New `macro_error` macro for building errors in format=like style. +* `MacroError` API had been reconsidered. It also now implements `quote::ToTokens`. + +# v0.2.6 (2019-09-02) +* Introduce support for dummy implementations via `dummy::set_dummy` +* `multi::*` is now deprecated, will be completely rewritten in v0.3 + +# v0.2.0 (2019-08-15) + +## Breaking changes +* `trigger_error` replaced with `MacroError::trigger` and `filter_macro_error_panics` + is hidden from docs. + This is not quite a breaking change since users weren't supposed to use these functions directly anyway. +* All dependencies are updated to `v1.*`. + +## New features +* Ability to stack multiple errors via `multi::MultiMacroErrors` and emit them at once. + +## Improvements +* Now `MacroError` implements `std::fmt::Display` instead of `std::string::ToString`. +* `MacroError::span` inherent method. +* `From<MacroError> for proc_macro/proc_macro2::TokenStream` implementations. +* `AsRef/AsMut<String> for MacroError` implementations. + +# v0.1.x (2019-07-XX) + +## New features +* An easy way to report errors inside within a proc-macro via `span_error`, + `call_site_error` and `filter_macro_errors`. diff --git a/proc-macro-error/Cargo.toml b/proc-macro-error/Cargo.toml new file mode 100644 index 0000000..6fa600e --- /dev/null +++ b/proc-macro-error/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +members = [ + "proc-macro-error", + "proc-macro-error-attr", + "test-crate", +] diff --git a/proc-macro-error/LICENSE-APACHE b/proc-macro-error/LICENSE-APACHE new file mode 100644 index 0000000..52ba334 --- /dev/null +++ b/proc-macro-error/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright 2019 CreepySkeleton <creepy-skeleton@yandex.ru>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/proc-macro-error/LICENSE-MIT b/proc-macro-error/LICENSE-MIT new file mode 100644 index 0000000..d01f775 --- /dev/null +++ b/proc-macro-error/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 CreepySkeleton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/proc-macro-error/README.md b/proc-macro-error/README.md new file mode 100644 index 0000000..d6d1086 --- /dev/null +++ b/proc-macro-error/README.md @@ -0,0 +1,206 @@ +# proc-macro-error + +[![travis ci](https://travis-ci.org/CreepySkeleton/proc-macro-error.svg?branch=master)](https://travis-ci.org/CreepySkeleton/proc-macro-error) +[![docs.rs](https://docs.rs/proc-macro-error/badge.svg)](https://docs.rs/proc-macro-error) + +This crate aims to make error reporting in proc-macros simple and easy to use. +Migrate from `panic!`-based errors for as little effort as possible! + +Also, there's ability to [append a dummy token stream][crate::dummy] to your errors. + +```toml +[dependencies] +proc-macro-error = "0.4" +``` + +*Supports rustc 1.31 and up* + +[Documentation and guide][guide] + +## What emitted errors look like + +``` +error: multiple error part: multi2 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:18 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ +``` + +## Examples + +### Panic-like usage + +```rust +use proc_macro_error::*; +use proc_macro::TokenStream; +use syn::{DeriveInput, parse_macro_input}; +use quote::quote; + +// This is your main entry point +#[proc_macro] +// this attribute *MUST* be placed on top of the #[proc_macro] function +#[proc_macro_error] +pub fn make_answer(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + if let Err(err) = some_logic(&input) { + // we've got a span to blame, let's use it + // This immediately aborts the proc-macro and shows the error + abort!(err.span, "You made an error, go fix it: {}", err.msg); + } + + // `Result` has some handy shortcuts if your error type implements + // `Into<MacroError>`. `Option` has one unconditionally. + more_logic(&input).expect_or_abort("What a careless user, behave!"); + + if !more_logic_for_logic_god(&input) { + // We don't have an exact location this time, + // so just highlight the proc-macro invocation itself + abort_call_site!( + "Bad, bad user! Now go stand in the corner and think about what you did!"); + } + + // Now all the processing is done, return `proc_macro::TokenStream` + quote!(/* stuff */).into() +} +``` + +### `proc_macro::Diagnostic`-like usage + +```rust +use proc_macro_error::*; +use proc_macro::TokenStream; +use syn::{spanned::Spanned, DeriveInput, ItemStruct, Fields, Attribute , parse_macro_input}; +use quote::quote; + +fn process_attrs(attrs: &[Attribute]) -> Vec<Attribute> { + attrs + .iter() + .filter_map(|attr| match process_attr(attr) { + Ok(res) => Some(res), + Err(msg) => { + emit_error!(attr.span(), "Invalid attribute: {}", msg); + None + } + }) + .collect() +} + +fn process_fields(_attrs: &Fields) -> Vec<TokenStream> { + // processing fields in pretty much the same way as attributes + unimplemented!() +} + +#[proc_macro] +#[proc_macro_error] +pub fn make_answer(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + let attrs = process_attrs(&input.attrs); + + // abort right now if some errors were encountered + // at the attributes processing stage + abort_if_dirty(); + + let fields = process_fields(&input.fields); + + // no need to think about emitted errors + // #[proc_macro_error] will handle them for you + // + // just return a TokenStream as you normally would + quote!(/* stuff */).into() +} +``` + +## Limitations + +- Warnings are emitted only on nightly, they're ignored on stable. +- "help" suggestions cannot have their own span info on stable, (they inherit parent span). +- If a panic occurs somewhere in your macro no errors will be displayed. This is not a + technical limitation but intentional design, `panic` is not for error reporting. + +## MSRV policy + +`proc_macro_error` will always be compatible with proc-macro holy trinity: +`proc_macro2`, `syn`, `quote` crates. In other words, if the trinity is available +to you than `proc_macro_error` is available too. + +## Motivation + +Error handling in proc-macros sucks. There's not much of a choice today: +you either "bubble up" the error up to the top-level of your macro and convert it to +a [`compile_error!`][compl_err] invocation or just use a good old panic. Both these ways suck: + +- Former sucks because it's quite redundant to unroll a proper error handling + just for critical errors that will crash the macro anyway so people mostly + choose not to bother with it at all and use panic. Almost nobody does it, + simple `.expect` is too tempting. + + Also, if you do decide to implement this `Result`-based architecture in your macro + you're going to have to rewrite it entirely once [`proc_macro::Diagnostic`][] is finally stable. + Not cool. + +- Later sucks because there's no way to carry out span info via `panic!`. `rustc` will highlight + the whole invocation itself but not some specific token inside it. + + Furthermore, panics aren't for error-reporting at all; panics are for bug-detecting + (like unwrapping on `None` or out-of-range indexing) or for early development stages + when you need a prototype ASAP and error handling can wait. Mixing these usages only + messes things up. + +- There is [`proc_macro::Diagnostic`][] which is awesome but it has been experimental + for more than a year and is unlikely to be stabilized any time soon. + + This crate's API is intentionally designed to be compatible with `proc_macro::Diagnostic` + and delegates to it whenever possible. Once `Diagnostics` is stable this crate + will **always** delegate to it, no code changes will be required on user side. + +That said, we need a solution, but this solution must meet these conditions: + +- It must be better than `panic!`. The main point: it must offer a way to carry span information + over to user. +- It must take as little effort as possible to migrate from `panic!`. Ideally, a new + macro with the same semantics plus ability to carry out span info. We should also keep + in mind the existence of [`proc_macro::Diagnostic`][] . +- **It must be usable on stable**. + +This crate aims to provide such a mechanism. All you have to do is annotate your top-level +`#[proc_macro]` function with `#[proc_macro_errors]` attribute and change panics to +[`abort!`]/[`abort_call_site!`] where appropriate, see [the Guide][guide]. + +## Disclaimer +Please note that **this crate is not intended to be used in any way other +than proc-macro error reporting**, use `Result` and `?` for anything else. + +<br> + +#### License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> + +<br> + +<sub> +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. +</sub> + + +[compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html +[`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html + +[crate::dummy]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/dummy/index.html +[crate::multi]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/multi/index.html + +[`abort_call_site!`]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/macro.abort_call_site.html +[`abort!`]: https://docs.rs/proc-macro-error/0.4/proc_macro_error/macro.abort.html +[guide]: https://docs.rs/proc-macro-error diff --git a/proc-macro-error/proc-macro-error-attr/Cargo.toml b/proc-macro-error/proc-macro-error-attr/Cargo.toml new file mode 100644 index 0000000..6dee68e --- /dev/null +++ b/proc-macro-error/proc-macro-error-attr/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "proc-macro-error-attr" +version = "0.4.3" +authors = ["CreepySkeleton <creepy-skeleton@yandex.ru>"] +edition = "2018" +description = "attribute macro for proc-macro-error crate" +license = "MIT OR Apache-2.0" +repository = "https://gitlab.com/CreepySkeleton/proc-macro-error" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "1", default-features = false, features = ["derive", "parsing", "proc-macro"] } +syn-mid = "0.4" +rustversion = "1.0" diff --git a/proc-macro-error/proc-macro-error-attr/src/lib.rs b/proc-macro-error/proc-macro-error-attr/src/lib.rs new file mode 100644 index 0000000..3c1013b --- /dev/null +++ b/proc-macro-error/proc-macro-error-attr/src/lib.rs @@ -0,0 +1,162 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use std::iter::FromIterator; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Attribute, Token, +}; +use syn_mid::{Block, ItemFn}; + +use self::Setting::*; + +#[proc_macro_attribute] +pub fn proc_macro_error(attr: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + let mut settings = match syn::parse::<Settings>(attr) { + Ok(settings) => settings, + Err(err) => { + let err = err.to_compile_error(); + return quote!(#input #err).into(); + } + }; + + let is_proc_macro = is_proc_macro(&input.attrs); + if is_proc_macro { + settings.set(AssertUnwindSafe); + } + + if detect_proc_macro_hack(&input.attrs) { + settings.set(ProcMacroHack); + } + + if !(settings.is_set(AllowNotMacro) || is_proc_macro) { + return quote!( + #input + compile_error!("#[proc_macro_error] attribute can be used only with a proc-macro\n\n \ + hint: if you are really sure that #[proc_macro_error] should be applied \ + to this exact function use #[proc_macro_error(allow_not_macro)]\n"); + ) + .into(); + } + + let ItemFn { + attrs, + vis, + constness, + asyncness, + unsafety, + abi, + fn_token, + ident, + generics, + inputs, + output, + block, + .. + } = input; + + let body = gen_body(block, settings); + + quote!( + #(#attrs)* + #vis + #constness + #asyncness + #unsafety + #abi + #fn_token + #ident + #generics + (#inputs) + #output + + { #body } + ) + .into() +} + +#[derive(PartialEq)] +enum Setting { + AssertUnwindSafe, + AllowNotMacro, + ProcMacroHack, +} + +impl Parse for Setting { + fn parse(input: ParseStream) -> syn::Result<Self> { + let ident: Ident = input.parse()?; + match &*ident.to_string() { + "assert_unwind_safe" => Ok(AssertUnwindSafe), + "allow_not_macro" => Ok(AllowNotMacro), + "proc_macro_hack" => Ok(ProcMacroHack), + _ => Err(syn::Error::new( + ident.span(), + format!( + "unknown setting `{}`, expected one of \ + `assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack`", + ident + ), + )), + } + } +} + +struct Settings(Vec<Setting>); +impl Parse for Settings { + fn parse(input: ParseStream) -> syn::Result<Self> { + let punct = Punctuated::<Setting, Token![,]>::parse_terminated(input)?; + Ok(Settings(Vec::from_iter(punct))) + } +} + +impl Settings { + fn is_set(&self, setting: Setting) -> bool { + self.0.iter().any(|s| *s == setting) + } + + fn set(&mut self, setting: Setting) { + self.0.push(setting) + } +} + +#[rustversion::since(1.37)] +fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream { + let is_proc_macro_hack = settings.is_set(ProcMacroHack); + let closure = if settings.is_set(AssertUnwindSafe) { + quote!(::std::panic::AssertUnwindSafe(|| #block )) + } else { + quote!(|| #block) + }; + + quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) ) +} + +// FIXME: +// proc_macro::TokenStream does not implement UnwindSafe until 1.37.0. +// Considering this is the closure's return type the unwind safety check would fail +// for virtually every closure possible, the check is meaningless. +#[rustversion::before(1.37)] +fn gen_body(block: Block, settings: Settings) -> proc_macro2::TokenStream { + let is_proc_macro_hack = settings.is_set(ProcMacroHack); + let closure = quote!(::std::panic::AssertUnwindSafe(|| #block )); + quote!( ::proc_macro_error::entry_point(#closure, #is_proc_macro_hack) ) +} + +fn detect_proc_macro_hack(attrs: &[Attribute]) -> bool { + attrs + .iter() + .any(|attr| attr.path.is_ident("proc_macro_hack")) +} + +fn is_proc_macro(attrs: &[Attribute]) -> bool { + attrs.iter().any(|attr| { + attr.path.is_ident("proc_macro") + || attr.path.is_ident("proc_macro_derive") + || attr.path.is_ident("proc_macro_attribute") + }) +} diff --git a/proc-macro-error/proc-macro-error/Cargo.toml b/proc-macro-error/proc-macro-error/Cargo.toml new file mode 100644 index 0000000..2da213a --- /dev/null +++ b/proc-macro-error/proc-macro-error/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "proc-macro-error" +version = "0.4.4" +authors = ["CreepySkeleton <creepy-skeleton@yandex.ru>"] +description = "Almost drop-in replacement to panics in proc-macros" + +repository = "https://gitlab.com/CreepySkeleton/proc-macro-error" +readme = "../README.md" +keywords = ["proc-macro", "error", "errors"] +categories = ["development-tools::procedural-macro-helpers"] +license = "MIT OR Apache-2.0" + +edition = "2018" +build = "build.rs" + +[badges] +maintenance = { status = "actively-developed" } + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "1", default-features = false } +proc-macro-error-attr = { path = "../proc-macro-error-attr", version = "0.4.3"} + +[build-dependencies] +rustversion = "1.0" diff --git a/proc-macro-error/proc-macro-error/build.rs b/proc-macro-error/proc-macro-error/build.rs new file mode 100644 index 0000000..04ddc11 --- /dev/null +++ b/proc-macro-error/proc-macro-error/build.rs @@ -0,0 +1,11 @@ +#[rustversion::nightly] +fn nightly() { + println!("cargo:rustc-cfg=pme_nightly"); +} + +#[rustversion::not(nightly)] +fn nightly() {} + +fn main() { + nightly() +} diff --git a/proc-macro-error/proc-macro-error/src/dummy.rs b/proc-macro-error/proc-macro-error/src/dummy.rs new file mode 100644 index 0000000..f7443b3 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/dummy.rs @@ -0,0 +1,136 @@ +//! Facility to emit dummy implementations (or whatever) in case +//! an error happen. +//! +//! `compile_error!` does not abort a compilation right away. This means +//! `rustc` doesn't just show you the error and abort, it carries on the +//! compilation process looking for other errors to report. +//! +//! Let's consider an example: +//! +//! ```rust,ignore +//! use proc_macro::TokenStream; +//! use proc_macro_error::*; +//! +//! trait MyTrait { +//! fn do_thing(); +//! } +//! +//! // this proc macro is supposed to generate MyTrait impl +//! #[proc_macro_derive(MyTrait)] +//! #[proc_macro_error] +//! fn example(input: TokenStream) -> TokenStream { +//! // somewhere deep inside +//! abort!(span, "something's wrong"); +//! +//! // this implementation will be generated if no error happened +//! quote! { +//! impl MyTrait for #name { +//! fn do_thing() {/* whatever */} +//! } +//! } +//! } +//! +//! // ================ +//! // in main.rs +//! +//! // this derive triggers an error +//! #[derive(MyTrait)] // first BOOM! +//! struct Foo; +//! +//! fn main() { +//! Foo::do_thing(); // second BOOM! +//! } +//! ``` +//! +//! The problem is: the generated token stream contains only `compile_error!` +//! invocation, the impl was not generated. That means user will see two compilation +//! errors: +//! +//! ```text +//! error: something's wrong +//! --> $DIR/probe.rs:9:10 +//! | +//! 9 |#[proc_macro_derive(MyTrait)] +//! | ^^^^^^^ +//! +//! error[E0599]: no function or associated item named `do_thing` found for type `Foo` in the current scope +//! --> src\main.rs:3:10 +//! | +//! 1 | struct Foo; +//! | ----------- function or associated item `do_thing` not found for this +//! 2 | fn main() { +//! 3 | Foo::do_thing(); // second BOOM! +//! | ^^^^^^^^ function or associated item not found in `Foo` +//! ``` +//! +//! But the second error is meaningless! We definitely need to fix this. +//! +//! Most used approach in cases like this is "dummy implementation" - +//! omit `impl MyTrait for #name` and fill functions bodies with `unimplemented!()`. +//! +//! This is how you do it: +//! +//! ```rust,ignore +//! use proc_macro::TokenStream; +//! use proc_macro_error::*; +//! +//! trait MyTrait { +//! fn do_thing(); +//! } +//! +//! // this proc macro is supposed to generate MyTrait impl +//! #[proc_macro_derive(MyTrait)] +//! #[proc_macro_error] +//! fn example(input: TokenStream) -> TokenStream { +//! // first of all - we set a dummy impl which will be appended to +//! // `compile_error!` invocations in case a trigger does happen +//! set_dummy(quote! { +//! impl MyTrait for #name { +//! fn do_thing() { unimplemented!() } +//! } +//! }); +//! +//! // somewhere deep inside +//! abort!(span, "something's wrong"); +//! +//! // this implementation will be generated if no error happened +//! quote! { +//! impl MyTrait for #name { +//! fn do_thing() {/* whatever */} +//! } +//! } +//! } +//! +//! // ================ +//! // in main.rs +//! +//! // this derive triggers an error +//! #[derive(MyTrait)] // first BOOM! +//! struct Foo; +//! +//! fn main() { +//! Foo::do_thing(); // no more errors! +//! } +//! ``` + +use proc_macro2::TokenStream; +use std::cell::Cell; + +use crate::check_correctness; + +thread_local! { + static DUMMY_IMPL: Cell<Option<TokenStream>> = Cell::new(None); +} + +/// Sets dummy token stream which will be appended to `compile_error!(msg);...` +/// invocations in case you'll emit any errors. +/// +/// See [guide](../index.html#guide). +pub fn set_dummy(dummy: TokenStream) -> Option<TokenStream> { + check_correctness(); + DUMMY_IMPL.with(|old_dummy| old_dummy.replace(Some(dummy))) +} + +pub(crate) fn cleanup() -> Option<TokenStream> { + DUMMY_IMPL.with(|old_dummy| old_dummy.replace(None)) +} diff --git a/proc-macro-error/proc-macro-error/src/lib.rs b/proc-macro-error/proc-macro-error/src/lib.rs new file mode 100644 index 0000000..5fb0223 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/lib.rs @@ -0,0 +1,514 @@ +//! # proc-macro-error +//! +//! This crate aims to make error reporting in proc-macros simple and easy to use. +//! Migrate from `panic!`-based errors for as little effort as possible! +//! +//! Also, there's ability to [append a dummy token stream](dummy/index.html) to your errors. +//! +//! ## Limitations +//! +//! - Warnings are emitted only on nightly, they're ignored on stable. +//! - "help" suggestions cannot have their own span info on stable, (they inherit parent span). +//! - If a panic occurs somewhere in your macro no errors will be displayed. This is not a +//! technical limitation but intentional design, `panic` is not for error reporting. +//! +//! ## Guide +//! +//! ### Macros +//! +//! First of all - **all the emitting-related API must be used within a function +//! annotated with [`#[proc_macro_error]`](#proc_macro_error-attribute) attribute**. You'll just get a +//! panic otherwise, no errors will be shown. +//! +//! For most of the time you will be using macros. +//! +//! - [`abort!`]: +//! +//! Very much panic-like usage - abort execution and show the error. Expands to [`!`] (never type). +//! +//! - [`abort_call_site!`]: +//! +//! Shortcut for `abort!(Span::call_site(), ...)`. Expands to [`!`] (never type). +//! +//! - [`emit_error!`]: +//! +//! [`proc_macro::Diagnostic`]-like usage - emit the error but do not abort the macro. +//! The compilation will fail nonetheless. Expands to [`()`] (unit type). +//! +//! - [`emit_call_site_error!`]: +//! +//! Shortcut for `emit_error!(Span::call_site(), ...)`. Expands to [`()`] (unit type). +//! +//! - [`emit_warning!`]: +//! +//! Like `emit_error!` but emit a warning instead of error. The compilation won't fail +//! because of warnings. +//! Expands to [`()`] (unit type). +//! +//! **Beware**: warnings are nightly only, they are completely ignored on stable. +//! +//! - [`emit_call_site_warning!`]: +//! +//! Shortcut for `emit_warning!(Span::call_site(), ...)`. Expands to `()` (unit type). +//! +//! - [`diagnostic`]: +//! +//! Build instance of `Diagnostic` in format-like style. +//! +//! ### Syntax +//! +//! All the macros have pretty much the same syntax: +//! +//! 1. ```ignore +//! abort!(single_expr) +//! ``` +//! Shortcut for `Diagnostic::from().abort()` +//! +//! 2. ```ignore +//! abort!(span, message) +//! ``` +//! Shortcut for `Diagnostic::spanned(span, message.to_string()).abort()` +//! +//! 3. ```ignore +//! abort!(span, format_literal, format_args...) +//! ``` +//! Shortcut for `Diagnostic::spanned(span, format!(format_literal, format_args...)).abort()` +//! +//! That's it. `abort!`, `emit_warning`, `emit_error` share this exact syntax. +//! `abort_call_site!`, `emit_call_site_warning`, `emit_call_site_error` lack 1 form +//! and do not take span in 2 and 3 forms. +//! +//! `diagnostic!` require `Level` instance between `span` and second argument (1 form is the same). +//! +//! #### Note attachments +//! +//! 3. Every macro can have "note" attachments (only 2 and 3 form). +//! ```ignore +//! let opt_help = if have_some_info { Some("did you mean `this`?") } else { None }; +//! +//! abort!( +//! span, message; // <--- attachments start with `;` (semicolon) +//! +//! help = "format {} {}", "arg1", "arg2"; // <--- every attachment ends with `;`, +//! // maybe except the last one +//! +//! note = "to_string"; // <--- one arg uses `.to_string()` instead of `format!()` +//! +//! yay = "I see what {} did here", "you"; // <--- "help =" and "hint =" are mapped to Diagnostic::help +//! // anything else is Diagnostic::note +//! +//! wow = note_span => "custom span"; // <--- attachments can have their own span +//! // it takes effect only on nightly though +//! +//! hint =? opt_help; // <-- "optional" attachment, get displayed only if `Some` +//! // must be single `Option` expression +//! +//! note =? note_span => opt_help // <-- optional attachments can have custom spans too +//! ) +//! ``` +//! +//! ### `#[proc_macro_error]` attribute +//! +//! **This attribute MUST be present on the top level of your macro.** +//! +//! This attribute performs the setup and cleanup necessary to make things work. +//! +//! #### Syntax +//! +//! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...` +//! is a comma-separated list of: +//! +//! - `proc_macro_hack`: +//! +//! To correctly cooperate with `#[proc_macro_hack]` `#[proc_macro_error]` +//! attribute must be placed *before* (above) it, like this: +//! +//! ```ignore +//! #[proc_macro_error] +//! #[proc_macro_hack] +//! #[proc_macro] +//! fn my_macro(input: TokenStream) -> TokenStream { +//! unimplemented!() +//! } +//! ``` +//! +//! If, for some reason, you can't place it like that you can use +//! `#[proc_macro_error(proc_macro_hack)]` instead. +//! +//! - `allow_not_macro`: +//! +//! By default, the attribute checks that it's applied to a proc-macro. +//! If none of `#[proc_macro]`, `#[proc_macro_derive]` nor `#[proc_macro_attribute]` are +//! present it will panic. It's the intention - this crate is supposed to be used only with +//! proc-macros. This setting is made to bypass the check, useful in certain +//! circumstances. +//! +//! Please note: the function this attribute is applied to must return `proc_macro::TokenStream`. +//! +//! - `assert_unwind_safe`: +//! +//! By default, your code must be [unwind safe]. If your code is not unwind safe but you believe +//! it's correct you can use this setting to bypass the check. This is typically needed +//! for code that uses `lazy_static` or `thread_local` with `Cell/RefCell` inside. +//! +//! This setting is implied if `#[proc_macro_error]` is applied to a function +//! marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`. +//! +//! ### Diagnostic type +//! +//! [`Diagnostic`] type is intentionally designed to be API compatible with [`proc_macro::Diagnostic`]. +//! Not all API is implemented, only the part that can be reasonably implemented on stable. +//! +//! +//! [`abort!`]: macro.abort.html +//! [`emit_warning!`]: macro.emit_warning.html +//! [`emit_error!`]: macro.emit_error.html +//! [`abort_call_site!`]: macro.abort_call_site.html +//! [`emit_call_site_warning!`]: macro.emit_call_site_error.html +//! [`emit_call_site_error!`]: macro.emit_call_site_warning.html +//! [`diagnostic!`]: macro.diagnostic.html +//! [proc_macro_error]: ./../proc_macro_error_attr/attr.proc_macro_error.html +//! [`Diagnostic`]: struct.Diagnostic.html +//! [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html +//! [unwind safe]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwind-safety +//! [`!`]: https://doc.rust-lang.org/std/primitive.never.html +//! [`()`]: https://doc.rust-lang.org/std/primitive.unit.html + +#![cfg_attr(pme_nightly, feature(proc_macro_diagnostic))] +#![forbid(unsafe_code)] + +// reexports for use in macros +#[doc(hidden)] +pub extern crate proc_macro; +#[doc(hidden)] +pub extern crate proc_macro2; + +pub use self::dummy::set_dummy; +pub use proc_macro_error_attr::proc_macro_error; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use quote::{quote_spanned, ToTokens}; +use std::cell::Cell; +use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; + +pub mod dummy; + +mod macros; + +#[cfg(not(any(pme_nightly, nightly_fmt)))] +#[path = "stable.rs"] +mod imp; + +#[cfg(any(pme_nightly, nightly_fmt))] +#[path = "nightly.rs"] +mod imp; + +/// Represents a diagnostic level +/// +/// # Warnings +/// +/// Warnings are ignored on stable/beta +#[derive(Debug, PartialEq)] +pub enum Level { + Error, + Warning, + #[doc(hidden)] + NonExhaustive, +} + +/// Represents a single diagnostic message +#[derive(Debug)] +pub struct Diagnostic { + level: Level, + span: Span, + msg: String, + suggestions: Vec<(SuggestionKind, String, Option<Span>)>, +} + +/// This traits expands `Result<T, Into<Diagnostic>>` with some handy shortcuts. +pub trait ResultExt { + type Ok; + + /// Behaves like `Result::unwrap`: if self is `Ok` yield the contained value, + /// otherwise abort macro execution via `abort!`. + fn unwrap_or_abort(self) -> Self::Ok; + + /// Behaves like `Result::expect`: if self is `Ok` yield the contained value, + /// otherwise abort macro execution via `abort!`. + /// If it aborts then resulting error message will be preceded with `message`. + fn expect_or_abort(self, msg: &str) -> Self::Ok; +} + +/// This traits expands `Option` with some handy shortcuts. +pub trait OptionExt { + type Some; + + /// Behaves like `Option::expect`: if self is `Some` yield the contained value, + /// otherwise abort macro execution via `abort_call_site!`. + /// If it aborts the `message` will be used for [`compile_error!`][compl_err] invocation. + /// + /// [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html + fn expect_or_abort(self, msg: &str) -> Self::Some; +} + +impl Diagnostic { + /// Create a new diagnostic message that points to `Span::call_site()` + pub fn new(level: Level, message: String) -> Self { + Diagnostic::spanned(Span::call_site(), level, message) + } + + /// Create a new diagnostic message that points to the `span` + pub fn spanned(span: Span, level: Level, message: String) -> Self { + Diagnostic { + level, + span, + msg: message, + suggestions: vec![], + } + } + + /// Attach a "help" note to your main message, note will have it's own span on nightly. + /// + /// # Span + /// + /// The span is ignored on stable, the note effectively inherits its parent's (main message) span + pub fn span_help(mut self, span: Span, msg: String) -> Self { + self.suggestions + .push((SuggestionKind::Help, msg, Some(span))); + self + } + + /// Attach a "help" note to your main message, + pub fn help(mut self, msg: String) -> Self { + self.suggestions.push((SuggestionKind::Help, msg, None)); + self + } + + /// Attach a note to your main message, note will have it's own span on nightly. + /// + /// # Span + /// + /// The span is ignored on stable, the note effectively inherits its parent's (main message) span + pub fn span_note(mut self, span: Span, msg: String) -> Self { + self.suggestions + .push((SuggestionKind::Note, msg, Some(span))); + self + } + + /// Attach a note to your main message + pub fn note(mut self, msg: String) -> Self { + self.suggestions.push((SuggestionKind::Note, msg, None)); + self + } + + /// The message of main warning/error (no notes attached) + pub fn message(&self) -> &str { + &self.msg + } + + /// Abort the proc-macro's execution and display the diagnostic. + /// + /// # Warnings + /// + /// Warnings do not get emitted on stable/beta but this function will abort anyway. + pub fn abort(self) -> ! { + self.emit(); + abort_now() + } + + /// Display the diagnostic while not aborting macro execution. + /// + /// # Warnings + /// + /// Warnings are ignored on stable/beta + pub fn emit(self) { + imp::emit_diagnostic(self); + } +} + +/// Abort macro execution and display all the emitted errors, if any. +/// +/// Does nothing if no errors were emitted (warnings do not count). +pub fn abort_if_dirty() { + imp::abort_if_dirty(); +} + +#[doc(hidden)] +impl Diagnostic { + pub fn span_suggestion(self, span: Span, suggestion: &str, msg: String) -> Self { + match suggestion { + "help" | "hint" => self.span_help(span, msg), + _ => self.span_note(span, msg), + } + } + + pub fn suggestion(self, suggestion: &str, msg: String) -> Self { + match suggestion { + "help" | "hint" => self.help(msg), + _ => self.note(msg), + } + } +} + +impl ToTokens for Diagnostic { + fn to_tokens(&self, ts: &mut TokenStream) { + use std::borrow::Cow; + + fn ensure_lf(buf: &mut String, s: &str) { + if s.ends_with('\n') { + buf.push_str(s); + } else { + buf.push_str(s); + buf.push('\n'); + } + } + + let Diagnostic { + ref msg, + ref suggestions, + ref level, + .. + } = *self; + + if *level == Level::Warning { + return; + } + + let message = if suggestions.is_empty() { + Cow::Borrowed(msg) + } else { + let mut message = String::new(); + ensure_lf(&mut message, msg); + message.push('\n'); + + for (kind, note, _span) in suggestions { + message.push_str(" = "); + message.push_str(kind.name()); + message.push_str(": "); + ensure_lf(&mut message, note); + } + message.push('\n'); + + Cow::Owned(message) + }; + + let span = &self.span; + let msg = syn::LitStr::new(&*message, *span); + ts.extend(quote_spanned!(*span=> compile_error!(#msg); )); + } +} + +impl<T, E: Into<Diagnostic>> ResultExt for Result<T, E> { + type Ok = T; + + fn unwrap_or_abort(self) -> T { + match self { + Ok(res) => res, + Err(e) => e.into().abort(), + } + } + + fn expect_or_abort(self, message: &str) -> T { + match self { + Ok(res) => res, + Err(e) => { + let mut e = e.into(); + e.msg = format!("{}: {}", message, e.msg); + e.abort() + } + } + } +} + +impl<T> OptionExt for Option<T> { + type Some = T; + + fn expect_or_abort(self, message: &str) -> T { + match self { + Some(res) => res, + None => abort_call_site!(message), + } + } +} + +#[derive(Debug)] +enum SuggestionKind { + Help, + Note, +} + +impl SuggestionKind { + fn name(&self) -> &'static str { + match self { + SuggestionKind::Note => "note", + SuggestionKind::Help => "help", + } + } +} + +impl From<syn::Error> for Diagnostic { + fn from(e: syn::Error) -> Self { + Diagnostic::spanned(e.span(), Level::Error, e.to_string()) + } +} + +/// This is the entry point for a proc-macro. +/// +/// **NOT PUBLIC API, SUBJECT TO CHANGE WITHOUT ANY NOTICE** +#[doc(hidden)] +pub fn entry_point<F>(f: F, proc_macro_hack: bool) -> proc_macro::TokenStream +where + F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, +{ + ENTERED_ENTRY_POINT.with(|flag| flag.set(true)); + let caught = catch_unwind(f); + let dummy = dummy::cleanup(); + let err_storage = imp::cleanup(); + ENTERED_ENTRY_POINT.with(|flag| flag.set(false)); + + let mut appendix = TokenStream::new(); + if proc_macro_hack { + appendix.extend(quote! { + #[allow(unused)] + macro_rules! proc_macro_call { + () => ( unimplemented!() ) + } + }); + } + + match caught { + Ok(ts) => { + if err_storage.is_empty() { + ts + } else { + quote!( #(#err_storage)* #dummy #appendix ).into() + } + } + + Err(boxed) => match boxed.downcast::<AbortNow>() { + Ok(_) => quote!( #(#err_storage)* #dummy #appendix ).into(), + Err(boxed) => resume_unwind(boxed), + }, + } +} + +fn abort_now() -> ! { + check_correctness(); + panic!(AbortNow) +} + +thread_local! { + static ENTERED_ENTRY_POINT: Cell<bool> = Cell::new(false); +} + +struct AbortNow; + +fn check_correctness() { + if !ENTERED_ENTRY_POINT.with(|flag| flag.get()) { + panic!( + "proc-macro-error API cannot be used outside of `entry_point` invocation, \ + perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]" + ); + } +} diff --git a/proc-macro-error/proc-macro-error/src/macros.rs b/proc-macro-error/proc-macro-error/src/macros.rs new file mode 100644 index 0000000..117612c --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/macros.rs @@ -0,0 +1,257 @@ +// FIXME: this can be greatly simplified via $()? +// as soon as MRSV hits 1.32 + +/// Build [`Diagnostic`](struct.Diagnostic.html) instance from provided arguments. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! diagnostic { + // from alias + ($err:expr) => { $crate::Diagnostic::from($err) }; + + // span, message, help + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+ ; $($rest:tt)+) => {{ + let diag = $crate::Diagnostic::spanned( + $span.into(), + $level, + format!($fmt, $($args),*) + ); + $crate::__pme__suggestions!(diag $($rest)*); + diag + }}; + + ($span:expr, $level:expr, $msg:expr ; $($rest:tt)+) => {{ + let diag = $crate::Diagnostic::spanned($span.into(), $level, $msg.to_string()); + $crate::__pme__suggestions!(diag $($rest)*); + diag + }}; + + // span, message, no help + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+) => {{ + $crate::Diagnostic::spanned( + $span.into(), + $level, + format!($fmt, $($args),*) + ) + }}; + + ($span:expr, $level:expr, $msg:expr) => {{ + $crate::Diagnostic::spanned($span.into(), $level, $msg.to_string()) + }}; + + + // trailing commas + + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+, ; $($rest:tt)+) => { + $crate::diagnostic!($span, $level, $fmt, $($args),* ; $($rest)*) + }; + ($span:expr, $level:expr, $msg:expr, ; $($rest:tt)+) => { + $crate::diagnostic!($span, $level, $msg ; $($rest)*) + }; + ($span:expr, $level:expr, $fmt:expr, $($args:expr),+,) => { + $crate::diagnostic!($span, $level, $fmt, $($args),*) + }; + ($span:expr, $level:expr, $msg:expr,) => { + $crate::diagnostic!($span, $level, $msg) + }; + // ($err:expr,) => { $crate::diagnostic!($err) }; +} + +/// Abort proc-macro execution right now and display the error. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +#[macro_export] +macro_rules! abort { + ($err:expr) => { + $crate::diagnostic!($err).abort() + }; + + ($span:expr, $($tts:tt)*) => { + $crate::diagnostic!($span, $crate::Level::Error, $($tts)*).abort() + }; +} + +/// Shortcut for `abort!(Span::call_site(), msg...)`. This macro +/// is still preferable over plain panic, panics are not for error reporting. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! abort_call_site { + ($($tts:tt)*) => { + $crate::diagnostic!( + $crate::proc_macro2::Span::call_site(), + $crate::Level::Error, + $($tts)* + ).abort() + }; +} + +/// Emit an error while not aborting the proc-macro right away. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_error { + ($err:expr) => { + $crate::diagnostic!($err).emit() + }; + + ($span:expr, $($tts:tt)*) => {{ + let level = $crate::Level::Error; + $crate::diagnostic!($span, level, $($tts)*).emit() + }}; +} + +/// Shortcut for `emit_error!(Span::call_site(), ...)`. This macro +/// is still preferable over plain panic, panics are not for error reporting.. +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_call_site_error { + ($($tts:tt)*) => { + $crate::diagnostic!( + $crate::proc_macro2::Span()::call_site(), + $crate::Level::Error, + $($tts)* + ).emit() + }; +} + +/// Emit a warning. Warnings are not errors and compilation won't fail because of them. +/// +/// **Does nothing on stable** +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_warning { + ($span:expr, $($tts:tt)*) => { + $crate::diagnostic!($span, $crate::Level::Warning, $($tts)*).emit() + }; +} + +/// Shortcut for `emit_warning!(Span::call_site(), ...)`. +/// +/// **Does nothing on stable** +/// +/// # Syntax +/// +/// See [the guide](index.html#guide). +/// +#[macro_export] +macro_rules! emit_call_site_warning { + ($($tts:tt)*) => {{ + let span = $crate::proc_macro2::Span()::call_site(); + $crate::diagnostic!(span, $crate::Level::Warning, $($tts)*).emit() + }}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __pme__suggestions { + ($var:ident) => (); + + ($var:ident $help:ident =? $msg:expr) => { + let $var = if let Some(msg) = $msg { + $var.suggestion(stringify!($help), msg.to_string()) + } else { + $var + }; + }; + ($var:ident $help:ident =? $span:expr => $msg:expr) => { + let $var = if let Some(msg) = $msg { + $var.span_suggestion($span.into(), stringify!($help), msg.to_string()) + } else { + $var + }; + }; + + ($var:ident $help:ident =? $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help =? $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident =? $span:expr => $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help =? $span => $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + + + ($var:ident $help:ident = $msg:expr) => { + let $var = $var.suggestion(stringify!($help), $msg.to_string()); + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+) => { + let $var = $var.suggestion( + stringify!($help), + format!($fmt, $($args),*) + ); + }; + ($var:ident $help:ident = $span:expr => $msg:expr) => { + let $var = $var.span_suggestion($span.into(), stringify!($help), $msg.to_string()); + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+) => { + let $var = $var.span_suggestion( + $span.into(), + stringify!($help), + format!($fmt, $($args),*) + ); + }; + + ($var:ident $help:ident = $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args),*); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $span:expr => $msg:expr ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $msg); + $crate::__pme__suggestions!($var $($rest)*); + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+ ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*); + $crate::__pme__suggestions!($var $($rest)*); + }; + + // trailing commas + + ($var:ident $help:ident = $msg:expr,) => { + $crate::__pme__suggestions!($var $help = $msg) + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+,) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args)*) + }; + ($var:ident $help:ident = $span:expr => $msg:expr,) => { + $crate::__pme__suggestions!($var $help = $span => $msg) + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),*,) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args)*) + }; + ($var:ident $help:ident = $msg:expr, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $msg; $($rest)*) + }; + ($var:ident $help:ident = $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $fmt, $($args),*; $($rest)*) + }; + ($var:ident $help:ident = $span:expr => $msg:expr, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $msg; $($rest)*) + }; + ($var:ident $help:ident = $span:expr => $fmt:expr, $($args:expr),+, ; $($rest:tt)*) => { + $crate::__pme__suggestions!($var $help = $span => $fmt, $($args),*; $($rest)*) + }; +} diff --git a/proc-macro-error/proc-macro-error/src/nightly.rs b/proc-macro-error/proc-macro-error/src/nightly.rs new file mode 100644 index 0000000..d053c2f --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/nightly.rs @@ -0,0 +1,49 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use proc_macro::{Diagnostic as PDiag, Level as PLevel}; + +use crate::{abort_now, check_correctness, Diagnostic, Level, SuggestionKind}; + +pub fn abort_if_dirty() { + check_correctness(); + if IS_DIRTY.load(Ordering::SeqCst) { + abort_now() + } +} + +pub(crate) fn cleanup() -> Vec<Diagnostic> { + vec![] +} + +pub(crate) fn emit_diagnostic(diag: Diagnostic) { + let Diagnostic { + level, + span, + msg, + suggestions, + } = diag; + + let level = match level { + Level::Warning => PLevel::Warning, + Level::Error => { + IS_DIRTY.store(true, Ordering::SeqCst); + PLevel::Error + } + _ => unreachable!(), + }; + + let mut res = PDiag::spanned(span.unwrap(), level, msg); + + for (kind, msg, span) in suggestions { + res = match (kind, span) { + (SuggestionKind::Note, Some(span)) => res.span_note(span.unwrap(), msg), + (SuggestionKind::Help, Some(span)) => res.span_help(span.unwrap(), msg), + (SuggestionKind::Note, None) => res.note(msg), + (SuggestionKind::Help, None) => res.help(msg), + } + } + + res.emit() +} + +static IS_DIRTY: AtomicBool = AtomicBool::new(false); diff --git a/proc-macro-error/proc-macro-error/src/stable.rs b/proc-macro-error/proc-macro-error/src/stable.rs new file mode 100644 index 0000000..07042d3 --- /dev/null +++ b/proc-macro-error/proc-macro-error/src/stable.rs @@ -0,0 +1,26 @@ +use std::cell::RefCell; + +use crate::{abort_now, check_correctness, Diagnostic, Level}; + +pub fn abort_if_dirty() { + check_correctness(); + ERR_STORAGE.with(|storage| { + if !storage.borrow().is_empty() { + abort_now() + } + }); +} + +pub(crate) fn cleanup() -> Vec<Diagnostic> { + ERR_STORAGE.with(|storage| storage.replace(Vec::new())) +} + +pub(crate) fn emit_diagnostic(diag: Diagnostic) { + if diag.level == Level::Error { + ERR_STORAGE.with(|storage| storage.borrow_mut().push(diag)); + } +} + +thread_local! { + static ERR_STORAGE: RefCell<Vec<Diagnostic>> = RefCell::new(Vec::new()); +} diff --git a/proc-macro-error/test-crate/Cargo.toml b/proc-macro-error/test-crate/Cargo.toml new file mode 100644 index 0000000..96dadc3 --- /dev/null +++ b/proc-macro-error/test-crate/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "test-crate" +version = "0.0.0" +authors = ["CreepySkeleton <creepy-skeleton@yandex.ru>"] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = { path = "../proc-macro-error"} +quote = "1" +proc-macro2 = "1" +syn = {version = "1", default-features = false } + +[dev-dependencies] +trybuild = "1.0" +rustversion = "1.0" +toml = "=0.5.2" # DO NOT BUMP diff --git a/proc-macro-error/test-crate/src/lib.rs b/proc-macro-error/test-crate/src/lib.rs new file mode 100644 index 0000000..2ea9175 --- /dev/null +++ b/proc-macro-error/test-crate/src/lib.rs @@ -0,0 +1,123 @@ +#[macro_use] +extern crate proc_macro_error; +#[macro_use] +extern crate syn; +extern crate proc_macro; + +use proc_macro2::Span; +use proc_macro_error::{set_dummy, Level, OptionExt, ResultExt}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Ident, +}; + +struct IdentOrUnderscore { + span: Span, + part: String, +} + +impl IdentOrUnderscore { + fn new(span: Span, part: String) -> Self { + IdentOrUnderscore { span, part } + } +} + +impl Parse for IdentOrUnderscore { + fn parse(input: ParseStream) -> syn::Result<Self> { + let la = input.lookahead1(); + + if la.peek(Ident) { + let t = input.parse::<Ident>().unwrap(); + Ok(IdentOrUnderscore::new(t.span(), t.to_string())) + } else if la.peek(Token![_]) { + let t = input.parse::<Token![_]>().unwrap(); + Ok(IdentOrUnderscore::new(t.span(), "_".to_string())) + } else { + Err(la.error()) + } + } +} + +struct Args(Vec<IdentOrUnderscore>); + +impl Parse for Args { + fn parse(input: ParseStream) -> syn::Result<Self> { + let args = Punctuated::<_, Token![,]>::parse_terminated(input)?; + Ok(Args(args.into_iter().collect())) + } +} + +#[proc_macro] +#[proc_macro_error] +pub fn make_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let mut name = String::new(); + let input = parse_macro_input!(input as Args); + + for arg in input.0 { + match &*arg.part { + "abort" => abort!( + arg.span, + "abort! 3{} args {}", "+", "test"; + hint = "help {} test", "message" + ), + + "abort_call_site" => abort_call_site!( + "abort_call_site! 2{} args {}", "+", "test"; + help = "help {} test", "message" + ), + + "direct_abort" => { + diagnostic!(arg.span, Level::Error, "direct MacroError::abort() test").abort() + } + + "result_expect" => { + let e = syn::Error::new(arg.span, "error"); + Err(e).expect_or_abort("Result::expect_or_abort() test") + } + + "result_unwrap" => { + let e = syn::Error::new(arg.span, "Result::unwrap_or_abort() test"); + Err(e).unwrap_or_abort() + } + + "option_expect" => None.expect_or_abort("Option::expect_or_abort() test"), + + "need_default" => { + set_dummy(quote! { + impl Default for NeedDefault { + fn default() -> Self { + NeedDefault::A + } + } + }); + + abort!(arg.span, "set_dummy test") + } + + part if part.starts_with("multi") => { + let no_help: Option<String> = Option::None; + let help = Some("Option help test"); + emit_error!( + arg.span, + "multiple error part: {}", part; + note = "help {} test", "message"; + hint =? help; + wow = "I see what you did here..."; + help =? no_help + ) + } + + _ => name.push_str(&arg.part), + } + } + + // test that unrelated panics are not affected + if name.is_empty() { + panic!("unrelated panic test") + } + + let name = Ident::new(&name, Span::call_site()); + quote!( fn #name() {} ).into() +} diff --git a/proc-macro-error/test-crate/tests/macro-errors.rs b/proc-macro-error/test-crate/tests/macro-errors.rs new file mode 100644 index 0000000..8c672eb --- /dev/null +++ b/proc-macro-error/test-crate/tests/macro-errors.rs @@ -0,0 +1,6 @@ +#[rustversion::attr(any(not(stable), before(1.39)), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/proc-macro-error/test-crate/tests/ok.rs b/proc-macro-error/test-crate/tests/ok.rs new file mode 100644 index 0000000..9b6a3d1 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ok.rs @@ -0,0 +1,9 @@ +extern crate test_crate; + +use test_crate::make_fn; + +make_fn!(it, _, works); + +fn main() { + it_works(); +} diff --git a/proc-macro-error/test-crate/tests/ui/abort.rs b/proc-macro-error/test-crate/tests/ui/abort.rs new file mode 100644 index 0000000..717d772 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/abort.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(abort); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/abort.stderr b/proc-macro-error/test-crate/tests/ui/abort.stderr new file mode 100644 index 0000000..7c4e6a0 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/abort.stderr @@ -0,0 +1,8 @@ +error: abort! 3+ args test + + = help: help message test + + --> $DIR/abort.rs:4:10 + | +4 | make_fn!(abort); + | ^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/call_site.rs b/proc-macro-error/test-crate/tests/ui/call_site.rs new file mode 100644 index 0000000..7184cc4 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/call_site.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(abort_call_site); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/call_site.stderr b/proc-macro-error/test-crate/tests/ui/call_site.stderr new file mode 100644 index 0000000..d630a3a --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/call_site.stderr @@ -0,0 +1,8 @@ +error: abort_call_site! 2+ args test + + = help: help message test + + --> $DIR/call_site.rs:4:1 + | +4 | make_fn!(abort_call_site); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation diff --git a/proc-macro-error/test-crate/tests/ui/direct_abort.rs b/proc-macro-error/test-crate/tests/ui/direct_abort.rs new file mode 100644 index 0000000..b5a4c97 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/direct_abort.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(direct_abort); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/direct_abort.stderr b/proc-macro-error/test-crate/tests/ui/direct_abort.stderr new file mode 100644 index 0000000..7cfbae8 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/direct_abort.stderr @@ -0,0 +1,5 @@ +error: direct MacroError::abort() test + --> $DIR/direct_abort.rs:4:10 + | +4 | make_fn!(direct_abort); + | ^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/dummy.rs b/proc-macro-error/test-crate/tests/ui/dummy.rs new file mode 100644 index 0000000..7514fe0 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/dummy.rs @@ -0,0 +1,16 @@ +extern crate test_crate; +use test_crate::make_fn; + +enum NeedDefault { + A, + B +} + +make_fn!(need_default); + +fn main() { + let _ = NeedDefault::default(); +} + + + diff --git a/proc-macro-error/test-crate/tests/ui/dummy.stderr b/proc-macro-error/test-crate/tests/ui/dummy.stderr new file mode 100644 index 0000000..fd531be --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/dummy.stderr @@ -0,0 +1,5 @@ +error: set_dummy test + --> $DIR/dummy.rs:9:10 + | +9 | make_fn!(need_default); + | ^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/multi-error.rs b/proc-macro-error/test-crate/tests/ui/multi-error.rs new file mode 100644 index 0000000..07fbb03 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/multi-error.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(multi1, multi2, _, multi3); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/multi-error.stderr b/proc-macro-error/test-crate/tests/ui/multi-error.stderr new file mode 100644 index 0000000..25174d5 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/multi-error.stderr @@ -0,0 +1,32 @@ +error: multiple error part: multi1 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:10 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ + +error: multiple error part: multi2 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:18 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ + +error: multiple error part: multi3 + + = note: help message test + = help: Option help test + = note: I see what you did here... + + --> $DIR/multi-error.rs:4:29 + | +4 | make_fn!(multi1, multi2, _, multi3); + | ^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs b/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs new file mode 100644 index 0000000..e241c5c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/not_proc_macro.rs @@ -0,0 +1,4 @@ +use proc_macro_error::proc_macro_error; + +#[proc_macro_error] +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr b/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr new file mode 100644 index 0000000..52d6a09 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/not_proc_macro.stderr @@ -0,0 +1,8 @@ +error: #[proc_macro_error] attribute can be used only with a proc-macro + + hint: if you are really sure that #[proc_macro_error] should be applied to this exact function use #[proc_macro_error(allow_not_macro)] + + --> $DIR/not_proc_macro.rs:3:1 + | +3 | #[proc_macro_error] + | ^^^^^^^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/option_expect.rs b/proc-macro-error/test-crate/tests/ui/option_expect.rs new file mode 100644 index 0000000..20288ca --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/option_expect.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(option_expect); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/option_expect.stderr b/proc-macro-error/test-crate/tests/ui/option_expect.stderr new file mode 100644 index 0000000..dd9ecd8 --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/option_expect.stderr @@ -0,0 +1,5 @@ +error: Option::expect_or_abort() test + --> $DIR/option_expect.rs:4:1 + | +4 | make_fn!(option_expect); + | ^^^^^^^^^^^^^^^^^^^^^^^^ in this macro invocation diff --git a/proc-macro-error/test-crate/tests/ui/result_expect.rs b/proc-macro-error/test-crate/tests/ui/result_expect.rs new file mode 100644 index 0000000..a42740b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_expect.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(result_expect); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/result_expect.stderr b/proc-macro-error/test-crate/tests/ui/result_expect.stderr new file mode 100644 index 0000000..c2dd81c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_expect.stderr @@ -0,0 +1,5 @@ +error: Result::expect_or_abort() test: error + --> $DIR/result_expect.rs:4:10 + | +4 | make_fn!(result_expect); + | ^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/result_unwrap.rs b/proc-macro-error/test-crate/tests/ui/result_unwrap.rs new file mode 100644 index 0000000..9b7fb1c --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_unwrap.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(result_unwrap); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr b/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr new file mode 100644 index 0000000..2e614bd --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/result_unwrap.stderr @@ -0,0 +1,5 @@ +error: Result::unwrap_or_abort() test + --> $DIR/result_unwrap.rs:4:10 + | +4 | make_fn!(result_unwrap); + | ^^^^^^^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/unknown_setting.rs b/proc-macro-error/test-crate/tests/ui/unknown_setting.rs new file mode 100644 index 0000000..d8e58ea --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unknown_setting.rs @@ -0,0 +1,4 @@ +use proc_macro_error::proc_macro_error; + +#[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr b/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr new file mode 100644 index 0000000..a55de0b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unknown_setting.stderr @@ -0,0 +1,5 @@ +error: unknown setting `trololo`, expected one of `assert_unwind_safe`, `allow_not_macro`, `proc_macro_hack` + --> $DIR/unknown_setting.rs:3:57 + | +3 | #[proc_macro_error(allow_not_macro, assert_unwind_safe, trololo)] + | ^^^^^^^ diff --git a/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs b/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs new file mode 100644 index 0000000..4863e5b --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unrelated_panic.rs @@ -0,0 +1,6 @@ +extern crate test_crate; +use test_crate::make_fn; + +make_fn!(); + +fn main() {} diff --git a/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr b/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr new file mode 100644 index 0000000..b852cfd --- /dev/null +++ b/proc-macro-error/test-crate/tests/ui/unrelated_panic.stderr @@ -0,0 +1,7 @@ +error: proc macro panicked + --> $DIR/unrelated_panic.rs:4:1 + | +4 | make_fn!(); + | ^^^^^^^^^^^ + | + = help: message: unrelated panic test |