├── .clippy.toml ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── demo-hack-impl ├── Cargo.toml └── src │ └── lib.rs ├── demo-hack ├── Cargo.toml └── src │ └── lib.rs ├── example ├── Cargo.toml └── src │ └── main.rs ├── nested ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── build.rs └── src │ └── lib.rs ├── src ├── error.rs ├── iter.rs ├── lib.rs ├── parse.rs └── quote.rs └── tests ├── compiletest.rs └── ui ├── private.rs ├── private.stderr ├── unexpected-arg.rs ├── unexpected-arg.stderr ├── unexpected.rs ├── unexpected.stderr ├── unknown-arg.rs └── unknown-arg.stderr /.clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.31.0" 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: [cron: "40 1 * * *"] 7 | 8 | permissions: 9 | contents: read 10 | 11 | env: 12 | RUSTFLAGS: -Dwarnings 13 | 14 | jobs: 15 | test: 16 | name: ${{matrix.name || format('Rust {0}', matrix.rust)}} 17 | runs-on: ${{matrix.os || 'ubuntu'}}-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | rust: [beta, stable, 1.56.0, 1.31.0] 22 | include: 23 | - rust: nightly 24 | - rust: nightly 25 | name: Windows 26 | os: windows 27 | timeout-minutes: 45 28 | steps: 29 | - uses: actions/checkout@v3 30 | - uses: dtolnay/rust-toolchain@master 31 | with: 32 | toolchain: ${{matrix.rust}} 33 | - if: matrix.rust == '1.31.0' 34 | run: echo 'extern crate proc_macro;' >> demo-hack-impl/src/lib.rs 35 | - run: cargo run --manifest-path example/Cargo.toml 36 | - if: matrix.rust != '1.31.0' 37 | run: cargo test --all 38 | 39 | clippy: 40 | name: Clippy 41 | runs-on: ubuntu-latest 42 | if: github.event_name != 'pull_request' 43 | timeout-minutes: 45 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: dtolnay/rust-toolchain@clippy 47 | - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic 48 | 49 | outdated: 50 | name: Outdated 51 | runs-on: ubuntu-latest 52 | if: github.event_name != 'pull_request' 53 | timeout-minutes: 45 54 | steps: 55 | - uses: actions/checkout@v3 56 | - uses: dtolnay/install@cargo-outdated 57 | - run: cargo outdated --workspace --exit-code 1 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-hack" 3 | version = "0.5.20+deprecated" 4 | authors = ["David Tolnay "] 5 | categories = ["development-tools::procedural-macro-helpers"] 6 | description = "Procedural macros in expression position" 7 | documentation = "https://docs.rs/proc-macro-hack" 8 | edition = "2018" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/dtolnay/proc-macro-hack" 11 | rust-version = "1.31" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dev-dependencies] 17 | demo-hack = { version = "0.0.5", path = "demo-hack" } 18 | demo-hack-impl = { version = "0.0.5", path = "demo-hack-impl" } 19 | quote = "1.0" 20 | rustversion = "1.0" 21 | syn = "1.0.5" 22 | trybuild = { version = "1.0.49", features = ["diff"] } 23 | 24 | [workspace] 25 | members = ["demo-hack", "demo-hack-impl", "example", "nested"] 26 | 27 | [package.metadata.docs.rs] 28 | targets = ["x86_64-unknown-linux-gnu"] 29 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 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 | Copyright (c) 2018 David Tolnay 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Procedural macros in expression position 2 | ======================================== 3 | 4 | [github](https://github.com/dtolnay/proc-macro-hack) 5 | [crates.io](https://crates.io/crates/proc-macro-hack) 6 | [docs.rs](https://docs.rs/proc-macro-hack) 7 | [build status](https://github.com/dtolnay/proc-macro-hack/actions?query=branch%3Amaster) 8 | 9 |

