├── .github └── workflows │ ├── docs.yml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── macro │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── tests │ │ ├── test.rs │ │ ├── ui.rs │ │ └── ui │ │ ├── emit.rs │ │ ├── emit.stderr │ │ ├── parse.rs │ │ └── parse.stderr └── no_macro │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── tests │ ├── test.rs │ ├── ui.rs │ └── ui │ ├── emit.rs │ ├── emit.stderr │ ├── parse.rs │ └── parse.stderr ├── macros ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── rustfmt.toml └── src ├── error.rs ├── lib.rs ├── macro_rules.rs ├── parse_to_tokens.rs └── span_ranged.rs /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Git Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: pages 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | docs: 20 | environment: 21 | name: github-pages 22 | url: ${{ steps.deployment.outputs.page_url }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.cargo/ 30 | target/ 31 | key: docs-${{ hashFiles('Cargo.toml') }} 32 | restore-keys: | 33 | docs- 34 | - uses: hecrj/setup-rust-action@v1 35 | with: 36 | rust-version: nightly 37 | - name: Generate Docs (reference docs.rs) 38 | run: | 39 | cargo rustdoc -- --cfg docsrs -Z unstable-options $(cargo metadata --format-version 1 | jq --raw-output '.packages | map("--extern-html-root-url=\(.name)=https://docs.rs/\(.name)/\(.version)") | join(" ")') 40 | - uses: actions/upload-pages-artifact@v1 41 | with: 42 | path: 'target/doc' 43 | - id: deployment 44 | uses: actions/deploy-pages@v1 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: taiki-e/create-gh-release-action@v1 17 | with: 18 | changelog: CHANGELOG.md 19 | branch: main 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: null 5 | pull_request: null 6 | schedule: 7 | - cron: '0 12 * * *' 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - windows-latest 17 | - macos-latest 18 | rust: 19 | - stable 20 | - nightly 21 | include: 22 | - rust: nightly 23 | cargo_flags: -Z minimal-versions 24 | 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: actions/cache@v3 29 | with: 30 | path: | 31 | ~/.cargo/ 32 | target 33 | key: ${{ matrix.os }}-${{ matrix.rust }}-${{ hashFiles('**/Cargo.toml') }} 34 | restore-keys: | 35 | ${{ matrix.os }}-${{ matrix.rust }}- 36 | - uses: hecrj/setup-rust-action@v1 37 | with: 38 | rust-version: ${{ matrix.rust }} 39 | - uses: bruxisma/setup-cargo-hack@v1 40 | with: 41 | cargo-hack-version: "0.5" 42 | - name: Build 43 | run: cargo hack build --feature-powerset ${{ matrix.cargo_flags }} 44 | - name: Test 45 | run: cargo hack test --feature-powerset --all-targets --no-fail-fast --workspace 46 | - name: Doc Test 47 | run: cargo test --all-features --doc --no-fail-fast --workspace 48 | - name: Build Docs 49 | run: cargo doc --all-features --workspace 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - support for syn's `Brace`, `Paren`, `Bracket` to `span_range` 10 | - support for `DelimSpan` to `span_range` 11 | 12 | ## [0.11.4] - 2024-08-25 13 | - Updated `proc-macro-utils` 14 | 15 | ## [0.11.3] - 2024-07-30 16 | ### Added 17 | - implementations of `Add` and `AddAssign` to `manyhow::Error`/`manyhow::ErrorMessage` 18 | 19 | ## [0.11.2] - 2024-07-20 20 | ### Fixed 21 | - adjusted spans for `#[manyhow]` on `use` items to make go-to-definition work better. 22 | 23 | ## [0.11.1] - 2024-03-16 24 | ### Fixed 25 | - fix `item_as_dummy` for attribute macros 26 | 27 | ## [0.11.0] - 2024-01-27 28 | ### Added 29 | - `{Function,Attribute,Derive}MacroHandler`, 30 | 31 | ### Removed 32 | - **Breaking Change** `MacroHandler` was replaced by dedicated traits for each macro type, this probably doesn't affect any usages 33 | 34 | ### Fixed 35 | - `syn::Result` could not be used as return type of macro handlers 36 | 37 | ## [0.10.4] - 2023-11-24 38 | ### Changed 39 | - Allow parsing of types that do not implement `ToTokens` 40 | 41 | ## [0.10.3] - 2023-11-23 42 | ### Added 43 | - `impl SpanRanged for Range` 44 | 45 | ## [0.10.2] - 2023-11-20 46 | ### Added 47 | - `SpanRanged::span_joined` a function to return joined spans on nightly (to replace `SpanRanged::joined`). 48 | 49 | ## [0.10.1] - 2023-11-20 50 | ### Added 51 | - `SpanRanged::joined` a function to return joined spans on nightly. 52 | ## [0.10.0] - 2023-11-13 53 | ### Added 54 | - support `(impl ToTokens, impl ToTokens)` tuples for span range 55 | 56 | ## [0.9.0] - 2023-11-06 57 | ### Added 58 | - support `impl Parse` inputs and `impl ToTokens` outputs. 59 | - added macro alternatives to the `function()`, `derive()` and `attribute()` functions to support `impl Parse/ToTokens`. 60 | 61 | ## [0.8.1] - 2023-09-17 62 | ### Fixed 63 | - `ensure!(let...)` had compile error in its expansion. 64 | 65 | ## [0.8.0] - 2023-09-17 66 | ### Changed 67 | - `ensure!` now supports `let ... = ...` as condition. 68 | 69 | ## [0.7.0] - 2023-09-17 70 | ### Added 71 | - `ensure!` macro. 72 | 73 | ## [0.6.0] - 2023-09-09 74 | ### Added 75 | - Support attribute on use statement of function. 76 | - Support `#[manyhow(proc_macro*)]` to specify proc-macro kind 77 | 78 | ## [0.5.1] - 2023-07-21 79 | Something went wrong with previous release. 80 | 81 | ## [0.5.0] - 2023-07-20 82 | ### Added 83 | - `Emitter::new()` and `Emitter::into_error()` to enable using the Emitter manually. 84 | - Implemented `Extend` for `Emitter` and `Error`. 85 | - Added `emit!` macro for adding errors to `Emitter`. 86 | - Added support for converting `darling::Error` to `manyhow::Error` (available via `darling` feature). 87 | 88 | ### Changed 89 | - **Breaking Change** replaced `Emitter::fail_if_dirty` with `Emitter::into_result`. 90 | 91 | ## [0.4.2] - 2023-05-15 92 | ### Fixed 93 | - `ToTokens`' `SpanRange` conversion should work without `proc_macro`. 94 | 95 | ## [0.4.1] - 2023-05-14 96 | ### Fixed 97 | - `manyhow_macros` version 98 | 99 | ## [0.4.0] - 2023-05-14 100 | ### Added 101 | - `impl_fn` flag to create separate implementation function types. 102 | 103 | ## [0.3.0] - 2023-05-02 104 | ### Added 105 | - `SpanRanged` implementation for `Option`. 106 | 107 | ## [0.2.0] - 2023-04-19 108 | ### Changed 109 | - Moved `Error::join` to `JoinToTokensError` trait. 110 | 111 | ## [0.1.1] - 2023-04-16 112 | Only documentation changes. 113 | 114 | ## [v0.1.0] 115 | **Initial Release** 116 | 117 | [unreleased]: https://github.com/ModProg/manyhow/compare/v0.11.4...HEAD 118 | [0.11.4]: https://github.com/ModProg/manyhow/compare/v0.11.3...v0.11.4 119 | [0.11.3]: https://github.com/ModProg/manyhow/compare/v0.11.2...v0.11.3 120 | [0.11.2]: https://github.com/ModProg/manyhow/compare/v0.11.1...v0.11.2 121 | [0.11.1]: https://github.com/ModProg/manyhow/compare/v0.11.0...v0.11.1 122 | [0.11.0]: https://github.com/ModProg/manyhow/compare/v0.10.4...v0.11.0 123 | [0.10.4]: https://github.com/ModProg/manyhow/compare/v0.10.3...v0.10.4 124 | [0.10.3]: https://github.com/ModProg/manyhow/compare/v0.10.2...v0.10.3 125 | [0.10.2]: https://github.com/ModProg/manyhow/compare/v0.10.1...v0.10.2 126 | [0.10.1]: https://github.com/ModProg/manyhow/compare/v0.10.0...v0.10.1 127 | [0.10.0]: https://github.com/ModProg/manyhow/compare/v0.9.0...v0.10.0 128 | [0.9.0]: https://github.com/ModProg/manyhow/compare/v0.8.1...v0.9.0 129 | [0.8.1]: https://github.com/ModProg/manyhow/compare/v0.8.0...v0.8.1 130 | [0.8.0]: https://github.com/ModProg/manyhow/compare/v0.7.0...v0.8.0 131 | [0.7.0]: https://github.com/ModProg/manyhow/compare/v0.6.0...v0.7.0 132 | [0.6.0]: https://github.com/ModProg/manyhow/compare/v0.5.1...v0.6.0 133 | [0.5.1]: https://github.com/ModProg/manyhow/compare/v0.5.0...v0.5.1 134 | [0.5.0]: https://github.com/ModProg/manyhow/compare/v0.4.2...v0.5.0 135 | [0.4.2]: https://github.com/ModProg/manyhow/compare/v0.4.1...v0.4.2 136 | [0.4.1]: https://github.com/ModProg/manyhow/compare/v0.4.0...v0.4.1 137 | [0.4.0]: https://github.com/ModProg/manyhow/compare/v0.3.0...v0.4.0 138 | [0.3.0]: https://github.com/ModProg/manyhow/compare/v0.2.0...v0.3.0 139 | [0.2.0]: https://github.com/ModProg/manyhow/compare/v0.1.1...v0.2.0 140 | [0.1.1]: https://github.com/ModProg/manyhow/compare/v0.1.0...v0.1.1 141 | [v0.1.0]: https://github.com/ModProg/manyhow/tree/v0.1.0 142 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "manyhow" 3 | version = "0.11.4" 4 | edition = "2021" 5 | categories = ["development-tools::procedural-macro-helpers", "rust-patterns"] 6 | description = "proc macro error handling à la anyhow x proc-macro-error" 7 | keywords = ["proc-macro", "error", "error-handling"] 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | repository = "https://github.com/ModProg/manyhow" 11 | documentation = "https://docs.rs/manyhow" 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [workspace] 15 | members = [ "macros", ".", "examples/macro", "examples/no_macro" ] 16 | 17 | [dependencies] 18 | macros = { package = "manyhow-macros", path = "macros", version = "0.11.4", optional = true} 19 | proc-macro2 = "1.0.60" 20 | quote = "1" 21 | syn1 = { package = "syn", version = "1", default-features = false, optional = true, features = ["printing"] } 22 | syn2 = { package = "syn", version = "2", default-features = false, optional = true, features = ["printing", "parsing"] } 23 | darling_core = { version = "0.20.1", optional = true } 24 | 25 | [features] 26 | default = ["syn", "macros"] 27 | syn = ["syn2"] 28 | darling = ["darling_core"] 29 | 30 | [dev-dependencies] 31 | proc-macro-utils = "0.8.0" 32 | proc-macro2 = { version = "1", features = ["span-locations"] } 33 | syn2 = {package = "syn", version = "2", features = ["full"]} 34 | 35 | [package.metadata.docs.rs] 36 | all-features = true 37 | rustdoc-args = ["--cfg", "docsrs"] 38 | 39 | [package.metadata.release] 40 | shared-version = true 41 | 42 | [[package.metadata.release.pre-release-replacements]] 43 | file = "CHANGELOG.md" 44 | search = '## \[Unreleased\]' 45 | replace = """ 46 | 47 | ## [{{version}}] - {{date}}\ 48 | """ 49 | [[package.metadata.release.pre-release-replacements]] 50 | file = "CHANGELOG.md" 51 | search = '\[unreleased\]: (.*)/(v.*)\.\.\.HEAD' 52 | replace = """ 53 | [unreleased]: $1/{{tag_name}}...HEAD 54 | [{{version}}]: $1/$2...{{tag_name}}\ 55 | """ 56 | -------------------------------------------------------------------------------- /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 peretual, 75 | worldwide, non-exclusive, no-cpharge, 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 it s Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | 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) 2023 Roland Fredenhagen 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 | # manyhow 2 | ## anyhow for proc macros 3 | [![CI Status](https://github.com/ModProg/manyhow/actions/workflows/test.yaml/badge.svg)](https://github.com/ModProg/manyhow/actions/workflows/test.yaml) 4 | [![Crates.io](https://img.shields.io/crates/v/manyhow)](https://crates.io/crates/manyhow) 5 | [![Docs.rs](https://img.shields.io/crates/v/template?color=informational&label=docs.rs)](https://docs.rs/manyhow) 6 | [![Documentation for `main`](https://img.shields.io/badge/docs-main-informational)](https://modprog.github.io/manyhow/manyhow/) 7 | 8 | Proc **m**acro **anyhow**, a combination of ideas from 9 | [`anyhow`](https://docs.rs/anyhow) and 10 | [`proc-macro-error`](https://docs.rs/proc-macro-error) to improve proc macro 11 | development, especially focused on the error handling. 12 | 13 | ## Motivation 14 | Error handling in proc-macros is unideal, as the top level functions of proc 15 | macros can only return `TokenStreams` both in success and failure case. This 16 | means that I often write code like this, moving the actual implementation in 17 | a separate function to be able to use the ergonomic rust error handling with 18 | e.g., `?`. 19 | ```rust 20 | use proc_macro2::TokenStream as TokenStream2; 21 | 22 | #[proc_macro] 23 | pub fn my_macro(input: TokenStream) -> TokenStream { 24 | match actual_implementation(input.into()) { 25 | Ok(output) => output, 26 | Err(error) => error.into_compile_error(), 27 | } 28 | .into() 29 | } 30 | 31 | fn actual_implementation(input: TokenStream2) -> syn::Result { 32 | // .. 33 | } 34 | ``` 35 | 36 | ## Using the `#[manyhow]` macro 37 | To activate the error handling, just add `#[manyhow]` above any 38 | proc macro implementation, reducing the above example to: 39 | 40 | ```rust 41 | use manyhow::manyhow; 42 | use proc_macro2::TokenStream as TokenStream2; 43 | 44 | #[manyhow] 45 | #[proc_macro] 46 | fn my_macro(input: TokenStream2) -> syn::Result { 47 | // .. 48 | } 49 | ``` 50 | 51 | See [Without macros](#without-macros) to see what this expands to under the 52 | hood. 53 | 54 | A proc macro function marked as `#[manyhow]` can take and return any 55 | `TokenStream` and can also return `Result` where `E` implements `ToTokensError`. As additional parameters a 57 | [dummy](#dummy-mut-tokenstream) and/or [emitter](#emitter-mut-emitter) can 58 | be specified. 59 | 60 | The `manyhow` attribute takes one optional flag when used for `proc_macro` 61 | and `proc_macro_attribute`. `#[manyhow(input_as_dummy)]` will take the input 62 | of a function like `proc_macro` to initialize the [dummy `&mut 63 | TokenStream`](#dummy-mut-tokenstream) while `#[manyhow(item_as_dummy)]` on 64 | `proc_macro_attribute` will initialize the dummy with the annotated item. 65 | 66 | ## Without macros 67 | `manyhow` can be used without proc macros, and they can be disabled by 68 | adding `manyhow` with `default-features=false`. 69 | 70 | The usage is more or less the same, though with some added boilerplate from 71 | needing to invoke one of `function`, `attribute` or `derive` 72 | directly. 73 | 74 | While the examples use closures, functions can be passed in as well. The 75 | above example would then change to: 76 | ```rust 77 | use proc_macro2::TokenStream as TokenStream2; 78 | 79 | #[proc_macro] 80 | pub fn my_macro(input: TokenStream) -> TokenStream { 81 | manyhow::function( 82 | input, 83 | false, 84 | |input: TokenStream2| -> syn::Result { 85 | // .. 86 | }, 87 | ) 88 | } 89 | ``` 90 | [`Emitter`](#emitter-mut-emitter) and [dummy 91 | `TokenStream`](#dummy-mut-tokenstream) can also be used. `function` and 92 | `attribute` take an additional boolean parameter controlling whether the 93 | input/item will be used as initial dummy. 94 | 95 | ## `emitter: &mut Emitter` 96 | `MacroHandler`s (the trait defining what closures/functions can be used 97 | with `manyhow`) can take a mutable reference to an `Emitter`. This 98 | allows to collect errors, but not fail immediately. 99 | 100 | `Emitter::into_result` can be used to return if an `Emitter` contains 101 | any values. 102 | 103 | ```rust 104 | use manyhow::{manyhow, Emitter, ErrorMessage}; 105 | use proc_macro2::TokenStream as TokenStream2; 106 | 107 | #[manyhow] 108 | #[proc_macro] 109 | fn my_macro(input: TokenStream2, emitter: &mut Emitter) -> manyhow::Result { 110 | // .. 111 | emitter.emit(ErrorMessage::call_site("A fun error!")); 112 | emitter.into_result()?; 113 | // .. 114 | } 115 | ``` 116 | 117 | ## `dummy: &mut TokenStream` 118 | `MacroHandler`s also take a mutable reference to a `TokenStream`, to 119 | enable emitting some dummy code to be used in case the macro errors. 120 | 121 | This allows either appending tokens e.g., with `ToTokens::to_tokens` or 122 | directly setting the dummy code e.g., `*dummy = quote!{some tokens}`. 123 | -------------------------------------------------------------------------------- /examples/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | manyhow = {path = "../..", default-features = false, features = ["macros", "syn"]} 12 | proc-macro2 = "1" 13 | quote = "1" 14 | syn = { version = "2", features = ["full"] } 15 | 16 | [dev-dependencies] 17 | trybuild = "1.0.80" 18 | 19 | [package.metadata.release] 20 | release = false 21 | -------------------------------------------------------------------------------- /examples/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use manyhow::{bail, manyhow, Emitter, ErrorMessage, Result, SilentError}; 2 | use proc_macro::TokenStream; 3 | use proc_macro2::{Span, TokenStream as TokenStream2}; 4 | use quote::quote; 5 | 6 | type SilentResult = Result; 7 | 8 | #[manyhow(item_as_dummy)] 9 | #[proc_macro_attribute] 10 | pub fn attr_item_as_dummy(_input: TokenStream, _item: TokenStream) -> SilentResult { 11 | Err(SilentError) 12 | } 13 | 14 | #[manyhow(item_as_dummy)] 15 | #[proc_macro_attribute] 16 | pub fn attr_item_as_dummy_ok(_input: TokenStream, _item: TokenStream) -> TokenStream2 { 17 | quote!() 18 | } 19 | 20 | #[manyhow()] 21 | #[proc_macro_attribute] 22 | pub fn attr_no_dummy(_input: TokenStream, _item: TokenStream) -> SilentResult { 23 | Err(SilentError) 24 | } 25 | 26 | #[manyhow()] 27 | #[proc_macro_attribute] 28 | pub fn attr_custom_dummy( 29 | _input: TokenStream, 30 | _item: TokenStream, 31 | dummy: &mut TokenStream2, 32 | ) -> SilentResult { 33 | *dummy = quote! {fn dummy(){}}; 34 | Err(SilentError) 35 | } 36 | 37 | #[manyhow] 38 | #[proc_macro_attribute] 39 | pub fn attr_emit(_: TokenStream, _: TokenStream, emitter: &mut Emitter) -> TokenStream2 { 40 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 41 | quote! {fn output(){}} 42 | } 43 | 44 | #[manyhow(proc_macro_attribute)] 45 | pub fn attr_flag(_input: TokenStream, _item: TokenStream) -> SilentResult { 46 | Err(SilentError) 47 | } 48 | 49 | #[manyhow(proc_macro_attribute, item_as_dummy)] 50 | pub fn attr_flag_dummy(_input: TokenStream, _item: TokenStream) -> SilentResult { 51 | Err(SilentError) 52 | } 53 | 54 | #[manyhow(input_as_dummy)] 55 | #[proc_macro] 56 | pub fn input_as_dummy(_: TokenStream) -> SilentResult { 57 | Err(SilentError) 58 | } 59 | 60 | #[manyhow] 61 | #[proc_macro] 62 | pub fn no_dummy(_: TokenStream) -> SilentResult { 63 | Err(SilentError) 64 | } 65 | 66 | #[manyhow] 67 | #[proc_macro] 68 | pub fn custom_dummy(_: TokenStream, dummy: &mut TokenStream2) -> SilentResult { 69 | *dummy = quote! {fn dummy(){}}; 70 | Err(SilentError) 71 | } 72 | 73 | #[manyhow] 74 | #[proc_macro] 75 | pub fn emit(_t: TokenStream, emitter: &mut Emitter) -> TokenStream2 { 76 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 77 | quote! {fn output(){}} 78 | } 79 | 80 | #[manyhow(proc_macro)] 81 | pub fn flag(_: TokenStream) -> SilentResult { 82 | Err(SilentError) 83 | } 84 | 85 | #[manyhow] 86 | #[proc_macro_derive(NoDummy)] 87 | pub fn derive_no_dummy(_: TokenStream) -> SilentResult { 88 | Err(SilentError) 89 | } 90 | 91 | #[manyhow] 92 | #[proc_macro_derive(Dummy)] 93 | pub fn derive_dummy(_: TokenStream, dummy: &mut TokenStream2) -> SilentResult { 94 | *dummy = quote! {fn dummy(){}}; 95 | Err(SilentError) 96 | } 97 | 98 | #[manyhow] 99 | #[proc_macro_derive(Emit)] 100 | pub fn derive_emit(_: TokenStream, emitter: &mut Emitter) -> TokenStream2 { 101 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 102 | quote! {fn output(){}} 103 | } 104 | 105 | #[manyhow(proc_macro_derive(Flag))] 106 | pub fn derive_flag(_: TokenStream) -> SilentResult { 107 | Err(SilentError) 108 | } 109 | 110 | #[manyhow(impl_fn)] 111 | #[proc_macro] 112 | pub fn impl_fn(input: TokenStream2) -> TokenStream2 { 113 | input 114 | } 115 | 116 | #[test] 117 | fn unit_test() { 118 | assert_eq!(impl_fn_impl(quote!(Hello World)).to_string(), "Hello World"); 119 | } 120 | 121 | #[manyhow(impl_fn, input_as_dummy)] 122 | #[proc_macro] 123 | pub fn impl_fn_with_dummy(input: TokenStream2) -> TokenStream2 { 124 | input 125 | } 126 | 127 | #[test] 128 | fn unit_test_with_dummy() { 129 | assert_eq!( 130 | impl_fn_with_dummy_impl(quote!(Hello World)).to_string(), 131 | "Hello World" 132 | ); 133 | } 134 | 135 | mod module { 136 | use manyhow::SilentError; 137 | use proc_macro2::TokenStream; 138 | 139 | use crate::SilentResult; 140 | 141 | pub fn attr_use(_input: TokenStream, _item: TokenStream) -> SilentResult { 142 | Err(SilentError) 143 | } 144 | } 145 | 146 | #[manyhow(proc_macro_attribute)] 147 | pub use module::attr_use; 148 | 149 | #[manyhow] 150 | #[proc_macro] 151 | pub fn parse_quote(input: syn::LitStr) -> syn::LitStr { 152 | input 153 | } 154 | 155 | #[manyhow(input_as_dummy)] 156 | #[proc_macro] 157 | pub fn parse_quote_dummy(input: syn::DeriveInput) -> syn::DeriveInput { 158 | input 159 | } 160 | 161 | #[manyhow(input_as_dummy)] 162 | #[proc_macro] 163 | pub fn parse_quote_dummy_error(_: TokenStream) -> Result { 164 | bail!("error message") 165 | } 166 | 167 | #[manyhow(input_as_dummy)] 168 | #[proc_macro] 169 | pub fn parse_quote_dummy_error_syn_result(_: TokenStream) -> syn::Result { 170 | bail!("error message") 171 | } 172 | 173 | #[manyhow] 174 | #[proc_macro_attribute] 175 | pub fn parse_quote_attribute(_: syn::LitStr, item: syn::DeriveInput) -> syn::DeriveInput { 176 | item 177 | } 178 | 179 | #[manyhow(item_as_dummy)] 180 | #[proc_macro_attribute] 181 | pub fn parse_quote_dummy_attribute(_: syn::LitStr, item: syn::DeriveInput) -> syn::DeriveInput { 182 | item 183 | } 184 | 185 | #[manyhow(item_as_dummy)] 186 | #[proc_macro_attribute] 187 | pub fn parse_quote_dummy_error_attribute(_: TokenStream, _: TokenStream) -> Result { 188 | bail!("error message") 189 | } 190 | 191 | #[manyhow(item_as_dummy)] 192 | #[proc_macro_attribute] 193 | pub fn parse_quote_dummy_error_attribute_syn_result( 194 | _: TokenStream, 195 | _: TokenStream, 196 | ) -> syn::Result { 197 | bail!("error message") 198 | } 199 | 200 | #[manyhow] 201 | #[proc_macro_derive(ParseQuote)] 202 | pub fn parse_quote_derive(item: syn::ItemStruct) -> syn::ItemStruct { 203 | item 204 | } 205 | 206 | #[manyhow] 207 | #[proc_macro_derive(ParseQuoteSynResult)] 208 | pub fn parse_quote_derive_syn_result(item: syn::ItemStruct) -> syn::Result { 209 | Ok(item) 210 | } 211 | -------------------------------------------------------------------------------- /examples/macro/tests/test.rs: -------------------------------------------------------------------------------- 1 | use example_macro::*; 2 | 3 | #[test] 4 | fn attr() { 5 | #[attr_item_as_dummy] 6 | struct ItemAsDummy; 7 | _ = ItemAsDummy; 8 | 9 | #[attr_item_as_dummy_ok] // does not conflict with above 10 | struct ItemAsDummy; 11 | 12 | #[attr_no_dummy] 13 | struct ItemAsDummy; // does not conflict with above 14 | 15 | #[attr_custom_dummy] 16 | struct ItemAsDummy; // does not conflict with above 17 | dummy(); 18 | 19 | #[parse_quote_attribute("string")] 20 | struct Struct; 21 | _ = Struct; 22 | } 23 | 24 | #[test] 25 | fn function() { 26 | input_as_dummy!( 27 | struct InputAsDummy; 28 | ); 29 | _ = InputAsDummy; 30 | no_dummy!( 31 | // does not conflict with above 32 | struct InputAsDummy; 33 | ); 34 | custom_dummy!( 35 | // does not conflict with above 36 | struct InputAsDummy; 37 | ); 38 | dummy(); 39 | 40 | assert_eq!("hello", parse_quote!("hello")); 41 | } 42 | 43 | #[test] 44 | fn derive() { 45 | #[derive(NoDummy)] 46 | struct NoDummy; 47 | _ = NoDummy; 48 | #[derive(Dummy)] 49 | struct Dummy; 50 | _ = Dummy; 51 | dummy(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/macro/tests/ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /examples/macro/tests/ui/emit.rs: -------------------------------------------------------------------------------- 1 | use example_macro::*; 2 | fn attr() { 3 | #[attr_emit] 4 | struct Struct; 5 | output(); 6 | } 7 | fn function() { 8 | emit!( 9 | struct Struct; 10 | ); 11 | output(); 12 | } 13 | fn derive() { 14 | #[derive(Emit)] 15 | struct Struct; 16 | _ = Struct; 17 | output(); 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /examples/macro/tests/ui/emit.stderr: -------------------------------------------------------------------------------- 1 | error: example error 2 | --> tests/ui/emit.rs:3:5 3 | | 4 | 3 | #[attr_emit] 5 | | ^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `attr_emit` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: example error 10 | --> tests/ui/emit.rs:8:5 11 | | 12 | 8 | / emit!( 13 | 9 | | struct Struct; 14 | 10 | | ); 15 | | |_____^ 16 | | 17 | = note: this error originates in the macro `emit` (in Nightly builds, run with -Z macro-backtrace for more info) 18 | 19 | error: example error 20 | --> tests/ui/emit.rs:14:14 21 | | 22 | 14 | #[derive(Emit)] 23 | | ^^^^ 24 | | 25 | = note: this error originates in the derive macro `Emit` (in Nightly builds, run with -Z macro-backtrace for more info) 26 | -------------------------------------------------------------------------------- /examples/macro/tests/ui/parse.rs: -------------------------------------------------------------------------------- 1 | use example_macro::*; 2 | 3 | #[parse_quote_attribute("hello")] 4 | fn test() {} 5 | 6 | #[parse_quote_attribute(1)] 7 | fn test() {} 8 | 9 | #[parse_quote_attribute] 10 | fn test() {} 11 | 12 | #[parse_quote_dummy_attribute] 13 | fn test_dummy() {} 14 | 15 | #[parse_quote_dummy_error_attribute] 16 | fn test_dummy2() {} 17 | 18 | parse_quote_dummy!( 19 | fn test_dummy3() {} 20 | ); 21 | parse_quote_dummy_error!( 22 | fn test_dummy4() {} 23 | ); 24 | 25 | #[derive(ParseQuote)] 26 | enum NoStruct{} 27 | 28 | fn main() { 29 | // can be resolved through dummy 30 | test_dummy(); 31 | test_dummy2(); 32 | 33 | test_dummy3(); 34 | test_dummy4(); 35 | } 36 | -------------------------------------------------------------------------------- /examples/macro/tests/ui/parse.stderr: -------------------------------------------------------------------------------- 1 | error: expected one of: `struct`, `enum`, `union` 2 | --> tests/ui/parse.rs:4:1 3 | | 4 | 4 | fn test() {} 5 | | ^^ 6 | 7 | error: expected string literal 8 | --> tests/ui/parse.rs:6:25 9 | | 10 | 6 | #[parse_quote_attribute(1)] 11 | | ^ 12 | 13 | error: unexpected end of input, expected string literal 14 | --> tests/ui/parse.rs:9:1 15 | | 16 | 9 | #[parse_quote_attribute] 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 18 | | 19 | = note: this error originates in the attribute macro `parse_quote_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 20 | 21 | error: while parsing attribute argument (`#[... (...)]`) 22 | --> tests/ui/parse.rs:9:1 23 | | 24 | 9 | #[parse_quote_attribute] 25 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 26 | | 27 | = note: this error originates in the attribute macro `parse_quote_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | 29 | error: unexpected end of input, expected string literal 30 | --> tests/ui/parse.rs:12:1 31 | | 32 | 12 | #[parse_quote_dummy_attribute] 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | | 35 | = note: this error originates in the attribute macro `parse_quote_dummy_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 36 | 37 | error: while parsing attribute argument (`#[... (...)]`) 38 | --> tests/ui/parse.rs:12:1 39 | | 40 | 12 | #[parse_quote_dummy_attribute] 41 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | | 43 | = note: this error originates in the attribute macro `parse_quote_dummy_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 44 | 45 | error: error message 46 | --> tests/ui/parse.rs:15:1 47 | | 48 | 15 | #[parse_quote_dummy_error_attribute] 49 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 | | 51 | = note: this error originates in the attribute macro `parse_quote_dummy_error_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 52 | 53 | error: expected one of: `struct`, `enum`, `union` 54 | --> tests/ui/parse.rs:19:5 55 | | 56 | 19 | fn test_dummy3() {} 57 | | ^^ 58 | 59 | error: error message 60 | --> tests/ui/parse.rs:21:1 61 | | 62 | 21 | / parse_quote_dummy_error!( 63 | 22 | | fn test_dummy4() {} 64 | 23 | | ); 65 | | |_^ 66 | | 67 | = note: this error originates in the macro `parse_quote_dummy_error` (in Nightly builds, run with -Z macro-backtrace for more info) 68 | 69 | error: expected `struct` 70 | --> tests/ui/parse.rs:26:1 71 | | 72 | 26 | enum NoStruct{} 73 | | ^^^^ 74 | -------------------------------------------------------------------------------- /examples/no_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-no-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | manyhow = { path = "../..", default-features = false, features = ["syn"] } 12 | proc-macro2 = "1" 13 | quote = "1" 14 | syn = { version = "2", features = ["full"] } 15 | 16 | [dev-dependencies] 17 | trybuild = "1.0.80" 18 | 19 | [package.metadata.release] 20 | release = false 21 | -------------------------------------------------------------------------------- /examples/no_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use manyhow::{attribute, bail, derive, function, Emitter, ErrorMessage, Result, SilentError}; 2 | use proc_macro::TokenStream; 3 | use proc_macro2::{Span, TokenStream as TokenStream2}; 4 | use quote::quote; 5 | 6 | type SilentResult = Result; 7 | 8 | #[proc_macro_attribute] 9 | pub fn attr_item_as_dummy(input: TokenStream, item: TokenStream) -> TokenStream { 10 | attribute( 11 | input, 12 | item, 13 | true, 14 | |_: TokenStream2, _: TokenStream2| -> SilentResult { Err(SilentError) }, 15 | ) 16 | } 17 | 18 | #[proc_macro_attribute] 19 | pub fn attr_no_dummy(input: TokenStream, item: TokenStream) -> TokenStream { 20 | attribute( 21 | input, 22 | item, 23 | false, 24 | |_: TokenStream2, _: TokenStream2| -> SilentResult { Err(SilentError) }, 25 | ) 26 | } 27 | 28 | #[proc_macro_attribute] 29 | pub fn attr_custom_dummy(input: TokenStream, item: TokenStream) -> TokenStream { 30 | attribute( 31 | input, 32 | item, 33 | false, 34 | |_: TokenStream2, _: TokenStream2, dummy: &mut TokenStream2| -> SilentResult { 35 | *dummy = quote! {fn dummy(){}}; 36 | Err(SilentError) 37 | }, 38 | ) 39 | } 40 | 41 | #[proc_macro_attribute] 42 | pub fn attr_emit(input: TokenStream, item: TokenStream) -> TokenStream { 43 | attribute( 44 | input, 45 | item, 46 | false, 47 | |_: TokenStream2, _: TokenStream2, emitter: &mut Emitter| -> TokenStream2 { 48 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 49 | quote! {fn output(){}} 50 | }, 51 | ) 52 | } 53 | 54 | #[proc_macro] 55 | pub fn input_as_dummy(input: TokenStream) -> TokenStream { 56 | function(input, true, |_: TokenStream2| -> SilentResult { 57 | Err(SilentError) 58 | }) 59 | } 60 | 61 | #[proc_macro] 62 | pub fn no_dummy(input: TokenStream) -> TokenStream { 63 | function(input, false, |_: TokenStream2| -> SilentResult { 64 | Err(SilentError) 65 | }) 66 | } 67 | 68 | #[proc_macro] 69 | pub fn custom_dummy(input: TokenStream) -> TokenStream { 70 | function( 71 | input, 72 | false, 73 | |_: TokenStream2, dummy: &mut TokenStream2| -> SilentResult { 74 | *dummy = quote! {fn dummy(){}}; 75 | Err(SilentError) 76 | }, 77 | ) 78 | } 79 | 80 | #[proc_macro] 81 | pub fn emit(input: TokenStream) -> TokenStream { 82 | function( 83 | input, 84 | false, 85 | |_: TokenStream2, emitter: &mut Emitter| -> TokenStream2 { 86 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 87 | quote! {fn output(){}} 88 | }, 89 | ) 90 | } 91 | 92 | #[proc_macro] 93 | pub fn no_closure(input: TokenStream) -> TokenStream { 94 | function(input, true, no_closure_impl) 95 | } 96 | 97 | fn no_closure_impl(_: TokenStream2) -> SilentResult { 98 | Err(SilentError) 99 | } 100 | 101 | #[proc_macro_derive(NoDummy)] 102 | pub fn derive_no_dummy(item: TokenStream) -> TokenStream { 103 | derive(item, |_: TokenStream2| -> SilentResult { Err(SilentError) }) 104 | } 105 | 106 | #[proc_macro_derive(Dummy)] 107 | pub fn derive_dummy(item: TokenStream) -> TokenStream { 108 | derive( 109 | item, 110 | |_: TokenStream2, dummy: &mut TokenStream2| -> SilentResult { 111 | *dummy = quote! {fn dummy(){}}; 112 | Err(SilentError) 113 | }, 114 | ) 115 | } 116 | 117 | #[proc_macro_derive(Emit)] 118 | pub fn derive_emit(item: TokenStream) -> TokenStream { 119 | derive( 120 | item, 121 | |_: TokenStream2, emitter: &mut Emitter| -> TokenStream2 { 122 | emitter.emit(ErrorMessage::new(Span::call_site(), "example error")); 123 | quote! {fn output(){}} 124 | }, 125 | ) 126 | } 127 | 128 | #[proc_macro] 129 | pub fn parse_quote(input: TokenStream) -> TokenStream { 130 | function!(input, |lit: syn::LitStr| lit) 131 | } 132 | 133 | #[proc_macro] 134 | pub fn parse_quote_dummy(input: TokenStream) -> TokenStream { 135 | function!( 136 | #[as_dummy] 137 | input, 138 | |input: syn::DeriveInput| input 139 | ) 140 | } 141 | 142 | #[proc_macro] 143 | pub fn parse_quote_dummy_error(input: TokenStream) -> TokenStream { 144 | function!( 145 | #[as_dummy] 146 | input, 147 | |_: TokenStream| -> Result { bail!("error message") } 148 | ) 149 | } 150 | 151 | #[proc_macro] 152 | pub fn parse_quote_dummy_error_syn_result(input: TokenStream) -> TokenStream { 153 | function!( 154 | #[as_dummy] 155 | input, 156 | |_: TokenStream| -> syn::Result { bail!("error message") } 157 | ) 158 | } 159 | 160 | #[proc_macro_attribute] 161 | pub fn parse_quote_attribute(input: TokenStream, item: TokenStream) -> TokenStream { 162 | attribute!(input, item, |_: syn::LitStr, item: syn::DeriveInput| item) 163 | } 164 | 165 | #[proc_macro_attribute] 166 | pub fn parse_quote_dummy_attribute(input: TokenStream, item: TokenStream) -> TokenStream { 167 | attribute!( 168 | input, 169 | #[as_dummy] 170 | item, 171 | |_: TokenStream, item: syn::DeriveInput| item 172 | ) 173 | } 174 | 175 | #[proc_macro_attribute] 176 | pub fn parse_quote_dummy_error_attribute(input: TokenStream, item: TokenStream) -> TokenStream { 177 | attribute!( 178 | input, 179 | #[as_dummy] 180 | item, 181 | |_: TokenStream, _: TokenStream| -> Result { bail!("error message") } 182 | ) 183 | } 184 | 185 | #[proc_macro_attribute] 186 | pub fn parse_quote_dummy_error_attribute_syn_result( 187 | input: TokenStream, 188 | item: TokenStream, 189 | ) -> TokenStream { 190 | attribute!( 191 | input, 192 | #[as_dummy] 193 | item, 194 | |_: TokenStream, _: TokenStream| -> syn::Result { bail!("error message") } 195 | ) 196 | } 197 | 198 | #[proc_macro_derive(ParseQuote)] 199 | pub fn parse_quote_derive(item: TokenStream) -> TokenStream { 200 | derive!(item, |item: syn::ItemStruct| item) 201 | } 202 | 203 | #[proc_macro_derive(ParseQuoteSynResult)] 204 | pub fn parse_quote_derive_syn_result(item: TokenStream) -> TokenStream { 205 | derive!( 206 | item, 207 | |item: syn::ItemStruct| -> syn::Result { Ok(item) } 208 | ) 209 | } 210 | -------------------------------------------------------------------------------- /examples/no_macro/tests/test.rs: -------------------------------------------------------------------------------- 1 | use example_no_macro::*; 2 | 3 | #[test] 4 | fn attr() { 5 | #[attr_item_as_dummy] 6 | struct ItemAsDummy; 7 | _ = ItemAsDummy; 8 | 9 | #[attr_no_dummy] 10 | struct ItemAsDummy; // does not conflict with above 11 | 12 | #[attr_custom_dummy] 13 | struct ItemAsDummy; // does not conflict with above 14 | dummy(); 15 | 16 | #[parse_quote_attribute("string")] 17 | struct Struct; 18 | _ = Struct; 19 | } 20 | 21 | #[test] 22 | fn function() { 23 | input_as_dummy!( 24 | struct InputAsDummy; 25 | ); 26 | _ = InputAsDummy; 27 | no_dummy!( 28 | // does not conflict with above 29 | struct InputAsDummy; 30 | ); 31 | custom_dummy!( 32 | // does not conflict with above 33 | struct InputAsDummy; 34 | ); 35 | dummy(); 36 | 37 | assert_eq!("hello", parse_quote!("hello")); 38 | } 39 | 40 | #[test] 41 | fn derive() { 42 | #[derive(NoDummy)] 43 | struct NoDummy; 44 | _ = NoDummy; 45 | #[derive(Dummy)] 46 | struct Dummy; 47 | _ = Dummy; 48 | dummy(); 49 | } 50 | -------------------------------------------------------------------------------- /examples/no_macro/tests/ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /examples/no_macro/tests/ui/emit.rs: -------------------------------------------------------------------------------- 1 | use example_no_macro::*; 2 | fn attr() { 3 | #[attr_emit] 4 | struct Struct; 5 | output(); 6 | } 7 | fn function() { 8 | emit!( 9 | struct Struct; 10 | ); 11 | output(); 12 | } 13 | fn derive() { 14 | #[derive(Emit)] 15 | struct Struct; 16 | _ = Struct; 17 | output(); 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /examples/no_macro/tests/ui/emit.stderr: -------------------------------------------------------------------------------- 1 | error: example error 2 | --> tests/ui/emit.rs:3:5 3 | | 4 | 3 | #[attr_emit] 5 | | ^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `attr_emit` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: example error 10 | --> tests/ui/emit.rs:8:5 11 | | 12 | 8 | / emit!( 13 | 9 | | struct Struct; 14 | 10 | | ); 15 | | |_____^ 16 | | 17 | = note: this error originates in the macro `emit` (in Nightly builds, run with -Z macro-backtrace for more info) 18 | 19 | error: example error 20 | --> tests/ui/emit.rs:14:14 21 | | 22 | 14 | #[derive(Emit)] 23 | | ^^^^ 24 | | 25 | = note: this error originates in the derive macro `Emit` (in Nightly builds, run with -Z macro-backtrace for more info) 26 | -------------------------------------------------------------------------------- /examples/no_macro/tests/ui/parse.rs: -------------------------------------------------------------------------------- 1 | use example_no_macro::*; 2 | 3 | #[parse_quote_attribute("hello")] 4 | fn test() {} 5 | 6 | #[parse_quote_attribute] 7 | fn test() {} 8 | 9 | #[parse_quote_dummy_attribute] 10 | fn test_dummy() {} 11 | 12 | #[parse_quote_dummy_error_attribute] 13 | fn test_dummy2() {} 14 | 15 | parse_quote_dummy!( 16 | fn test_dummy3() {} 17 | ); 18 | parse_quote_dummy_error!( 19 | fn test_dummy4() {} 20 | ); 21 | 22 | #[derive(ParseQuote)] 23 | enum NoStruct{} 24 | 25 | fn main() { 26 | // can be resolved through dummy 27 | test_dummy(); 28 | test_dummy2(); 29 | 30 | test_dummy3(); 31 | test_dummy4(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/no_macro/tests/ui/parse.stderr: -------------------------------------------------------------------------------- 1 | error: expected one of: `struct`, `enum`, `union` 2 | --> tests/ui/parse.rs:4:1 3 | | 4 | 4 | fn test() {} 5 | | ^^ 6 | 7 | error: unexpected end of input, expected string literal 8 | --> tests/ui/parse.rs:6:1 9 | | 10 | 6 | #[parse_quote_attribute] 11 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 12 | | 13 | = note: this error originates in the attribute macro `parse_quote_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | error: while parsing attribute argument (`#[... (...)]`) 16 | --> tests/ui/parse.rs:6:1 17 | | 18 | 6 | #[parse_quote_attribute] 19 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 20 | | 21 | = note: this error originates in the attribute macro `parse_quote_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 22 | 23 | error: expected one of: `struct`, `enum`, `union` 24 | --> tests/ui/parse.rs:10:1 25 | | 26 | 10 | fn test_dummy() {} 27 | | ^^ 28 | 29 | error: error message 30 | --> tests/ui/parse.rs:12:1 31 | | 32 | 12 | #[parse_quote_dummy_error_attribute] 33 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | | 35 | = note: this error originates in the attribute macro `parse_quote_dummy_error_attribute` (in Nightly builds, run with -Z macro-backtrace for more info) 36 | 37 | error: expected one of: `struct`, `enum`, `union` 38 | --> tests/ui/parse.rs:16:5 39 | | 40 | 16 | fn test_dummy3() {} 41 | | ^^ 42 | 43 | error: error message 44 | --> tests/ui/parse.rs:18:1 45 | | 46 | 18 | / parse_quote_dummy_error!( 47 | 19 | | fn test_dummy4() {} 48 | 20 | | ); 49 | | |_^ 50 | | 51 | = note: this error originates in the macro `parse_quote_dummy_error` (in Nightly builds, run with -Z macro-backtrace for more info) 52 | 53 | error: expected `struct` 54 | --> tests/ui/parse.rs:23:1 55 | | 56 | 23 | enum NoStruct{} 57 | | ^^^^ 58 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "manyhow-macros" 3 | description = "Macro for manyhow" 4 | version = "0.11.4" 5 | edition = "2021" 6 | categories = ["development-tools::procedural-macro-helpers", "rust-patterns"] 7 | keywords = ["proc-macro", "error", "error-handling"] 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | repository = "https://github.com/ModProg/manyhow" 11 | documentation = "https://docs.rs/manyhow" 12 | 13 | [lib] 14 | proc-macro = true 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | proc-macro-utils = "0.10.0" 19 | proc-macro2 = "1" 20 | quote = "1" 21 | 22 | [package.metadata.release] 23 | tag = false 24 | shared-version = true 25 | -------------------------------------------------------------------------------- /macros/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Roland Fredenhagen 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 | -------------------------------------------------------------------------------- /macros/README.md: -------------------------------------------------------------------------------- 1 | # manyhow Macros 2 | 3 | This crate provides the [`#[manyhow]`](https://docs.rs/manyhow/latest/manyhow/attr.manyhow.html) attribute macro. 4 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Write}; 2 | use std::mem; 3 | 4 | use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree}; 5 | use proc_macro_utils::{Delimited, TokenStream2Ext, TokenStreamExt, TokenTree2Ext, TokenTreePunct}; 6 | use quote::{format_ident, quote, quote_spanned, ToTokens}; 7 | 8 | #[derive(PartialEq, Eq, Clone, Copy)] 9 | enum ProcMacroType { 10 | Function, 11 | Derive, 12 | Attribute, 13 | } 14 | impl ProcMacroType { 15 | fn to_signature(self, tokens: &mut TokenStream) { 16 | match self { 17 | ProcMacroType::Function | ProcMacroType::Derive => quote! { 18 | (__input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream 19 | }, 20 | ProcMacroType::Attribute => quote! { 21 | (__input: ::proc_macro::TokenStream, __item: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream 22 | }, 23 | } 24 | .to_tokens(tokens) 25 | } 26 | 27 | fn dummy_flag(self) -> &'static str { 28 | match self { 29 | ProcMacroType::Function => "input_as_dummy", 30 | ProcMacroType::Derive => "", 31 | ProcMacroType::Attribute => "item_as_dummy", 32 | } 33 | } 34 | } 35 | impl ProcMacroType { 36 | fn to_tokens(self, impl_path: TokenStream, as_dummy: bool) -> TokenStream { 37 | let mut as_dummy = if as_dummy { 38 | quote!(#[as_dummy]) 39 | } else { 40 | quote!() 41 | }; 42 | 43 | let fn_name = match self { 44 | ProcMacroType::Function => quote!(function), 45 | ProcMacroType::Derive => quote!(derive), 46 | ProcMacroType::Attribute => quote!(attribute), 47 | }; 48 | 49 | let item = if self == ProcMacroType::Attribute { 50 | let as_dummy = mem::take(&mut as_dummy); 51 | quote!(, #as_dummy __item) 52 | } else { 53 | quote!() 54 | }; 55 | quote! { 56 | ::manyhow::#fn_name!(#as_dummy __input #item, #impl_path) 57 | } 58 | } 59 | } 60 | 61 | enum Param { 62 | Flag(Ident), 63 | Complex(Ident, Group), 64 | } 65 | 66 | impl Param { 67 | fn span(&self) -> Span { 68 | self.ident().span() 69 | } 70 | 71 | fn ident(&self) -> &Ident { 72 | let (Param::Flag(ident) | Param::Complex(ident, _)) = self; 73 | ident 74 | } 75 | } 76 | 77 | impl Display for Param { 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 | match self { 80 | Param::Flag(ident) => ident.fmt(f), 81 | Param::Complex(ident, tokens) => ident.fmt(f).and(tokens.fmt(f)), 82 | } 83 | } 84 | } 85 | 86 | /// Attribute macro to remove boilerplate from proc macro entry points. 87 | /// 88 | /// See [the documentation at the crate root for more 89 | /// details](https://docs.rs/manyhow#using-the-manyhow-macro). 90 | #[proc_macro_attribute] 91 | pub fn manyhow( 92 | input: proc_macro::TokenStream, 93 | item: proc_macro::TokenStream, 94 | ) -> proc_macro::TokenStream { 95 | let mut parser = item.clone().parser(); 96 | let mut output = TokenStream::default(); 97 | 98 | // For now, we will keep all attributes on the outer function 99 | let mut kind = None; 100 | let mut kind_attribute = None; 101 | let mut set_kind = |ident: &Ident, create_attribute: bool| { 102 | let new_kind = match ident.to_string().as_str() { 103 | "proc_macro" => ProcMacroType::Function, 104 | "proc_macro_attribute" => ProcMacroType::Attribute, 105 | "proc_macro_derive" => ProcMacroType::Derive, 106 | _ => return Ok(()), 107 | }; 108 | if let Some((_, span)) = kind { 109 | Err(with_helpful_error( 110 | with_helpful_error( 111 | item.clone(), 112 | span, 113 | "proc_macro kind specified multiple times", 114 | "try removing this", 115 | ), 116 | ident.span(), 117 | "proc_macro kind specified multiple times", 118 | "try removing this", 119 | )) 120 | } else { 121 | kind = Some((new_kind, ident.span())); 122 | if create_attribute { 123 | kind_attribute = Some(quote!(#[#ident])); 124 | } 125 | Ok(()) 126 | } 127 | }; 128 | while let Some(pound) = parser.next_tt_pound() { 129 | output.extend(pound); 130 | let attribute_content = parser 131 | .next_bracketed() 132 | .expect("rust should only allow valid attributes"); 133 | let ident = attribute_content 134 | .stream() 135 | .parser() 136 | .next_ident() 137 | .expect("rust should only allow valid attributes"); 138 | output.push(attribute_content.into()); 139 | if let Err(err) = set_kind(&ident, false) { 140 | return err; 141 | } 142 | } 143 | 144 | let mut flags = Vec::new(); 145 | let mut input = input.parser(); 146 | while !input.is_empty() { 147 | let Some(ident) = input.next_ident() else { 148 | return with_helpful_error( 149 | item, 150 | input.next().unwrap().span(), 151 | "manyhow expects a comma seperated list of flags", 152 | format_args!("try `#[manyhow(impl_fn)]`"), 153 | ); 154 | }; 155 | if ident == "proc_macro_derive" { 156 | let Some(group) = input.next_group() else { 157 | return with_helpful_error( 158 | item, 159 | input.next().unwrap_or(ident.into()).span(), 160 | "`proc_macro_derive` expects `(TraitName)`", 161 | format_args!("try `#[manyhow(proc_macro_derive(YourTraitName))]`"), 162 | ); 163 | }; 164 | // We set it manually here 165 | if let Err(error) = set_kind(&ident, false) { 166 | return error; 167 | } 168 | quote!(#[#ident #group]).to_tokens(&mut output); 169 | flags.push(Param::Complex(ident, group)); 170 | } else { 171 | if let Err(error) = set_kind(&ident, true) { 172 | return error; 173 | } 174 | flags.push(Param::Flag(ident)); 175 | } 176 | // This technically allows `flag flag flag` but it's fine IMO 177 | _ = input.next_tt_comma(); 178 | } 179 | 180 | output.extend(kind_attribute); 181 | 182 | let Some((kind, _)) = kind else { 183 | return with_helpful_error( 184 | item, 185 | Span::call_site(), 186 | "expected proc_macro* attribute below `#[manyhow]` or a flag as parameter of the \ 187 | attribute", 188 | "try adding `#[proc_macro]`, `#[proc_macro_attribute]`, or `#[proc_macro_derive]` \ 189 | below `#[manyhow]` or adding a flag to `#[manyhow]`, i.e., `#[manyhow(proc_macro)]`, \ 190 | `#[manyhow(proc_macro_attribute)]` or `#[manyhow(proc_macro_derive)]` ", 191 | ); 192 | }; 193 | 194 | let flags_replace = |i: usize, replacement: Option<&str>| { 195 | let mut flags = flags.iter().map(ToString::to_string).collect::>(); 196 | if let Some(replacement) = replacement { 197 | replacement.clone_into(&mut flags[i]); 198 | } else { 199 | flags.remove(i); 200 | } 201 | if flags.is_empty() { 202 | "".to_owned() 203 | } else { 204 | format!("({})", flags.join(", ")) 205 | } 206 | }; 207 | 208 | let mut as_dummy = false; 209 | let mut create_impl_fn = None; 210 | for (i, param) in flags.iter().enumerate() { 211 | let ident = param.ident(); 212 | match (ident.to_string().as_str(), kind) { 213 | ("impl_fn", _) => create_impl_fn = Some((param.ident(), i)), 214 | ("item_as_dummy", ProcMacroType::Attribute) => as_dummy = true, 215 | ("item_as_dummy", ProcMacroType::Function) => { 216 | return with_helpful_error( 217 | item, 218 | param.span(), 219 | format_args!( 220 | "`item_as_dummy` is only supported with `#[proc_macro_attribute]`" 221 | ), 222 | format_args!( 223 | "try `#[manyhow{}]` instead", 224 | flags_replace(i, Some("input_as_dummy")) 225 | ), 226 | ); 227 | } 228 | ("input_as_dummy", ProcMacroType::Function) => as_dummy = true, 229 | ("input_as_dummy", ProcMacroType::Attribute) => { 230 | return with_helpful_error( 231 | item, 232 | param.span(), 233 | format_args!("`input_as_dummy` is only supported with `#[proc_macro]`"), 234 | format_args!( 235 | "try `#[manyhow{}]` instead", 236 | flags_replace(i, Some("item_as_dummy")) 237 | ), 238 | ); 239 | } 240 | ("input_as_dummy" | "item_as_dummy", ProcMacroType::Derive) => { 241 | return with_helpful_error( 242 | item, 243 | param.span(), 244 | format_args!( 245 | "only `#[proc_macro]` and `#[proc_macro_attribute]` support `*_as_dummy` \ 246 | flags" 247 | ), 248 | format_args!("try `#[manyhow{}]` instead", flags_replace(i, None)), 249 | ); 250 | } 251 | ("proc_macro" | "proc_macro_attribute" | "proc_macro_derive", _) => {} 252 | _ => { 253 | return with_helpful_error( 254 | item, 255 | param.span(), 256 | format_args!( 257 | "only `proc_macro`, `proc_macro_attribute`, `proc_macro_derive`, `{}`, \ 258 | and `impl_fn` are supported", 259 | kind.dummy_flag(), 260 | ), 261 | format_args!("try `#[manyhow{}]", flags_replace(i, None)), 262 | ); 263 | } 264 | } 265 | } 266 | // All attributes are parsed now there should only be a public function 267 | 268 | // vis 269 | output.extend(parser.next_if(|tt| matches!(tt, TokenTree::Ident(ident) if ident == "pub"))); 270 | 271 | let outer_impl_fn: Option; 272 | let impl_fn_path: TokenStream; 273 | let inner_impl_fn: Option; 274 | 275 | // we support both use and fn 276 | if parser.next_keyword("use").is_some() { 277 | if let Some((ident, i)) = create_impl_fn { 278 | return with_helpful_error( 279 | item, 280 | ident.span(), 281 | "`impl_fn` is not supported on use statements", 282 | format_args!("try `#[manyhow{}]", flags_replace(i, None)), 283 | ); 284 | } 285 | 286 | let mut path = parser.collect::>(); 287 | assert!( 288 | path.pop().as_ref().is_some_and(TokenTreePunct::is_semi), 289 | "use statement should end with semi" 290 | ); 291 | 292 | let fn_name = path 293 | .last() 294 | .expect("use statement should contain at least on item"); 295 | 296 | let Some(mut fn_name) = fn_name.ident().cloned() else { 297 | return with_helpful_error( 298 | item, 299 | fn_name.span(), 300 | "only use statements for a single function, i.e., `use ...::fn_name;` are \ 301 | supported", 302 | "try splitting the use statment", 303 | ); 304 | }; 305 | 306 | fn_name.set_span(Span::call_site()); 307 | 308 | quote!(fn #fn_name).to_tokens(&mut output); 309 | impl_fn_path = path.into_iter().collect(); 310 | 311 | outer_impl_fn = None; 312 | inner_impl_fn = None; 313 | } else { 314 | // fn 315 | output.push(match parser.next() { 316 | Some(TokenTree::Ident(ident)) if ident == "fn" => ident.into(), 317 | token => { 318 | return with_error( 319 | item, 320 | token.as_ref().map_or_else(Span::call_site, TokenTree::span), 321 | "expected function", 322 | ); 323 | } 324 | }); 325 | let Some(fn_name) = parser.next_ident() else { 326 | return with_error( 327 | item, 328 | parser 329 | .next() 330 | .as_ref() 331 | .map_or_else(Span::call_site, TokenTree::span), 332 | "expected function name", 333 | ); 334 | }; 335 | // function name 336 | fn_name.to_tokens(&mut output); 337 | 338 | // there should not be any generics 339 | match parser.next_tt_lt() { 340 | None => {} 341 | Some(lt) => { 342 | return with_error( 343 | item, 344 | lt.into_iter().next().unwrap().span(), 345 | "proc macros cannot have generics", 346 | ); 347 | } 348 | } 349 | // (...) 350 | let params = parser.next_group().expect("params"); 351 | // -> 352 | let Some(arrow) = parser.next_tt_r_arrow() else { 353 | return with_helpful_error( 354 | item, 355 | params.span_close(), 356 | "expected return type", 357 | "try adding either `-> TokenStream` or `-> manyhow::Result`", 358 | ); 359 | }; 360 | // return type 361 | let ret_ty = parser 362 | .next_until(|tt| tt.is_braced()) 363 | .expect("return type after ->"); 364 | // {...} 365 | let body = parser.next_group().expect("body"); 366 | assert!(parser.is_empty(), "no tokens after function body"); 367 | 368 | if create_impl_fn.is_some() { 369 | impl_fn_path = format_ident!("{fn_name}_impl").to_token_stream(); 370 | outer_impl_fn = Some(quote!(fn #impl_fn_path #params #arrow #ret_ty #body)); 371 | inner_impl_fn = None; 372 | } else { 373 | impl_fn_path = fn_name.to_token_stream(); 374 | inner_impl_fn = Some(quote!(fn #fn_name #params #arrow #ret_ty #body)); 375 | outer_impl_fn = None; 376 | } 377 | } 378 | 379 | kind.to_signature(&mut output); 380 | 381 | let kind = kind.to_tokens(impl_fn_path, as_dummy); 382 | 383 | quote! { 384 | { 385 | #inner_impl_fn 386 | #kind 387 | } 388 | } 389 | .to_tokens(&mut output); 390 | 391 | outer_impl_fn.to_tokens(&mut output); 392 | output.into() 393 | } 394 | 395 | fn with_error( 396 | item: proc_macro::TokenStream, 397 | span: Span, 398 | error: impl Display, 399 | ) -> proc_macro::TokenStream { 400 | let mut item = item.into(); 401 | self::error(span, error).to_tokens(&mut item); 402 | item.into() 403 | } 404 | 405 | fn with_helpful_error( 406 | item: proc_macro::TokenStream, 407 | span: Span, 408 | error: impl Display, 409 | help: impl Display, 410 | ) -> proc_macro::TokenStream { 411 | let mut item = item.into(); 412 | self::error_help(span, error, help).to_tokens(&mut item); 413 | item.into() 414 | } 415 | 416 | fn error(span: Span, error: impl Display) -> TokenStream { 417 | let error = error.to_string(); 418 | quote_spanned! {span=> 419 | ::core::compile_error!{ #error } 420 | } 421 | } 422 | 423 | fn error_help(span: Span, error: impl Display, help: impl Display) -> TokenStream { 424 | let mut error = error.to_string(); 425 | write!(error, "\n\n = help: {help}").unwrap(); 426 | quote_spanned! {span=> 427 | ::core::compile_error!{ #error } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | format_code_in_doc_comments = true 3 | format_macro_matchers = true 4 | format_strings = true 5 | hex_literal_case = "Upper" 6 | imports_granularity = "Module" 7 | normalize_comments = true 8 | normalize_doc_attributes = true 9 | overflow_delimited_expr = true 10 | reorder_impl_items = true 11 | group_imports = "StdExternalCrate" 12 | unstable_features = true 13 | use_field_init_shorthand = true 14 | version = "Two" 15 | wrap_comments = true 16 | single_line_let_else_max_width=80 17 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_errors_doc)] 2 | use std::convert::Infallible; 3 | use std::fmt::{Debug, Display}; 4 | use std::mem; 5 | use std::ops::{Add, AddAssign, Range}; 6 | 7 | #[cfg(feature = "darling")] 8 | use darling_core::Error as DarlingError; 9 | use proc_macro2::{Span, TokenStream}; 10 | use quote::{quote_spanned, ToTokens}; 11 | #[cfg(feature = "syn1")] 12 | use syn1::Error as Syn1Error; 13 | #[cfg(feature = "syn2")] 14 | use syn2::Error as Syn2Error; 15 | 16 | #[cfg(doc)] 17 | use crate::MacroOutput; 18 | use crate::{to_tokens_span_range, SpanRanged}; 19 | 20 | /// An alias for [`Result`](std::result::Result) suited for use with this crate 21 | pub type Result = std::result::Result; 22 | 23 | /// Error that does not expand to any [`compile_error!`] and therefor does not 24 | /// cause compilation to fail. 25 | #[derive(Debug)] 26 | pub struct SilentError; 27 | 28 | /// This crates Error type 29 | #[derive(Debug)] 30 | #[must_use] 31 | pub struct Error(Vec>); 32 | #[cfg(feature = "syn1")] 33 | impl From for Error { 34 | fn from(error: Syn1Error) -> Self { 35 | Self::from(error) 36 | } 37 | } 38 | #[cfg(feature = "syn2")] 39 | impl From for Error { 40 | fn from(error: Syn2Error) -> Self { 41 | Self::from(error) 42 | } 43 | } 44 | #[cfg(feature = "darling")] 45 | impl From for Error { 46 | fn from(error: DarlingError) -> Self { 47 | Self::from(error) 48 | } 49 | } 50 | impl From for Error { 51 | fn from(error: ErrorMessage) -> Self { 52 | Self::from(error) 53 | } 54 | } 55 | impl From for Error { 56 | fn from(_: SilentError) -> Self { 57 | Self(Vec::new()) 58 | } 59 | } 60 | 61 | impl Add for Error { 62 | type Output = Error; 63 | 64 | fn add(self, rhs: T) -> Self::Output { 65 | self.join(rhs) 66 | } 67 | } 68 | 69 | impl AddAssign for Error { 70 | fn add_assign(&mut self, rhs: T) { 71 | self.push(rhs); 72 | } 73 | } 74 | 75 | impl Error { 76 | /// Mimics [`From for Error`](From) implementation to 77 | /// not conflict std's `From for T` 78 | pub fn from(error: impl ToTokensError + 'static) -> Self { 79 | Self(vec![Box::new(error)]) 80 | } 81 | 82 | /// Pushes an additional `Error` 83 | /// 84 | /// Alternatively errors can also be "added": 85 | /// 86 | /// ``` 87 | /// use manyhow::{error_message, Error}; 88 | /// let mut error = Error::from(error_message!("Hello Rust!")); 89 | /// error += error_message!("Hello 🦀!"); 90 | /// # use manyhow::ToTokensError; 91 | /// # proc_macro_utils::assert_tokens!(error.into_token_stream(), 92 | /// # { ::core::compile_error!{"Hello Rust!"} ::core::compile_error!{"Hello 🦀!"} }); 93 | /// ``` 94 | pub fn push(&mut self, error: impl ToTokensError + 'static) { 95 | self.0.push(Box::new(error)); 96 | } 97 | } 98 | 99 | impl Extend for Error { 100 | fn extend>(&mut self, iter: T) { 101 | self.0.extend( 102 | iter.into_iter() 103 | .map(|i| Box::new(i) as Box), 104 | ); 105 | } 106 | } 107 | 108 | /// A single error message 109 | /// 110 | /// Can take additional attachments like [`help`](Self::help) or 111 | /// [`note`](Self::note). 112 | /// 113 | /// Implements `ToTokensError` and can therefore be used with 114 | /// [`MacroOutput`]s. 115 | #[derive(Debug)] 116 | #[must_use] 117 | pub struct ErrorMessage { 118 | span: Range, 119 | msg: String, 120 | attachments: Vec<(&'static str, String)>, 121 | } 122 | impl Display for ErrorMessage { 123 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 124 | write!(f, "{}", self.msg.trim_end())?; 125 | if !self.attachments.is_empty() { 126 | write!(f, "\n\n")?; 127 | } 128 | for (label, attachment) in &self.attachments { 129 | let mut attachment = attachment.lines(); 130 | writeln!( 131 | f, 132 | " = {label}: {}", 133 | attachment.next().expect("should return at least one line") 134 | )?; 135 | for line in attachment { 136 | // `labels` should always be one char per cell 137 | writeln!(f, " {1:2$} {}", line, "", label.len())?; 138 | } 139 | } 140 | Ok(()) 141 | } 142 | } 143 | impl ToTokensError for ErrorMessage { 144 | fn to_tokens(&self, tokens: &mut TokenStream) { 145 | let msg = self.to_string(); 146 | let msg = quote_spanned!(self.span.end => {#msg}); 147 | quote_spanned! {self.span.start => 148 | ::core::compile_error! #msg 149 | } 150 | .to_tokens(tokens); 151 | } 152 | } 153 | 154 | #[cfg(feature = "syn1")] 155 | impl From for Syn1Error { 156 | fn from(value: ErrorMessage) -> Self { 157 | Self::new_spanned(value.to_token_stream(), value) 158 | } 159 | } 160 | #[cfg(feature = "syn2")] 161 | impl From for Syn2Error { 162 | fn from(value: ErrorMessage) -> Self { 163 | Self::new_spanned(value.to_token_stream(), value) 164 | } 165 | } 166 | 167 | impl Add for ErrorMessage { 168 | type Output = Error; 169 | 170 | fn add(self, rhs: T) -> Self::Output { 171 | self.join(rhs) 172 | } 173 | } 174 | 175 | impl ErrorMessage { 176 | /// Creates a new error message at the specified span 177 | /// 178 | /// This function takes a [`SpanRanged`] meaning you can also pass a 179 | /// [`Range`](Range)[``](Span) (i.e. `first..last`) for better error 180 | /// messages on multi token values, for details see 181 | /// [SpanRanged#motivation](SpanRanged#motivation) 182 | /// 183 | /// If your type implements [`ToTokens`] use [`ErrorMessage::spanned`] 184 | /// instead. 185 | pub fn new(span: impl SpanRanged, msg: impl Display) -> Self { 186 | Self { 187 | span: span.span_range(), 188 | msg: msg.to_string(), 189 | attachments: Vec::new(), 190 | } 191 | } 192 | 193 | /// Creates an error message pointing to the complete token stream `tokens` 194 | /// expands to 195 | pub fn spanned(tokens: impl ToTokens, msg: impl Display) -> Self { 196 | Self { 197 | span: to_tokens_span_range(tokens), 198 | msg: msg.to_string(), 199 | attachments: Vec::new(), 200 | } 201 | } 202 | 203 | /// Creates a new error message at [`Span::call_site`] prefer 204 | /// [`ErrorMessage::new`] or [`ErrorMessage::spanned`] with the correct span 205 | /// for a more helpful output. 206 | pub fn call_site(msg: impl Display) -> Self { 207 | Self::new(Span::call_site(), msg) 208 | } 209 | 210 | /// Attaches an additional message to `self` reusing the same 211 | /// span, and the specified `label`. 212 | pub fn attachment(mut self, label: &'static str, msg: impl Display) -> Self { 213 | self.attachments.push((label, msg.to_string())); 214 | self 215 | } 216 | 217 | /// Attaches a new `error` message to `self` reusing the same span 218 | pub fn error(self, msg: impl Display) -> Self { 219 | self.attachment("error", msg) 220 | } 221 | 222 | /// Attaches a new `warning` message to `self` reusing the same span 223 | pub fn warning(self, msg: impl Display) -> Self { 224 | self.attachment("warning", msg) 225 | } 226 | 227 | /// Attaches a new `note` message to `self` reusing the same span 228 | pub fn note(self, msg: impl Display) -> Self { 229 | self.attachment("note", msg) 230 | } 231 | 232 | /// Attaches a new `help` message to `self` reusing the same span 233 | pub fn help(self, msg: impl Display) -> Self { 234 | self.attachment("help", msg) 235 | } 236 | } 237 | 238 | /// Exposes [`ErrorMessage::attachment`] as a trait to allow 239 | /// [`ResultExt::attachment`]. 240 | pub trait Attachment: Sized { 241 | /// Attaches an additional message to `self` reusing the same 242 | /// span, and the specified `label`. 243 | #[must_use] 244 | fn attachment(self, label: &'static str, msg: impl Display) -> Self; 245 | } 246 | 247 | impl Attachment for ErrorMessage { 248 | fn attachment(mut self, label: &'static str, msg: impl Display) -> Self { 249 | self.attachments.push((label, msg.to_string())); 250 | self 251 | } 252 | } 253 | 254 | /// Allows emitting errors without returning. 255 | #[derive(Default, Debug)] 256 | pub struct Emitter(Vec>); 257 | 258 | impl AddAssign for Emitter { 259 | fn add_assign(&mut self, rhs: T) { 260 | self.emit(rhs); 261 | } 262 | } 263 | 264 | impl Emitter { 265 | /// Creates an `Emitter`, this can be used to collect errors than can later 266 | /// be converted with [`Emitter::into_result()`]. 267 | #[must_use] 268 | pub fn new() -> Self { 269 | Emitter(Vec::new()) 270 | } 271 | 272 | pub(crate) fn to_tokens(&self, tokens: &mut TokenStream) { 273 | for error in &self.0 { 274 | error.to_tokens(tokens); 275 | } 276 | } 277 | 278 | /// Emits an error 279 | /// 280 | /// Alternatively errors can also be "added": 281 | /// 282 | /// ``` 283 | /// let mut emitter = manyhow::Emitter::new(); 284 | /// emitter += manyhow::error_message!("Hello World!"); 285 | /// # use manyhow::ToTokensError; 286 | /// # proc_macro_utils::assert_tokens!(emitter.into_result().unwrap_err().into_token_stream(), { ::core::compile_error!{"Hello World!"} }); 287 | /// ``` 288 | pub fn emit(&mut self, error: impl ToTokensError + 'static) { 289 | self.0.push(Box::new(error)); 290 | } 291 | 292 | /// Checks if any errors were emitted 293 | #[must_use] 294 | pub fn is_empty(&self) -> bool { 295 | self.0.is_empty() 296 | } 297 | 298 | /// Removes all emitted errors 299 | pub fn clear(&mut self) { 300 | self.0.clear(); 301 | } 302 | 303 | /// Returns emitted errors if not [`Self::is_empty`]. 304 | /// 305 | /// If no errors where emitted, returns `Ok(())`. 306 | /// 307 | /// *Note:* This clears the emitter to avoid returning duplicate errors. 308 | pub fn into_result(&mut self) -> Result<(), Error> { 309 | if self.is_empty() { 310 | Ok(()) 311 | } else { 312 | Err(Error(mem::take(&mut self.0))) 313 | } 314 | } 315 | } 316 | 317 | impl Extend for Emitter { 318 | fn extend>(&mut self, iter: T) { 319 | self.0.extend( 320 | iter.into_iter() 321 | .map(|i| Box::new(i) as Box), 322 | ); 323 | } 324 | } 325 | 326 | /// Error that can be converted to a [`TokenStream`] required to be used with 327 | /// [`MacroOutput`] 328 | /// 329 | /// This trait is equivalent to [`ToTokens`]. 330 | pub trait ToTokensError: Debug { 331 | /// Equivalent to [`ToTokens::to_tokens`] 332 | fn to_tokens(&self, tokens: &mut TokenStream); 333 | /// Equivalent to [`ToTokens::to_token_stream`] 334 | fn to_token_stream(&self) -> TokenStream { 335 | let mut tokens = TokenStream::new(); 336 | self.to_tokens(&mut tokens); 337 | tokens 338 | } 339 | /// Equivalent to [`ToTokens::into_token_stream`] 340 | fn into_token_stream(self) -> TokenStream 341 | where 342 | Self: Sized, 343 | { 344 | self.to_token_stream() 345 | } 346 | } 347 | 348 | /// Allows to call `.join(..)` on any `impl ToTokensError` 349 | pub trait JoinToTokensError { 350 | /// Joins two `Error`s 351 | /// 352 | /// ``` 353 | /// use manyhow::error_message; 354 | /// # use crate::manyhow::JoinToTokensError; 355 | /// 356 | /// error_message!("test").join(error_message!("another")); 357 | /// ``` 358 | /// 359 | /// Some errors like [`manyhow::Error`] and [`manyhow::ErrorMessage`] can 360 | /// also be "added": 361 | /// 362 | /// ``` 363 | /// # use manyhow::ToTokensError; 364 | /// use manyhow::{error_message, Error}; 365 | /// # proc_macro_utils::assert_tokens!(( 366 | /// error_message!("Hello Rust!") + error_message!("Hello 🦀!") 367 | /// # ).into_token_stream(), { ::core::compile_error!{"Hello Rust!"} ::core::compile_error!{"Hello 🦀!"} }); 368 | /// # proc_macro_utils::assert_tokens!(( 369 | /// Error::from(error_message!("Hello Rust!")) + error_message!("Hello 🦀!") 370 | /// # ).into_token_stream(), { ::core::compile_error!{"Hello Rust!"} ::core::compile_error!{"Hello 🦀!"} }); 371 | /// ``` 372 | fn join(self, error: impl ToTokensError + 'static) -> Error; 373 | } 374 | 375 | impl JoinToTokensError for T { 376 | fn join(self, error: impl ToTokensError + 'static) -> Error { 377 | let mut this = Error::from(self); 378 | this.push(error); 379 | this 380 | } 381 | } 382 | 383 | impl ToTokensError for Infallible { 384 | fn to_tokens(&self, _: &mut TokenStream) { 385 | unreachable!() 386 | } 387 | } 388 | #[cfg(feature = "syn1")] 389 | impl ToTokensError for Syn1Error { 390 | fn to_tokens(&self, tokens: &mut TokenStream) { 391 | self.to_compile_error().to_tokens(tokens); 392 | } 393 | } 394 | #[cfg(feature = "syn")] 395 | impl ToTokensError for Syn2Error { 396 | fn to_tokens(&self, tokens: &mut TokenStream) { 397 | self.to_compile_error().to_tokens(tokens); 398 | } 399 | } 400 | #[cfg(feature = "darling")] 401 | impl ToTokensError for DarlingError { 402 | fn to_tokens(&self, tokens: &mut TokenStream) { 403 | self.clone().write_errors().to_tokens(tokens); 404 | } 405 | } 406 | impl ToTokensError for Error { 407 | fn to_tokens(&self, tokens: &mut TokenStream) { 408 | for error in &self.0 { 409 | error.to_tokens(tokens); 410 | } 411 | } 412 | } 413 | impl ToTokensError for SilentError { 414 | fn to_tokens(&self, _: &mut TokenStream) {} 415 | } 416 | 417 | /// Some utilities on [`Result`](ToTokensError) 418 | pub trait ResultExt: Sized { 419 | /// If self is error, attaches another error 420 | fn context(self, error: impl ToTokensError + 'static) -> Result { 421 | self.context_with(|| error) 422 | } 423 | 424 | /// If self is error, attaches another error, closure is only executed if 425 | /// the `Result` is `Err` 426 | fn context_with( 427 | self, 428 | error: impl FnOnce() -> C, 429 | ) -> Result; 430 | /// If self is error, extend error message 431 | /// 432 | /// Only works if `E` implements [`Attachment`] which is the case for 433 | /// [`ErrorMessage`] 434 | #[must_use] 435 | fn attachment(self, label: &'static str, msg: impl Display) -> Self 436 | where 437 | E: Attachment; 438 | 439 | /// Attaches a new `error` message to `self` reusing the same span 440 | #[must_use] 441 | fn error(self, msg: impl Display) -> Self 442 | where 443 | E: Attachment, 444 | { 445 | self.attachment("error", msg) 446 | } 447 | 448 | /// Attaches a new `warning` message to `self` reusing the same span 449 | #[must_use] 450 | fn warning(self, msg: impl Display) -> Self 451 | where 452 | E: Attachment, 453 | { 454 | self.attachment("warning", msg) 455 | } 456 | 457 | /// Attaches a new `note` message to `self` reusing the same span 458 | #[must_use] 459 | fn note(self, msg: impl Display) -> Self 460 | where 461 | E: Attachment, 462 | { 463 | self.attachment("note", msg) 464 | } 465 | 466 | /// Attaches a new `help` message to `self` reusing the same span 467 | #[must_use] 468 | fn help(self, msg: impl Display) -> Self 469 | where 470 | E: Attachment, 471 | { 472 | self.attachment("help", msg) 473 | } 474 | } 475 | 476 | impl ResultExt for Result { 477 | fn context_with( 478 | self, 479 | error: impl FnOnce() -> C, 480 | ) -> Result { 481 | self.map_err(|e| { 482 | let mut e = Error::from(e); 483 | e.push(error()); 484 | e 485 | }) 486 | } 487 | 488 | fn attachment(self, label: &'static str, msg: impl Display) -> Result 489 | where 490 | E: Attachment, 491 | { 492 | self.map_err(|e| e.attachment(label, msg)) 493 | } 494 | } 495 | 496 | #[cfg(test)] 497 | mod test { 498 | use proc_macro_utils::assert_tokens; 499 | 500 | use super::*; 501 | 502 | #[test] 503 | fn error_message() { 504 | let error_message = ErrorMessage::new(Span::call_site(), "test message") 505 | .help("try to call your dog") 506 | .note("you could use the banana phone") 507 | .warning("be careful") 508 | .error("you cannot reach them"); 509 | assert_tokens! {error_message.to_token_stream(), { 510 | ::core::compile_error! { 511 | "test message\n\n = help: try to call your dog\n = note: you could use the banana phone\n = warning: be careful\n = error: you cannot reach them\n" 512 | } 513 | }} 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![warn(clippy::pedantic, missing_docs)] 3 | #![allow(clippy::module_name_repetitions)] 4 | //! Proc **m**acro **anyhow**, a combination of ideas from 5 | //! [`anyhow`](docs.rs/anyhow) and 6 | //! [`proc-macro-error`](docs.rs/proc-macro-error) to improve proc macro 7 | //! development, especially focused on the error handling. 8 | //! 9 | //! # Motivation 10 | //! Error handling in proc-macros is unideal, as the top level functions of 11 | //! proc-macros can only return `TokenStreams` both in success and failure case. 12 | //! This means that I often write code like this, moving the actual 13 | //! implementation in a separate function to be able to use the ergonomic rust 14 | //! error handling with e.g., `?`. 15 | //! ``` 16 | //! # use proc_macro2::TokenStream; 17 | //! # use quote::quote; 18 | //! # use syn2 as syn; 19 | //! use proc_macro2::TokenStream as TokenStream2; 20 | //! 21 | //! # let _ = quote!{ 22 | //! #[proc_macro] 23 | //! # }; 24 | //! pub fn my_macro(input: TokenStream) -> TokenStream { 25 | //! match actual_implementation(input.into()) { 26 | //! Ok(output) => output, 27 | //! Err(error) => error.into_compile_error(), 28 | //! } 29 | //! .into() 30 | //! } 31 | //! 32 | //! fn actual_implementation(input: TokenStream2) -> syn::Result { 33 | //! // .. 34 | //! # Ok(quote!()) 35 | //! } 36 | //! ``` 37 | //! 38 | //! # Using the `#[manyhow]` macro 39 | //! To activate the error handling, just add [`#[manyhow]`](manyhow) above any 40 | //! proc-macro implementation, reducing the above example to: 41 | //! 42 | //! ``` 43 | //! # use quote::quote; 44 | //! # use syn2 as syn; 45 | //! use manyhow::manyhow; 46 | //! use proc_macro2::TokenStream as TokenStream2; 47 | //! 48 | //! # let _ = quote!{ 49 | //! #[manyhow] 50 | //! #[proc_macro] 51 | //! # }; 52 | //! // You can also merge the two attributes: #[manyhow(proc_macro)] 53 | //! fn my_macro(input: TokenStream2) -> syn::Result { 54 | //! // .. 55 | //! # Ok(quote!()) 56 | //! } 57 | //! 58 | //! // On top of the TokenStreams any type that implements [`Parse`] is supported 59 | //! # let _ = quote!{ 60 | //! #[manyhow(proc_macro_derive(MyMacro))] 61 | //! #[proc_macro] 62 | //! # }; 63 | //! // The output can also be anything that implements `quote::ToTokens` 64 | //! fn my_derive_macro(input: syn::DeriveInput) -> manyhow::Result { 65 | //! // .. 66 | //! # manyhow::bail!("hello") 67 | //! } 68 | //! ``` 69 | //! 70 | //! See [Without macros](#without-macros) to see what this expands to under the 71 | //! hood. 72 | //! 73 | //! You can also use the `#[manyhow]` attributes on a use statement, useful when 74 | //! moving your proc-macro implementations in separate modules. 75 | //! 76 | //! ``` 77 | //! # use quote::quote; 78 | //! use manyhow::manyhow; 79 | //! 80 | //! mod module { 81 | //! # use quote::quote; 82 | //! # use syn2 as syn; 83 | //! use proc_macro2::TokenStream as TokenStream2; 84 | //! 85 | //! pub fn my_macro(input: TokenStream2) -> syn::Result { 86 | //! // .. 87 | //! # Ok(quote!()) 88 | //! } 89 | //! } 90 | //! 91 | //! # let _ = quote!{ 92 | //! #[manyhow] 93 | //! #[proc_macro] 94 | //! # }; 95 | //! // You can also merge the two attributes: #[manyhow(proc_macro)] 96 | //! pub use module::my_macro; 97 | //! ``` 98 | //! 99 | //! A proc macro function marked as `#[manyhow]` can take and return any 100 | //! [`TokenStream`](AnyTokenStream), and can also return `Result` where `E` implements [`ToTokensError`]. As additional parameters a 102 | //! [dummy](#dummy-mut-tokenstream) and/or [emitter](#emitter-mut-emitter) can 103 | //! be specified. 104 | //! 105 | //! The `manyhow` attribute takes optional flags to configure its behavior. 106 | //! 107 | //! When used for `proc_macro` and `proc_macro_attribute`, 108 | //! `#[manyhow(input_as_dummy, ...)]` will take the input of a function like 109 | //! `proc_macro` to initialize the [dummy `&mut TokenStream`](# 110 | //! dummy-mut-tokenstream) while `#[manyhow(item_as_dummy, ...)]` on 111 | //! `proc_macro_attribute` will initialize the dummy with the annotated item. 112 | //! 113 | //! You can merge the `#[proc_macro*]` attribute inside the manyhow flags e.g., 114 | //! `#[manyhow(proc_macro)]` or `#[manyhow(proc_macro_derive(SomeTrait, ...))]`. 115 | //! 116 | //! The `#[manyhow(impl_fn, ...)]` flag will put the actual macro implementation 117 | //! in a separate function. Making it available for e.g., unit testing with 118 | //! [`proc_macro_utils::assert_expansion!`](https://docs.rs/proc-macro-utils/latest/proc_macro_utils/macro.assert_expansion.html). 119 | //! 120 | //! ```ignore 121 | //! #[manyhow(impl_fn)] 122 | //! #[proc_macro] 123 | //! pub fn actual_macro(input: TokenStream2) -> TokenStream2 { 124 | //! // ... 125 | //! } 126 | //! // would roughly expand to 127 | //! #[proc_macro] 128 | //! pub fn actual_macro(input: TokenStream) -> TokenStream { 129 | //! actual_macro_impl(input.into()).into() 130 | //! } 131 | //! fn actual_macro_impl(input: TokenStream2) -> TokenStream2 { 132 | //! // ... 133 | //! } 134 | //! ``` 135 | //! 136 | //! # Without macros 137 | //! `manyhow` can be used without proc macros, and they can be disabled by 138 | //! adding `manyhow` with `default-features=false`. 139 | //! 140 | //! The usage is more or less the same, though with some added boilerplate from 141 | //! needing to invoke one of [`function()`] ([`function!`]), [`attribute()`] 142 | //! ([`attribute!`]) or [`derive()`] ([`derive!`]) directly. For each version 143 | //! there exists a function and a `macro_rules` macro, while the function only 144 | //! supports [`proc_macro::TokenStream`] and [`proc_macro2::TokenStream`], the 145 | //! macro versions also support any type that implements [`Parse`] 146 | //! and [`ToTokens`] respectively. 147 | //! 148 | //! While the examples use closures, functions can be passed in as well. The 149 | //! above example would then change to: 150 | //! ``` 151 | //! # use proc_macro2::TokenStream; 152 | //! # use quote::quote; 153 | //! # use syn2 as syn; 154 | //! use proc_macro2::TokenStream as TokenStream2; 155 | //! 156 | //! # let _ = quote!{ 157 | //! #[proc_macro] 158 | //! # }; 159 | //! pub fn my_macro(input: TokenStream) -> TokenStream { 160 | //! # let tmp = input.clone(); 161 | //! # let output: TokenStream = 162 | //! manyhow::function( 163 | //! input, 164 | //! false, 165 | //! |input: TokenStream2| -> syn::Result { 166 | //! // .. 167 | //! # Ok(quote!()) 168 | //! }, 169 | //! ) 170 | //! # ; 171 | //! # let input = tmp; 172 | //! // Or 173 | //! manyhow::function!( 174 | //! input, 175 | //! |input: syn::DeriveInput| -> manyhow::Result { 176 | //! // .. 177 | //! # manyhow::bail!("error") 178 | //! }, 179 | //! ) 180 | //! } 181 | //! ``` 182 | //! [`Emitter`](#emitter-mut-emitter) and [dummy 183 | //! `TokenStream`](#dummy-mut-tokenstream) can also be used. [`function()`] 184 | //! ([`function!`]) and [`attribute()`] ([`attribute!`]) take an additional 185 | //! boolean parameter controlling whether the input/item will be used as initial 186 | //! dummy. 187 | //! 188 | //! # `emitter: &mut Emitter` 189 | //! [`*MacroHandler`](FunctionMacroHandler)s (the traits defining what 190 | //! closures/functions can be used with `manyhow`) can take a mutable reference 191 | //! to an [`Emitter`]. This allows collecting errors, but not fail immediately. 192 | //! 193 | //! [`Emitter::into_result`] can be used to return if an [`Emitter`] contains 194 | //! any values. 195 | //! 196 | //! ``` 197 | //! # use quote::quote; 198 | //! # use syn2 as syn; 199 | //! use manyhow::{manyhow, Emitter, ErrorMessage}; 200 | //! use proc_macro2::TokenStream as TokenStream2; 201 | //! 202 | //! # let _ = quote!{ 203 | //! #[manyhow] 204 | //! #[proc_macro] 205 | //! # }; 206 | //! fn my_macro(input: TokenStream2, emitter: &mut Emitter) -> manyhow::Result { 207 | //! // .. 208 | //! emitter.emit(ErrorMessage::call_site("A fun error!")); 209 | //! emitter.into_result()?; 210 | //! // .. 211 | //! # Ok(quote!()) 212 | //! } 213 | //! ``` 214 | //! 215 | //! # `dummy: &mut TokenStream` 216 | //! [`*MacroHandler`](FunctionMacroHandler)s can also take a mutable reference 217 | //! to a `TokenStream`, to enable emitting some dummy code to be used in case 218 | //! the macro errors. 219 | //! 220 | //! This allows either appending tokens e.g., with [`ToTokens::to_tokens`] or 221 | //! directly setting the dummy code e.g., `*dummy = quote!{some tokens}`. 222 | //! 223 | //! # Crate features 224 | //! 225 | //! - `macros` **default** Enables [`#[manyhow]`](macros::manyhow) attribute 226 | //! macro. 227 | //! - `syn`/`syn2` **default** Enables errors for [`syn` 2.x](https://docs.rs/syn/latest/syn/). 228 | //! - `syn1` Enables errors for [`syn` 1.x](https://docs.rs/syn/1.0.109/syn/index.html). 229 | //! - `darling` Enables errors for [`darling`](https://docs.rs/darling/latest/index.html). 230 | 231 | #[cfg(feature = "macros")] 232 | pub use macros::manyhow; 233 | use proc_macro2::TokenStream; 234 | #[cfg(doc)] 235 | use {quote::ToTokens, syn2::parse::Parse}; 236 | 237 | extern crate proc_macro; 238 | 239 | #[macro_use] 240 | mod span_ranged; 241 | pub use span_ranged::{to_tokens_span_range, SpanRanged}; 242 | #[macro_use] 243 | mod macro_rules; 244 | mod error; 245 | pub use error::*; 246 | 247 | mod parse_to_tokens; 248 | 249 | #[doc(hidden)] 250 | pub mod __private { 251 | pub use std::prelude::rust_2021::*; 252 | 253 | use proc_macro2::TokenStream; 254 | pub use quote; 255 | 256 | pub use crate::span_ranged::*; 257 | pub type Dummy = Option; 258 | 259 | pub use crate::parse_to_tokens::*; 260 | } 261 | 262 | /// Marker trait for [`proc_macro::TokenStream`] and 263 | /// [`proc_macro2::TokenStream`] 264 | pub trait AnyTokenStream: Clone + From + Into + Default {} 265 | impl AnyTokenStream for TokenStream {} 266 | impl AnyTokenStream for proc_macro::TokenStream {} 267 | 268 | #[macro_export] 269 | #[doc(hidden)] 270 | macro_rules! __macro_handler { 271 | ($name:ident; $($(#attr=$attr:tt)? $n:ident: $input:expr),+; $impl:expr$(; dummy:$dummy:expr)?) => { 272 | $crate::__macro_handler! {! $name; $($(#attr=$attr)? $n: $input.clone()),+; $impl $(; $crate::__private::Some($dummy))?} 273 | }; 274 | ($name:ident; $($(#attr=$attr:tt)? $n:ident: $input:expr),+; $impl:expr; dummy) => { 275 | $crate::__macro_handler! {! $name; $($(#attr=$attr)? $n: $input),+; $impl; $crate::__private::Dummy::None} 276 | }; 277 | (! $name:ident; $($(#attr=$attr:tt)? $n:ident: $input:expr),+; $impl:expr $(; $dummy:expr)?) => {{ 278 | use $crate::__private::{ManyhowParse, ManyhowToTokens, ManyhowTry}; 279 | let implementation = $impl; 280 | $(let $n = &$crate::__private::WhatType::new();)+ 281 | if false { 282 | _ = $crate::__private::$name($($n.identify(),)+ $($dummy,)? implementation); 283 | unreachable!(); 284 | } else { 285 | match $crate::__private::$name($( 286 | {#[allow(unused)] 287 | let attr = false; 288 | $(let attr = $attr;)? 289 | $n.manyhow_parse($input, attr)}, 290 | )+ $($dummy,)? implementation) 291 | { 292 | Err(tokens) => tokens.into(), 293 | Ok((output, mut tokens, mut dummy)) => { 294 | match (&$crate::__private::WhatType::from(&output)).manyhow_try(output) { 295 | Err(error) => { 296 | dummy.extend(tokens); 297 | tokens = dummy; 298 | (&$crate::__private::WhatType::from(&error)).manyhow_to_tokens(error, &mut tokens); 299 | }, 300 | Ok(output) => (&$crate::__private::WhatType::from(&output)).manyhow_to_tokens(output, &mut tokens), 301 | }; 302 | tokens.into() 303 | } 304 | } 305 | } 306 | }}; 307 | } 308 | 309 | /// Handles [`proc_macro_attribute`](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) 310 | /// implementation 311 | /// 312 | /// Takes any `TokenStream` for `input` and `item` and returns any 313 | /// `TokenStream`. If `item_as_dummy = true` the item input will be used as 314 | /// default dummy code on error. `body` takes a [`AttributeMacroHandler`] with 315 | /// two `TokenStream` parameters. And an optional [`&mut Emitter`](Emitter) and 316 | /// a `&mut TokenStream` for storing a dummy output. 317 | /// 318 | /// ``` 319 | /// # use proc_macro_utils::assert_tokens; 320 | /// # use quote::{quote, ToTokens}; 321 | /// use manyhow::{attribute, Emitter, Result}; 322 | /// use proc_macro2::TokenStream; 323 | /// # let input = quote!(); 324 | /// # let item = quote!(); 325 | /// # let output: TokenStream = 326 | /// attribute( 327 | /// input, 328 | /// item, 329 | /// false, 330 | /// |input: TokenStream, 331 | /// item: TokenStream, 332 | /// dummy: &mut TokenStream, 333 | /// emitter: &mut Emitter| 334 | /// -> Result { 335 | /// // .. 336 | /// # Ok(quote!()) 337 | /// }, 338 | /// ); 339 | /// ``` 340 | /// 341 | /// *Note:* When `item_as_dummy = true` the `dummy: &mut TokenStream` will be 342 | /// initialized with `item`. To override assign a new `TokenStream`: 343 | /// ``` 344 | /// # use proc_macro_utils::assert_tokens; 345 | /// use manyhow::{attribute, Result, SilentError}; 346 | /// use proc_macro2::TokenStream; 347 | /// use quote::{quote, ToTokens}; 348 | /// # let input = quote!(input); 349 | /// let item = quote!( 350 | /// struct Struct; 351 | /// ); 352 | /// let output: TokenStream = attribute( 353 | /// input, 354 | /// item, 355 | /// true, 356 | /// |input: TokenStream, 357 | /// item: TokenStream, 358 | /// dummy: &mut TokenStream| 359 | /// -> Result { 360 | /// assert_tokens!(dummy.to_token_stream(), { 361 | /// struct Struct; 362 | /// }); 363 | /// *dummy = quote! { 364 | /// struct Struct(HelloWorld); 365 | /// }; 366 | /// // .. 367 | /// Err(SilentError) 368 | /// }, 369 | /// ); 370 | /// 371 | /// assert_tokens! {output, {struct Struct(HelloWorld);}}; 372 | /// ``` 373 | pub fn attribute< 374 | Input: AnyTokenStream, 375 | Item: AnyTokenStream, 376 | Dummy: AnyTokenStream, 377 | Output: MacroOutput, 378 | Return: AnyTokenStream, 379 | Function, 380 | >( 381 | input: impl AnyTokenStream, 382 | item: impl AnyTokenStream, 383 | item_as_dummy: bool, 384 | body: impl AttributeMacroHandler< 385 | Function, 386 | Item = Item, 387 | Input = Input, 388 | Dummy = Dummy, 389 | Output = Output, 390 | >, 391 | ) -> Return { 392 | #[allow(unused_mut)] 393 | let mut tokens = Dummy::default(); 394 | let mut tokens = if item_as_dummy { 395 | item.clone().into().into() 396 | } else { 397 | tokens 398 | }; 399 | let mut emitter = Emitter::new(); 400 | let output = body.call( 401 | input.into().into(), 402 | item.into().into(), 403 | &mut tokens, 404 | &mut emitter, 405 | ); 406 | let mut tokens = tokens.into(); 407 | let mut tokens = match output.convert() { 408 | Ok(tokens) => tokens, 409 | Err(error) => { 410 | error.to_tokens(&mut tokens); 411 | tokens 412 | } 413 | }; 414 | emitter.to_tokens(&mut tokens); 415 | tokens.into() 416 | } 417 | 418 | /// Handles [`proc_macro_attribute`](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) 419 | /// implementation 420 | /// 421 | /// Takes any `TokenStream` for `input` and `item` and its return value. If 422 | /// `#[as_dummy]` is specified on item, it will be used as default 423 | /// dummy code on error. `body` takes a [`AttributeMacroHandler`] with two 424 | /// `TokenStream`s or types implementing [`Parse`] parameters and returning a 425 | /// `TokenStream` or type implementing [`ToTokens`]. And an optional [`&mut 426 | /// Emitter`](Emitter) and a `&mut TokenStream` for storing a dummy output. 427 | /// 428 | /// 429 | /// ``` 430 | /// # use proc_macro_utils::assert_tokens; 431 | /// # use quote::{quote, ToTokens}; 432 | /// use manyhow::{attribute, Emitter, Result}; 433 | /// use proc_macro2::TokenStream; 434 | /// # let input = quote!(); 435 | /// # let item = quote!(); 436 | /// # let output: TokenStream = 437 | /// attribute!(input, item, |input: TokenStream, 438 | /// item: TokenStream, 439 | /// dummy: &mut TokenStream, 440 | /// emitter: &mut Emitter| 441 | /// -> Result { 442 | /// // .. 443 | /// # Ok(quote!()) 444 | /// }); 445 | /// ``` 446 | /// 447 | /// *Note:* When `#[as_dummy]` is specified the `dummy: &mut TokenStream` will 448 | /// be initialized with `item`. To override assign a new `TokenStream`: 449 | /// ``` 450 | /// # use proc_macro_utils::assert_tokens; 451 | /// # use syn2 as syn; 452 | /// use manyhow::{attribute, Result, SilentError}; 453 | /// use proc_macro2::TokenStream; 454 | /// use quote::{quote, ToTokens}; 455 | /// # let input = quote!(input); 456 | /// let item = quote!( 457 | /// struct Struct; 458 | /// ); 459 | /// let output: TokenStream = attribute!( 460 | /// input, 461 | /// #[as_dummy] 462 | /// item, 463 | /// |input: TokenStream, 464 | /// item: syn::ItemStruct, 465 | /// dummy: &mut TokenStream| 466 | /// -> Result { 467 | /// assert_tokens!(dummy.to_token_stream(), { 468 | /// struct Struct; 469 | /// }); 470 | /// *dummy = quote! { 471 | /// struct Struct(HelloWorld); 472 | /// }; 473 | /// // .. 474 | /// Err(SilentError) 475 | /// }, 476 | /// ); 477 | /// 478 | /// assert_tokens! {output, {struct Struct(HelloWorld);}}; 479 | /// ``` 480 | #[macro_export] 481 | macro_rules! attribute { 482 | ($input:expr, #[as_dummy] $item:expr, $impl:expr $(,)?) => { 483 | $crate::__macro_handler!{attribute_transparent; #attr=true input: $input, item: $item.clone(); $impl; dummy: $item} 484 | }; 485 | ($input:expr, $item:expr, $impl:expr $(,)?) => { 486 | $crate::__macro_handler!{attribute_transparent; #attr=true input: $input, item: $item; $impl; dummy} 487 | }; 488 | } 489 | 490 | /// Handles [`proc_macro_derive`](https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros) 491 | /// implementation. 492 | /// 493 | /// Use [`derive!`] to support [`Parse`] and [`ToTokens`] as well. 494 | /// 495 | /// Takes any `TokenStream` for `item` and returns any `TokenStream`. `body` 496 | /// takes a [`DeriveMacroHandler`] with one `TokenStream` parameter. And an 497 | /// optional [`&mut Emitter`](Emitter) and `&mut TokenStream` for storing a 498 | /// dummy output. 499 | /// 500 | /// ``` 501 | /// # use proc_macro_utils::assert_tokens; 502 | /// # use quote::{quote, ToTokens}; 503 | /// use manyhow::{derive, Emitter, Result}; 504 | /// use proc_macro2::TokenStream; 505 | /// # let item = quote!(); 506 | /// # let output: TokenStream = 507 | /// derive( 508 | /// item, 509 | /// |item: TokenStream, dummy: &mut TokenStream, emitter: &mut Emitter| -> Result { 510 | /// // .. 511 | /// # Ok(quote!()) 512 | /// }, 513 | /// ); 514 | /// ``` 515 | pub fn derive< 516 | Item: AnyTokenStream, 517 | Dummy: AnyTokenStream, 518 | Output: MacroOutput, 519 | Return: AnyTokenStream, 520 | Function, 521 | >( 522 | item: impl AnyTokenStream, 523 | body: impl DeriveMacroHandler, 524 | ) -> Return { 525 | let mut tokens = Dummy::default(); 526 | let mut emitter = Emitter::new(); 527 | let output = body.call(item.into().into(), &mut tokens, &mut emitter); 528 | let mut tokens = tokens.into(); 529 | let mut tokens = match output.convert() { 530 | Ok(tokens) => tokens, 531 | Err(error) => { 532 | error.to_tokens(&mut tokens); 533 | tokens 534 | } 535 | }; 536 | emitter.to_tokens(&mut tokens); 537 | tokens.into() 538 | } 539 | 540 | /// Handles [`proc_macro_derive`](https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros) 541 | /// implementation. 542 | /// 543 | /// Takes any `TokenStream` for `item` and returns any `TokenStream`. `body` 544 | /// takes a [`DeriveMacroHandler`] with one `TokenStream` or type implementing 545 | /// [`Parse`] parameter and returns a `TokenStream` or type implementing 546 | /// [`ToTokens`]. And an optional [`&mut Emitter`](Emitter) and `&mut 547 | /// TokenStream` for storing a dummy output. 548 | /// 549 | /// ``` 550 | /// # use proc_macro_utils::assert_tokens; 551 | /// # use quote::{quote, ToTokens}; 552 | /// # use syn2 as syn; 553 | /// use manyhow::{derive, Emitter, Result}; 554 | /// use proc_macro2::TokenStream; 555 | /// # let item = quote!(); 556 | /// # let output: TokenStream = 557 | /// derive!(item, |item: syn::DeriveInput, 558 | /// dummy: &mut TokenStream, 559 | /// emitter: &mut Emitter| 560 | /// -> Result { 561 | /// // .. 562 | /// # Ok(quote!()) 563 | /// }); 564 | /// ``` 565 | #[macro_export] 566 | macro_rules! derive { 567 | ($item:expr, $impl:expr $(,)?) => { 568 | $crate::__macro_handler! {derive_transparent; item: $item; $impl} 569 | }; 570 | } 571 | 572 | /// Handles function like [`proc_macro`](https://doc.rust-lang.org/reference/procedural-macros.html#function-like-procedural-macros) 573 | /// implementation 574 | /// 575 | /// Use [`function!`] to support [`Parse`] and [`ToTokens`] as well. 576 | /// 577 | /// Takes any `TokenStream` for `input` and returns any 578 | /// `TokenStream`. If `input_as_dummy = true` the item input will be used as 579 | /// default dummy code on error. `body` takes a [`FunctionMacroHandler`] with 580 | /// one `TokenStream` parameter. And an optional [`&mut Emitter`](Emitter) and a 581 | /// `&mut TokenStream` for storing a dummy output. 582 | /// 583 | /// ``` 584 | /// # use proc_macro_utils::assert_tokens; 585 | /// # use quote::{quote, ToTokens}; 586 | /// use manyhow::{function, Emitter, Result}; 587 | /// use proc_macro2::TokenStream; 588 | /// # let input = quote!(); 589 | /// # let output: TokenStream = 590 | /// function( 591 | /// input, 592 | /// false, 593 | /// |input: TokenStream, dummy: &mut TokenStream, emitter: &mut Emitter| -> Result { 594 | /// // .. 595 | /// # Ok(quote!()) 596 | /// }, 597 | /// ); 598 | /// ``` 599 | /// 600 | /// *Note:* When `input_as_dummy = true` the `dummy: &mut TokenStream` will be 601 | /// initialized with `input`. To override assign a new `TokenStream`: 602 | /// ``` 603 | /// # use proc_macro_utils::assert_tokens; 604 | /// use manyhow::{function, Result, SilentError}; 605 | /// use proc_macro2::TokenStream; 606 | /// use quote::{quote, ToTokens}; 607 | /// let input = quote!(some input); 608 | /// let output: TokenStream = function( 609 | /// input, 610 | /// true, 611 | /// |input: TokenStream, 612 | /// dummy: &mut TokenStream| 613 | /// -> Result { 614 | /// assert_tokens!(dummy.to_token_stream(), { 615 | /// some input 616 | /// }); 617 | /// *dummy = quote! { 618 | /// another input 619 | /// }; 620 | /// // .. 621 | /// Err(SilentError) 622 | /// }, 623 | /// ); 624 | /// 625 | /// assert_tokens! {output, {another input}}; 626 | /// ``` 627 | pub fn function< 628 | Input: AnyTokenStream, 629 | Dummy: AnyTokenStream, 630 | Output: MacroOutput, 631 | Return: AnyTokenStream, 632 | Function, 633 | >( 634 | input: impl AnyTokenStream, 635 | input_as_dummy: bool, 636 | body: impl FunctionMacroHandler, 637 | ) -> Return { 638 | let mut tokens = if input_as_dummy { 639 | input.clone().into().into() 640 | } else { 641 | Dummy::default() 642 | }; 643 | let mut emitter = Emitter::new(); 644 | let output = body.call(input.into().into(), &mut tokens, &mut emitter); 645 | let mut tokens = tokens.into(); 646 | let mut tokens = match output.convert() { 647 | Ok(tokens) => tokens, 648 | Err(error) => { 649 | error.to_tokens(&mut tokens); 650 | tokens 651 | } 652 | }; 653 | emitter.to_tokens(&mut tokens); 654 | tokens.into() 655 | } 656 | 657 | /// Handles function like [`proc_macro`](https://doc.rust-lang.org/reference/procedural-macros.html#function-like-procedural-macros) 658 | /// implementation 659 | /// 660 | /// Takes any `TokenStream` for `input` and returns any `TokenStream`. If 661 | /// `#[as_dummy]` is specified on input, it will be used as default 662 | /// dummy code on error. `body` takes a [`FunctionMacroHandler`] with one 663 | /// `TokenStream` or type implementing [`Parse`] parameter and returns a 664 | /// `TokenStream` or type implementing [`ToTokens`]. And an optional [`&mut 665 | /// Emitter`](Emitter) and a `&mut TokenStream` for storing a dummy output. 666 | /// 667 | /// ``` 668 | /// # use proc_macro_utils::assert_tokens; 669 | /// # use quote::{quote, ToTokens}; 670 | /// # use syn2 as syn; 671 | /// use manyhow::{function, Emitter, Result}; 672 | /// use proc_macro2::TokenStream; 673 | /// # let input = quote!(); 674 | /// # let output: TokenStream = 675 | /// function!(input, |input: syn::Item, 676 | /// dummy: &mut TokenStream, 677 | /// emitter: &mut Emitter| 678 | /// -> Result { 679 | /// // .. 680 | /// # manyhow::bail!("unimplemented") 681 | /// }); 682 | /// ``` 683 | /// 684 | /// *Note:* When `#[as_dummy]` is specified on the input, the `dummy: &mut 685 | /// TokenStream` will be initialized with `input`. To override assign a new 686 | /// `TokenStream`: 687 | /// 688 | /// ``` 689 | /// use proc_macro_utils::assert_tokens; 690 | /// use manyhow::{function, Result, SilentError}; 691 | /// use proc_macro2::TokenStream; 692 | /// use quote::{quote, ToTokens}; 693 | /// 694 | /// let input = quote!(some input); 695 | /// let output: TokenStream = function!( 696 | /// #[as_dummy] input, 697 | /// |input: TokenStream, dummy: &mut TokenStream| 698 | /// -> Result { 699 | /// assert_tokens!(dummy.to_token_stream(), { 700 | /// some input 701 | /// }); 702 | /// *dummy = quote! { 703 | /// another input 704 | /// }; 705 | /// // .. 706 | /// Err(SilentError) 707 | /// }, 708 | /// ); 709 | /// 710 | /// assert_tokens! {output, {another input}}; 711 | /// ``` 712 | #[macro_export] 713 | macro_rules! function { 714 | (#[as_dummy] $input:expr, $impl:expr $(,)?) => { 715 | $crate::__macro_handler! {function_transparent; input: $input; $impl; dummy: $input} 716 | }; 717 | ($input:expr, $impl:expr $(,)?) => { 718 | $crate::__macro_handler! {function_transparent; input: $input; $impl; dummy} 719 | }; 720 | } 721 | 722 | #[test] 723 | fn function_macro() { 724 | use proc_macro::TokenStream as TokenStream1; 725 | use quote::quote; 726 | // proc_macro2::TokenStream 727 | let output: TokenStream = 728 | function!(quote!(hello), |input: TokenStream| -> TokenStream { input }); 729 | assert_eq!(output.to_string(), "hello"); 730 | // proc_macro::TokenStream do not run :D 731 | if false { 732 | let _: TokenStream1 = function!( 733 | TokenStream1::from(quote!(hello)), 734 | |input: TokenStream1| -> TokenStream1 { input } 735 | ); 736 | } 737 | 738 | #[cfg(feature = "syn2")] 739 | { 740 | use quote::ToTokens; 741 | let output: TokenStream = function!( 742 | #[as_dummy] 743 | quote!(hello;), 744 | |input: syn2::LitInt| -> TokenStream { input.into_token_stream() } 745 | ); 746 | assert_eq!( 747 | output.to_string(), 748 | quote!(hello; ::core::compile_error! { "expected integer literal" }).to_string() 749 | ); 750 | let output: TokenStream = function!(quote!(20), |_input: syn2::LitInt| -> syn2::Ident { 751 | syn2::parse_quote!(hello) 752 | }); 753 | assert_eq!(output.to_string(), "hello"); 754 | } 755 | } 756 | 757 | macro_rules! macro_input { 758 | ($MacroInput:ident; $($input:ident: $Input:ident),+; $a:literal; $name:literal; $token_streams:literal) => { 759 | /// Input of 760 | #[doc = $a] 761 | #[doc = $name] 762 | /// proc-macro 763 | /// 764 | /// Note: for `TokenStream` either [`proc_macro::TokenStream`] or 765 | /// [`proc_macro2::TokenStream`] can be used. 766 | /// 767 | /// Trait is implemented for any [`function`](FnOnce), taking in 768 | #[doc = concat!($token_streams, ".")] 769 | /// Additionally, they can take optionally in any order a [`&mut 770 | /// Emitter`](Emitter) which allows emitting errors without returning early. And 771 | /// a `&mut TokenStream` to return a dummy `TokenStream` on failure. 772 | /// 773 | /// When used with 774 | #[doc = concat!("[`", $name, "()`]")] 775 | /// it must return a type implementing [`MacroOutput`], with 776 | #[doc = concat!("[`", $name, "!`]")] 777 | /// it can accept types implementing [`Parse`] and return types 778 | /// implementing [`ToTokens`](quote::ToTokens). 779 | #[allow(missing_docs)] 780 | pub trait $MacroInput { 781 | $(type $Input;)+ 782 | type Dummy; 783 | type Output; 784 | #[allow(clippy::missing_errors_doc)] 785 | fn call( 786 | self, 787 | $(item: Self::$Input,)+ 788 | dummy: &mut Self::Dummy, 789 | emitter: &mut Emitter, 790 | ) -> Self::Output; 791 | } 792 | 793 | macro_input_impl!([$($Input,)+ Dummy: Clone]; $MacroInput; $($input: $Input),*; &mut Dummy, &mut Emitter; dummy: Dummy dummy; emitter emitter); 794 | macro_input_impl!([$($Input,)+ Dummy: Clone]; $MacroInput; $($input: $Input),*; &mut Dummy; dummy: Dummy dummy; _emitter); 795 | macro_input_impl!([$($Input),+]; $MacroInput; $($input: $Input),*; &mut Emitter; _dummy: TokenStream; emitter emitter); 796 | macro_input_impl!([$($Input),+]; $MacroInput; $($input: $Input),*; ; _dummy: TokenStream; _emitter); 797 | }; 798 | } 799 | 800 | macro_rules! macro_input_impl { 801 | ([$($gen:tt)*]; $MacroInput:ident; $($input:ident: $Input:ident),+; $($Extra:ty),*; $dummy1:ident: $Dummy:ident $($dummy2:ident)?; $emitter1:ident $($emitter2:ident)?) => { 802 | impl<$($gen)*, Output, Function> $MacroInput<($($Input,)+ $($Extra,)* Output)> for Function 803 | where 804 | Function: Fn($($Input,)+ $($Extra,)*) -> Output, 805 | { 806 | type Dummy = $Dummy; 807 | $(type $Input = $Input;)* 808 | type Output = Output; 809 | 810 | fn call( 811 | self, 812 | $($input: Self::$Input,)* 813 | $dummy1: &mut Self::Dummy, 814 | $emitter1: &mut Emitter, 815 | ) -> Self::Output { 816 | self($($input,)+ $($dummy2,)? $($emitter2,)?) 817 | } 818 | } 819 | 820 | } 821 | } 822 | 823 | macro_input!(FunctionMacroHandler; input: Input; "a"; "function"; "one `TokenStream`"); 824 | macro_input!(DeriveMacroHandler; item: Item; "a"; "derive"; "one `TokenStream`"); 825 | macro_input!(AttributeMacroHandler; input: Input, item: Item; "an"; "attribute"; "two `TokenStream`s"); 826 | 827 | #[rustfmt::skip] 828 | #[allow(clippy::doc_markdown)] 829 | /// Output of a macro handler. 830 | /// 831 | /// Enables support for returning any [`TokenStream`](AnyTokenStream) or 832 | /// [Result]<[TokenStream](AnyTokenStream), [impl ToTokensError](ToTokensError)> 833 | /// from a proc-macro implementation. 834 | pub trait MacroOutput { 835 | /// Handles conversion into a [Result]<[TokenStream](AnyTokenStream), [Error]>. 836 | #[allow(clippy::missing_errors_doc)] 837 | fn convert(self) -> Result; 838 | } 839 | 840 | impl MacroOutput for T { 841 | fn convert(self) -> Result { 842 | Ok(self.into()) 843 | } 844 | } 845 | 846 | impl MacroOutput for Result { 847 | fn convert(self) -> Result { 848 | self.map_err(Error::from).and_then(MacroOutput::convert) 849 | } 850 | } 851 | -------------------------------------------------------------------------------- /src/macro_rules.rs: -------------------------------------------------------------------------------- 1 | #[cfg(doc)] 2 | use proc_macro2::Span; 3 | #[cfg(doc)] 4 | use quote::ToTokens; 5 | 6 | #[cfg(doc)] 7 | use crate::{Emitter, Error, ErrorMessage, SpanRanged}; 8 | 9 | #[doc(hidden)] 10 | #[macro_export] 11 | macro_rules! __error_message_internal { 12 | ((cs($($fmt:tt)*)$(.$fn:ident($($fmt_fn:tt)*))*), (), ()) => { 13 | $crate::ErrorMessage::call_site($($fmt)*) 14 | $(.attachment(::core::stringify!($fn), $($fmt_fn)*))* 15 | }; 16 | ((new($span:expr)($($fmt:tt)*)$(.$fn:ident($($fmt_fn:tt)*))*), (), ()) => { 17 | $crate::ErrorMessage::new( 18 | $crate::span_range!($span), 19 | $($fmt)* 20 | ) 21 | $(.attachment(::core::stringify!($fn), $($fmt_fn)*))* 22 | }; 23 | // ident = expr 24 | ($head:tt, ($($fmt:tt)*), (, $ident:ident = $expr:expr, $($tail:tt)*)) => { 25 | $crate::__error_message_internal!($head, ($($fmt)*, $ident = $expr), (, $($tail)*)) 26 | }; 27 | ($head:tt, ($($fmt:tt)*), (, $ident:ident = $expr:expr; $($tail:tt)*)) => { 28 | $crate::__error_message_internal!($head, ($($fmt)*, $ident = $expr), (; $($tail)*)) 29 | }; 30 | ($head:tt, ($($fmt:tt)*), (, $ident:ident = $expr:expr)) => { 31 | $crate::__error_message_internal!($head, ($($fmt)*, $ident = $expr), ()) 32 | }; 33 | // expr, 34 | ($head:tt, ($($fmt:tt)*), (, $expr:expr, $($tail:tt)*)) => { 35 | $crate::__error_message_internal!($head, ($($fmt)*, $expr), (, $($tail)*)) 36 | }; 37 | ($head:tt, ($($fmt:tt)*), (, $expr:expr; $($tail:tt)*)) => { 38 | $crate::__error_message_internal!($head, ($($fmt)*, $expr), (; $($tail)*)) 39 | }; 40 | ($head:tt, ($($fmt:tt)*), (, $expr:expr)) => { 41 | $crate::__error_message_internal!($head, ($($fmt)*, $expr), ()) 42 | }; 43 | // ; ident = "format", arguments 44 | (($($head:tt)*), $fmt:tt, ($(,)?$(;)?)) => { 45 | $crate::__error_message_internal!(($($head)*(::core::format_args!$fmt)), (), ()) 46 | }; 47 | (($($head:tt)*), $fmt:tt, ($(,)?; $attachment:ident = $fmt_str:literal $($tail:tt)*)) => { 48 | $crate::__error_message_internal!(($($head)*(::core::format_args!$fmt).$attachment), ($fmt_str), ($($tail)*)) 49 | }; 50 | } 51 | 52 | /// Creates an [`ErrorMessage`], comparable to the [`anyhow!`](https://docs.rs/anyhow/latest/anyhow/macro.anyhow.html) macro 53 | /// 54 | /// If the first argument is not a literal it is taken as the span of the error. 55 | /// The span expression can **either** implement [`SpanRanged`] or implement 56 | /// [`ToTokens`]. Otherwise, [`Span::call_site`] is used. 57 | /// 58 | /// ``` 59 | /// # use proc_macro2::Span; 60 | /// # use quote::quote; 61 | /// # use manyhow::error_message; 62 | /// assert_eq!( 63 | /// error_message!("format {} string{named}", "<3", named = "!").to_string(), 64 | /// "format <3 string!" 65 | /// ); 66 | /// // Span can either be `proc_macro::Span` or `proc_macro2::Span` 67 | /// assert_eq!( 68 | /// error_message!(Span::call_site(), "spanned error").to_string(), 69 | /// "spanned error" 70 | /// ); 71 | /// # if false { 72 | /// // Or any expression implementing `quote::ToTokens` 73 | /// assert_eq!( 74 | /// error_message!(quote!(some tokens), "spanned error").to_string(), 75 | /// "spanned error" 76 | /// ); 77 | /// # } 78 | /// ``` 79 | /// 80 | /// On top of the standard [`format_args!`] parameters additional attachments 81 | /// can be specified delimited with `;`. 82 | /// 83 | /// ``` 84 | /// # use proc_macro2::Span; 85 | /// # use quote::quote; 86 | /// # use manyhow::error_message; 87 | /// assert_eq!( 88 | /// error_message!( 89 | /// "format {} string{named}", "<3", named = "!"; 90 | /// error = "some additional error"; 91 | /// info = "some info as well"; 92 | /// custom_attachment = "amazing" 93 | /// ).to_string(), 94 | /// "format <3 string! 95 | /// 96 | /// = error: some additional error 97 | /// = info: some info as well 98 | /// = custom_attachment: amazing 99 | /// " 100 | /// ); 101 | /// ``` 102 | #[macro_export] 103 | macro_rules! error_message { 104 | ($fmt:literal $($tt:tt)*) => { 105 | $crate::__error_message_internal!((cs), ($fmt), ($($tt)*)) 106 | }; 107 | ($span:expr, $fmt:literal $($tt:tt)*) => { 108 | $crate::__error_message_internal!((new($span)), ($fmt), ($($tt)*)) 109 | }; 110 | } 111 | 112 | /// Exit by returning error, matching [`anyhow::bail!`](https://docs.rs/anyhow/latest/anyhow/macro.bail.html). 113 | /// 114 | /// The syntax is identical to [`error_message!`], the only difference is, that 115 | /// a single expression with an error is supported as well. 116 | /// ```should_panic 117 | /// # use manyhow::bail; 118 | /// # use proc_macro2::Span; 119 | /// # use syn2 as syn; 120 | /// bail!("an error message"; error = "with attachments"); 121 | /// let span = Span::call_site(); 122 | /// bail!(span, "error message"); 123 | /// let error = syn::Error::new(Span::call_site(), "an error"); 124 | /// bail!(error); 125 | /// # Ok::<_, manyhow::Error>(()) 126 | /// ``` 127 | #[macro_export] 128 | macro_rules! bail { 129 | ($msg:literal) => { 130 | return ::core::result::Result::Err($crate::error_message!($msg).into()); 131 | }; 132 | ($error:expr) => { 133 | return ::core::result::Result::Err($error.into()); 134 | }; 135 | ($($tt:tt)*) => { 136 | return ::core::result::Result::Err($crate::error_message!($($tt)*).into()); 137 | }; 138 | } 139 | 140 | /// Return early with an error, if a condition is not satisfied, matching 141 | /// [`anyhow::ensure!`](https://docs.rs/anyhow/latest/anyhow/macro.ensure.html). 142 | /// 143 | /// The syntax is identical to [`bail!`], with an additional leading condition. 144 | /// 145 | /// Additional to a boolean expression, the expression can also be a `let ... = 146 | /// ...` pattern matching, and will expand to `let ... else`. 147 | /// ``` 148 | /// # use manyhow::ensure; 149 | /// ensure!(true, "an error message"; help = "with attachments"); 150 | /// 151 | /// ensure!(let Some(a) = Some(1), "error"); 152 | /// assert_eq!(a, 1); 153 | /// 154 | /// # Ok::<_, manyhow::Error>(()) 155 | /// ``` 156 | /// ```should_panic 157 | /// # use manyhow::ensure; 158 | /// # use proc_macro2::Span; 159 | /// # use syn2 as syn; 160 | /// let span = Span::call_site(); 161 | /// ensure!(false, span, "error message"); 162 | /// let error = syn::Error::new(Span::call_site(), "an error"); 163 | /// ensure!(false, error); 164 | /// # Ok::<_, manyhow::Error>(()) 165 | /// ``` 166 | #[macro_export] 167 | macro_rules! ensure { 168 | ($cond:expr, $($bail_args:tt)*) => { 169 | if !$cond { 170 | $crate::bail!($($bail_args)*); 171 | } 172 | }; 173 | (let $pat:pat = $expr:expr, $($bail_args:tt)*) => { 174 | let $pat = $expr else { 175 | $crate::bail!($($bail_args)*); 176 | }; 177 | }; 178 | } 179 | 180 | /// Push an error to an emitter. 181 | /// 182 | /// The syntax is identical to [`error_message!`] and [`bail!`], but the first 183 | /// argument is the [`Emitter`]. 184 | /// ``` 185 | /// # use manyhow::{emit, Emitter}; 186 | /// # use proc_macro2::Span; 187 | /// # use syn2 as syn; 188 | /// let mut emitter = Emitter::new(); 189 | /// emit!(emitter, "an error message"); 190 | /// emit!(emitter, "an error message"; error = "with attachments"); 191 | /// let span = Span::call_site(); 192 | /// emit!(emitter, span, "error message"); 193 | /// let error = syn::Error::new(Span::call_site(), "an error"); 194 | /// emit!(emitter, error); 195 | /// ``` 196 | /// 197 | /// It can also be used with [`Error`]. 198 | /// ``` 199 | /// # use manyhow::{emit, error_message, Error}; 200 | /// # use proc_macro2::Span; 201 | /// # use syn2 as syn; 202 | /// let mut error: Error = error_message!("initial error").into(); 203 | /// emit!(error, "an error message"); 204 | /// ``` 205 | /// 206 | /// Or any collection implementing [`Extend`]. 207 | /// ``` 208 | /// # use manyhow::emit; 209 | /// # use proc_macro2::Span; 210 | /// # use syn2 as syn; 211 | /// let mut errors = Vec::new(); 212 | /// emit!(errors, "an error message"); 213 | /// ``` 214 | #[macro_export] 215 | macro_rules! emit { 216 | ($emitter:expr, $msg:literal) => { 217 | $emitter.extend(::core::iter::once::<$crate::ErrorMessage>($crate::error_message!($msg))); 218 | }; 219 | ($emitter:expr, $error:expr) => { 220 | $emitter.extend(::core::iter::once($error)); 221 | }; 222 | ($emitter:expr, $($tt:tt)*) => { 223 | $emitter.extend(::core::iter::once::<$crate::ErrorMessage>($crate::error_message!($($tt)*).into())); 224 | }; 225 | } 226 | 227 | #[cfg(test)] 228 | mod test { 229 | use proc_macro::Span; 230 | use quote::quote; 231 | 232 | use crate::{Emitter, ErrorMessage}; 233 | 234 | macro_rules! returned { 235 | ($ty:ty, $expr:expr) => { 236 | #[allow(unreachable_code)] 237 | (|| -> $ty { 238 | $expr; 239 | unreachable!(); 240 | })() 241 | }; 242 | } 243 | #[test] 244 | fn bail() { 245 | assert_eq!( 246 | returned!(Result<(), ErrorMessage>, bail!("format")) 247 | .unwrap_err() 248 | .to_string(), 249 | "format" 250 | ); 251 | assert_eq!( 252 | returned!(Result<(), ErrorMessage>, bail!("format {}", 1)) 253 | .unwrap_err() 254 | .to_string(), 255 | "format 1" 256 | ); 257 | let b = "ho"; 258 | assert_eq!( 259 | returned!(Result<(), ErrorMessage>, bail!("format {} {a} {} {b}", 1, 2, a = 4)) 260 | .unwrap_err() 261 | .to_string(), 262 | "format 1 4 2 ho" 263 | ); 264 | } 265 | 266 | #[test] 267 | fn error_message() { 268 | assert_eq!(error_message!("test").to_string(), "test"); 269 | assert_eq!(error_message!("test";).to_string(), "test"); 270 | assert_eq!( 271 | error_message!( 272 | "test"; 273 | error = "hello {} {a}", 1 + 4, a = "" 274 | ) 275 | .to_string(), 276 | "test\n\n = error: hello 5 \n" 277 | ); 278 | assert_eq!( 279 | error_message!( 280 | "test"; 281 | error = "hello {} {a}", 1 + 4, a = ""; 282 | ) 283 | .to_string(), 284 | "test\n\n = error: hello 5 \n" 285 | ); 286 | assert_eq!( 287 | error_message!( 288 | "test"; 289 | error = "hello {} {a}", 1 + 4, a = "",; 290 | hint = "a hint" 291 | ) 292 | .to_string(), 293 | "test\n\n = error: hello 5 \n = hint: a hint\n" 294 | ); 295 | } 296 | 297 | #[test] 298 | fn emit() { 299 | let mut emitter = Emitter::new(); 300 | emit!(emitter, "an error message"; error = "with attachments"); 301 | let span = proc_macro2::Span::call_site(); 302 | emit!(emitter, span, "error message"); 303 | #[cfg(feature = "syn2")] 304 | { 305 | let error = syn2::Error::new(proc_macro2::Span::call_site(), "an error"); 306 | emit!(emitter, error); 307 | } 308 | } 309 | 310 | // Only tests that it compiles 311 | fn _error_message_spanned() { 312 | let span = Span::call_site(); 313 | _ = error_message!(span, "test"); 314 | 315 | let span = proc_macro::Span::call_site(); 316 | _ = error_message!(span, "test"); 317 | 318 | let tokens = quote!(test); 319 | _ = error_message!(tokens, "an error message",); 320 | _ = error_message!(tokens, "an error message",;); 321 | _ = error_message!(tokens, "an error message",; warning="and a warning";); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/parse_to_tokens.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, clippy::pedantic)] 2 | use std::convert::Infallible; 3 | use std::marker::PhantomData; 4 | 5 | use proc_macro2::TokenStream; 6 | 7 | use crate::{ 8 | AnyTokenStream, AttributeMacroHandler, DeriveMacroHandler, Emitter, FunctionMacroHandler, 9 | ToTokensError, 10 | }; 11 | pub trait ManyhowParse { 12 | fn manyhow_parse(&self, input: impl AnyTokenStream, attr: bool) -> Result; 13 | } 14 | pub trait ManyhowToTokens { 15 | fn manyhow_to_tokens(&self, input: T, tokens: &mut TokenStream); 16 | } 17 | pub trait ManyhowTry { 18 | type Ok; 19 | type Err; 20 | fn manyhow_try(&self, value: T) -> Result; 21 | } 22 | 23 | pub struct WhatType(PhantomData); 24 | 25 | impl WhatType { 26 | /// Always panics 27 | pub fn identify(&self) -> Result { 28 | unimplemented!("DON'T YOU DARE CALL ME") 29 | } 30 | 31 | pub fn from(_ty: &T) -> Self { 32 | Self(PhantomData) 33 | } 34 | 35 | #[allow(clippy::new_without_default)] 36 | pub fn new() -> Self { 37 | Self(PhantomData) 38 | } 39 | } 40 | 41 | impl Clone for WhatType { 42 | fn clone(&self) -> Self { 43 | *self 44 | } 45 | } 46 | 47 | impl Copy for WhatType {} 48 | 49 | impl + From> ManyhowParse for WhatType { 50 | fn manyhow_parse(&self, input: impl AnyTokenStream, _attr: bool) -> Result { 51 | Ok(input.into().into()) 52 | } 53 | } 54 | 55 | impl ManyhowToTokens for WhatType { 56 | fn manyhow_to_tokens(&self, input: TokenStream, tokens: &mut TokenStream) { 57 | tokens.extend(input); 58 | } 59 | } 60 | 61 | impl ManyhowToTokens for WhatType { 62 | fn manyhow_to_tokens(&self, input: proc_macro::TokenStream, tokens: &mut TokenStream) { 63 | tokens.extend(TokenStream::from(input)); 64 | } 65 | } 66 | 67 | impl ManyhowToTokens for WhatType { 68 | fn manyhow_to_tokens(&self, input: E, tokens: &mut TokenStream) { 69 | input.to_tokens(tokens); 70 | } 71 | } 72 | 73 | impl ManyhowTry> for WhatType> { 74 | type Err = E; 75 | type Ok = T; 76 | 77 | fn manyhow_try(&self, value: Result) -> Result { 78 | value 79 | } 80 | } 81 | 82 | impl ManyhowTry for &WhatType { 83 | type Err = Infallible; 84 | type Ok = T; 85 | 86 | fn manyhow_try(&self, value: T) -> Result { 87 | Ok(value) 88 | } 89 | } 90 | 91 | #[cfg(feature = "syn2")] 92 | impl ManyhowParse for &WhatType { 93 | fn manyhow_parse(&self, input: impl AnyTokenStream, attr: bool) -> Result { 94 | let input = input.into(); 95 | let empty = input.is_empty(); 96 | syn2::parse2(input).map_err(|e| { 97 | let mut e = e.into_compile_error(); 98 | if attr && empty { 99 | error_message!("while parsing attribute argument (`#[... (...)]`)") 100 | .to_tokens(&mut e) 101 | } 102 | e 103 | }) 104 | } 105 | } 106 | #[cfg(feature = "syn2")] 107 | impl ManyhowToTokens for &WhatType { 108 | fn manyhow_to_tokens(&self, input: T, tokens: &mut TokenStream) { 109 | input.to_tokens(tokens); 110 | } 111 | } 112 | 113 | #[cfg(feature = "syn2")] 114 | #[test] 115 | #[allow(unused)] 116 | fn test_inference() { 117 | use syn2::parse::Parse; 118 | 119 | if false { 120 | let wt = &WhatType::new(); 121 | let ts: proc_macro::TokenStream = wt.manyhow_parse(quote::quote!(test), false).unwrap(); 122 | let wt = &WhatType::new(); 123 | if false { 124 | let wt: Result = wt.identify(); 125 | } 126 | let ts: syn2::Ident = wt.manyhow_parse(quote::quote!(test), false).unwrap(); 127 | 128 | struct Parsable; 129 | impl Parse for Parsable { 130 | fn parse(input: syn2::parse::ParseStream) -> syn2::Result { 131 | todo!() 132 | } 133 | } 134 | let wt = &WhatType::new(); 135 | let _: Result = wt.identify(); 136 | let ts = wt.manyhow_parse(quote::quote!(test), false).unwrap(); 137 | } 138 | } 139 | 140 | macro_rules! transparent_handlers { 141 | ($name:ident; $MacroInput:ident; $($input:ident: $Input:ident $($context:expr)?),*; $($dummy:ident)?) => { 142 | /// Internal implementation for macro. 143 | pub fn $name<$($Input,)* Dummy: AnyTokenStream, Output, Function,>( 144 | $($input: Result<$Input, TokenStream>,)* 145 | $($dummy: Option,)? 146 | body: impl $MacroInput, 147 | ) -> Result<(Output, TokenStream, TokenStream), TokenStream> { 148 | // use $crate::ToTokensError as _; 149 | #[allow(unused)] 150 | let mut dummy = TokenStream::new(); 151 | $(let mut dummy = $dummy.unwrap_or_default().into();)? 152 | $(let $input = match $input { 153 | Ok($input) => $input, 154 | Err(tokens) => { 155 | dummy.extend(tokens); 156 | $($crate::error_message!($context).to_tokens(&mut dummy);)? 157 | return Err(dummy); 158 | } 159 | };)* 160 | let mut dummy = dummy.into(); 161 | let mut emitter = Emitter::new(); 162 | let output = body.call($($input,)+ &mut dummy, &mut emitter); 163 | let mut tokens = TokenStream::new(); 164 | emitter.to_tokens(&mut tokens); 165 | Ok((output, tokens, dummy.into())) 166 | } 167 | }; 168 | } 169 | 170 | transparent_handlers! { function_transparent; FunctionMacroHandler; input: Input; dummy } 171 | transparent_handlers! { derive_transparent; DeriveMacroHandler; item: Item;} 172 | transparent_handlers! { attribute_transparent; AttributeMacroHandler; input: Input, item: Item; dummy } 173 | -------------------------------------------------------------------------------- /src/span_ranged.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use proc_macro2::Span; 4 | use quote::ToTokens; 5 | 6 | #[cfg(doc)] 7 | use crate::ErrorMessage; 8 | 9 | /// Get a [`Range`](std::ops::Range)[``](proc_macro2::Span) from a 10 | /// type that **either** implements [`SpanRanged`] or [`ToTokens`] (**NOT** 11 | /// both). 12 | #[macro_export] 13 | macro_rules! span_range { 14 | ($span:expr) => {{ 15 | // Warning is triggered if span is incorrect type 16 | #[allow(unused_imports)] 17 | use $crate::__private::*; 18 | ($span).FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens() 19 | }}; 20 | } 21 | 22 | /// Returns the [`Range`](Range)[``](Span) from the start to the end of 23 | /// multi-token structures. 24 | /// 25 | /// `start` and `end` can be the same when called on single Tokens or [`Span`]. 26 | /// 27 | /// Due to compiler limitations, it is currently not possible to implement 28 | /// `SpanRanged for T: ToTokens`, therefor there is 29 | /// [`to_tokens_span_range()`]. 30 | /// 31 | /// For types that **either** implement [`SpanRanged`] or [`ToTokens`] (but 32 | /// **NOT** both) the [`span_range!`] macro can be used as well. 33 | /// 34 | /// # Motivation 35 | /// This is superior to a normal [`Span`] (at least until [`Span::join`] works 36 | /// on stable), because it leads to better error messages: 37 | /// 38 | /// Given the following expression 39 | /// ``` 40 | /// let a = |something: usize| something; 41 | /// ``` 42 | /// 43 | /// [`ErrorMessage::new(first_pipe_span, "error message")`](ErrorMessage::new) 44 | /// would result in something like 45 | /// 46 | /// ```text 47 | /// error: error message 48 | /// 49 | /// let a = |something: usize| something; 50 | /// ^ 51 | /// ``` 52 | /// 53 | /// While [`ErrorMessage::new(first_pipe_span..something_span, 54 | /// "error message")`](ErrorMessage::new) would improve the error message to: 55 | /// ```text 56 | /// error: error message 57 | /// 58 | /// let a = |something: usize| something; 59 | /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 60 | /// ``` 61 | pub trait SpanRanged { 62 | /// Returns the [`Range`](Range)[``](Span) fully encompasing `self` 63 | fn span_range(&self) -> Range; 64 | 65 | /// Returns [`Self::span_range`] as a single span if possible, currently 66 | /// only possible on nightly. [more](proc_macro2::Span::join) 67 | fn span_joined(&self) -> Option { 68 | let range = self.span_range(); 69 | range.start.join(range.end) 70 | } 71 | 72 | #[doc(hidden)] 73 | #[deprecated] 74 | fn joined(&self) -> Option { 75 | self.span_joined() 76 | } 77 | } 78 | 79 | impl SpanRanged for &T { 80 | fn span_range(&self) -> Range { 81 | (*self).span_range() 82 | } 83 | } 84 | 85 | impl SpanRanged for Option { 86 | fn span_range(&self) -> Range { 87 | self.as_ref() 88 | .map_or_else(|| Span::call_site().span_range(), SpanRanged::span_range) 89 | } 90 | } 91 | 92 | impl SpanRanged for (A, B) { 93 | fn span_range(&self) -> Range { 94 | (self.0.span_range().start)..(self.1.span_range().end) 95 | } 96 | } 97 | 98 | impl SpanRanged for Span { 99 | fn span_range(&self) -> Range { 100 | *self..*self 101 | } 102 | } 103 | impl SpanRanged for proc_macro::Span { 104 | fn span_range(&self) -> Range { 105 | (*self).into()..(*self).into() 106 | } 107 | } 108 | 109 | impl SpanRanged for Range { 110 | fn span_range(&self) -> Range { 111 | self.start.span_range().start..self.end.span_range().end 112 | } 113 | } 114 | 115 | impl SpanRanged for proc_macro::TokenStream { 116 | fn span_range(&self) -> Range { 117 | let mut this = self.clone().into_iter(); 118 | let first = this 119 | .next() 120 | .as_ref() 121 | .map_or_else(proc_macro::Span::call_site, proc_macro::TokenTree::span); 122 | 123 | let last = this 124 | .last() 125 | .as_ref() 126 | .map_or(first, proc_macro::TokenTree::span); 127 | first.into()..last.into() 128 | } 129 | } 130 | 131 | impl SpanRanged for proc_macro2::extra::DelimSpan { 132 | fn span_range(&self) -> Range { 133 | self.join().span_range() 134 | } 135 | } 136 | 137 | #[cfg(feature = "syn2")] 138 | const _: () = { 139 | impl SpanRanged for syn2::token::Brace { 140 | fn span_range(&self) -> Range { 141 | self.span.span_range() 142 | } 143 | } 144 | impl SpanRanged for syn2::token::Bracket { 145 | fn span_range(&self) -> Range { 146 | self.span.span_range() 147 | } 148 | } 149 | impl SpanRanged for syn2::token::Paren { 150 | fn span_range(&self) -> Range { 151 | self.span.span_range() 152 | } 153 | } 154 | }; 155 | 156 | /// Implementation of [`SpanRanged`](SpanRanged)` for T: `[`ToTokens`] 157 | /// 158 | /// This is necessary to put in a standalone function due to compiler 159 | /// limitations. 160 | pub fn to_tokens_span_range(tokens: impl ToTokens) -> Range { 161 | proc_macro::TokenStream::from(tokens.to_token_stream()).span_range() 162 | } 163 | 164 | #[doc(hidden)] 165 | pub trait SpanRangedToSpanRange { 166 | #[allow(non_snake_case)] 167 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range; 168 | } 169 | impl SpanRangedToSpanRange for T { 170 | #[allow(non_snake_case)] 171 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range { 172 | self.span_range() 173 | } 174 | } 175 | 176 | #[doc(hidden)] 177 | pub trait ToTokensToSpanRange { 178 | #[allow(non_snake_case)] 179 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range; 180 | } 181 | impl ToTokensToSpanRange for T { 182 | #[allow(non_snake_case)] 183 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range { 184 | let mut this = self.to_token_stream().into_iter(); 185 | let first = this 186 | .next() 187 | .as_ref() 188 | .map_or_else(proc_macro2::Span::call_site, proc_macro2::TokenTree::span); 189 | 190 | let last = this 191 | .last() 192 | .as_ref() 193 | .map_or(first, proc_macro2::TokenTree::span); 194 | first..last 195 | } 196 | } 197 | 198 | #[doc(hidden)] 199 | pub trait ToTokensTupleToSpanRange { 200 | #[allow(non_snake_case)] 201 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range; 202 | } 203 | impl ToTokensTupleToSpanRange for (A, B) { 204 | #[allow(non_snake_case)] 205 | fn FIRST_ARG_MUST_IMPLEMENT_SpanRanged_OR_ToTokens(&self) -> Range { 206 | let first = self 207 | .0 208 | .to_token_stream() 209 | .into_iter() 210 | .next() 211 | .as_ref() 212 | .map_or_else(proc_macro2::Span::call_site, proc_macro2::TokenTree::span); 213 | 214 | let last = self 215 | .1 216 | .to_token_stream() 217 | .into_iter() 218 | .last() 219 | .as_ref() 220 | .map_or(first, proc_macro2::TokenTree::span); 221 | 222 | first..last 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod test { 228 | #[test] 229 | fn test() { 230 | span_range!(1); 231 | span_range!((1, 2)); 232 | } 233 | } 234 | --------------------------------------------------------------------------------