10 | Note: As of Rust 1.45 this crate is superseded by native support for 11 | #[proc_macro] in expression position. Only consider using this crate if you care 12 | about supporting compilers between 1.31 and 1.45. 13 |
14 | 15 | Since Rust 1.30, the language supports user-defined function-like procedural 16 | macros. However these can only be invoked in item position, not in statements or 17 | expressions. 18 | 19 | This crate implements an alternative type of procedural macro that can be 20 | invoked in statement or expression position. 21 | 22 | This approach works with any Rust version 1.31+. 23 | 24 | ## Defining procedural macros 25 | 26 | Two crates are required to define a procedural macro. 27 | 28 | ### The implementation crate 29 | 30 | This crate must contain nothing but procedural macros. Private helper 31 | functions and private modules are fine but nothing can be public. 32 | 33 | [» example of an implementation crate][demo-hack-impl] 34 | 35 | Just like you would use a #\[proc_macro\] attribute to define a natively 36 | supported procedural macro, use proc-macro-hack's #\[proc_macro_hack\] 37 | attribute to define a procedural macro that works in expression position. 38 | The function signature is the same as for ordinary function-like procedural 39 | macros. 40 | 41 | ```rust 42 | use proc_macro::TokenStream; 43 | use proc_macro_hack::proc_macro_hack; 44 | use quote::quote; 45 | use syn::{parse_macro_input, Expr}; 46 | 47 | #[proc_macro_hack] 48 | pub fn add_one(input: TokenStream) -> TokenStream { 49 | let expr = parse_macro_input!(input as Expr); 50 | TokenStream::from(quote! { 51 | 1 + (#expr) 52 | }) 53 | } 54 | ``` 55 | 56 | ### The declaration crate 57 | 58 | This crate is allowed to contain other public things if you need, for 59 | example traits or functions or ordinary macros. 60 | 61 | [» example of a declaration crate][demo-hack] 62 | 63 | Within the declaration crate there needs to be a re-export of your 64 | procedural macro from the implementation crate. The re-export also carries a 65 | \#\[proc_macro_hack\] attribute. 66 | 67 | ```rust 68 | use proc_macro_hack::proc_macro_hack; 69 | 70 | /// Add one to an expression. 71 | /// 72 | /// (Documentation goes here on the re-export, not in the other crate.) 73 | #[proc_macro_hack] 74 | pub use demo_hack_impl::add_one; 75 | ``` 76 | 77 | Both crates depend on `proc-macro-hack`: 78 | 79 | ```toml 80 | [dependencies] 81 | proc-macro-hack = "0.5" 82 | ``` 83 | 84 | Additionally, your implementation crate (but not your declaration crate) is 85 | a proc macro crate: 86 | 87 | ```toml 88 | [lib] 89 | proc-macro = true 90 | ``` 91 | 92 | ## Using procedural macros 93 | 94 | Users of your crate depend on your declaration crate (not your 95 | implementation crate), then use your procedural macros as usual. 96 | 97 | [» example of a downstream crate][example] 98 | 99 | ```rust 100 | use demo_hack::add_one; 101 | 102 | fn main() { 103 | let two = 2; 104 | let nine = add_one!(two) + add_one!(2 + 3); 105 | println!("nine = {}", nine); 106 | } 107 | ``` 108 | 109 | [demo-hack-impl]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack-impl 110 | [demo-hack]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack 111 | [example]: https://github.com/dtolnay/proc-macro-hack/tree/master/example 112 | 113 | ## Limitations 114 | 115 | - Only proc macros in expression position are supported. Proc macros in pattern 116 | position ([#20]) are not supported. 117 | 118 | - By default, nested invocations are not supported i.e. the code emitted by a 119 | proc-macro-hack macro invocation cannot contain recursive calls to the same 120 | proc-macro-hack macro nor calls to any other proc-macro-hack macros. Use 121 | [`proc-macro-nested`] if you require support for nested invocations. 122 | 123 | - By default, hygiene is structured such that the expanded code can't refer to 124 | local variables other than those passed by name somewhere in the macro input. 125 | If your macro must refer to *local* variables that don't get named in the 126 | macro input, use `#[proc_macro_hack(fake_call_site)]` on the re-export in your 127 | declaration crate. *Most macros won't need this.* 128 | 129 | - On compilers that are new enough to natively support proc macros in expression 130 | position, proc-macro-hack does not automatically use that support, since the 131 | hygiene can be subtly different between the two implementations. To opt in to 132 | compiling your macro to native `#[proc_macro]` on sufficiently new compilers, 133 | use `#[proc_macro_hack(only_hack_old_rustc)]` on the re-export in your 134 | declaration crate. 135 | 136 | [#10]: https://github.com/dtolnay/proc-macro-hack/issues/10 137 | [#20]: https://github.com/dtolnay/proc-macro-hack/issues/20 138 | [`proc-macro-nested`]: https://docs.rs/proc-macro-nested 139 | 140 |
141 | 142 | #### License 143 | 144 | 145 | Licensed under either of Apache License, Version 146 | 2.0 or MIT license at your option. 147 | 148 | 149 |
150 | 151 | 152 | Unless you explicitly state otherwise, any contribution intentionally submitted 153 | for inclusion in this hack by you, as defined in the Apache-2.0 license, shall 154 | be dual licensed as above, without any additional terms or conditions. 155 | 156 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::Command; 3 | use std::str; 4 | 5 | // The rustc-cfg strings below are *not* public API. Please let us know by 6 | // opening a GitHub issue if your build environment requires some way to enable 7 | // these cfgs other than by executing our build script. 8 | fn main() { 9 | let minor = match rustc_minor_version() { 10 | Some(minor) => minor, 11 | None => return, 12 | }; 13 | 14 | // Function-like procedural macros in expressions, patterns, and statements 15 | // stabilized in Rust 1.45: 16 | // https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html#stabilizing-function-like-procedural-macros-in-expressions-patterns-and-statements 17 | if minor < 45 { 18 | println!("cargo:rustc-cfg=need_proc_macro_hack"); 19 | } 20 | } 21 | 22 | fn rustc_minor_version() -> Option { 23 | let rustc = env::var_os("RUSTC")?; 24 | let output = Command::new(rustc).arg("--version").output().ok()?; 25 | let version = str::from_utf8(&output.stdout).ok()?; 26 | let mut pieces = version.split('.'); 27 | if pieces.next() != Some("rustc 1") { 28 | return None; 29 | } 30 | pieces.next()?.parse().ok() 31 | } 32 | -------------------------------------------------------------------------------- /demo-hack-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-hack-impl" 3 | version = "0.0.5" 4 | authors = ["David Tolnay "] 5 | description = "Demo of proc-macro-hack" 6 | edition = "2018" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/dtolnay/proc-macro-hack" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro-hack = { version = "0.5", path = ".." } 15 | quote = "1.0" 16 | syn = { version = "1.0", features = ["full"] } 17 | 18 | [package.metadata.docs.rs] 19 | targets = ["x86_64-unknown-linux-gnu"] 20 | -------------------------------------------------------------------------------- /demo-hack-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | // Clippy bug: https://github.com/rust-lang/rust-clippy/issues/7422 3 | clippy::nonstandard_macro_braces, 4 | )] 5 | 6 | use proc_macro::TokenStream; 7 | use proc_macro_hack::proc_macro_hack; 8 | use quote::quote; 9 | use syn::{parse_macro_input, Expr}; 10 | 11 | #[proc_macro_hack] 12 | pub fn add_one(input: TokenStream) -> TokenStream { 13 | let expr = parse_macro_input!(input as Expr); 14 | TokenStream::from(quote! { 15 | 1 + (#expr) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /demo-hack/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-hack" 3 | version = "0.0.5" 4 | authors = ["David Tolnay "] 5 | description = "Demo of proc-macro-hack" 6 | edition = "2018" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/dtolnay/proc-macro-hack" 9 | 10 | [dependencies] 11 | proc-macro-hack = { version = "0.5", path = ".." } 12 | demo-hack-impl = { version = "0.0.5", path = "../demo-hack-impl" } 13 | 14 | [package.metadata.docs.rs] 15 | targets = ["x86_64-unknown-linux-gnu"] 16 | -------------------------------------------------------------------------------- /demo-hack/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | /// Add one to an expression. 4 | /// 5 | /// (Documentation goes here on the re-export, not in the other crate.) 6 | #[proc_macro_hack(only_hack_old_rustc)] 7 | pub use demo_hack_impl::add_one; 8 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | publish = false 8 | repository = "https://github.com/dtolnay/proc-macro-hack" 9 | 10 | [dependencies] 11 | demo-hack = { path = "../demo-hack" } 12 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | use demo_hack::add_one; 2 | 3 | fn main() { 4 | let two = 2; 5 | let nine = add_one!(two) + add_one!(2 + 3); 6 | println!("nine = {}", nine); 7 | } 8 | -------------------------------------------------------------------------------- /nested/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-nested" 3 | version = "0.1.7" 4 | authors = ["David Tolnay "] 5 | description = "Support for nested proc-macro-hack invocations" 6 | documentation = "https://docs.rs/proc-macro-nested" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/dtolnay/proc-macro-hack" 9 | 10 | [package.metadata.docs.rs] 11 | targets = ["x86_64-unknown-linux-gnu"] 12 | -------------------------------------------------------------------------------- /nested/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /nested/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /nested/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::iter; 4 | use std::path::{self, Path}; 5 | 6 | /* 7 | #[doc(hidden)] 8 | #[macro_export] 9 | macro_rules! count { 10 | () => { proc_macro_call_0!() }; 11 | (!) => { proc_macro_call_1!() }; 12 | (!!) => { proc_macro_call_2!() }; 13 | ... 14 | } 15 | */ 16 | 17 | fn main() { 18 | // Tell Cargo not to rerun on src/lib.rs changes. 19 | println!("cargo:rerun-if-changed=build.rs"); 20 | 21 | let mut content = String::new(); 22 | content += "#[doc(hidden)]\n"; 23 | content += "#[macro_export]\n"; 24 | content += "macro_rules! count {\n"; 25 | for i in 0..=64 { 26 | let bangs = iter::repeat("!").take(i).collect::(); 27 | content += &format!(" ({}) => {{ proc_macro_call_{}!() }};\n", bangs, i); 28 | } 29 | content += " ($(!)+) => {\n"; 30 | content += " compile_error! {\n"; 31 | content += " \"this macro does not support >64 nested macro invocations\"\n"; 32 | content += " }\n"; 33 | content += " };\n"; 34 | content += "}\n"; 35 | 36 | let content = content.as_bytes(); 37 | let out_dir = env::var("OUT_DIR").unwrap(); 38 | let ref dest_path = Path::new(&out_dir).join("count.rs"); 39 | 40 | // Avoid bumping filetime if content is up to date. Possibly related to 41 | // https://github.com/dtolnay/proc-macro-hack/issues/56 ...? 42 | if fs::read(dest_path) 43 | .map(|existing| existing != content) 44 | .unwrap_or(true) 45 | { 46 | fs::write(dest_path, content).unwrap(); 47 | } 48 | 49 | println!("cargo:rustc-env=PATH_SEPARATOR={}", path::MAIN_SEPARATOR); 50 | } 51 | -------------------------------------------------------------------------------- /nested/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Support for nested invocations of proc-macro-hack expression macros. 2 | //! 3 | //! By default, macros defined through proc-macro-hack do not support nested 4 | //! invocations, i.e. the code emitted by a proc-macro-hack macro invocation 5 | //! cannot contain recursive calls to the same proc-macro-hack macro nor calls 6 | //! to any other proc-macro-hack macros. 7 | //! 8 | //! This crate provides opt-in support for such nested invocations. 9 | //! 10 | //! To make a macro callable recursively, add a dependency on this crate from 11 | //! your declaration crate and update the `#[proc_macro_hack]` re-export as 12 | //! follows. 13 | //! 14 | //! ``` 15 | //! // Before 16 | //! # const IGNORE: &str = stringify! { 17 | //! #[proc_macro_hack] 18 | //! pub use demo_hack_impl::add_one; 19 | //! # }; 20 | //! ``` 21 | //! 22 | //! ``` 23 | //! // After 24 | //! # const IGNORE: &str = stringify! { 25 | //! #[proc_macro_hack(support_nested)] 26 | //! pub use demo_hack_impl::add_one; 27 | //! # }; 28 | //! ``` 29 | //! 30 | //! No change is required within your definition crate, only to the re-export in 31 | //! the declaration crate. 32 | //! 33 | //! # Limitations 34 | //! 35 | //! - Nested invocations are preprocessed by a TT-muncher, so the caller's crate 36 | //! will be required to contain `#![recursion_limit = "..."]` if there are 37 | //! lengthy macro invocations. 38 | //! 39 | //! - Only up to 64 nested invocations are supported. 40 | 41 | #![no_std] 42 | 43 | include!(concat!(env!("OUT_DIR"), env!("PATH_SEPARATOR"), "count.rs")); 44 | 45 | #[doc(hidden)] 46 | #[macro_export] 47 | macro_rules! dispatch { 48 | (() $($bang:tt)*) => { 49 | $crate::count!($($bang)*) 50 | }; 51 | ((($($first:tt)*) $($rest:tt)*) $($bang:tt)*) => { 52 | $crate::dispatch!(($($first)* $($rest)*) $($bang)*) 53 | }; 54 | (([$($first:tt)*] $($rest:tt)*) $($bang:tt)*) => { 55 | $crate::dispatch!(($($first)* $($rest)*) $($bang)*) 56 | }; 57 | (({$($first:tt)*} $($rest:tt)*) $($bang:tt)*) => { 58 | $crate::dispatch!(($($first)* $($rest)*) $($bang)*) 59 | }; 60 | ((! $($rest:tt)*) $($bang:tt)*) => { 61 | $crate::dispatch!(($($rest)*) $($bang)* !) 62 | }; 63 | ((!= $($rest:tt)*) $($bang:tt)*) => { 64 | $crate::dispatch!(($($rest)*) $($bang)* !) 65 | }; 66 | (($first:tt $($rest:tt)*) $($bang:tt)*) => { 67 | $crate::dispatch!(($($rest)*) $($bang)*) 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 2 | use std::iter::FromIterator; 3 | 4 | pub struct Error { 5 | span: Span, 6 | msg: String, 7 | } 8 | 9 | impl Error { 10 | pub fn new(span: Span, msg: impl Into) -> Self { 11 | Error { 12 | span, 13 | msg: msg.into(), 14 | } 15 | } 16 | } 17 | 18 | pub fn compile_error(err: Error) -> TokenStream { 19 | // compile_error!($msg) 20 | TokenStream::from_iter(vec![ 21 | TokenTree::Ident(Ident::new("compile_error", err.span)), 22 | TokenTree::Punct({ 23 | let mut punct = Punct::new('!', Spacing::Alone); 24 | punct.set_span(err.span); 25 | punct 26 | }), 27 | TokenTree::Group({ 28 | let mut group = Group::new(Delimiter::Brace, { 29 | TokenStream::from_iter(vec![TokenTree::Literal({ 30 | let mut string = Literal::string(&err.msg); 31 | string.set_span(err.span); 32 | string 33 | })]) 34 | }); 35 | group.set_span(err.span); 36 | group 37 | }), 38 | ]) 39 | } 40 | -------------------------------------------------------------------------------- /src/iter.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{token_stream, Delimiter, TokenStream, TokenTree}; 2 | 3 | pub type Iter<'a> = &'a mut IterImpl; 4 | 5 | pub struct IterImpl { 6 | stack: Vec, 7 | peeked: Option, 8 | } 9 | 10 | pub fn new(tokens: TokenStream) -> IterImpl { 11 | IterImpl { 12 | stack: vec![tokens.into_iter()], 13 | peeked: None, 14 | } 15 | } 16 | 17 | impl IterImpl { 18 | pub fn peek(&mut self) -> Option<&TokenTree> { 19 | self.peeked = self.next(); 20 | self.peeked.as_ref() 21 | } 22 | } 23 | 24 | impl Iterator for IterImpl { 25 | type Item = TokenTree; 26 | 27 | fn next(&mut self) -> Option { 28 | if let Some(tt) = self.peeked.take() { 29 | return Some(tt); 30 | } 31 | loop { 32 | let top = self.stack.last_mut()?; 33 | match top.next() { 34 | None => drop(self.stack.pop()), 35 | Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::None => { 36 | self.stack.push(group.stream().into_iter()); 37 | } 38 | Some(tt) => return Some(tt), 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/proc-macro-hack) [![crates-io]](https://crates.io/crates/proc-macro-hack) [![docs-rs]](https://docs.rs/proc-macro-hack) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //!

10 | //! Note: As of Rust 1.45 this crate is superseded by native support 11 | //! for #[proc_macro] in expression position. Only consider using this crate if 12 | //! you care about supporting compilers between 1.31 and 1.45. 13 | //!
14 | //! 15 | //! Since Rust 1.30, the language supports user-defined function-like procedural 16 | //! macros. However these can only be invoked in item position, not in 17 | //! statements or expressions. 18 | //! 19 | //! This crate implements an alternative type of procedural macro that can be 20 | //! invoked in statement or expression position. 21 | //! 22 | //! # Defining procedural macros 23 | //! 24 | //! Two crates are required to define a procedural macro. 25 | //! 26 | //! ## The implementation crate 27 | //! 28 | //! This crate must contain nothing but procedural macros. Private helper 29 | //! functions and private modules are fine but nothing can be public. 30 | //! 31 | //! [» example of an implementation crate][demo-hack-impl] 32 | //! 33 | //! Just like you would use a #\[proc_macro\] attribute to define a natively 34 | //! supported procedural macro, use proc-macro-hack's #\[proc_macro_hack\] 35 | //! attribute to define a procedural macro that works in expression position. 36 | //! The function signature is the same as for ordinary function-like procedural 37 | //! macros. 38 | //! 39 | //! ``` 40 | //! # extern crate proc_macro; 41 | //! # 42 | //! use proc_macro::TokenStream; 43 | //! use proc_macro_hack::proc_macro_hack; 44 | //! use quote::quote; 45 | //! use syn::{parse_macro_input, Expr}; 46 | //! 47 | //! # const IGNORE: &str = stringify! { 48 | //! #[proc_macro_hack] 49 | //! # }; 50 | //! pub fn add_one(input: TokenStream) -> TokenStream { 51 | //! let expr = parse_macro_input!(input as Expr); 52 | //! TokenStream::from(quote! { 53 | //! 1 + (#expr) 54 | //! }) 55 | //! } 56 | //! # 57 | //! # fn main() {} 58 | //! ``` 59 | //! 60 | //! ## The declaration crate 61 | //! 62 | //! This crate is allowed to contain other public things if you need, for 63 | //! example traits or functions or ordinary macros. 64 | //! 65 | //! [» example of a declaration crate][demo-hack] 66 | //! 67 | //! Within the declaration crate there needs to be a re-export of your 68 | //! procedural macro from the implementation crate. The re-export also carries a 69 | //! \#\[proc_macro_hack\] attribute. 70 | //! 71 | //! ``` 72 | //! use proc_macro_hack::proc_macro_hack; 73 | //! 74 | //! /// Add one to an expression. 75 | //! /// 76 | //! /// (Documentation goes here on the re-export, not in the other crate.) 77 | //! #[proc_macro_hack] 78 | //! pub use demo_hack_impl::add_one; 79 | //! # 80 | //! # fn main() {} 81 | //! ``` 82 | //! 83 | //! Both crates depend on `proc-macro-hack`: 84 | //! 85 | //! ```toml 86 | //! [dependencies] 87 | //! proc-macro-hack = "0.5" 88 | //! ``` 89 | //! 90 | //! Additionally, your implementation crate (but not your declaration crate) is 91 | //! a proc macro crate: 92 | //! 93 | //! ```toml 94 | //! [lib] 95 | //! proc-macro = true 96 | //! ``` 97 | //! 98 | //! # Using procedural macros 99 | //! 100 | //! Users of your crate depend on your declaration crate (not your 101 | //! implementation crate), then use your procedural macros as usual. 102 | //! 103 | //! [» example of a downstream crate][example] 104 | //! 105 | //! ``` 106 | //! use demo_hack::add_one; 107 | //! 108 | //! fn main() { 109 | //! let two = 2; 110 | //! let nine = add_one!(two) + add_one!(2 + 3); 111 | //! println!("nine = {}", nine); 112 | //! } 113 | //! ``` 114 | //! 115 | //! [demo-hack-impl]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack-impl 116 | //! [demo-hack]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack 117 | //! [example]: https://github.com/dtolnay/proc-macro-hack/tree/master/example 118 | //! 119 | //! # Limitations 120 | //! 121 | //! - Only proc macros in expression position are supported. Proc macros in 122 | //! pattern position ([#20]) are not supported. 123 | //! 124 | //! - By default, nested invocations are not supported i.e. the code emitted by 125 | //! a proc-macro-hack macro invocation cannot contain recursive calls to the 126 | //! same proc-macro-hack macro nor calls to any other proc-macro-hack macros. 127 | //! Use [`proc-macro-nested`] if you require support for nested invocations. 128 | //! 129 | //! - By default, hygiene is structured such that the expanded code can't refer 130 | //! to local variables other than those passed by name somewhere in the macro 131 | //! input. If your macro must refer to *local* variables that don't get named 132 | //! in the macro input, use `#[proc_macro_hack(fake_call_site)]` on the 133 | //! re-export in your declaration crate. *Most macros won't need this.* 134 | //! 135 | //! - On compilers that are new enough to natively support proc macros in 136 | //! expression position, proc-macro-hack does not automatically use that 137 | //! support, since the hygiene can be subtly different between the two 138 | //! implementations. To opt in to compiling your macro to native 139 | //! `#[proc_macro]` on sufficiently new compilers, use 140 | //! `#[proc_macro_hack(only_hack_old_rustc)]` on the re-export in your 141 | //! declaration crate. 142 | //! 143 | //! [#10]: https://github.com/dtolnay/proc-macro-hack/issues/10 144 | //! [#20]: https://github.com/dtolnay/proc-macro-hack/issues/20 145 | //! [`proc-macro-nested`]: https://docs.rs/proc-macro-nested 146 | 147 | #![recursion_limit = "512"] 148 | #![allow( 149 | clippy::doc_markdown, 150 | clippy::manual_strip, 151 | clippy::module_name_repetitions, 152 | clippy::needless_doctest_main, 153 | clippy::needless_pass_by_value, 154 | clippy::too_many_lines, 155 | clippy::toplevel_ref_arg 156 | )] 157 | 158 | extern crate proc_macro; 159 | 160 | #[macro_use] 161 | mod quote; 162 | 163 | mod error; 164 | mod iter; 165 | mod parse; 166 | 167 | use crate::error::{compile_error, Error}; 168 | use crate::iter::Iter; 169 | use crate::parse::{ 170 | parse_define_args, parse_enum_hack, parse_export_args, parse_fake_call_site, parse_input, 171 | }; 172 | use proc_macro::{Ident, Punct, Spacing, Span, TokenStream, TokenTree}; 173 | use std::fmt::Write; 174 | 175 | type Visibility = Option; 176 | 177 | enum Input { 178 | Export(Export), 179 | Define(Define), 180 | } 181 | 182 | // pub use demo_hack_impl::{m1, m2 as qrst}; 183 | struct Export { 184 | attrs: TokenStream, 185 | vis: Visibility, 186 | from: Ident, 187 | macros: Vec, 188 | } 189 | 190 | // pub fn m1(input: TokenStream) -> TokenStream { ... } 191 | struct Define { 192 | attrs: TokenStream, 193 | name: Ident, 194 | body: TokenStream, 195 | } 196 | 197 | struct Macro { 198 | name: Ident, 199 | export_as: Ident, 200 | } 201 | 202 | #[proc_macro_attribute] 203 | pub fn proc_macro_hack(args: TokenStream, input: TokenStream) -> TokenStream { 204 | let ref mut args = iter::new(args); 205 | let ref mut input = iter::new(input); 206 | expand_proc_macro_hack(args, input).unwrap_or_else(compile_error) 207 | } 208 | 209 | fn expand_proc_macro_hack(args: Iter, input: Iter) -> Result { 210 | match parse_input(input)? { 211 | Input::Export(export) => { 212 | let args = parse_export_args(args)?; 213 | Ok(expand_export(export, args)) 214 | } 215 | Input::Define(define) => { 216 | parse_define_args(args)?; 217 | Ok(expand_define(define)) 218 | } 219 | } 220 | } 221 | 222 | #[doc(hidden)] 223 | #[proc_macro_derive(ProcMacroHack)] 224 | pub fn enum_hack(input: TokenStream) -> TokenStream { 225 | let ref mut input = iter::new(input); 226 | parse_enum_hack(input).unwrap_or_else(compile_error) 227 | } 228 | 229 | struct FakeCallSite { 230 | derive: Ident, 231 | rest: TokenStream, 232 | } 233 | 234 | #[doc(hidden)] 235 | #[proc_macro_attribute] 236 | pub fn fake_call_site(args: TokenStream, input: TokenStream) -> TokenStream { 237 | let ref mut args = iter::new(args); 238 | let ref mut input = iter::new(input); 239 | expand_fake_call_site(args, input).unwrap_or_else(compile_error) 240 | } 241 | 242 | fn expand_fake_call_site(args: Iter, input: Iter) -> Result { 243 | let span = match args.next() { 244 | Some(token) => token.span(), 245 | None => return Ok(input.collect()), 246 | }; 247 | 248 | let input = parse_fake_call_site(input)?; 249 | let mut derive = input.derive; 250 | derive.set_span(span); 251 | let rest = input.rest; 252 | 253 | Ok(quote! { 254 | #[derive(#derive)] 255 | #rest 256 | }) 257 | } 258 | 259 | struct ExportArgs { 260 | support_nested: bool, 261 | internal_macro_calls: u16, 262 | fake_call_site: bool, 263 | only_hack_old_rustc: bool, 264 | } 265 | 266 | fn expand_export(export: Export, args: ExportArgs) -> TokenStream { 267 | if args.only_hack_old_rustc && cfg!(not(need_proc_macro_hack)) { 268 | return expand_export_nohack(export); 269 | } 270 | 271 | let dummy = dummy_name_for_export(&export); 272 | 273 | let attrs = export.attrs; 274 | let vis = export.vis; 275 | let macro_export = match vis { 276 | Some(_) => quote!(#[macro_export]), 277 | None => quote!(), 278 | }; 279 | let crate_prefix = vis.as_ref().map(|_| quote!($crate::)); 280 | let enum_variant = if args.support_nested { 281 | if args.internal_macro_calls == 0 { 282 | Ident::new("Nested", Span::call_site()) 283 | } else { 284 | let name = format!("Nested{}", args.internal_macro_calls); 285 | Ident::new(&name, Span::call_site()) 286 | } 287 | } else { 288 | Ident::new("Value", Span::call_site()) 289 | }; 290 | 291 | let from = export.from; 292 | let mut actual_names = TokenStream::new(); 293 | let mut export_dispatch = TokenStream::new(); 294 | let mut export_call_site = TokenStream::new(); 295 | let mut macro_rules = TokenStream::new(); 296 | for Macro { name, export_as } in &export.macros { 297 | let hacked = hacked_proc_macro_name(name); 298 | let dispatch = dispatch_macro_name(name); 299 | let call_site = call_site_macro_name(name); 300 | 301 | if !actual_names.is_empty() { 302 | actual_names.extend(quote!(,)); 303 | } 304 | actual_names.extend(quote!(#hacked)); 305 | 306 | if !export_dispatch.is_empty() { 307 | export_dispatch.extend(quote!(,)); 308 | } 309 | export_dispatch.extend(quote!(dispatch as #dispatch)); 310 | 311 | if !export_call_site.is_empty() { 312 | export_call_site.extend(quote!(,)); 313 | } 314 | export_call_site.extend(quote!(fake_call_site as #call_site)); 315 | 316 | let do_derive = if !args.fake_call_site { 317 | quote! { 318 | #[derive(#crate_prefix #hacked)] 319 | } 320 | } else if crate_prefix.is_some() { 321 | quote! { 322 | use #crate_prefix #hacked; 323 | #[#crate_prefix #call_site ($($proc_macro)*)] 324 | #[derive(#hacked)] 325 | } 326 | } else { 327 | quote! { 328 | #[#call_site ($($proc_macro)*)] 329 | #[derive(#hacked)] 330 | } 331 | }; 332 | 333 | let proc_macro_call = if args.support_nested { 334 | let extra_bangs = (0..args.internal_macro_calls) 335 | .map(|_| TokenTree::Punct(Punct::new('!', Spacing::Alone))) 336 | .collect::(); 337 | quote! { 338 | #crate_prefix #dispatch! { ($($proc_macro)*) #extra_bangs } 339 | } 340 | } else { 341 | quote! { 342 | proc_macro_call!() 343 | } 344 | }; 345 | 346 | macro_rules.extend(quote! { 347 | #attrs 348 | #macro_export 349 | macro_rules! #export_as { 350 | ($($proc_macro:tt)*) => {{ 351 | #do_derive 352 | #[allow(dead_code)] 353 | enum ProcMacroHack { 354 | #enum_variant = (stringify! { $($proc_macro)* }, 0).1, 355 | } 356 | #proc_macro_call 357 | }}; 358 | } 359 | }); 360 | } 361 | 362 | if export.macros.len() != 1 { 363 | export_dispatch = quote!({#export_dispatch}); 364 | export_call_site = quote!({#export_call_site}); 365 | actual_names = quote!({#actual_names}); 366 | } 367 | 368 | let export_dispatch = if args.support_nested { 369 | quote! { 370 | #[doc(hidden)] 371 | #vis use proc_macro_nested::#export_dispatch; 372 | } 373 | } else { 374 | quote!() 375 | }; 376 | 377 | let export_call_site = if args.fake_call_site { 378 | quote! { 379 | #[doc(hidden)] 380 | #vis use proc_macro_hack::#export_call_site; 381 | } 382 | } else { 383 | quote!() 384 | }; 385 | 386 | let expanded = quote! { 387 | #[doc(hidden)] 388 | #vis use #from::#actual_names; 389 | 390 | #export_dispatch 391 | #export_call_site 392 | 393 | #macro_rules 394 | }; 395 | 396 | wrap_in_enum_hack(dummy, expanded) 397 | } 398 | 399 | fn expand_export_nohack(export: Export) -> TokenStream { 400 | let attrs = export.attrs; 401 | let vis = export.vis; 402 | let from = export.from; 403 | let mut names = TokenStream::new(); 404 | 405 | for Macro { name, export_as } in &export.macros { 406 | let pub_name = pub_proc_macro_name(name); 407 | if !names.is_empty() { 408 | names.extend(quote!(,)); 409 | } 410 | names.extend(quote!(#pub_name as #export_as)); 411 | } 412 | 413 | if export.macros.len() != 1 { 414 | names = quote!({#names}); 415 | } 416 | 417 | quote! { 418 | #attrs 419 | #vis use #from::#names; 420 | } 421 | } 422 | 423 | fn expand_define(define: Define) -> TokenStream { 424 | let attrs = define.attrs; 425 | let name = define.name; 426 | let pub_name = pub_proc_macro_name(&name); 427 | let hacked = hacked_proc_macro_name(&name); 428 | let body = define.body; 429 | 430 | quote! { 431 | mod #pub_name { 432 | extern crate proc_macro; 433 | pub use self::proc_macro::*; 434 | } 435 | 436 | #attrs 437 | #[doc(hidden)] 438 | #[proc_macro_derive(#hacked)] 439 | pub fn #hacked(input: #pub_name::TokenStream) -> #pub_name::TokenStream { 440 | use std::iter::FromIterator; 441 | 442 | let mut iter = input.into_iter(); 443 | iter.next().unwrap(); // `enum` 444 | iter.next().unwrap(); // `ProcMacroHack` 445 | iter.next().unwrap(); // `#` 446 | iter.next().unwrap(); // `[allow(dead_code)]` 447 | 448 | let mut braces = match iter.next().unwrap() { 449 | #pub_name::TokenTree::Group(group) => group.stream().into_iter(), 450 | _ => unimplemented!(), 451 | }; 452 | let variant = braces.next().unwrap(); // `Value` or `Nested` 453 | let varname = variant.to_string(); 454 | let support_nested = varname.starts_with("Nested"); 455 | braces.next().unwrap(); // `=` 456 | 457 | let mut parens = match braces.next().unwrap() { 458 | #pub_name::TokenTree::Group(group) => group.stream().into_iter(), 459 | _ => unimplemented!(), 460 | }; 461 | parens.next().unwrap(); // `stringify` 462 | parens.next().unwrap(); // `!` 463 | 464 | let inner = match parens.next().unwrap() { 465 | #pub_name::TokenTree::Group(group) => group.stream(), 466 | _ => unimplemented!(), 467 | }; 468 | 469 | let output: #pub_name::TokenStream = #name(inner.clone()); 470 | 471 | fn count_bangs(input: #pub_name::TokenStream) -> usize { 472 | let mut count = 0; 473 | for token in input { 474 | match token { 475 | #pub_name::TokenTree::Punct(punct) => { 476 | if punct.as_char() == '!' { 477 | count += 1; 478 | } 479 | } 480 | #pub_name::TokenTree::Group(group) => { 481 | count += count_bangs(group.stream()); 482 | } 483 | _ => {} 484 | } 485 | } 486 | count 487 | } 488 | 489 | // macro_rules! proc_macro_call { 490 | // () => { #output } 491 | // } 492 | #pub_name::TokenStream::from_iter(vec![ 493 | #pub_name::TokenTree::Ident( 494 | #pub_name::Ident::new("macro_rules", #pub_name::Span::call_site()), 495 | ), 496 | #pub_name::TokenTree::Punct( 497 | #pub_name::Punct::new('!', #pub_name::Spacing::Alone), 498 | ), 499 | #pub_name::TokenTree::Ident( 500 | #pub_name::Ident::new( 501 | &if support_nested { 502 | let extra_bangs = if varname == "Nested" { 503 | 0 504 | } else { 505 | varname["Nested".len()..].parse().unwrap() 506 | }; 507 | format!("proc_macro_call_{}", extra_bangs + count_bangs(inner)) 508 | } else { 509 | String::from("proc_macro_call") 510 | }, 511 | #pub_name::Span::call_site(), 512 | ), 513 | ), 514 | #pub_name::TokenTree::Group( 515 | #pub_name::Group::new(#pub_name::Delimiter::Brace, #pub_name::TokenStream::from_iter(vec![ 516 | #pub_name::TokenTree::Group( 517 | #pub_name::Group::new(#pub_name::Delimiter::Parenthesis, #pub_name::TokenStream::new()), 518 | ), 519 | #pub_name::TokenTree::Punct( 520 | #pub_name::Punct::new('=', #pub_name::Spacing::Joint), 521 | ), 522 | #pub_name::TokenTree::Punct( 523 | #pub_name::Punct::new('>', #pub_name::Spacing::Alone), 524 | ), 525 | #pub_name::TokenTree::Group( 526 | #pub_name::Group::new(#pub_name::Delimiter::Brace, output), 527 | ), 528 | ])), 529 | ), 530 | ]) 531 | } 532 | 533 | #attrs 534 | #[proc_macro] 535 | pub fn #pub_name(input: #pub_name::TokenStream) -> #pub_name::TokenStream { 536 | #name(input) 537 | } 538 | 539 | fn #name #body 540 | } 541 | } 542 | 543 | fn pub_proc_macro_name(conceptual: &Ident) -> Ident { 544 | Ident::new( 545 | &format!("proc_macro_hack_{}", conceptual), 546 | conceptual.span(), 547 | ) 548 | } 549 | 550 | fn hacked_proc_macro_name(conceptual: &Ident) -> Ident { 551 | Ident::new( 552 | &format!("_proc_macro_hack_{}", conceptual), 553 | conceptual.span(), 554 | ) 555 | } 556 | 557 | fn dispatch_macro_name(conceptual: &Ident) -> Ident { 558 | Ident::new( 559 | &format!("proc_macro_call_{}", conceptual), 560 | conceptual.span(), 561 | ) 562 | } 563 | 564 | fn call_site_macro_name(conceptual: &Ident) -> Ident { 565 | Ident::new( 566 | &format!("proc_macro_fake_call_site_{}", conceptual), 567 | conceptual.span(), 568 | ) 569 | } 570 | 571 | fn dummy_name_for_export(export: &Export) -> String { 572 | let mut dummy = String::new(); 573 | let from = unraw(&export.from).to_string(); 574 | write!(dummy, "_{}{}", from.len(), from).unwrap(); 575 | for m in &export.macros { 576 | let name = unraw(&m.name).to_string(); 577 | write!(dummy, "_{}{}", name.len(), name).unwrap(); 578 | } 579 | dummy 580 | } 581 | 582 | fn unraw(ident: &Ident) -> Ident { 583 | let string = ident.to_string(); 584 | if string.starts_with("r#") { 585 | Ident::new(&string[2..], ident.span()) 586 | } else { 587 | ident.clone() 588 | } 589 | } 590 | 591 | fn wrap_in_enum_hack(dummy: String, inner: TokenStream) -> TokenStream { 592 | let dummy = Ident::new(&dummy, Span::call_site()); 593 | quote! { 594 | #[derive(proc_macro_hack::ProcMacroHack)] 595 | enum #dummy { 596 | Value = (stringify! { #inner }, 0).1, 597 | } 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::iter::{self, Iter, IterImpl}; 2 | use crate::{Define, Error, Export, ExportArgs, FakeCallSite, Input, Macro, Visibility}; 3 | use proc_macro::Delimiter::{Brace, Bracket, Parenthesis}; 4 | use proc_macro::{Delimiter, Ident, Span, TokenStream, TokenTree}; 5 | 6 | pub(crate) fn parse_input(tokens: Iter) -> Result { 7 | let attrs = parse_attributes(tokens)?; 8 | let vis = parse_visibility(tokens); 9 | let kw = parse_ident(tokens)?; 10 | if kw.to_string() == "use" { 11 | parse_export(attrs, vis, tokens).map(Input::Export) 12 | } else if kw.to_string() == "fn" { 13 | parse_define(attrs, vis, kw.span(), tokens).map(Input::Define) 14 | } else { 15 | Err(Error::new( 16 | kw.span(), 17 | "unexpected input to #[proc_macro_hack]", 18 | )) 19 | } 20 | } 21 | 22 | fn parse_export(attrs: TokenStream, vis: Visibility, tokens: Iter) -> Result { 23 | let _ = parse_punct(tokens, ':'); 24 | let _ = parse_punct(tokens, ':'); 25 | let from = parse_ident(tokens)?; 26 | parse_punct(tokens, ':')?; 27 | parse_punct(tokens, ':')?; 28 | 29 | let mut macros = Vec::new(); 30 | match tokens.peek() { 31 | Some(TokenTree::Group(group)) if group.delimiter() == Brace => { 32 | let ref mut content = iter::new(group.stream()); 33 | loop { 34 | macros.push(parse_macro(content)?); 35 | if content.peek().is_none() { 36 | break; 37 | } 38 | parse_punct(content, ',')?; 39 | if content.peek().is_none() { 40 | break; 41 | } 42 | } 43 | tokens.next().unwrap(); 44 | } 45 | _ => macros.push(parse_macro(tokens)?), 46 | } 47 | 48 | parse_punct(tokens, ';')?; 49 | Ok(Export { 50 | attrs, 51 | vis, 52 | from, 53 | macros, 54 | }) 55 | } 56 | 57 | fn parse_punct(tokens: Iter, ch: char) -> Result<(), Error> { 58 | match tokens.peek() { 59 | Some(TokenTree::Punct(punct)) if punct.as_char() == ch => { 60 | tokens.next().unwrap(); 61 | Ok(()) 62 | } 63 | tt => Err(Error::new( 64 | tt.map_or_else(Span::call_site, TokenTree::span), 65 | format!("expected `{}`", ch), 66 | )), 67 | } 68 | } 69 | 70 | fn parse_define( 71 | attrs: TokenStream, 72 | vis: Visibility, 73 | fn_token: Span, 74 | tokens: Iter, 75 | ) -> Result { 76 | if vis.is_none() { 77 | return Err(Error::new( 78 | fn_token, 79 | "functions tagged with `#[proc_macro_hack]` must be `pub`", 80 | )); 81 | } 82 | let name = parse_ident(tokens)?; 83 | let body = tokens.collect(); 84 | Ok(Define { attrs, name, body }) 85 | } 86 | 87 | fn parse_macro(tokens: Iter) -> Result { 88 | let name = parse_ident(tokens)?; 89 | let export_as = match tokens.peek() { 90 | Some(TokenTree::Ident(ident)) if ident.to_string() == "as" => { 91 | tokens.next().unwrap(); 92 | parse_ident(tokens)? 93 | } 94 | _ => name.clone(), 95 | }; 96 | Ok(Macro { name, export_as }) 97 | } 98 | 99 | fn parse_ident(tokens: Iter) -> Result { 100 | match tokens.next() { 101 | Some(TokenTree::Ident(ident)) => Ok(ident), 102 | tt => Err(Error::new( 103 | tt.as_ref().map_or_else(Span::call_site, TokenTree::span), 104 | "expected identifier", 105 | )), 106 | } 107 | } 108 | 109 | fn parse_keyword(tokens: Iter, kw: &'static str) -> Result<(), Error> { 110 | match &tokens.next() { 111 | Some(TokenTree::Ident(ident)) if ident.to_string() == kw => Ok(()), 112 | tt => Err(Error::new( 113 | tt.as_ref().map_or_else(Span::call_site, TokenTree::span), 114 | format!("expected `{}`", kw), 115 | )), 116 | } 117 | } 118 | 119 | fn parse_int(tokens: Iter) -> Result { 120 | match tokens.next() { 121 | Some(TokenTree::Literal(lit)) => lit.to_string().parse().map_err(|_| lit.span()), 122 | Some(tt) => Err(tt.span()), 123 | None => Err(Span::call_site()), 124 | } 125 | } 126 | 127 | fn parse_group(tokens: Iter, delimiter: Delimiter) -> Result { 128 | match &tokens.next() { 129 | Some(TokenTree::Group(group)) if group.delimiter() == delimiter => { 130 | Ok(iter::new(group.stream())) 131 | } 132 | tt => Err(Error::new( 133 | tt.as_ref().map_or_else(Span::call_site, TokenTree::span), 134 | "expected delimiter", 135 | )), 136 | } 137 | } 138 | 139 | fn parse_visibility(tokens: Iter) -> Visibility { 140 | if let Some(TokenTree::Ident(ident)) = tokens.peek() { 141 | if ident.to_string() == "pub" { 142 | match tokens.next().unwrap() { 143 | TokenTree::Ident(vis) => return Some(vis), 144 | _ => unreachable!(), 145 | } 146 | } 147 | } 148 | None 149 | } 150 | 151 | fn parse_attributes(tokens: Iter) -> Result { 152 | let mut attrs = TokenStream::new(); 153 | while let Some(TokenTree::Punct(punct)) = tokens.peek() { 154 | if punct.as_char() != '#' { 155 | break; 156 | } 157 | let span = punct.span(); 158 | attrs.extend(tokens.next()); 159 | match tokens.peek() { 160 | Some(TokenTree::Group(group)) if group.delimiter() == Bracket => { 161 | attrs.extend(tokens.next()); 162 | } 163 | _ => return Err(Error::new(span, "unexpected input")), 164 | } 165 | } 166 | Ok(attrs) 167 | } 168 | 169 | pub(crate) fn parse_export_args(tokens: Iter) -> Result { 170 | let mut args = ExportArgs { 171 | support_nested: false, 172 | internal_macro_calls: 0, 173 | fake_call_site: false, 174 | only_hack_old_rustc: false, 175 | }; 176 | 177 | while let Some(tt) = tokens.next() { 178 | match &tt { 179 | TokenTree::Ident(ident) if ident.to_string() == "support_nested" => { 180 | args.support_nested = true; 181 | } 182 | TokenTree::Ident(ident) if ident.to_string() == "internal_macro_calls" => { 183 | parse_punct(tokens, '=')?; 184 | let calls = parse_int(tokens).map_err(|span| { 185 | Error::new(span, "expected integer value for internal_macro_calls") 186 | })?; 187 | args.internal_macro_calls = calls; 188 | } 189 | TokenTree::Ident(ident) if ident.to_string() == "fake_call_site" => { 190 | args.fake_call_site = true; 191 | } 192 | TokenTree::Ident(ident) if ident.to_string() == "only_hack_old_rustc" => { 193 | args.only_hack_old_rustc = true; 194 | } 195 | _ => { 196 | return Err(Error::new( 197 | tt.span(), 198 | "expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`, `only_hack_old_rustc`", 199 | )); 200 | } 201 | } 202 | if tokens.peek().is_none() { 203 | break; 204 | } 205 | parse_punct(tokens, ',')?; 206 | } 207 | 208 | Ok(args) 209 | } 210 | 211 | pub(crate) fn parse_define_args(tokens: Iter) -> Result<(), Error> { 212 | match tokens.peek() { 213 | None => Ok(()), 214 | Some(token) => Err(Error::new( 215 | token.span(), 216 | "unexpected argument to proc_macro_hack macro implementation; args are only accepted on the macro declaration (the `pub use`)", 217 | )), 218 | } 219 | } 220 | 221 | pub(crate) fn parse_enum_hack(tokens: Iter) -> Result { 222 | parse_keyword(tokens, "enum")?; 223 | parse_ident(tokens)?; 224 | 225 | let ref mut braces = parse_group(tokens, Brace)?; 226 | parse_ident(braces)?; 227 | parse_punct(braces, '=')?; 228 | 229 | let ref mut parens = parse_group(braces, Parenthesis)?; 230 | parse_ident(parens)?; 231 | parse_punct(parens, '!')?; 232 | 233 | let ref mut inner = parse_group(parens, Brace)?; 234 | let token_stream = inner.collect(); 235 | 236 | parse_punct(parens, ',')?; 237 | let _ = parens.next(); 238 | parse_punct(braces, '.')?; 239 | let _ = braces.next(); 240 | parse_punct(braces, ',')?; 241 | 242 | Ok(token_stream) 243 | } 244 | 245 | pub(crate) fn parse_fake_call_site(tokens: Iter) -> Result { 246 | parse_punct(tokens, '#')?; 247 | let ref mut attr = parse_group(tokens, Bracket)?; 248 | parse_keyword(attr, "derive")?; 249 | let ref mut path = parse_group(attr, Parenthesis)?; 250 | Ok(FakeCallSite { 251 | derive: parse_ident(path)?, 252 | rest: tokens.collect(), 253 | }) 254 | } 255 | -------------------------------------------------------------------------------- /src/quote.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Ident, TokenStream, TokenTree}; 2 | use std::iter; 3 | 4 | macro_rules! quote { 5 | () => { 6 | ::proc_macro::TokenStream::new() 7 | }; 8 | ($($tt:tt)*) => {{ 9 | let mut tokens = ::proc_macro::TokenStream::new(); 10 | quote_each_token!(tokens $($tt)*); 11 | tokens 12 | }}; 13 | } 14 | 15 | macro_rules! quote_each_token { 16 | ($tokens:ident # $var:ident $($rest:tt)*) => { 17 | $crate::quote::Tokens::extend(&mut $tokens, &$var); 18 | quote_each_token!($tokens $($rest)*); 19 | }; 20 | ($tokens:ident $ident:ident $($rest:tt)*) => { 21 | <::proc_macro::TokenStream as ::std::iter::Extend<_>>::extend( 22 | &mut $tokens, 23 | ::std::iter::once( 24 | ::proc_macro::TokenTree::Ident( 25 | ::proc_macro::Ident::new( 26 | stringify!($ident), 27 | ::proc_macro::Span::call_site(), 28 | ), 29 | ), 30 | ), 31 | ); 32 | quote_each_token!($tokens $($rest)*); 33 | }; 34 | ($tokens:ident ( $($inner:tt)* ) $($rest:tt)*) => { 35 | <::proc_macro::TokenStream as ::std::iter::Extend<_>>::extend( 36 | &mut $tokens, 37 | ::std::iter::once( 38 | ::proc_macro::TokenTree::Group( 39 | ::proc_macro::Group::new( 40 | ::proc_macro::Delimiter::Parenthesis, 41 | quote!($($inner)*), 42 | ), 43 | ), 44 | ), 45 | ); 46 | quote_each_token!($tokens $($rest)*); 47 | }; 48 | ($tokens:ident [ $($inner:tt)* ] $($rest:tt)*) => { 49 | <::proc_macro::TokenStream as ::std::iter::Extend<_>>::extend( 50 | &mut $tokens, 51 | ::std::iter::once( 52 | ::proc_macro::TokenTree::Group( 53 | ::proc_macro::Group::new( 54 | ::proc_macro::Delimiter::Bracket, 55 | quote!($($inner)*), 56 | ), 57 | ), 58 | ), 59 | ); 60 | quote_each_token!($tokens $($rest)*); 61 | }; 62 | ($tokens:ident { $($inner:tt)* } $($rest:tt)*) => { 63 | <::proc_macro::TokenStream as ::std::iter::Extend<_>>::extend( 64 | &mut $tokens, 65 | ::std::iter::once( 66 | ::proc_macro::TokenTree::Group( 67 | ::proc_macro::Group::new( 68 | ::proc_macro::Delimiter::Brace, 69 | quote!($($inner)*), 70 | ), 71 | ), 72 | ), 73 | ); 74 | quote_each_token!($tokens $($rest)*); 75 | }; 76 | ($tokens:ident $punct:tt $($rest:tt)*) => { 77 | <::proc_macro::TokenStream as ::std::iter::Extend<_>>::extend( 78 | &mut $tokens, 79 | stringify!($punct).parse::<::proc_macro::TokenStream>(), 80 | ); 81 | quote_each_token!($tokens $($rest)*); 82 | }; 83 | ($tokens:ident) => {}; 84 | } 85 | 86 | pub trait Tokens { 87 | fn extend(tokens: &mut TokenStream, var: &Self); 88 | } 89 | 90 | impl Tokens for Ident { 91 | fn extend(tokens: &mut TokenStream, var: &Self) { 92 | tokens.extend(iter::once(TokenTree::Ident(var.clone()))); 93 | } 94 | } 95 | 96 | impl Tokens for TokenStream { 97 | fn extend(tokens: &mut TokenStream, var: &Self) { 98 | tokens.extend(var.clone()); 99 | } 100 | } 101 | 102 | impl Tokens for Option { 103 | fn extend(tokens: &mut TokenStream, var: &Self) { 104 | if let Some(var) = var { 105 | T::extend(tokens, var); 106 | } 107 | } 108 | } 109 | 110 | impl Tokens for &T { 111 | fn extend(tokens: &mut TokenStream, var: &Self) { 112 | T::extend(tokens, var); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/compiletest.rs: -------------------------------------------------------------------------------- 1 | #[rustversion::attr(not(nightly), ignore)] 2 | #[test] 3 | fn ui() { 4 | let t = trybuild::TestCases::new(); 5 | t.compile_fail("tests/ui/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/ui/private.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | #[proc_macro_hack] 4 | fn my_macro(input: TokenStream) -> TokenStream { 5 | unimplemented!() 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/ui/private.stderr: -------------------------------------------------------------------------------- 1 | error: functions tagged with `#[proc_macro_hack]` must be `pub` 2 | --> tests/ui/private.rs:4:1 3 | | 4 | 4 | fn my_macro(input: TokenStream) -> TokenStream { 5 | | ^^ 6 | -------------------------------------------------------------------------------- /tests/ui/unexpected-arg.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | #[proc_macro_hack(fake_call_site)] 4 | pub fn my_macro(input: TokenStream) -> TokenStream { 5 | unimplemented!() 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/ui/unexpected-arg.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected argument to proc_macro_hack macro implementation; args are only accepted on the macro declaration (the `pub use`) 2 | --> tests/ui/unexpected-arg.rs:3:19 3 | | 4 | 3 | #[proc_macro_hack(fake_call_site)] 5 | | ^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/unexpected.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | #[proc_macro_hack] 4 | pub struct What; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/ui/unexpected.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected input to #[proc_macro_hack] 2 | --> tests/ui/unexpected.rs:4:5 3 | | 4 | 4 | pub struct What; 5 | | ^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/unknown-arg.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | #[proc_macro_hack(fake_call_site, support_nexted)] 4 | pub use demo::some_macro; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/ui/unknown-arg.stderr: -------------------------------------------------------------------------------- 1 | error: expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`, `only_hack_old_rustc` 2 | --> tests/ui/unknown-arg.rs:3:35 3 | | 4 | 3 | #[proc_macro_hack(fake_call_site, support_nexted)] 5 | | ^^^^^^^^^^^^^^ 6 | --------------------------------------------------------------------------------