├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── core ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── core_macros ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── macros ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── publish.sh ├── src └── lib.rs └── tests ├── external_crate ├── Cargo.toml └── src │ └── lib.rs ├── external_file.rs ├── isolated_crate ├── Cargo.toml └── src │ └── lib.rs ├── middle_crate ├── Cargo.toml └── src │ └── lib.rs ├── test_macros ├── Cargo.toml └── src │ └── lib.rs └── tests.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | # Controls when the action will run. 3 | on: 4 | # Triggers the workflow on push or pull request events but only for the main branch 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | jobs: 10 | cargo-test: 11 | name: cargo test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - name: Set up Rust Toolchain 17 | run: curl https://sh.rustup.rs -sSf | sh -s -- -y 18 | - name: cargo test 19 | run: cargo test 20 | cargo-test-features: 21 | name: cargo test (all features) 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | - name: Set up Rust Toolchain 27 | run: curl https://sh.rustup.rs -sSf | sh -s -- -y 28 | - name: cargo test 29 | run: cargo test --workspace --all-features 30 | cargo-fmt: 31 | name: cargo fmt 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v3 36 | - name: Set up Rust Toolchain 37 | run: curl https://sh.rustup.rs -sSf | sh -s -- -y 38 | - name: cargo fmt 39 | run: cargo fmt -- --check 40 | cargo-doc: 41 | name: cargo doc 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v3 46 | - name: Set up Rust Toolchain 47 | run: curl https://sh.rustup.rs -sSf | sh -s -- -y 48 | - name: cargo doc 49 | run: cargo doc --workspace --all-features 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "core", 4 | "macros", 5 | "core_macros", 6 | "tests/test_macros", 7 | "tests/external_crate", 8 | "tests/middle_crate", 9 | "tests/isolated_crate", 10 | ] 11 | 12 | [package] 13 | name = "macro_magic" 14 | version = "0.6.0" 15 | edition = "2024" 16 | authors = ["sam0x17"] 17 | license = "MIT" 18 | keywords = ["macro", "macros", "proc-macro", "proc-macros", "magic"] 19 | categories = ["development-tools", "development-tools::procedural-macro-helpers", "rust-patterns"] 20 | homepage = "https://sam0x17.dev" 21 | repository = "https://github.com/sam0x17/macro_magic" 22 | description = "Allows the exporting and importing of the tokens of items across module, file, and crate boundaries" 23 | 24 | [package.metadata.docs.rs] 25 | all-features = true 26 | 27 | [dependencies] 28 | macro_magic_macros = { version = "0.6.0", path = "macros" } 29 | macro_magic_core = { version = "0.6.0", path = "core", optional = true } 30 | syn = { version = "2", features = ["full"], optional = true } 31 | quote = { version = "1", optional = true } 32 | 33 | [dev-dependencies] 34 | test_macros = { path = "tests/test_macros" } 35 | external_crate = { path = "tests/external_crate" } 36 | middle_crate = { path = "tests/middle_crate" } 37 | isolated_crate = { path = "tests/isolated_crate" } 38 | 39 | [features] 40 | default = [] 41 | proc_support = ["dep:macro_magic_core", "dep:syn", "dep:quote"] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Sam Johnson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the “Software”), to deal in the Software without 5 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macro Magic 🪄 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/macro_magic)](https://crates.io/crates/macro_magic) 4 | [![docs.rs](https://img.shields.io/docsrs/macro_magic?label=docs)](https://docs.rs/macro_magic/latest/macro_magic/) 5 | [![Build Status](https://img.shields.io/github/actions/workflow/status/sam0x17/macro_magic/ci.yaml)](https://github.com/sam0x17/macro_magic/actions/workflows/ci.yaml?query=branch%3Amain) 6 | [![MIT License](https://img.shields.io/github/license/sam0x17/macro_magic)](https://github.com/sam0x17/macro_magic/blob/main/LICENSE) 7 | 8 | This crate provides the `#[export_tokens]` macro and a number of companion macros, including 9 | the `#[import_tokens_proc]` and `#[import_tokens_attr]` macros. When used in tandem with 10 | `#[export_tokens]`, these macros allow you to create regular and attribute proc macros in which 11 | you can import and make use of the tokens of external/foreign items marked with 12 | `#[export_tokens]` in other modules, files, and even in other crates merely by referring to 13 | them by name/path. 14 | 15 | Among other things, the patterns introduced by `macro_magic` can be used to implement safe and 16 | efficient exportation and importation of item tokens within the same file, and even across file 17 | and crate boundaries. The only requirement is that you have control over (i.e. can add an attribute 18 | macro to) the source code of both locations. 19 | 20 | `macro_magic` is designed to work with stable Rust, and is fully `no_std` compatible (in fact, 21 | there is a unit test to ensure everything is `no_std` safe). 22 | 23 | ## General Syntax 24 | 25 | You can use `macro_magic` to build regular and attribute proc macros that look like this: 26 | 27 | ```rust 28 | #[my_attribute(path::to::MyItem)] 29 | trait SomeTrait { 30 | // .. 31 | } 32 | ``` 33 | 34 | this: 35 | 36 | ```rust 37 | do_something!(path::to::MyItem); 38 | ``` 39 | 40 | or even this: 41 | 42 | ```rust 43 | let foreign_tokens = my_macro!(path::to::MyItem); 44 | assert_eq!(foreign_tokens.to_string(), "struct MyItem {...}"); 45 | ``` 46 | 47 | where `path::to::MyItem` is the path to an item that has been marked with `#[export_tokens]`. 48 | 49 | All of this behavior is accomplished under the hood using proc macros that create 50 | `macro_rules`-based callbacks, but as a programmer this complexity is completely hidden from 51 | you via simple attribute macros you can apply to your proc macros to imbue them with the power 52 | of importing the tokens for external items based on their path. 53 | 54 | ## Attribute Example 55 | 56 | You could write an attribute macro to "inject" the fields of one struct into 57 | another as follows: 58 | 59 | ```rust 60 | #[import_tokens_attr] 61 | #[proc_macro_attribute] 62 | pub fn combine_structs(attr: TokenStream, tokens: TokenStream) -> TokenStream { 63 | let foreign_struct = parse_macro_input!(attr as ItemStruct); 64 | let local_struct = parse_macro_input!(tokens as ItemStruct); 65 | let Fields::Named(local_fields) = local_struct.fields else { 66 | return Error::new( 67 | local_struct.fields.span(), 68 | "unnamed fields are not supported" 69 | ).to_compile_error().into() 70 | }; 71 | let Fields::Named(foreign_fields) = foreign_struct.fields else { 72 | return Error::new( 73 | foreign_struct.fields.span(), 74 | "unnamed fields are not supported" 75 | ).to_compile_error().into() 76 | }; 77 | let local_fields = local_fields.named.iter(); 78 | let foreign_fields = foreign_fields.named.iter(); 79 | let attrs = local_struct.attrs; 80 | let generics = local_struct.generics; 81 | let ident = local_struct.ident; 82 | let vis = local_struct.vis; 83 | quote! { 84 | #(#attrs) 85 | * 86 | #vis struct #ident<#generics> { 87 | #(#local_fields), 88 | * 89 | , 90 | #(#foreign_fields), 91 | * 92 | } 93 | } 94 | .into() 95 | } 96 | ``` 97 | 98 | And then you could use the `#[combine_structs]` attribute as follows: 99 | 100 | ```rust 101 | #[export_tokens] 102 | struct ExternalStruct { 103 | foo: u32, 104 | bar: u64, 105 | fizz: i64, 106 | } 107 | 108 | #[combine_structs(ExternalStruct)] 109 | struct LocalStruct { 110 | biz: bool, 111 | baz: i32, 112 | } 113 | ``` 114 | 115 | Which would result in the following expanded output for `LocalStruct`: 116 | 117 | ```rust 118 | struct LocalStruct { 119 | foo: u32, 120 | bar: u64, 121 | fizz: i64, 122 | biz: bool, 123 | baz: i32, 124 | } 125 | ``` 126 | 127 | Note that the `attr` variable on the `combine_structs` proc macro, thanks to the powers of 128 | `#[import_tokens_attr]`, will receive the actual tokens for the `ExternalStruct` item, rather 129 | than merely receiving the tokens for the path `ExternalStruct`. 130 | 131 | This gives you the ability to write attribute macros that receive tokens for two items, one 132 | specified by path via the first argument `attr`, as well as the tokens for the item the 133 | attribute is attached to via the 2nd argument `tokens`. The only requirement is that the item 134 | specified by `attr` has been marked with `#[export_tokens]`. 135 | 136 | ## Proc Macro Example 137 | 138 | You could write a PHP/ruby/crystal-style verbatim import / `require` macro which blindly 139 | imports the tokens of the specified external module into the current context (with all the good 140 | and bad implications that would imply), like this: 141 | 142 | ```rust 143 | #[import_tokens_proc] 144 | #[proc_macro] 145 | pub fn require(tokens: TokenStream) -> TokenStream { 146 | let external_mod = parse_macro_input!(tokens as ItemMod); 147 | let Some((_, stmts)) = external_mod.content else { 148 | return Error::new( 149 | external_mod.span(), 150 | "cannot import tokens from a file-based module since custom file-level \ 151 | attributes are not yet supported by Rust" 152 | ).to_compile_error().into() 153 | }; 154 | quote! { 155 | #(#stmts) 156 | * 157 | } 158 | .into() 159 | } 160 | ``` 161 | 162 | You could then use the `require!` macro like this: 163 | 164 | ```rust 165 | // in some external crate 166 | #[export_tokens] 167 | mod an_external_module { 168 | fn my_cool_function() -> u32 { 169 | 567 170 | } 171 | 172 | fn my_other_function() -> u32 { 173 | 116 174 | } 175 | } 176 | ``` 177 | 178 | ```rust 179 | // in another crate where we will use the `require!` macro 180 | mod my_module { 181 | use my_macros::require; 182 | 183 | fn existing_stuff() { 184 | println!("foo!"); 185 | } 186 | 187 | require!(external_crate::an_external_module); 188 | } 189 | ``` 190 | 191 | which would result in this expansion of `require!` within `my_module`: 192 | 193 | ```rust 194 | mod my_module { 195 | use my_macros::require; 196 | 197 | fn existing_stuff() { 198 | println!("foo!"); 199 | } 200 | 201 | fn my_cool_function() -> u32 { 202 | 567 203 | } 204 | 205 | fn my_other_function() -> u32 { 206 | 116 207 | } 208 | } 209 | ``` 210 | 211 | Notice that this hypothetical `require!` macro is dangerous for two reasons: 212 | 213 | - Any types you may have brought into scope with `use` statements in the foreign module may or 214 | may not be available in their new context without additional use statements. 215 | - If existing items in the module or context where you use the `require!` macro conflict with 216 | something you are importing, you will get a compiler error (this is good, though). 217 | 218 | These are just _some_ of the capabilities of `macro_magic` 🪄. 219 | 220 | See the [`docs`](https://docs.rs/macro_magic/latest/macro_magic/) for more information. 221 | 222 | ## Features 223 | 224 | ### proc_support 225 | 226 | The `proc_support` feature _must_ be enabled in proc macro crates that make use of any import 227 | tokens functionality, including `#[import_tokens_attr]`, `#[import_tokens_proc]` and 228 | `import_tokens!`. Otherwise these macros will not function correctly and will issue compiler 229 | errors complaining about items not existing under `macro_magic::mm_core`. The 230 | `#[export_tokens]` macro does not require this feature to function correctly, so you can safely 231 | use it without enabling this feature. 232 | 233 | The reason for this feature gating is that things like `syn`, `quote`, `proc_macro2`, etc., are 234 | not 100% `no_std` compatible and should only be enabled in proc macro crates. For this reason, 235 | you _should not_ enable this feature in crates where you are merely using `#[export_tokens]` 236 | and nothing else within that crate. 237 | 238 | ## Limitations 239 | 240 | One thing that `macro_magic` _doesn't_ provide is the ability to build up state information 241 | across multiple macro invocations, however this problem can be tackled effectively using the 242 | [outer macro pattern](https://www.youtube.com/watch?v=aEWbZxNCH0A) or in some cases using 243 | static atomics and mutexes in your proc macro crate (which we actually do in this crate to keep 244 | track of unique identifiers). 245 | 246 | ## Breaking Changes 247 | 248 | - **0.4x** removed `#[use_attr]` and `#[use_proc]` (they are no longer needed with the new 249 | self-calling macro style that has been adopted in 0.4x) and also removed the ability to 250 | access `#[export_tokens]` invocations in inaccessible locations like inside of functions and 251 | across module permission boundaries like in an inaccessible private module. This feature may 252 | be re-added in the future if there is interest, however removing it allowed us to consolidate 253 | naming of our `macro_rules!` declarations and remove the need for `#[use_attr]` / 254 | `#[use_proc]`. 255 | - **0.2x** removed and/or re-wrote a number of features that relied on a non-future-proof 256 | behavior of writing/reading files in the `OUT_DIR`. Versions >= 0.2.0 are completely safe and 257 | no longer contain this behavior, however features that provided the ability to enumerate all 258 | the `#[export_tokens]` calls in a namespace have been removed. The proper way to do this is 259 | with the outer macro pattern or with global state mutexes/atomics in your proc macro crate, 260 | as mentioned above. 261 | 262 | More detailed historical change information can be found in 263 | [releases](https://github.com/sam0x17/docify/releases). 264 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macro_magic_core" 3 | version = "0.6.0" 4 | edition = "2024" 5 | description = "Core implementation behind macro_magic" 6 | repository = "https://github.com/sam0x17/macro_magic" 7 | homepage = "https://sam0x17.dev" 8 | license = "MIT" 9 | 10 | [package.metadata.docs.rs] 11 | all-features = true 12 | 13 | [dependencies] 14 | quote = "1" 15 | syn = { version = "2", features = ["full"] } 16 | derive-syn-parse = "0.2" 17 | proc-macro2 = "1" 18 | macro_magic_core_macros = { version = "0.6.0", path = "../core_macros" } 19 | const-random = "0.1.15" 20 | 21 | [features] 22 | default = [] 23 | -------------------------------------------------------------------------------- /core/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Sam Johnson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the “Software”), to deal in the Software without 5 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | Contains the implementation for [macro_magic](https://crates.io/crates/macro_magic)'s macros. 2 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains most of the internal implementation of the macros in the 2 | //! `macro_magic_macros` crate. For the most part, the proc macros in `macro_magic_macros` just 3 | //! call their respective `_internal` variants in this crate. 4 | #![warn(missing_docs)] 5 | 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use const_random::const_random; 9 | use derive_syn_parse::Parse; 10 | use macro_magic_core_macros::*; 11 | use proc_macro2::{Delimiter, Group, Punct, Spacing, Span, TokenStream as TokenStream2, TokenTree}; 12 | use quote::{ToTokens, TokenStreamExt, format_ident, quote}; 13 | use syn::{ 14 | Attribute, Error, Expr, FnArg, Ident, Item, ItemFn, Pat, Path, Result, Token, Visibility, 15 | parse::{Nothing, ParseStream}, 16 | parse_quote, parse2, 17 | spanned::Spanned, 18 | token::{Brace, Comma}, 19 | }; 20 | 21 | /// Constant used to load the configured location for `macro_magic` that will be used in 22 | /// generated macro code. 23 | /// 24 | /// See also [`get_macro_magic_root`]. 25 | pub const MACRO_MAGIC_ROOT: &str = get_macro_magic_root!(); 26 | 27 | /// A global counter, can be used to generate a relatively unique identifier. 28 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 29 | 30 | /// A compile-time random value used to help prevent collisions between hidden 31 | /// `__export_tokens_*` idents created by different crates and imported by glob imports into 32 | /// the same module/scope. Each instance of `macro_magic` will get a random compile-time 33 | /// [`u32`]. 34 | const COMPILATION_TAG: u32 = const_random!(u32); 35 | 36 | /// Private module containing custom keywords used for parsing in this crate 37 | mod keywords { 38 | use syn::custom_keyword; 39 | 40 | custom_keyword!(proc_macro_attribute); 41 | custom_keyword!(proc_macro); 42 | custom_keyword!(proc_macro_derive); 43 | 44 | // WARNING: Must be kept same as in macro expansions 45 | custom_keyword!(__private_macro_magic_tokens_forwarded); 46 | } 47 | 48 | /// Used to parse args that were passed to [`forward_tokens_internal`] and 49 | /// [`forward_tokens_inner_internal`]. 50 | /// 51 | /// You shouldn't need to use this directly. 52 | #[derive(Parse)] 53 | pub struct ForwardTokensExtraArg { 54 | #[brace] 55 | _brace: Brace, 56 | /// Contains the underlying [`TokenStream2`] inside the brace. 57 | #[inside(_brace)] 58 | pub stream: TokenStream2, 59 | } 60 | 61 | impl ToTokens for ForwardTokensExtraArg { 62 | fn to_tokens(&self, tokens: &mut TokenStream2) { 63 | let token = Group::new(Delimiter::Brace, self.stream.clone()); 64 | tokens.append(token); 65 | } 66 | } 67 | 68 | /// Used to parse args that were passed to [`forward_tokens_internal`]. 69 | /// 70 | /// You shouldn't need to use this directly. 71 | #[derive(Parse)] 72 | pub struct ForwardTokensArgs { 73 | /// The path of the item whose tokens are being forwarded 74 | pub source: Path, 75 | _comma1: Comma, 76 | /// The path of the macro that will receive the forwarded tokens 77 | pub target: Path, 78 | _comma2: Option, 79 | /// Contains the override path that will be used instead of `::macro_magic`, if specified. 80 | #[parse_if(_comma2.is_some())] 81 | pub mm_path: Option, 82 | _comma3: Option, 83 | /// Optional extra data. This is how [`import_tokens_attr_internal`] passes the item the 84 | /// attribute macro is attached to, but this can be repurposed for other things potentially as 85 | /// it wraps a token stream. 86 | #[parse_if(_comma3.is_some())] 87 | pub extra: Option, 88 | } 89 | 90 | /// Used to parse args that were passed to [`forward_tokens_inner_internal`]. 91 | /// 92 | /// You shouldn't need to use this directly. 93 | #[derive(Parse)] 94 | pub struct ForwardedTokens { 95 | /// The path of the macro that will receive the forwarded tokens 96 | pub target_path: Path, 97 | _comma1: Comma, 98 | /// The item whose tokens are being forwarded 99 | pub item: Item, 100 | _comma2: Option, 101 | /// Optional extra data. This is how [`import_tokens_attr_internal`] passes the item the 102 | /// attribute macro is attached to, but this can be repurposed for other things potentially as 103 | /// it wraps a token stream. 104 | #[parse_if(_comma2.is_some())] 105 | pub extra: Option, 106 | } 107 | 108 | /// Used to parse args passed to the inner pro macro auto-generated by 109 | /// [`import_tokens_attr_internal`]. 110 | /// 111 | /// You shouldn't need to use this directly. 112 | #[derive(Parse)] 113 | pub struct AttrItemWithExtra { 114 | /// Contains the [`Item`] that is being imported (i.e. the item whose tokens we are 115 | /// obtaining) 116 | pub imported_item: Item, 117 | _comma1: Comma, 118 | #[brace] 119 | _brace: Brace, 120 | #[brace] 121 | #[inside(_brace)] 122 | _tokens_ident_brace: Brace, 123 | /// A [`TokenStream2`] representing the raw tokens for the [`struct@Ident`] the generated 124 | /// macro will use to refer to the tokens argument of the macro. 125 | #[inside(_tokens_ident_brace)] 126 | pub tokens_ident: TokenStream2, 127 | #[inside(_brace)] 128 | _comma2: Comma, 129 | #[brace] 130 | #[inside(_brace)] 131 | _source_path_brace: Brace, 132 | /// Represents the path of the item that is being imported. 133 | #[inside(_source_path_brace)] 134 | pub source_path: TokenStream2, 135 | #[inside(_brace)] 136 | _comma3: Comma, 137 | #[brace] 138 | #[inside(_brace)] 139 | _custom_tokens_brace: Brace, 140 | /// when `#[with_custom_parsing(..)]` is used, the variable `__custom_tokens` will be 141 | /// populated in the resulting proc macro containing the raw [`TokenStream2`] for the 142 | /// tokens before custom parsing has been applied. This allows you to make use of any extra 143 | /// context information that may be obtained during custom parsing that you need to utilize 144 | /// in the final macro. 145 | #[inside(_custom_tokens_brace)] 146 | pub custom_tokens: TokenStream2, 147 | } 148 | 149 | /// Used to parse the args for the [`import_tokens_internal`] function. 150 | /// 151 | /// You shouldn't need to use this directly. 152 | #[derive(Parse)] 153 | pub struct ImportTokensArgs { 154 | _let: Token![let], 155 | /// The [`struct@Ident`] for the `tokens` variable. Usually called `tokens` but could be 156 | /// something different, hence this variable. 157 | pub tokens_var_ident: Ident, 158 | _eq: Token![=], 159 | /// The [`Path`] where the item we are importing can be found. 160 | pub source_path: Path, 161 | } 162 | 163 | /// Used to parse the args for the [`import_tokens_inner_internal`] function. 164 | /// 165 | /// You shouldn't need to use this directly. 166 | #[derive(Parse)] 167 | pub struct ImportedTokens { 168 | /// Represents the [`struct@Ident`] that was used to refer to the `tokens` in the original 169 | /// [`ImportTokensArgs`]. 170 | pub tokens_var_ident: Ident, 171 | _comma: Comma, 172 | /// Contains the [`Item`] that has been imported. 173 | pub item: Item, 174 | } 175 | 176 | /// Delineates the different types of proc macro 177 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 178 | pub enum ProcMacroType { 179 | /// Corresponds with `#[proc_macro]` 180 | Normal, 181 | /// Corresponds with `#[proc_macro_attribute]` 182 | Attribute, 183 | /// Corresponds with `#[proc_macro_derive]` 184 | Derive, 185 | } 186 | 187 | impl ProcMacroType { 188 | /// Gets the `&'static str` representation of this proc macro type 189 | pub fn to_str(&self) -> &'static str { 190 | match self { 191 | ProcMacroType::Normal => "#[proc_macro]", 192 | ProcMacroType::Attribute => "#[proc_macro_attribute]", 193 | ProcMacroType::Derive => "#[proc_macro_derive]", 194 | } 195 | } 196 | 197 | /// Gets the [`Attribute`] representation of this proc macro type 198 | pub fn to_attr(&self) -> Attribute { 199 | match self { 200 | ProcMacroType::Normal => parse_quote!(#[proc_macro]), 201 | ProcMacroType::Attribute => parse_quote!(#[proc_macro_attribute]), 202 | ProcMacroType::Derive => parse_quote!(#[proc_macro_derive]), 203 | } 204 | } 205 | } 206 | 207 | /// Should be implemented by structs that will be passed to `#[with_custom_parsing(..)]`. Such 208 | /// structs should also implement [`syn::parse::Parse`]. 209 | /// 210 | /// ## Example 211 | /// 212 | /// ```ignore 213 | /// #[derive(derive_syn_parse::Parse)] 214 | /// struct CustomParsingA { 215 | /// foreign_path: syn::Path, 216 | /// _comma: syn::token::Comma, 217 | /// custom_path: syn::Path, 218 | /// } 219 | /// 220 | /// impl ToTokens for CustomParsingA { 221 | /// fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 222 | /// tokens.extend(self.foreign_path.to_token_stream()); 223 | /// tokens.extend(self._comma.to_token_stream()); 224 | /// tokens.extend(self.custom_path.to_token_stream()); 225 | /// } 226 | /// } 227 | /// 228 | /// impl ForeignPath for CustomParsingA { 229 | /// fn foreign_path(&self) -> &syn::Path { 230 | /// &self.foreign_path 231 | /// } 232 | /// } 233 | /// ``` 234 | pub trait ForeignPath { 235 | /// Returns the path of the foreign item whose tokens will be imported. 236 | /// 237 | /// This is used with custom parsing. See [`ForeignPath`] for more info. 238 | fn foreign_path(&self) -> &syn::Path; 239 | } 240 | 241 | /// Generically parses a proc macro definition with support for all variants. 242 | #[derive(Clone)] 243 | pub struct ProcMacro { 244 | /// The underlying proc macro function definition 245 | pub proc_fn: ItemFn, 246 | /// Specified the type of this proc macro, i.e. attribute vs normal vs derive 247 | pub macro_type: ProcMacroType, 248 | /// Specifies the [`struct@Ident`] for the `tokens` parameter of this proc macro function 249 | /// definition. For normal and derive macros this is the only parameter, and for attribute 250 | /// macros this is the second parameter. 251 | pub tokens_ident: Ident, 252 | /// Specifies the [`struct@Ident`] for the `attr` parameter of this proc macro function 253 | /// definition, if it is an attribute macro. Otherwise this will be set to [`None`]. 254 | pub attr_ident: Option, 255 | } 256 | 257 | impl ProcMacro { 258 | /// Constructs a [`ProcMacro`] from anything compatible with [`TokenStream2`]. 259 | pub fn from>(tokens: T) -> Result { 260 | let proc_fn = parse2::(tokens.into())?; 261 | let Visibility::Public(_) = proc_fn.vis else { 262 | return Err(Error::new(proc_fn.vis.span(), "Visibility must be public")); 263 | }; 264 | let mut macro_type: Option = None; 265 | if proc_fn 266 | .attrs 267 | .iter() 268 | .find(|attr| { 269 | if syn::parse2::(attr.path().to_token_stream()).is_ok() { 270 | macro_type = Some(ProcMacroType::Normal); 271 | } else if syn::parse2::( 272 | attr.path().to_token_stream(), 273 | ) 274 | .is_ok() 275 | { 276 | macro_type = Some(ProcMacroType::Attribute); 277 | } else if syn::parse2::(attr.path().to_token_stream()).is_ok() 278 | { 279 | macro_type = Some(ProcMacroType::Derive); 280 | } 281 | macro_type.is_some() 282 | }) 283 | .is_none() 284 | { 285 | return Err(Error::new( 286 | proc_fn.sig.ident.span(), 287 | "can only be attached to a proc macro function definition", 288 | )); 289 | }; 290 | let macro_type = macro_type.unwrap(); 291 | 292 | // tokens_ident 293 | let Some(FnArg::Typed(tokens_arg)) = proc_fn.sig.inputs.last() else { 294 | unreachable!("missing tokens arg"); 295 | }; 296 | let Pat::Ident(tokens_ident) = *tokens_arg.pat.clone() else { 297 | unreachable!("invalid tokens arg"); 298 | }; 299 | let tokens_ident = tokens_ident.ident; 300 | 301 | // attr_ident (if applicable) 302 | let attr_ident = match macro_type { 303 | ProcMacroType::Attribute => { 304 | let Some(FnArg::Typed(attr_arg)) = proc_fn.sig.inputs.first() else { 305 | unreachable!("missing attr arg"); 306 | }; 307 | let Pat::Ident(attr_ident) = *attr_arg.pat.clone() else { 308 | unreachable!("invalid attr arg"); 309 | }; 310 | Some(attr_ident.ident) 311 | } 312 | _ => None, 313 | }; 314 | Ok(ProcMacro { 315 | proc_fn, 316 | macro_type, 317 | tokens_ident, 318 | attr_ident, 319 | }) 320 | } 321 | } 322 | 323 | /// Parses a proc macro function from a `TokenStream2` expecting only the specified `macro_type` 324 | pub fn parse_proc_macro_variant>( 325 | tokens: T, 326 | macro_type: ProcMacroType, 327 | ) -> Result { 328 | let proc_macro = ProcMacro::from(tokens.into())?; 329 | if proc_macro.macro_type != macro_type { 330 | let actual = proc_macro.macro_type.to_str(); 331 | let desired = macro_type.to_str(); 332 | return Err(Error::new( 333 | proc_macro.proc_fn.sig.ident.span(), 334 | format!( 335 | "expected a function definition with {} but found {} instead", 336 | actual, desired 337 | ), 338 | )); 339 | } 340 | Ok(proc_macro) 341 | } 342 | 343 | /// Safely access the `macro_magic` root based on the `MACRO_MAGIC_ROOT` env var, which 344 | /// defaults to `::macro_magic`, but can be configured via the `[env]` section of 345 | /// `.cargo/config.toml` 346 | pub fn macro_magic_root() -> Path { 347 | parse2::( 348 | MACRO_MAGIC_ROOT 349 | .parse::() 350 | .expect("environment var `MACRO_MAGIC_ROOT` must parse to a valid TokenStream2"), 351 | ) 352 | .expect("environment variable `MACRO_MAGIC_ROOT` must parse to a valid syn::Path") 353 | } 354 | 355 | /// Safely access a subpath of `macro_magic::__private` 356 | pub fn private_path + Clone>(subpath: &T) -> Path { 357 | let subpath = subpath.clone().into(); 358 | let root = macro_magic_root(); 359 | parse_quote!(#root::__private::#subpath) 360 | } 361 | 362 | /// Safely access a subpath of `macro_magic` 363 | pub fn macro_magic_path + Clone>(subpath: &T) -> Path { 364 | let subpath = subpath.clone().into(); 365 | let root = macro_magic_root(); 366 | parse_quote! { 367 | #root::#subpath 368 | } 369 | } 370 | 371 | /// Returns the specified string in snake_case 372 | pub fn to_snake_case(input: impl Into) -> String { 373 | let input: String = input.into(); 374 | if input.is_empty() { 375 | return input; 376 | } 377 | let mut prev_lower = input.chars().next().unwrap().is_lowercase(); 378 | let mut prev_whitespace = true; 379 | let mut first = true; 380 | let mut output: Vec = Vec::new(); 381 | for c in input.chars() { 382 | if c == '_' { 383 | prev_whitespace = true; 384 | output.push('_'); 385 | continue; 386 | } 387 | if !c.is_ascii_alphanumeric() && c != '_' && !c.is_whitespace() { 388 | continue; 389 | } 390 | if !first && c.is_whitespace() || c == '_' { 391 | if !prev_whitespace { 392 | output.push('_'); 393 | } 394 | prev_whitespace = true; 395 | } else { 396 | let current_lower = c.is_lowercase(); 397 | if ((prev_lower != current_lower && prev_lower) 398 | || (prev_lower == current_lower && !prev_lower)) 399 | && !first 400 | && !prev_whitespace 401 | { 402 | output.push('_'); 403 | } 404 | output.push(c.to_ascii_lowercase()); 405 | prev_lower = current_lower; 406 | prev_whitespace = false; 407 | } 408 | first = false; 409 | } 410 | output.iter().collect::() 411 | } 412 | 413 | /// "Flattens" an [`struct@Ident`] by converting it to snake case. 414 | /// 415 | /// Used by [`export_tokens_macro_ident`]. 416 | pub fn flatten_ident(ident: &Ident) -> Ident { 417 | Ident::new(to_snake_case(ident.to_string()).as_str(), ident.span()) 418 | } 419 | 420 | /// Produces the full path for the auto-generated callback-based decl macro that allows us to 421 | /// forward tokens across crate boundaries. 422 | /// 423 | /// Used by [`export_tokens_internal`] and several other functions. 424 | pub fn export_tokens_macro_ident(ident: &Ident) -> Ident { 425 | let ident = flatten_ident(ident); 426 | let ident_string = format!("__export_tokens_tt_{}", ident.to_token_stream()); 427 | Ident::new(ident_string.as_str(), Span::call_site()) 428 | } 429 | 430 | /// Resolves to the path of the `#[export_tokens]` macro for the given item path. 431 | /// 432 | /// If the specified [`Path`] doesn't exist or there isn't a valid `#[export_tokens]` attribute 433 | /// on the item at that path, the returned macro path will be invalid. 434 | pub fn export_tokens_macro_path(item_path: &Path) -> Path { 435 | let mut macro_path = item_path.clone(); 436 | let Some(last_seg) = macro_path.segments.pop() else { 437 | unreachable!("must have at least one segment") 438 | }; 439 | let last_seg = export_tokens_macro_ident(&last_seg.into_value().ident); 440 | macro_path.segments.push(last_seg.into()); 441 | macro_path 442 | } 443 | 444 | /// Generates a new unique `#[export_tokens]` macro identifier 445 | fn new_unique_export_tokens_ident(ident: &Ident) -> Ident { 446 | let unique_id = COUNTER.fetch_add(1, Ordering::SeqCst); 447 | let ident = flatten_ident(ident).to_token_stream().to_string(); 448 | let ident_string = format!("__export_tokens_tt_{COMPILATION_TAG}_{ident}_{unique_id}"); 449 | Ident::new(ident_string.as_str(), Span::call_site()) 450 | } 451 | 452 | /// The internal code behind the `#[export_tokens]` attribute macro. 453 | /// 454 | /// The `attr` variable contains the tokens for the optional naming [`struct@Ident`] (necessary 455 | /// on [`Item`]s that don't have an inherent [`struct@Ident`]), and the `tokens` variable is 456 | /// the tokens for the [`Item`] the attribute macro can be attached to. The `attr` variable can 457 | /// be blank tokens for supported items, which include every valid [`syn::Item`] except for 458 | /// [`syn::ItemForeignMod`], [`syn::ItemUse`], [`syn::ItemImpl`], and [`Item::Verbatim`], which 459 | /// all require `attr` to be specified. 460 | /// 461 | /// An empty [`TokenStream2`] is sufficient for opting out of using `attr` 462 | /// 463 | /// The `hide_exported_ident` variable specifies whether the macro uses an auto-generated name 464 | /// via [`export_tokens_macro_ident`] or the name of the item itself. 465 | pub fn export_tokens_internal, E: Into>( 466 | attr: T, 467 | tokens: E, 468 | emit: bool, 469 | hide_exported_ident: bool, 470 | ) -> Result { 471 | let attr = attr.into(); 472 | let item: Item = parse2(tokens.into())?; 473 | let ident = match item.clone() { 474 | Item::Const(item_const) => Some(item_const.ident), 475 | Item::Enum(item_enum) => Some(item_enum.ident), 476 | Item::ExternCrate(item_extern_crate) => Some(item_extern_crate.ident), 477 | Item::Fn(item_fn) => Some(item_fn.sig.ident), 478 | Item::Macro(item_macro) => item_macro.ident, // note this one might not have an Ident as well 479 | Item::Mod(item_mod) => Some(item_mod.ident), 480 | Item::Static(item_static) => Some(item_static.ident), 481 | Item::Struct(item_struct) => Some(item_struct.ident), 482 | Item::Trait(item_trait) => Some(item_trait.ident), 483 | Item::TraitAlias(item_trait_alias) => Some(item_trait_alias.ident), 484 | Item::Type(item_type) => Some(item_type.ident), 485 | Item::Union(item_union) => Some(item_union.ident), 486 | // Item::ForeignMod(item_foreign_mod) => None, 487 | // Item::Use(item_use) => None, 488 | // Item::Impl(item_impl) => None, 489 | // Item::Verbatim(_) => None, 490 | _ => None, 491 | }; 492 | let ident = match ident { 493 | Some(ident) => { 494 | if parse2::(attr.clone()).is_ok() { 495 | ident 496 | } else { 497 | parse2::(attr)? 498 | } 499 | } 500 | None => parse2::(attr)?, 501 | }; 502 | let macro_ident = new_unique_export_tokens_ident(&ident); 503 | let ident = if hide_exported_ident { 504 | export_tokens_macro_ident(&ident) 505 | } else { 506 | ident 507 | }; 508 | let item_emit = match emit { 509 | true => quote! { 510 | #[allow(unused)] 511 | #item 512 | }, 513 | false => quote!(), 514 | }; 515 | let output = quote! { 516 | #[doc(hidden)] 517 | #[macro_export] 518 | macro_rules! #macro_ident { 519 | // arm with extra support (used by attr) 520 | ( 521 | $(::)?$($tokens_var:ident)::*, 522 | $(::)?$($callback:ident)::*, 523 | { $( $extra:tt )* } 524 | ) => { 525 | $($callback)::*! { 526 | $($tokens_var)::*, 527 | #item, 528 | { $( $extra )* } 529 | } 530 | }; 531 | // regular arm (used by proc, import_tokens, etc) 532 | ($(::)?$($tokens_var:ident)::*, $(::)?$($callback:ident)::*) => { 533 | $($callback)::*! { 534 | $($tokens_var)::*, 535 | #item 536 | } 537 | }; 538 | } 539 | pub use #macro_ident as #ident; 540 | #item_emit 541 | }; 542 | Ok(output) 543 | } 544 | 545 | /// Internal implementation of `export_tokens_alias!`. Allows creating a renamed/rebranded 546 | /// macro that does the same thing as `#[export_tokens]` 547 | pub fn export_tokens_alias_internal>( 548 | tokens: T, 549 | emit: bool, 550 | hide_exported_ident: bool, 551 | ) -> Result { 552 | let alias = parse2::(tokens.into())?; 553 | let export_tokens_internal_path = macro_magic_path("e!(mm_core::export_tokens_internal)); 554 | Ok(quote! { 555 | #[proc_macro_attribute] 556 | pub fn #alias(attr: proc_macro::TokenStream, tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { 557 | match #export_tokens_internal_path(attr, tokens, #emit, #hide_exported_ident) { 558 | Ok(tokens) => tokens.into(), 559 | Err(err) => err.to_compile_error().into(), 560 | } 561 | } 562 | }) 563 | } 564 | 565 | /// The internal implementation for the `import_tokens` macro. 566 | /// 567 | /// You can call this in your own proc macros to make use of the `import_tokens` functionality 568 | /// directly, though this approach is limited. The arguments should be a [`TokenStream2`] that 569 | /// can parse into an [`ImportTokensArgs`] successfully. That is a valid `let` variable 570 | /// declaration set to equal a path where an `#[export_tokens]` with the specified ident can be 571 | /// found. 572 | /// 573 | /// ### Example: 574 | /// ``` 575 | /// use macro_magic_core::*; 576 | /// use quote::quote; 577 | /// 578 | /// let some_ident = quote!(my_tokens); 579 | /// let some_path = quote!(other_crate::exported_item); 580 | /// let tokens = import_tokens_internal(quote!(let #some_ident = other_crate::ExportedItem)).unwrap(); 581 | /// assert_eq!( 582 | /// tokens.to_string(), 583 | /// "other_crate :: __export_tokens_tt_exported_item ! { my_tokens , \ 584 | /// :: macro_magic :: __private :: import_tokens_inner }"); 585 | /// ``` 586 | /// If these tokens were emitted as part of a proc macro, they would expand to a variable 587 | /// declaration like: 588 | /// ```ignore 589 | /// let my_tokens: TokenStream2; 590 | /// ``` 591 | /// where `my_tokens` contains the tokens of `ExportedItem`. 592 | pub fn import_tokens_internal>(tokens: T) -> Result { 593 | let args = parse2::(tokens.into())?; 594 | let source_path = export_tokens_macro_path(&args.source_path); 595 | let inner_macro_path = private_path("e!(import_tokens_inner)); 596 | let tokens_var_ident = args.tokens_var_ident; 597 | Ok(quote! { 598 | #source_path! { #tokens_var_ident, #inner_macro_path } 599 | }) 600 | } 601 | 602 | /// The internal implementation for the `import_tokens_inner` macro. 603 | /// 604 | /// You shouldn't need to call this in any circumstances but it is provided just in case. 605 | pub fn import_tokens_inner_internal>(tokens: T) -> Result { 606 | let parsed = parse2::(tokens.into())?; 607 | let tokens_string = parsed.item.to_token_stream().to_string(); 608 | let ident = parsed.tokens_var_ident; 609 | let token_stream_2 = private_path("e!(TokenStream2)); 610 | Ok(quote! { 611 | let #ident = #tokens_string.parse::<#token_stream_2>().expect("failed to parse quoted tokens"); 612 | }) 613 | } 614 | 615 | /// The internal implementation for the `forward_tokens` macro. 616 | /// 617 | /// You shouldn't need to call this in any circumstances but it is provided just in case. 618 | pub fn forward_tokens_internal>( 619 | tokens: T, 620 | hidden_source_path: bool, 621 | ) -> Result { 622 | let args = parse2::(tokens.into())?; 623 | let mm_path = match args.mm_path { 624 | Some(path) => path, 625 | None => macro_magic_root(), 626 | }; 627 | let source_path = if hidden_source_path { 628 | export_tokens_macro_path(&args.source) 629 | } else { 630 | args.source 631 | }; 632 | let target_path = args.target; 633 | if let Some(extra) = args.extra { 634 | Ok(quote! { 635 | #source_path! { 636 | #target_path, 637 | #mm_path::__private::forward_tokens_inner, 638 | #extra 639 | } 640 | }) 641 | } else { 642 | Ok(quote! { 643 | #source_path! { #target_path, #mm_path::__private::forward_tokens_inner } 644 | }) 645 | } 646 | } 647 | 648 | /// Used by [`forward_tokens_internal`]. 649 | pub fn forward_tokens_inner_internal>(tokens: T) -> Result { 650 | let parsed = parse2::(tokens.into())?; 651 | let target_path = parsed.target_path; 652 | let imported_tokens = parsed.item; 653 | let tokens_forwarded_keyword = keywords::__private_macro_magic_tokens_forwarded::default(); 654 | let pound = Punct::new('#', Spacing::Alone); 655 | match parsed.extra { 656 | // some extra, used by attr, so expand to attribute macro 657 | Some(extra) => Ok(quote! { 658 | #pound [#target_path( 659 | #tokens_forwarded_keyword 660 | #imported_tokens, 661 | #extra 662 | )] type __Discarded = (); 663 | }), 664 | // no extra, used by proc, import_tokens, etc, so expand to proc macro 665 | None => Ok(quote! { 666 | #target_path! { 667 | #tokens_forwarded_keyword 668 | #imported_tokens 669 | } 670 | }), 671 | } 672 | } 673 | 674 | /// The internal implementation for the `#[with_custom_parsing(..)` attribute macro. 675 | /// 676 | /// Note that this implementation just does parsing and re-orders the attributes of the 677 | /// attached proc macro attribute definition such that the `#[import_tokens_attr]` attribute 678 | /// comes before this attribute. The real implementation for `#[with_custom_parsing(..)]` can 679 | /// be found in [`import_tokens_attr_internal`]. The purpose of this is to allow programmers to 680 | /// use either ordering and still have the proper compiler errors when something is invalid. 681 | /// 682 | /// The `import_tokens_att_name` argument is used when generating error messages and matching 683 | /// against the `#[import_tokens_attr]` macro this is to be used with. If you use a 684 | /// renamed/rebranded version of `#[import_tokens_attr]`, you should change this value to match 685 | /// the name of your macro. 686 | pub fn with_custom_parsing_internal, T2: Into>( 687 | attr: T1, 688 | tokens: T2, 689 | import_tokens_attr_name: &'static str, 690 | ) -> Result { 691 | // verify that we are attached to a valid #[import_tokens_attr] proc macro def 692 | let proc_macro = parse_proc_macro_variant(tokens, ProcMacroType::Attribute)?; 693 | if proc_macro 694 | .proc_fn 695 | .attrs 696 | .iter() 697 | .find(|attr| { 698 | if let Some(seg) = attr.meta.path().segments.last() { 699 | return seg.ident == import_tokens_attr_name; 700 | } 701 | false 702 | }) 703 | .is_none() 704 | { 705 | return Err(Error::new( 706 | Span::call_site(), 707 | format!( 708 | "Can only be attached to an attribute proc macro marked with `#[{}]`", 709 | import_tokens_attr_name 710 | ), 711 | )); 712 | } 713 | 714 | // ensure there is only one `#[with_custom_parsing]` 715 | if proc_macro 716 | .proc_fn 717 | .attrs 718 | .iter() 719 | .find(|attr| { 720 | if let Some(seg) = attr.meta.path().segments.last() { 721 | return seg.ident == "with_custom_parsing_internal"; 722 | } 723 | false 724 | }) 725 | .is_some() 726 | { 727 | return Err(Error::new( 728 | Span::call_site(), 729 | "Only one instance of #[with_custom_parsing] can be attached at a time.", 730 | )); 731 | } 732 | 733 | // parse attr to ensure it is a Path 734 | let custom_path = parse2::(attr.into())?; 735 | 736 | // emit original item unchanged now that parsing has passed 737 | let mut item_fn = proc_macro.proc_fn; 738 | item_fn 739 | .attrs 740 | .push(parse_quote!(#[with_custom_parsing(#custom_path)])); 741 | 742 | Ok(quote!(#item_fn)) 743 | } 744 | 745 | /// Parses the (attribute) args of [`import_tokens_attr_internal`] and 746 | /// [`import_tokens_proc_internal`], which can now evaluate to either a `Path` or an `Expr` 747 | /// that is expected to be able to be placed in a `String::from(x)`. 748 | enum OverridePath { 749 | Path(Path), 750 | Expr(Expr), 751 | } 752 | 753 | impl syn::parse::Parse for OverridePath { 754 | fn parse(input: ParseStream) -> Result { 755 | if input.is_empty() { 756 | return Ok(OverridePath::Path(macro_magic_root())); 757 | } 758 | let mut remaining = TokenStream2::new(); 759 | while !input.is_empty() { 760 | remaining.extend(input.parse::()?.to_token_stream()); 761 | } 762 | if let Ok(path) = parse2::(remaining.clone()) { 763 | return Ok(OverridePath::Path(path)); 764 | } 765 | match parse2::(remaining) { 766 | Ok(expr) => Ok(OverridePath::Expr(expr)), 767 | Err(mut err) => { 768 | err.combine(Error::new( 769 | input.span(), 770 | "Expected either a `Path` or an `Expr` that evaluates to something compatible with `Into`." 771 | )); 772 | Err(err) 773 | } 774 | } 775 | } 776 | } 777 | 778 | impl ToTokens for OverridePath { 779 | fn to_tokens(&self, tokens: &mut TokenStream2) { 780 | match self { 781 | OverridePath::Path(path) => { 782 | let path = path.to_token_stream().to_string(); 783 | tokens.extend(quote!(#path)) 784 | } 785 | OverridePath::Expr(expr) => tokens.extend(quote!(#expr)), 786 | } 787 | } 788 | } 789 | 790 | /// Internal implementation for the `#[import_tokens_attr]` attribute. 791 | /// 792 | /// You shouldn't need to use this directly, but it may be useful if you wish to rebrand/rename 793 | /// the `#[import_tokens_attr]` macro without extra indirection. 794 | pub fn import_tokens_attr_internal, T2: Into>( 795 | attr: T1, 796 | tokens: T2, 797 | hidden_source_path: bool, 798 | ) -> Result { 799 | let attr = attr.into(); 800 | let mm_override_path = parse2::(attr)?; 801 | let mm_path = macro_magic_root(); 802 | let mut proc_macro = parse_proc_macro_variant(tokens, ProcMacroType::Attribute)?; 803 | 804 | // params 805 | let attr_ident = proc_macro.attr_ident.unwrap(); 806 | let tokens_ident = proc_macro.tokens_ident; 807 | 808 | // handle custom parsing, if applicable 809 | let path_resolver = if let Some(index) = proc_macro.proc_fn.attrs.iter().position(|attr| { 810 | if let Some(seg) = attr.meta.path().segments.last() { 811 | return seg.ident == "with_custom_parsing"; 812 | } 813 | false 814 | }) { 815 | let custom_attr = &proc_macro.proc_fn.attrs[index]; 816 | let custom_struct_path: Path = custom_attr.parse_args()?; 817 | 818 | proc_macro.proc_fn.attrs.remove(index); 819 | quote! { 820 | let custom_parsed = syn::parse_macro_input!(#attr_ident as #custom_struct_path); 821 | let path = (&custom_parsed as &dyn ForeignPath).foreign_path(); 822 | let _ = (&custom_parsed as &dyn quote::ToTokens); 823 | } 824 | } else { 825 | quote! { 826 | let custom_parsed = quote::quote!(); 827 | let path = syn::parse_macro_input!(#attr_ident as syn::Path); 828 | } 829 | }; 830 | 831 | // outer macro 832 | let orig_sig = proc_macro.proc_fn.sig; 833 | let orig_stmts = proc_macro.proc_fn.block.stmts; 834 | let orig_attrs = proc_macro.proc_fn.attrs; 835 | let orig_sig_ident = &orig_sig.ident; 836 | 837 | // inner macro 838 | let inner_macro_ident = format_ident!("__import_tokens_attr_{}_inner", orig_sig.ident); 839 | let mut inner_sig = orig_sig.clone(); 840 | inner_sig.ident = inner_macro_ident.clone(); 841 | inner_sig.inputs.pop().unwrap(); 842 | 843 | let pound = Punct::new('#', Spacing::Alone); 844 | 845 | // final quoted tokens 846 | let output = quote! { 847 | #(#orig_attrs) 848 | * 849 | pub #orig_sig { 850 | pub #inner_sig { 851 | let __combined_args = #mm_path::__private::syn::parse_macro_input!(#attr_ident as #mm_path::mm_core::AttrItemWithExtra); 852 | 853 | let #attr_ident: proc_macro::TokenStream = __combined_args.imported_item.to_token_stream().into(); 854 | let #tokens_ident: proc_macro::TokenStream = __combined_args.tokens_ident.into(); 855 | let __source_path: proc_macro::TokenStream = __combined_args.source_path.into(); 856 | let __custom_tokens: proc_macro::TokenStream = __combined_args.custom_tokens.into(); 857 | 858 | #(#orig_stmts) 859 | * 860 | } 861 | 862 | // This is to avoid corrupting the scope with imports below 863 | fn isolated_mm_override_path() -> String { 864 | String::from(#mm_override_path) 865 | } 866 | 867 | use #mm_path::__private::*; 868 | use #mm_path::__private::quote::ToTokens; 869 | use #mm_path::mm_core::*; 870 | 871 | syn::custom_keyword!(__private_macro_magic_tokens_forwarded); 872 | 873 | let mut cloned_attr = #attr_ident.clone().into_iter(); 874 | let first_attr_token = cloned_attr.next(); 875 | let attr_minus_first_token = proc_macro::TokenStream::from_iter(cloned_attr); 876 | 877 | let forwarded = first_attr_token.map_or(false, |token| { 878 | syn::parse::<__private_macro_magic_tokens_forwarded>(token.into()).is_ok() 879 | }); 880 | 881 | if forwarded { 882 | #inner_macro_ident(attr_minus_first_token) 883 | } else { 884 | let attached_item = syn::parse_macro_input!(#tokens_ident as syn::Item); 885 | let attached_item = attached_item.to_token_stream(); 886 | #path_resolver 887 | let path = path.to_token_stream(); 888 | let custom_parsed = custom_parsed.to_token_stream(); 889 | let mm_override_tokenstream = isolated_mm_override_path().parse().unwrap(); 890 | let resolved_mm_override_path = match syn::parse2::(mm_override_tokenstream) { 891 | Ok(res) => res, 892 | Err(err) => return err.to_compile_error().into() 893 | }; 894 | if #hidden_source_path { 895 | quote::quote! { 896 | #pound resolved_mm_override_path::forward_tokens! { 897 | #pound path, 898 | #orig_sig_ident, 899 | #pound resolved_mm_override_path, 900 | { 901 | { #pound attached_item }, 902 | { #pound path }, 903 | { #pound custom_parsed } 904 | } 905 | } 906 | }.into() 907 | } else { 908 | quote::quote! { 909 | #pound resolved_mm_override_path::forward_tokens_verbatim! { 910 | #pound path, 911 | #orig_sig_ident, 912 | #pound resolved_mm_override_path, 913 | { 914 | { #pound attached_item }, 915 | { #pound path }, 916 | { #pound custom_parsed } 917 | } 918 | } 919 | }.into() 920 | } 921 | } 922 | } 923 | }; 924 | Ok(output) 925 | } 926 | 927 | /// Internal implementation for the `#[import_tokens_proc]` attribute. 928 | /// 929 | /// You shouldn't need to use this directly, but it may be useful if you wish to rebrand/rename 930 | /// the `#[import_tokens_proc]` macro without extra indirection. 931 | pub fn import_tokens_proc_internal, T2: Into>( 932 | attr: T1, 933 | tokens: T2, 934 | ) -> Result { 935 | let attr = attr.into(); 936 | let mm_override_path = parse2::(attr)?; 937 | let mm_path = macro_magic_root(); 938 | let proc_macro = parse_proc_macro_variant(tokens, ProcMacroType::Normal)?; 939 | 940 | // outer macro 941 | let orig_sig = proc_macro.proc_fn.sig; 942 | let orig_stmts = proc_macro.proc_fn.block.stmts; 943 | let orig_attrs = proc_macro.proc_fn.attrs; 944 | let orig_sig_ident = &orig_sig.ident; 945 | 946 | // inner macro 947 | let inner_macro_ident = format_ident!("__import_tokens_proc_{}_inner", orig_sig.ident); 948 | let mut inner_sig = orig_sig.clone(); 949 | inner_sig.ident = inner_macro_ident.clone(); 950 | inner_sig.inputs = inner_sig.inputs.iter().rev().cloned().collect(); 951 | 952 | // params 953 | let tokens_ident = proc_macro.tokens_ident; 954 | 955 | let pound = Punct::new('#', Spacing::Alone); 956 | 957 | // TODO: add support for forwarding source_path for these as well 958 | 959 | Ok(quote! { 960 | #(#orig_attrs) 961 | * 962 | pub #orig_sig { 963 | #inner_sig { 964 | #(#orig_stmts) 965 | * 966 | } 967 | 968 | // This is to avoid corrupting the scope with imports below 969 | fn isolated_mm_override_path() -> String { 970 | String::from(#mm_override_path) 971 | } 972 | 973 | use #mm_path::__private::*; 974 | use #mm_path::__private::quote::ToTokens; 975 | 976 | syn::custom_keyword!(__private_macro_magic_tokens_forwarded); 977 | 978 | let mut cloned_tokens = #tokens_ident.clone().into_iter(); 979 | let first_token = cloned_tokens.next(); 980 | let tokens_minus_first = proc_macro::TokenStream::from_iter(cloned_tokens); 981 | 982 | let forwarded = first_token.map_or(false, |token| { 983 | syn::parse::<__private_macro_magic_tokens_forwarded>(token.into()).is_ok() 984 | }); 985 | 986 | if forwarded { 987 | #inner_macro_ident(tokens_minus_first) 988 | } else { 989 | use #mm_path::__private::*; 990 | use #mm_path::__private::quote::ToTokens; 991 | let source_path = match syn::parse::(#tokens_ident) { 992 | Ok(path) => path, 993 | Err(e) => return e.to_compile_error().into(), 994 | }; 995 | let mm_override_tokenstream = isolated_mm_override_path().parse().unwrap(); 996 | let resolved_mm_override_path = match syn::parse2::(mm_override_tokenstream) { 997 | Ok(res) => res, 998 | Err(err) => return err.to_compile_error().into() 999 | }; 1000 | quote::quote! { 1001 | #pound resolved_mm_override_path::forward_tokens! { 1002 | #pound source_path, 1003 | #orig_sig_ident, 1004 | #pound resolved_mm_override_path 1005 | } 1006 | }.into() 1007 | } 1008 | } 1009 | }) 1010 | } 1011 | 1012 | #[cfg(test)] 1013 | mod tests { 1014 | use super::*; 1015 | 1016 | #[test] 1017 | fn export_tokens_internal_missing_ident() { 1018 | assert!( 1019 | export_tokens_internal(quote!(), quote!(impl MyTrait for Something), true, true) 1020 | .is_err() 1021 | ); 1022 | } 1023 | 1024 | #[test] 1025 | fn export_tokens_internal_normal_no_ident() { 1026 | assert!( 1027 | export_tokens_internal( 1028 | quote!(), 1029 | quote!( 1030 | struct MyStruct {} 1031 | ), 1032 | true, 1033 | true 1034 | ) 1035 | .unwrap() 1036 | .to_string() 1037 | .contains("my_struct") 1038 | ); 1039 | } 1040 | 1041 | #[test] 1042 | fn export_tokens_internal_normal_ident() { 1043 | assert!( 1044 | export_tokens_internal( 1045 | quote!(some_name), 1046 | quote!( 1047 | struct Something {} 1048 | ), 1049 | true, 1050 | true 1051 | ) 1052 | .unwrap() 1053 | .to_string() 1054 | .contains("some_name") 1055 | ); 1056 | } 1057 | 1058 | #[test] 1059 | fn export_tokens_internal_generics_no_ident() { 1060 | assert!( 1061 | export_tokens_internal( 1062 | quote!(), 1063 | quote!( 1064 | struct MyStruct {} 1065 | ), 1066 | true, 1067 | true 1068 | ) 1069 | .unwrap() 1070 | .to_string() 1071 | .contains("__export_tokens_tt_my_struct") 1072 | ); 1073 | } 1074 | 1075 | #[test] 1076 | fn export_tokens_internal_bad_ident() { 1077 | assert!( 1078 | export_tokens_internal( 1079 | quote!(Something), 1080 | quote!( 1081 | struct MyStruct {} 1082 | ), 1083 | true, 1084 | true 1085 | ) 1086 | .is_err() 1087 | ); 1088 | assert!( 1089 | export_tokens_internal( 1090 | quote!(some::path), 1091 | quote!( 1092 | struct MyStruct {} 1093 | ), 1094 | true, 1095 | true 1096 | ) 1097 | .is_err() 1098 | ); 1099 | } 1100 | 1101 | #[test] 1102 | fn test_export_tokens_no_emit() { 1103 | assert!( 1104 | export_tokens_internal( 1105 | quote!(some_name), 1106 | quote!( 1107 | struct Something {} 1108 | ), 1109 | false, 1110 | true 1111 | ) 1112 | .unwrap() 1113 | .to_string() 1114 | .contains("some_name") 1115 | ); 1116 | } 1117 | 1118 | #[test] 1119 | fn export_tokens_internal_verbatim_ident() { 1120 | assert!( 1121 | export_tokens_internal( 1122 | quote!(), 1123 | quote!( 1124 | struct MyStruct {} 1125 | ), 1126 | true, 1127 | false 1128 | ) 1129 | .unwrap() 1130 | .to_string() 1131 | .contains("MyStruct") 1132 | ); 1133 | } 1134 | 1135 | #[test] 1136 | fn import_tokens_internal_simple_path() { 1137 | assert!( 1138 | import_tokens_internal(quote!(let tokens = my_crate::SomethingCool)) 1139 | .unwrap() 1140 | .to_string() 1141 | .contains("__export_tokens_tt_something_cool") 1142 | ); 1143 | } 1144 | 1145 | #[test] 1146 | fn import_tokens_internal_flatten_long_paths() { 1147 | assert!( 1148 | import_tokens_internal(quote!(let tokens = my_crate::some_mod::complex::SomethingElse)) 1149 | .unwrap() 1150 | .to_string() 1151 | .contains("__export_tokens_tt_something_else") 1152 | ); 1153 | } 1154 | 1155 | #[test] 1156 | fn import_tokens_internal_invalid_token_ident() { 1157 | assert!(import_tokens_internal(quote!(let 3 * 2 = my_crate::something)).is_err()); 1158 | } 1159 | 1160 | #[test] 1161 | fn import_tokens_internal_invalid_path() { 1162 | assert!(import_tokens_internal(quote!(let my_tokens = 2 - 2)).is_err()); 1163 | } 1164 | 1165 | #[test] 1166 | fn import_tokens_inner_internal_basic() { 1167 | assert!( 1168 | import_tokens_inner_internal(quote! { 1169 | my_ident, 1170 | fn my_function() -> u32 { 1171 | 33 1172 | } 1173 | }) 1174 | .unwrap() 1175 | .to_string() 1176 | .contains("my_ident") 1177 | ); 1178 | } 1179 | 1180 | #[test] 1181 | fn import_tokens_inner_internal_impl() { 1182 | assert!( 1183 | import_tokens_inner_internal(quote! { 1184 | another_ident, 1185 | impl Something for MyThing { 1186 | fn something() -> CoolStuff { 1187 | CoolStuff {} 1188 | } 1189 | } 1190 | }) 1191 | .unwrap() 1192 | .to_string() 1193 | .contains("something ()") 1194 | ); 1195 | } 1196 | 1197 | #[test] 1198 | fn import_tokens_inner_internal_missing_comma() { 1199 | assert!( 1200 | import_tokens_inner_internal(quote! { 1201 | { 1202 | another_ident 1203 | impl Something for MyThing { 1204 | fn something() -> CoolStuff { 1205 | CoolStuff {} 1206 | } 1207 | } 1208 | } 1209 | }) 1210 | .is_err() 1211 | ); 1212 | } 1213 | 1214 | #[test] 1215 | fn import_tokens_inner_internal_non_item() { 1216 | assert!( 1217 | import_tokens_inner_internal(quote! { 1218 | { 1219 | another_ident, 1220 | 2 + 2 1221 | } 1222 | }) 1223 | .is_err() 1224 | ); 1225 | } 1226 | 1227 | #[test] 1228 | fn test_snake_case() { 1229 | assert_eq!(to_snake_case("ThisIsATriumph"), "this_is_a_triumph"); 1230 | assert_eq!( 1231 | to_snake_case("IAmMakingANoteHere"), 1232 | "i_am_making_a_note_here" 1233 | ); 1234 | assert_eq!(to_snake_case("huge_success"), "huge_success"); 1235 | assert_eq!( 1236 | to_snake_case("It's hard to Overstate my satisfaction!!!"), 1237 | "its_hard_to_overstate_my_satisfaction" 1238 | ); 1239 | assert_eq!( 1240 | to_snake_case("__aperature_science__"), 1241 | "__aperature_science__" 1242 | ); 1243 | assert_eq!( 1244 | to_snake_case("WeDoWhatWeMustBecause!()"), 1245 | "we_do_what_we_must_because_we_can" 1246 | ); 1247 | assert_eq!( 1248 | to_snake_case("For_The_Good_of_all_of_us_Except_TheOnes_Who Are Dead".to_string()), 1249 | "for_the_good_of_all_of_us_except_the_ones_who_are_dead" 1250 | ); 1251 | assert_eq!(to_snake_case("".to_string()), ""); 1252 | } 1253 | } 1254 | -------------------------------------------------------------------------------- /core_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macro_magic_core_macros" 3 | version = "0.6.0" 4 | edition = "2024" 5 | description = "Support macros for macro_magic_core" 6 | repository = "https://github.com/sam0x17/macro_magic" 7 | homepage = "https://sam0x17.dev" 8 | license = "MIT" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | quote = "1" 15 | syn = { version = "2", features = ["full"] } 16 | proc-macro2 = "1" 17 | -------------------------------------------------------------------------------- /core_macros/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Sam Johnson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the “Software”), to deal in the Software without 5 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /core_macros/README.md: -------------------------------------------------------------------------------- 1 | Internal proc-macro crate in support of [macro_magic](https://crates.io/crates/macro_magic). 2 | -------------------------------------------------------------------------------- /core_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::{parse::Nothing, parse_macro_input}; 6 | 7 | #[proc_macro] 8 | pub fn get_macro_magic_root(tokens: TokenStream) -> TokenStream { 9 | let _ = parse_macro_input!(tokens as Nothing); 10 | let root = option_env!("MACRO_MAGIC_ROOT").unwrap_or("::macro_magic"); 11 | quote!(#root).into() 12 | } 13 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macro_magic_macros" 3 | version = "0.6.0" 4 | edition = "2024" 5 | description = "Proc-macro sub-crate for macro_magic" 6 | repository = "https://github.com/sam0x17/macro_magic" 7 | homepage = "https://sam0x17.dev" 8 | license = "MIT" 9 | 10 | [package.metadata.docs.rs] 11 | all-features = true 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | quote = "1" 20 | syn = { version = "2", features = ["full"] } 21 | macro_magic_core = { version = "0.6.0", path = "../core"} 22 | -------------------------------------------------------------------------------- /macros/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Sam Johnson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the “Software”), to deal in the Software without 5 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /macros/README.md: -------------------------------------------------------------------------------- 1 | # macro_magic_macros 2 | 3 | This crate contains proc macros from the [macro_magic](https://crates.io/crates/macro_magic) 4 | crate, which allows for exporting and importing of external items as a `TokenStream2` across 5 | file and crate boundaries when writing proc macros. See the main crate for more information and 6 | to make use of these macros. 7 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use macro_magic_core::*; 4 | use proc_macro::TokenStream; 5 | 6 | /// Can be applied to any item. Doing so will make the tokens for this item available for 7 | /// import by the other macros in this crate. 8 | /// 9 | /// An optional argument can be provided specifying an override export name to use instead of 10 | /// the regular name of the item, such as `#[export_tokens(MyCoolName)]` or 11 | /// `#[export_tokens(some_name)]`. Syntactically this name is parsed as a `syn::Ident` and is 12 | /// then normalized by converting to snake_case. Note that because of this, `MyCoolName` would 13 | /// collide with `my_cool_name`, resulting in a compiler error if these items are being 14 | /// exported from the same module. 15 | /// 16 | /// Note that some types of items, namely `syn::ItemForeignMod`, `syn::ItemUse`, 17 | /// `syn::ItemImpl`, and `syn::Item::Verbatim`, do not have an inherent concept of a naming 18 | /// ident, and so for these items specifying an override name is required or you will get a 19 | /// compiler error. This also applies to `macro_rules!` definitions that do not specify a name. 20 | /// 21 | /// Note also that while you can presently _attach_ `#[export_tokens]` to anything attributes 22 | /// can be attached to, some of these items do not exist at the module path level, and 23 | /// therefore cannot be accessed. You should only attach `#[export_tokens]` to items that are 24 | /// accessible by path from the location where you wish to use their tokens. 25 | /// 26 | /// ## Examples 27 | /// 28 | /// Applied to a regular function definition: 29 | /// ```ignore 30 | /// #[export_tokens] 31 | /// fn my_function() { 32 | /// println!("hey"); 33 | /// } 34 | /// ``` 35 | /// 36 | /// Applied to a module: 37 | /// ```ignore 38 | /// #[export_tokens] 39 | /// mod my_module() { 40 | /// fn some_fn() { 41 | /// stuff(); 42 | /// } 43 | /// } 44 | /// ``` 45 | /// 46 | /// Applied to an `impl` requiring an override name: 47 | /// ```ignore 48 | /// #[export_tokens(impl_my_trait_for_my_item)] 49 | /// impl MyTrait for MyItem { 50 | /// fn something() { 51 | /// do_stuff(); 52 | /// } 53 | /// } 54 | /// ``` 55 | /// 56 | /// Applied to a struct, but specifying an override name: 57 | /// ```ignore 58 | /// #[export_tokens(SomeOtherName)] 59 | /// struct MyStruct { 60 | /// field: u32, 61 | /// } 62 | /// ``` 63 | /// 64 | /// Previously it was possible to access `#[export_tokens]` items defined in 65 | /// private/inaccessible contexts, however this was removed in 0.4.x. 66 | #[proc_macro_attribute] 67 | pub fn export_tokens(attr: TokenStream, tokens: TokenStream) -> TokenStream { 68 | match export_tokens_internal(attr, tokens, true, true) { 69 | Ok(tokens) => tokens.into(), 70 | Err(err) => err.to_compile_error().into(), 71 | } 72 | } 73 | 74 | /// Like [`#[export_tokens]`](`macro@export_tokens`) but does not emit the tokens of the 75 | /// attached item locally. 76 | /// 77 | /// This is useful for scenarios where the local tokens would not compile anyway locally, 78 | /// and/or do not need to be used locally. 79 | #[proc_macro_attribute] 80 | pub fn export_tokens_no_emit(attr: TokenStream, tokens: TokenStream) -> TokenStream { 81 | match export_tokens_internal(attr, tokens, false, true) { 82 | Ok(tokens) => tokens.into(), 83 | Err(err) => err.to_compile_error().into(), 84 | } 85 | } 86 | 87 | /// Creates an attribute proc macro that is an alias for 88 | /// [`#[export_tokens]`](`macro@export_tokens`). 89 | /// 90 | /// Simply pass an ident to this proc macro, and an alias for 91 | /// [`#[export_tokens]`](`macro@export_tokens`) will be created with the specified name. 92 | /// 93 | /// Can only be used within a proc macro crate. 94 | #[proc_macro] 95 | pub fn export_tokens_alias(tokens: TokenStream) -> TokenStream { 96 | match export_tokens_alias_internal(tokens, true, true) { 97 | Ok(tokens) => tokens.into(), 98 | Err(err) => err.to_compile_error().into(), 99 | } 100 | } 101 | 102 | /// Like [`#[export_tokens]`](`macro@export_tokens`) but intead creates an alias for 103 | /// [`#[export_tokens_no_emit]`](`macro@export_tokens_no_emit`) 104 | /// 105 | /// Can only be used within a proc macro crate. 106 | #[proc_macro] 107 | pub fn export_tokens_alias_no_emit(tokens: TokenStream) -> TokenStream { 108 | match export_tokens_alias_internal(tokens, false, true) { 109 | Ok(tokens) => tokens.into(), 110 | Err(err) => err.to_compile_error().into(), 111 | } 112 | } 113 | 114 | /// "Forwards" the tokens of the specified exported item (specified by path as the first arg) 115 | /// to the specified proc or `macro_rules!` macro (specified by path as the second arg). 116 | /// 117 | /// This is used internally as the basis for many of the other macros in this crate, but can 118 | /// also be useful in its own right in certain situations. 119 | /// 120 | /// Note that the referenced item _must_ have the [`#[export_tokens]`][`macro@export_tokens`] 121 | /// attribute attached to it, or this will not work. 122 | /// 123 | /// There is also an optional third argument called "extra" which allows you to forward 124 | /// arbitrary data to the target macro. This is used by 125 | /// [`#[import_tokens_attr]`](`macro@import_tokens_proc`) to pass the tokens for the attached 126 | /// item in addition to the tokens for the external item. 127 | /// 128 | /// ## Example 129 | /// 130 | /// ```ignore 131 | /// #[macro_export] 132 | /// macro_rules! receiver { 133 | /// ($tokens:item) => { 134 | /// stringify!($tokens) 135 | /// }; 136 | /// } 137 | /// 138 | /// let result = forward_tokens!(LionStruct, receiver); 139 | /// assert_eq!(result, "struct LionStruct {}"); 140 | /// ``` 141 | #[proc_macro] 142 | pub fn forward_tokens(tokens: TokenStream) -> TokenStream { 143 | match forward_tokens_internal(tokens, true) { 144 | Ok(tokens) => tokens.into(), 145 | Err(err) => err.to_compile_error().into(), 146 | } 147 | } 148 | 149 | #[proc_macro] 150 | pub fn forward_tokens_verbatim(tokens: TokenStream) -> TokenStream { 151 | match forward_tokens_internal(tokens, false) { 152 | Ok(tokens) => tokens.into(), 153 | Err(err) => err.to_compile_error().into(), 154 | } 155 | } 156 | 157 | /// Allows you to import the tokens of an external item marked with 158 | /// [`#[export_tokens]`][`macro@export_tokens`] whose path is already known at compile-time 159 | /// without having to do any additional parsing. 160 | /// 161 | /// If the path of the item is defined by the downstream programmer and is not "hard-coded", 162 | /// then you should instead use [`#[import_tokens_attr]`](`macro@import_tokens_attr`) / 163 | /// [`#[import_tokens_proc]`](`macro@import_tokens_proc`). 164 | /// 165 | /// The macro lets you define as its argument a let variable declaration that will expand to 166 | /// that variable being set to the tokens of the specified external item at compile-time. 167 | /// 168 | /// For example: 169 | /// 170 | /// ```ignore 171 | /// import_tokens!(let tokens = external_crate::SomeItem); 172 | /// ``` 173 | /// 174 | /// will expand such that a `tokens` variable will be created containing the tokens for the 175 | /// `SomeItem` item that exists in an external crate. For this to work, 176 | /// `external_crate::SomeItem` must be the path of an item that has 177 | /// [`#[export_tokens]`][`macro@export_tokens`] attached to it. The imported tokens wil be of 178 | /// type `TokenStream2`. 179 | /// 180 | /// Unfortunately this macro isn't very useful, because it is quite rare that you already know 181 | /// the path of the item you want to import _inside_ your proc macro. Note that having the 182 | /// _tokens_ for the path you want isn't the same as having those tokens already expanded in 183 | /// the current context. 184 | /// 185 | /// That said, this can be quite useful for scenarios where for whatever reason you have an 186 | /// item with a set-in-stone path whose tokens you need to access at compile time. 187 | #[proc_macro] 188 | pub fn import_tokens(tokens: TokenStream) -> TokenStream { 189 | match import_tokens_internal(tokens) { 190 | Ok(tokens) => tokens.into(), 191 | Err(err) => err.to_compile_error().into(), 192 | } 193 | } 194 | 195 | /// An attribute macro that can be attached to a proc macro function definition that will cause 196 | /// it to receive the tokens of the external item referred to by its argument as input to your 197 | /// proc macro. 198 | /// 199 | /// For example: 200 | /// 201 | /// ```ignore 202 | /// #[import_tokens_proc] 203 | /// #[proc_macro] 204 | /// pub fn my_macro(tokens: TokenStream) -> TokenStream { 205 | /// // `tokens` will contain the tokens of 206 | /// let item = parse_macro_input!(tokens as Item); 207 | /// // you can now do stuff with `item` 208 | /// // ... 209 | /// } 210 | /// ``` 211 | /// 212 | /// Which you could use like this: 213 | /// 214 | /// ```ignore 215 | /// my_macro!(some_crate::some_item); 216 | /// ``` 217 | /// 218 | /// In this case the `tokens` variable will contain the tokens for the `some_crate::some_item` 219 | /// item, as long as it has been marked with [`#[export_tokens]`][`macro@export_tokens`]. 220 | /// 221 | /// Note that this attribute can only be used within a proc macro crate. 222 | /// 223 | /// ## Overriding [`MACRO_MAGIC_ROOT`]: 224 | /// 225 | /// You can also provide a module path as an optional argument to this attribute macro and that 226 | /// path will be used as the override for [`MACRO_MAGIC_ROOT`] within the context of code 227 | /// generated by this attribute. Instead of a `Path`, you are also free to provide any `Expr` 228 | /// that evaluates to something compatible with [`Into`] so you can dynamically 229 | /// generate this path based on `format!` and other string manipulation machinery, if 230 | /// necessary. 231 | /// 232 | /// Here is an example of providing a `Path` as the override for [`MACRO_MAGIC_ROOT`]: 233 | /// 234 | /// ```ignore 235 | /// #[import_tokens_proc(my_crate::__private::macro_magic)] 236 | /// pub fn my_macro(tokens: TokenStream) -> TokenStream { 237 | /// // .. 238 | /// } 239 | /// ``` 240 | /// 241 | /// and here is an example of providing an [`Into`]-compatible `Expr` as the override 242 | /// for [`MACRO_MAGIC_ROOT`]: 243 | /// 244 | /// ```ignore 245 | /// #[import_tokens_proc(format!("{}::__private::macro_magic", generate_crate_access_2018("my_crate")))] 246 | /// pub fn my_macro(tokens: TokenStream) -> TokenStream { 247 | /// // .. 248 | /// } 249 | /// ``` 250 | #[proc_macro_attribute] 251 | pub fn import_tokens_proc(attr: TokenStream, tokens: TokenStream) -> TokenStream { 252 | match import_tokens_proc_internal(attr, tokens) { 253 | Ok(tokens) => tokens.into(), 254 | Err(err) => err.to_compile_error().into(), 255 | } 256 | } 257 | 258 | /// Can be attached to an attribute proc macro function, causing it to receive the tokens for 259 | /// the external item referred to by the path provided as the `attr` / first argument to the 260 | /// attribute macro. 261 | /// 262 | /// The item whose path is provided as the `attr` / first argument _must_ have the 263 | /// [`#[export_tokens]`][`macro@export_tokens`] attribute attached to it, or this will not 264 | /// work. 265 | /// 266 | /// For example: 267 | /// 268 | /// ```ignore 269 | /// #[import_tokens_attr] 270 | /// #[proc_macro_attribute] 271 | /// pub fn my_attribute(attr: TokenStream, tokens: TokenStream) -> TokenStream { 272 | /// let external_item = parse_macro_input!(attr as Item); 273 | /// let attached_item = parse_macro_input!(tokens as Item); 274 | /// // ... 275 | /// } 276 | /// ``` 277 | /// 278 | /// Which could then be used like: 279 | /// 280 | /// ```ignore 281 | /// #[my_attribute(path::to::AnItem)] 282 | /// mod my_mod { 283 | /// // ... 284 | /// } 285 | /// ``` 286 | /// 287 | /// This would result in the `external_item` variable having the parsed tokens of the external 288 | /// `path::to::AnItem` item, and the `attached_item` variable having the parsed tokens of the 289 | /// item the attribute is attached to (`my_mod`) as usual. 290 | /// 291 | /// This allows for the creation of extremely powerful attribute macros that take in an export 292 | /// tokens path as their `attr` and internally receive the tokens for that external item. For 293 | /// example you could write an attribute macro that combines two modules or two structs 294 | /// together, among many other things. Custom parsing, covered below, makes these capabilities 295 | /// even more powerful. 296 | /// 297 | /// ## Overriding [`MACRO_MAGIC_ROOT`] 298 | /// 299 | /// You can also provide a module path as an optional argument to this attribute macro and that 300 | /// path will be used as the override for [`MACRO_MAGIC_ROOT`] within the context of code 301 | /// generated by this attribute. Instead of a `Path`, you are also free to provide any `Expr` 302 | /// that evaluates to something compatible with [`Into`] so you can dynamically 303 | /// generate this path based on `format!` and other string manipulation machinery, if 304 | /// necessary. 305 | /// 306 | /// Here is an example of providing a `Path` as the override for [`MACRO_MAGIC_ROOT`]: 307 | /// 308 | /// ```ignore 309 | /// #[import_tokens_attr(my_crate::__private::macro_magic)] 310 | /// pub fn my_macro(attr: TokenStream, tokens: TokenStream) -> TokenStream { 311 | /// // .. 312 | /// } 313 | /// ``` 314 | /// 315 | /// and here is an example of providing an [`Into`]-compatible `Expr` as the override 316 | /// for [`MACRO_MAGIC_ROOT`]: 317 | /// 318 | /// ```ignore 319 | /// #[import_tokens_proc(format!("{}::__private::macro_magic", generate_crate_access_2018("my_crate")))] 320 | /// pub fn my_macro(attr: TokenStream, tokens: TokenStream) -> TokenStream { 321 | /// // .. 322 | /// } 323 | /// ``` 324 | /// 325 | /// 326 | /// ## Optional Feature: `#[with_custom_parsing(..)]` 327 | /// 328 | /// By default, [`#[import_tokens_attr]`](`macro@import_tokens_attr`)-based attribute macros 329 | /// expect the foreign item path to be passed directly as the only argument to the resulting 330 | /// macro. Sometimes, however, it is desirable to support multiple arguments, or otherwise 331 | /// implement some kind of custom parsing that determines how the foreign path is obtained. You 332 | /// can do this by attaching the optional attribute 333 | /// [`#[with_custom_parsing(..)]`](`macro@with_custom_parsing`) to the same proc macro 334 | /// attribute definition that you attached `#[import_tokens_attr]` to. 335 | /// 336 | /// This optional attribute takes one argument, which should be the path to a struct that 337 | /// implements `syn::parse::Parse`, `quote::ToTokens`, and [`ForeignPath`]. To access the 338 | /// tokens for your custom parsed input, you can use the magic variable `__custom_tokens: 339 | /// TokenStream` anywhere in your attribute proc macro. 340 | /// 341 | /// Here is a full example: 342 | /// 343 | /// ```ignore 344 | /// #[derive(Parse)] 345 | /// struct MyCustomParsing { 346 | /// foreign_path: syn::Path, 347 | /// _comma: syn::token::Comma, 348 | /// custom_path: syn::Path, 349 | /// } 350 | /// 351 | /// impl ToTokens for MyCustomParsing { 352 | /// fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 353 | /// tokens.extend(self.foreign_path.to_token_stream()); 354 | /// tokens.extend(self._comma.to_token_stream()); 355 | /// tokens.extend(self.custom_path.to_token_stream()); 356 | /// } 357 | /// } 358 | /// 359 | /// impl ForeignPath for MyCustomParsing { 360 | /// fn foreign_path(&self) -> &syn::Path { 361 | /// &self.foreign_path 362 | /// } 363 | /// } 364 | /// #[import_tokens_attr] 365 | /// #[with_custom_parsing(MyCustomParsing)] 366 | /// #[proc_macro_attribute] 367 | /// pub fn my_attribute(attr: TokenStream, tokens: TokenStream) -> TokenStream { 368 | /// let external_item = parse_macro_input!(attr as Item); 369 | /// let attached_item = parse_macro_input!(tokens as Item); 370 | /// let custom_parsed_item = parse_macro_input!(__custom_tokens as MyCustomParsing); 371 | /// // ... 372 | /// } 373 | /// ``` 374 | /// 375 | /// Usage would look like: 376 | /// ```ignore 377 | /// #[my_attribute(foreign::path, some_other::path)] 378 | /// struct SomeItem {} 379 | /// ``` 380 | /// 381 | /// This is just an example, you could implement the parsing any way you want, maybe even using 382 | /// something that isn't initially a `syn::Path` but is transformed into one. The possibilities 383 | /// are endless. 384 | /// 385 | /// ## Notes 386 | /// 387 | /// * See `tests.rs` for more examples. 388 | /// * Can only be used within a proc macro crate. 389 | /// * A handy `__source_path: TokenStream` variable is also injected into your proc macro 390 | /// function definition which provides access to the original `syn::Path` that was provided 391 | /// as the path for the foreign item before its tokens were imported. You can access this 392 | /// directly simply by referring to `__source_path`. This should parse to a `syn::Path`. 393 | /// * When using the custom parsing feature, you can also access the original tokens for the 394 | /// input attribute within your proc macro body using the magic variable `__custom_tokens`. 395 | /// For more information and an example see [`macro@with_custom_parsing`]. 396 | #[proc_macro_attribute] 397 | pub fn import_tokens_attr(attr: TokenStream, tokens: TokenStream) -> TokenStream { 398 | match import_tokens_attr_internal(attr, tokens, true) { 399 | Ok(tokens) => tokens.into(), 400 | Err(err) => err.to_compile_error().into(), 401 | } 402 | } 403 | 404 | #[proc_macro_attribute] 405 | pub fn import_tokens_attr_verbatim(attr: TokenStream, tokens: TokenStream) -> TokenStream { 406 | match import_tokens_attr_internal(attr, tokens, false) { 407 | Ok(tokens) => tokens.into(), 408 | Err(err) => err.to_compile_error().into(), 409 | } 410 | } 411 | 412 | /// To be used in tandem with [`#[import_tokens_attr]`](`macro@import_tokens_attr`) 413 | /// 414 | /// Example: 415 | /// ```ignore 416 | /// #[import_tokens_attr] 417 | /// #[with_custom_parsing(MyCustomParsing)] 418 | /// #[proc_macro_attribute] 419 | /// pub fn my_attribute(attr: TokenStream, tokens: TokenStream) -> TokenStream { 420 | /// let external_item = parse_macro_input!(attr as Item); 421 | /// let attached_item = parse_macro_input!(tokens as Item); 422 | /// let custom_parsed_item = parse_macro_input!(__custom_tokens as MyCustomParsing); 423 | /// // ... 424 | /// } 425 | /// ``` 426 | #[proc_macro_attribute] 427 | pub fn with_custom_parsing(attr: TokenStream, tokens: TokenStream) -> TokenStream { 428 | match with_custom_parsing_internal(attr, tokens, "import_tokens_attr") { 429 | Ok(tokens) => tokens.into(), 430 | Err(err) => err.to_compile_error().into(), 431 | } 432 | } 433 | 434 | /// Deprecated: No-op 435 | #[deprecated( 436 | note = "`use_attr` is no longer needed for importing or re-exporting, implementation is no-op, it can be removed safely" 437 | )] 438 | #[proc_macro_attribute] 439 | pub fn use_attr(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 440 | tokens 441 | } 442 | 443 | /// Deprecated: No-op 444 | #[deprecated( 445 | note = "`use_proc` is no longer needed for importing or re-exporting, implementation is no-op, it can be removed safely" 446 | )] 447 | #[proc_macro_attribute] 448 | pub fn use_proc(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 449 | tokens 450 | } 451 | 452 | /// A helper macro used by [`macro@import_tokens`]. Hidden from docs. 453 | #[doc(hidden)] 454 | #[proc_macro] 455 | pub fn import_tokens_inner(tokens: TokenStream) -> TokenStream { 456 | match import_tokens_inner_internal(tokens) { 457 | Ok(tokens) => tokens.into(), 458 | Err(err) => err.to_compile_error().into(), 459 | } 460 | } 461 | 462 | /// A helper macro used by [`macro@forward_tokens`]. Hidden from docs. 463 | #[doc(hidden)] 464 | #[proc_macro] 465 | pub fn forward_tokens_inner(tokens: TokenStream) -> TokenStream { 466 | match forward_tokens_inner_internal(tokens) { 467 | Ok(tokens) => tokens.into(), 468 | Err(err) => err.to_compile_error().into(), 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | cargo doc --all-features 4 | cargo test --all-features --workspace 5 | cd core_macros 6 | cargo publish 7 | cd .. 8 | cd core 9 | cargo publish 10 | cd .. 11 | cd macros 12 | cargo publish 13 | cd .. 14 | cargo publish 15 | echo "published successfully." 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Macro Magic 🪄 2 | //! 3 | //! ![Build Status](https://img.shields.io/github/actions/workflow/status/sam0x17/macro_magic/ci.yaml) 4 | //! ![GitHub](https://img.shields.io/github/license/sam0x17/macro_magic) 5 | //! ![Crates.io](https://img.shields.io/crates/d/macro_magic) 6 | //! ![docs.rs](https://img.shields.io/docsrs/macro_magic?label=docs) 7 | //! 8 | //! ## Overview 9 | //! 10 | //! This crate provides an [`#[export_tokens]`](`export_tokens`) attribute macro, and a number 11 | //! of companion macros, most prominently [`#[import_tokens_proc]`](`import_tokens_proc`) and 12 | //! [`#[import_tokens_attr]`](`import_tokens_attr`), which, when used in tandem with 13 | //! [`#[export_tokens]`](`export_tokens`), allow you to create regular and attribute proc 14 | //! macros in which you can import and make use of the tokens of external/foreign items marked 15 | //! with [`#[export_tokens]`](`export_tokens`) in other modules, files, and even in other 16 | //! crates merely by referring to them by name/path. 17 | //! 18 | //! Among other things, the patterns introduced by `macro_magic` can be used to implement safe 19 | //! and efficient exportation and importation of item tokens within the same file, and even 20 | //! across file and crate boundaries. 21 | //! 22 | //! ## no_std 23 | //! 24 | //! `macro_magic` is designed to work with stable Rust, and is fully `no_std` compatible (in 25 | //! fact, there is a unit test to ensure everything is `no_std` safe). 26 | //! 27 | //! ## Features 28 | //! 29 | //! ### proc_support 30 | //! 31 | //! The `proc_support` feature _must_ be enabled in proc macro crates that make use of any 32 | //! import tokens functionality, including [`#[import_tokens_attr]`](`import_tokens_attr`), 33 | //! [`#[import_tokens_proc]`](`import_tokens_proc`) and [`import_tokens!`]. Otherwise these 34 | //! macros will not function correctly and will issue compiler errors complaining about items 35 | //! not existing under [`mm_core`]. The [`#[export_tokens]`](`export_tokens`) macro does not 36 | //! require this feature to function correctly, so you can safely use it without enabling this 37 | //! feature. 38 | //! 39 | //! The reason for this feature gating is that things like `syn`, `quote`, `proc_macro2`, etc., 40 | //! are not 100% `no_std` compatible and should only be enabled in proc macro crates. 41 | //! 42 | //! ## Limitations 43 | //! 44 | //! One thing that `macro_magic` _doesn't_ provide is the ability to build up state information 45 | //! across multiple macro invocations, however this problem can be tackled effectively using 46 | //! the [outer macro pattern](https://www.youtube.com/watch?v=aEWbZxNCH0A) or in some cases 47 | //! using static atomics and mutexes in your proc macro crate (which we actually do in this 48 | //! crate to keep track of unique identifiers). 49 | //! 50 | //! ## Breaking Changes 51 | //! 52 | //! - **0.4x** removed `#[use_attr]` and `#[use_proc]` (they are no longer needed with the new 53 | //! self-calling macro style that has been adopted in 0.4x) and also removed the ability to 54 | //! access `#[export_tokens]` invocations in inaccessible locations like inside of functions 55 | //! and across module permission boundaries like in an inaccessible private module. This 56 | //! feature may be re-added in the future if there is interest, however removing it allowed 57 | //! us to consolidate naming of our `macro_rules!` declarations and remove the need for 58 | //! `#[use_attr]` / `#[use_proc]`. 59 | //! - **0.2x** removed and/or re-wrote a number of features that relied on a non-future-proof 60 | //! behavior of writing/reading files in the `OUT_DIR`. Versions >= 0.2.0 are completely safe 61 | //! and no longer contain this behavior, however features that provided the ability to 62 | //! enumerate all the `#[export_tokens]` calls in a namespace have been removed. The proper 63 | //! way to do this is with the outer macro pattern or with global state mutexes/atomics in 64 | //! your proc macro crate, as mentioned above. 65 | //! 66 | //! More detailed historical change information can be found in 67 | //! [releases](https://github.com/sam0x17/docify/releases). 68 | 69 | #![no_std] 70 | 71 | /// Contains the internal code behind the `macro_magic` macros in a re-usable form, in case you 72 | /// need to design new macros that utilize some of the internal functionality of `macro_magic`. 73 | pub mod mm_core { 74 | #[cfg(feature = "proc_support")] 75 | pub use macro_magic_core::*; 76 | } 77 | 78 | pub use macro_magic_macros::{ 79 | export_tokens, export_tokens_alias, export_tokens_no_emit, forward_tokens, 80 | forward_tokens_verbatim, use_attr, use_proc, 81 | }; 82 | 83 | #[cfg(feature = "proc_support")] 84 | pub use macro_magic_macros::{ 85 | import_tokens, import_tokens_attr, import_tokens_attr_verbatim, import_tokens_proc, 86 | with_custom_parsing, 87 | }; 88 | 89 | /// Contains re-exports required at compile-time by the macro_magic macros and support 90 | /// functions. 91 | #[doc(hidden)] 92 | pub mod __private { 93 | pub use macro_magic_macros::*; 94 | 95 | #[cfg(feature = "proc_support")] 96 | pub use quote; 97 | 98 | #[cfg(feature = "proc_support")] 99 | pub use syn; 100 | 101 | #[cfg(feature = "proc_support")] 102 | pub use syn::__private::TokenStream2; 103 | } 104 | -------------------------------------------------------------------------------- /tests/external_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "external_crate" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | [dependencies] 8 | macro_magic = { path = "../../" } 9 | -------------------------------------------------------------------------------- /tests/external_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] // test that `#[export_tokens]` works with `no_std` 2 | #![allow(unused)] 3 | 4 | use macro_magic::*; 5 | 6 | pub mod some_submodule { 7 | use macro_magic::*; 8 | 9 | struct FooBarStruct {} 10 | 11 | trait FooBarTrait { 12 | fn foo(n: u32) -> u32; 13 | fn bar(n: i32) -> i32; 14 | fn fizz(v: bool) -> bool; 15 | } 16 | 17 | #[export_tokens(AnExternalTraitImpl)] 18 | impl FooBarTrait for FooBarStruct { 19 | fn foo(n: u32) -> u32 { 20 | n + 1 21 | } 22 | 23 | fn bar(n: i32) -> i32 { 24 | n - 1 25 | } 26 | 27 | fn fizz(v: bool) -> bool { 28 | !v 29 | } 30 | } 31 | } 32 | 33 | #[export_tokens] 34 | fn an_external_function(my_num: u32) -> u32 { 35 | my_num + 33 36 | } 37 | 38 | #[export_tokens] 39 | mod an_external_module { 40 | fn my_cool_function() -> u32 { 41 | 567 42 | } 43 | } 44 | 45 | macro_rules! another_macro { 46 | () => { 47 | let a = 2; 48 | }; 49 | } 50 | 51 | #[export_tokens] 52 | fn external_fn_with_local_macro_calls() -> u32 { 53 | another_macro!(); 54 | 1337 55 | } 56 | -------------------------------------------------------------------------------- /tests/external_file.rs: -------------------------------------------------------------------------------- 1 | use macro_magic::*; 2 | 3 | #[export_tokens] 4 | fn external_fn_with_println() { 5 | println!("testing"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/isolated_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isolated_crate" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | middle_crate = { path = "../middle_crate" } 11 | -------------------------------------------------------------------------------- /tests/isolated_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![cfg(test)] 3 | 4 | use middle_crate::distant_re_export_attr; 5 | use middle_crate::distant_re_export_proc; 6 | 7 | #[distant_re_export_attr(middle_crate::ForeignItem)] 8 | struct AttachedItem { 9 | // TODO: implement as UI test maybe 10 | // Uncomment and see the error message with span 11 | // foo: unresolved_path 12 | } 13 | 14 | #[test] 15 | fn test_distant_re_export_attr() { 16 | assert_eq!(DISTANT_ATTR_ATTACHED_ITEM, "struct AttachedItem {}"); 17 | assert_eq!(DISTANT_ATTR_IMPORTED_ITEM, "struct ForeignItem {}"); 18 | } 19 | 20 | #[test] 21 | fn test_distant_re_export_proc() { 22 | let tokens_str = distant_re_export_proc!(middle_crate::ForeignItem); 23 | assert_eq!(tokens_str, "struct ForeignItem {}"); 24 | } 25 | -------------------------------------------------------------------------------- /tests/middle_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middle_crate" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | macro_magic = { path = "../../" } 11 | test_macros = { path = "../test_macros" } 12 | -------------------------------------------------------------------------------- /tests/middle_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod export_mod { 2 | pub mod sub_mod { 3 | pub use macro_magic; 4 | } 5 | } 6 | 7 | #[macro_magic::export_tokens] 8 | struct ForeignItem {} 9 | 10 | pub use test_macros::distant_re_export_attr; 11 | pub use test_macros::distant_re_export_proc; 12 | -------------------------------------------------------------------------------- /tests/test_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_macros" 3 | version = "0.1.0" 4 | edition = "2024" 5 | publish = false 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | macro_magic = { path = "../../", features = ["proc_support"] } 12 | syn = { version = "2", features = ["full"] } 13 | quote = "1" 14 | proc-macro2 = "1" 15 | derive-syn-parse = "0.2" 16 | -------------------------------------------------------------------------------- /tests/test_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use derive_syn_parse::Parse; 2 | use macro_magic::{mm_core::ForeignPath, *}; 3 | use proc_macro::TokenStream; 4 | use quote::{ToTokens, quote}; 5 | use syn::{ 6 | Error, Fields, Ident, Item, ItemMod, ItemStruct, Path, parse_macro_input, spanned::Spanned, 7 | }; 8 | 9 | /// An example proc macro built on top of `import_tokens_internal`. 10 | /// 11 | /// ## Example 12 | /// 13 | /// ```ignore 14 | /// #[include_impl(path::to::exported::impl_block)] 15 | /// mod my_module { 16 | /// // ... 17 | /// } 18 | /// ``` 19 | /// would result in all of the items within the specified `impl` getting reproduced/imported 20 | /// within the module the attribute is attached to, possibly resulting in a compiler error if 21 | /// the specified `impl` block has generics. 22 | #[proc_macro_attribute] 23 | pub fn include_impl(attr: TokenStream, tokens: TokenStream) -> TokenStream { 24 | let external_path = parse_macro_input!(attr as Path); 25 | let _item_mod = parse_macro_input!(tokens as ItemMod); 26 | let mm_path = macro_magic::mm_core::macro_magic_root(); 27 | quote! { 28 | #mm_path::forward_tokens! { #external_path, include_impl_inner } 29 | } 30 | .into() 31 | } 32 | 33 | #[proc_macro] 34 | pub fn include_impl_inner(_tokens: TokenStream) -> TokenStream { 35 | // println!("GOT TOKENS: {}", tokens.to_string()); 36 | quote!().into() 37 | } 38 | 39 | #[proc_macro] 40 | pub fn some_macro(tokens: TokenStream) -> TokenStream { 41 | let source_path = parse_macro_input!(tokens as Path); 42 | quote! { 43 | forward_tokens!(#source_path, test_macros::some_other_macro); 44 | } 45 | .into() 46 | } 47 | 48 | #[derive(Parse)] 49 | struct SomeOtherMacroArgs { 50 | forwarded_ident: Ident, 51 | item: Item, 52 | } 53 | 54 | #[proc_macro] 55 | pub fn some_other_macro(tokens: TokenStream) -> TokenStream { 56 | let args = parse_macro_input!(tokens as SomeOtherMacroArgs); 57 | assert_eq!( 58 | args.forwarded_ident.to_token_stream().to_string(), 59 | "__private_macro_magic_tokens_forwarded", 60 | ); 61 | assert_eq!( 62 | args.item.to_token_stream().to_string(), 63 | "struct SomeStruct { field1 : u32, field2 : bool, }" 64 | ); 65 | let item = args.item; 66 | quote! { 67 | #[allow(unused)] 68 | #item 69 | } 70 | .into() 71 | } 72 | 73 | // as demonstrated here, `import_tokens_attr` can take a path or an expression that evaluates 74 | // to something compatible with `Into` 75 | #[import_tokens_attr(format!("{}::export_mod::sub_mod::macro_magic", "middle_crate"))] 76 | #[proc_macro_attribute] 77 | pub fn distant_re_export_attr(attr: TokenStream, tokens: TokenStream) -> TokenStream { 78 | let imported_item = parse_macro_input!(attr as Item); 79 | let attached_item = parse_macro_input!(tokens as Item); 80 | let imported_item_str = imported_item.to_token_stream().to_string(); 81 | let attached_item_str = attached_item.to_token_stream().to_string(); 82 | quote! { 83 | const DISTANT_ATTR_ATTACHED_ITEM: &'static str = #attached_item_str; 84 | const DISTANT_ATTR_IMPORTED_ITEM: &'static str = #imported_item_str; 85 | #attached_item 86 | } 87 | .into() 88 | } 89 | 90 | #[import_tokens_proc(format!("middle_crate::export_mod::{}::macro_magic", "sub_mod"))] 91 | #[proc_macro] 92 | pub fn distant_re_export_proc(tokens: TokenStream) -> TokenStream { 93 | let imported_item = parse_macro_input!(tokens as Item); 94 | let imported_item_str = imported_item.to_token_stream().to_string(); 95 | quote!(#imported_item_str).into() 96 | } 97 | 98 | #[import_tokens_attr(example_export::subpath)] 99 | #[proc_macro_attribute] 100 | pub fn test_tokens_attr1(attr: TokenStream, tokens: TokenStream) -> TokenStream { 101 | let imported_item = parse_macro_input!(attr as Item); 102 | let attached_item = parse_macro_input!(tokens as Item); 103 | let imported_item_str = imported_item.to_token_stream().to_string(); 104 | let attached_item_str = attached_item.to_token_stream().to_string(); 105 | assert_eq!(imported_item_str, "struct AnotherStruct { field1 : u32, }"); 106 | assert_eq!( 107 | attached_item_str, 108 | "pub mod hunter { pub fn stuff() { println! (\"things\"); } }" 109 | ); 110 | quote! { 111 | #attached_item 112 | } 113 | .into() 114 | } 115 | 116 | #[import_tokens_attr] 117 | #[proc_macro_attribute] 118 | pub fn test_tokens_attr2(attr: TokenStream, tokens: TokenStream) -> TokenStream { 119 | let imported_item = parse_macro_input!(attr as Item); 120 | let attached_item = parse_macro_input!(tokens as Item); 121 | let imported_item_str = imported_item.to_token_stream().to_string(); 122 | let attached_item_str = attached_item.to_token_stream().to_string(); 123 | assert_eq!( 124 | imported_item_str, 125 | "impl FooBarTrait for FooBarStruct\n{\n fn foo(n : u32) -> u32 { n + 1 } \ 126 | fn bar(n : i32) -> i32 { n - 1 } fn\n fizz(v : bool) -> bool { ! v }\n}" 127 | ); 128 | assert_eq!(attached_item_str, "struct LocalItemStruct {}"); 129 | quote! { 130 | #attached_item 131 | } 132 | .into() 133 | } 134 | 135 | #[derive(Parse)] 136 | struct CustomParsingA { 137 | foreign_path: syn::Path, 138 | _comma: syn::token::Comma, 139 | custom_path: syn::Path, 140 | } 141 | 142 | impl ToTokens for CustomParsingA { 143 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 144 | tokens.extend(self.foreign_path.to_token_stream()); 145 | tokens.extend(self._comma.to_token_stream()); 146 | tokens.extend(self.custom_path.to_token_stream()); 147 | } 148 | } 149 | 150 | impl ForeignPath for CustomParsingA { 151 | fn foreign_path(&self) -> &syn::Path { 152 | &self.foreign_path 153 | } 154 | } 155 | 156 | #[with_custom_parsing(CustomParsingA)] 157 | #[import_tokens_attr] 158 | #[proc_macro_attribute] 159 | pub fn import_tokens_attr_with_custom_parsing_a( 160 | attr: TokenStream, 161 | tokens: TokenStream, 162 | ) -> TokenStream { 163 | let imported_item = parse_macro_input!(attr as Item); 164 | let attached_item = parse_macro_input!(tokens as Item); 165 | let imported_item_str = imported_item.to_token_stream().to_string(); 166 | let attached_item_str = attached_item.to_token_stream().to_string(); 167 | let custom_path_str = __custom_tokens.to_string(); 168 | assert_eq!( 169 | imported_item_str, 170 | "struct CustomParsingStructForeign { field : bool, }" 171 | ); 172 | assert_eq!( 173 | custom_path_str, 174 | "CustomParsingStructForeign, some :: cool :: path" 175 | ); 176 | assert_eq!( 177 | attached_item_str, 178 | "struct CustomParsingStructLocal { field : u32, }" 179 | ); 180 | quote! { 181 | #attached_item 182 | } 183 | .into() 184 | } 185 | 186 | /// we do this one to check that both orderings work 187 | #[import_tokens_attr] 188 | #[with_custom_parsing(CustomParsingA)] 189 | #[proc_macro_attribute] 190 | pub fn import_tokens_attr_with_custom_parsing_b( 191 | attr: TokenStream, 192 | tokens: TokenStream, 193 | ) -> TokenStream { 194 | let imported_item = parse_macro_input!(attr as Item); 195 | let attached_item = parse_macro_input!(tokens as Item); 196 | let imported_item_str = imported_item.to_token_stream().to_string(); 197 | let attached_item_str = attached_item.to_token_stream().to_string(); 198 | let custom_path_str = __custom_tokens.to_string(); 199 | assert_eq!( 200 | imported_item_str, 201 | "struct CustomParsingStructForeign { field : bool, }" 202 | ); 203 | assert_eq!( 204 | custom_path_str, 205 | "CustomParsingStructForeign, some :: cool :: path" 206 | ); 207 | assert_eq!( 208 | attached_item_str, 209 | "struct CustomParsingStructLocal2 { field : u32, }" 210 | ); 211 | quote! { 212 | #attached_item 213 | } 214 | .into() 215 | } 216 | 217 | #[proc_macro_attribute] 218 | #[import_tokens_attr] 219 | pub fn test_tokens_attr_direct_import(attr: TokenStream, tokens: TokenStream) -> TokenStream { 220 | let imported_item = parse_macro_input!(attr as Item); 221 | let attached_item = parse_macro_input!(tokens as Item); 222 | quote! { 223 | #imported_item 224 | #attached_item 225 | } 226 | .into() 227 | } 228 | 229 | #[import_tokens_proc] 230 | #[proc_macro] 231 | pub fn example_tokens_proc(tokens: TokenStream) -> TokenStream { 232 | let imported_item = parse_macro_input!(tokens as Item); 233 | let item_as_string = imported_item.to_token_stream().to_string(); 234 | quote!(#item_as_string).into() 235 | } 236 | 237 | #[import_tokens_proc(example_export::subpath)] 238 | #[proc_macro] 239 | pub fn item_level_proc(tokens: TokenStream) -> TokenStream { 240 | let _imported_item = parse_macro_input!(tokens as Item); 241 | quote!( 242 | struct SomeInjectedStruct {} 243 | ) 244 | .into() 245 | } 246 | 247 | #[import_tokens_attr] 248 | #[proc_macro_attribute] 249 | pub fn emit_foreign_path(attr: TokenStream, tokens: TokenStream) -> TokenStream { 250 | let path = __source_path.to_string(); 251 | let foreign_item_str = attr.to_string(); 252 | let item = parse_macro_input!(tokens as Item); 253 | quote! { 254 | const foreign_item_str: &'static str = #foreign_item_str; 255 | const emitted_path: &'static str = #path; 256 | #item 257 | } 258 | .into() 259 | } 260 | 261 | #[import_tokens_attr] 262 | #[proc_macro_attribute] 263 | pub fn combine_structs(attr: TokenStream, tokens: TokenStream) -> TokenStream { 264 | let foreign_struct = parse_macro_input!(attr as ItemStruct); 265 | let local_struct = parse_macro_input!(tokens as ItemStruct); 266 | let Fields::Named(local_fields) = local_struct.fields else { 267 | return Error::new( 268 | local_struct.fields.span(), 269 | "unnamed fields are not supported", 270 | ) 271 | .to_compile_error() 272 | .into(); 273 | }; 274 | let Fields::Named(foreign_fields) = foreign_struct.fields else { 275 | return Error::new( 276 | foreign_struct.fields.span(), 277 | "unnamed fields are not supported", 278 | ) 279 | .to_compile_error() 280 | .into(); 281 | }; 282 | let local_fields = local_fields.named.iter(); 283 | let foreign_fields = foreign_fields.named.iter(); 284 | let attrs = local_struct.attrs; 285 | let generics = local_struct.generics; 286 | let ident = local_struct.ident; 287 | let vis = local_struct.vis; 288 | quote! { 289 | #(#attrs) 290 | * 291 | #vis struct #ident<#generics> { 292 | #(#local_fields), 293 | * 294 | , 295 | #(#foreign_fields), 296 | * 297 | } 298 | } 299 | .into() 300 | } 301 | 302 | #[import_tokens_proc] 303 | #[proc_macro] 304 | pub fn require(tokens: TokenStream) -> TokenStream { 305 | let external_mod = parse_macro_input!(tokens as ItemMod); 306 | let Some((_, stmts)) = external_mod.content else { 307 | return Error::new( 308 | external_mod.span(), 309 | "cannot import tokens from a file-based module since custom file-level \ 310 | attributes are not yet supported by Rust", 311 | ) 312 | .to_compile_error() 313 | .into(); 314 | }; 315 | quote! { 316 | #(#stmts) 317 | * 318 | } 319 | .into() 320 | } 321 | 322 | export_tokens_alias!(custom_export_tokens); 323 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | #![allow(dead_code)] 3 | 4 | use macro_magic::*; 5 | 6 | use macro_magic_macros::export_tokens_no_emit; 7 | use test_macros::{custom_export_tokens, include_impl, include_impl_inner}; 8 | 9 | #[cfg(feature = "proc_support")] 10 | use test_macros::some_macro; 11 | 12 | use test_macros::combine_structs; 13 | use test_macros::emit_foreign_path; 14 | use test_macros::example_tokens_proc; 15 | use test_macros::import_tokens_attr_with_custom_parsing_a; 16 | use test_macros::import_tokens_attr_with_custom_parsing_b; 17 | use test_macros::item_level_proc; 18 | use test_macros::require; 19 | use test_macros::test_tokens_attr1; 20 | use test_macros::test_tokens_attr2; 21 | 22 | /// Some doc comment 23 | pub use test_macros::test_tokens_attr_direct_import; 24 | 25 | #[export_tokens] 26 | struct CustomParsingStructForeign { 27 | field: bool, 28 | } 29 | 30 | #[import_tokens_attr_with_custom_parsing_a(CustomParsingStructForeign, some::cool::path)] 31 | struct CustomParsingStructLocal { 32 | field: u32, 33 | } 34 | 35 | #[import_tokens_attr_with_custom_parsing_b(CustomParsingStructForeign, some::cool::path)] 36 | struct CustomParsingStructLocal2 { 37 | field: u32, 38 | } 39 | 40 | pub mod example_export { 41 | pub mod subpath { 42 | pub use ::macro_magic::*; 43 | } 44 | } 45 | 46 | mod external_file; 47 | 48 | #[export_tokens] 49 | struct SomeStruct { 50 | field1: u32, 51 | field2: bool, 52 | } 53 | 54 | #[export_tokens(charlie)] 55 | struct Struct2 { 56 | field1: i64, 57 | field2: usize, 58 | } 59 | 60 | mod some_module { 61 | use macro_magic::*; 62 | 63 | #[export_tokens] 64 | fn plus_plus>(n: T) -> i64 { 65 | n.into() + 1 66 | } 67 | 68 | #[export_tokens(MinusMinus)] 69 | fn minus_minus>(n: T) -> i32 { 70 | n.into() - 1 71 | } 72 | } 73 | 74 | #[include_impl(SomeStruct)] 75 | mod some_mod {} 76 | 77 | #[export_tokens] 78 | struct AnotherStruct { 79 | field1: u32, 80 | } 81 | 82 | #[test_tokens_attr1(AnotherStruct)] 83 | pub mod hunter { 84 | pub fn stuff() { 85 | println!("things"); 86 | } 87 | } 88 | 89 | #[test_tokens_attr2(external_crate::some_submodule::AnExternalTraitImpl)] 90 | struct LocalItemStruct {} 91 | 92 | #[test_tokens_attr_direct_import(external_crate::an_external_function)] 93 | fn cute_little_fn() { 94 | println!("hey!"); 95 | } 96 | 97 | #[export_tokens] 98 | struct LionStruct {} 99 | 100 | #[export_tokens] 101 | struct TigerStruct {} 102 | 103 | // test proc item position 104 | item_level_proc!(external_crate::some_submodule::AnExternalTraitImpl); 105 | 106 | #[test] 107 | fn test_import_tokens_proc_item_position() { 108 | let _foo = SomeInjectedStruct {}; 109 | } 110 | 111 | #[test] 112 | fn test_import_tokens_proc_statement_position() { 113 | example_tokens_proc!(LionStruct); 114 | example_tokens_proc!(external_crate::some_submodule::AnExternalTraitImpl); 115 | } 116 | 117 | #[test] 118 | fn test_import_tokens_proc_expr_position() { 119 | let something = example_tokens_proc!(TigerStruct); 120 | assert_eq!(something.to_string(), "struct TigerStruct {}"); 121 | let _something_else = example_tokens_proc!(external_crate::some_submodule::AnExternalTraitImpl); 122 | } 123 | 124 | #[test] 125 | fn attr_direct_import() { 126 | assert_eq!(an_external_function(4), 37); 127 | } 128 | 129 | #[macro_export] 130 | macro_rules! receiver { 131 | (__private_macro_magic_tokens_forwarded $tokens:item) => { 132 | stringify!($tokens) 133 | }; 134 | } 135 | 136 | #[test] 137 | fn test_forward_tokens() { 138 | let result = forward_tokens!(LionStruct, receiver); 139 | assert_eq!(result, "struct LionStruct {}"); 140 | } 141 | 142 | #[cfg(feature = "proc_support")] 143 | #[test] 144 | fn import_tokens_same_mod_no_ident() { 145 | some_macro!(SomeStruct); 146 | import_tokens!(let tokens = SomeStruct); 147 | assert!(tokens.to_string().contains("field1")); 148 | } 149 | 150 | #[cfg(feature = "proc_support")] 151 | #[test] 152 | fn import_tokens_same_mod_ident() { 153 | import_tokens!(let tokens = charlie); 154 | assert!(tokens.to_string().contains("field2 : usize")); 155 | } 156 | 157 | #[cfg(feature = "proc_support")] 158 | #[test] 159 | fn import_tokens_different_mod_no_ident() { 160 | import_tokens!(let tokens = some_module::PlusPlus); 161 | assert_eq!( 162 | tokens.to_string(), 163 | "fn plus_plus < T : Into < i64 > > (n : T) -> i64 { n . into () + 1 }" 164 | ); 165 | } 166 | 167 | #[cfg(feature = "proc_support")] 168 | #[test] 169 | fn import_tokens_different_mod_ident() { 170 | import_tokens!(let tokens = some_module::MinusMinus); 171 | assert_eq!( 172 | tokens.to_string(), 173 | "fn minus_minus < T : Into < i32 > > (n : T) -> i32 { n . into () - 1 }" 174 | ); 175 | } 176 | 177 | #[export_tokens] 178 | fn a_random_fn() { 179 | println!("hey"); 180 | } 181 | 182 | #[test] 183 | fn println_inside_fn_current_file() { 184 | let tokens = example_tokens_proc!(a_random_fn); 185 | assert_eq!( 186 | tokens.to_string(), 187 | "fn a_random_fn() { println! (\"hey\"); }" 188 | ); 189 | } 190 | 191 | #[test] 192 | fn println_inside_fn_external_file() { 193 | let tokens = example_tokens_proc!(external_file::external_fn_with_println); 194 | assert_eq!( 195 | tokens.to_string(), 196 | "fn external_fn_with_println() { println! (\"testing\"); }" 197 | ); 198 | } 199 | 200 | #[test] 201 | fn macro_calls_inside_fn_external_crate() { 202 | let tokens = example_tokens_proc!(external_crate::external_fn_with_local_macro_calls); 203 | assert_eq!( 204 | tokens.to_string(), 205 | "fn external_fn_with_local_macro_calls() -> u32 { another_macro! (); 1337 }" 206 | ); 207 | } 208 | 209 | #[export_tokens] 210 | struct ExternalStruct { 211 | foo: u32, 212 | bar: u64, 213 | fizz: i64, 214 | } 215 | 216 | #[combine_structs(ExternalStruct)] 217 | struct LocalStruct { 218 | biz: bool, 219 | baz: i32, 220 | } 221 | 222 | #[test] 223 | fn test_combine_structs_example() { 224 | let _something = LocalStruct { 225 | foo: 42, 226 | bar: 19, 227 | fizz: -22, 228 | biz: true, 229 | baz: 87, 230 | }; 231 | } 232 | 233 | #[test] 234 | fn test_require_example() { 235 | require!(external_crate::an_external_module); 236 | assert_eq!(my_cool_function(), 567); 237 | } 238 | 239 | #[custom_export_tokens] 240 | struct Wombat { 241 | field1: u32, 242 | field2: u64, 243 | } 244 | 245 | #[test] 246 | fn test_export_tokens_alias() { 247 | let tokens = example_tokens_proc!(Wombat); 248 | assert_eq!( 249 | tokens.to_string(), 250 | "struct Wombat { field1 : u32, field2 : u64, }" 251 | ); 252 | } 253 | 254 | #[emit_foreign_path(external_crate::an_external_function)] 255 | struct YetAnotherStruct {} 256 | 257 | #[test] 258 | fn test_foreign_path_emission() { 259 | assert_eq!(emitted_path, "external_crate :: an_external_function"); 260 | assert_eq!( 261 | foreign_item_str, 262 | "fn an_external_function(my_num : u32) -> u32 { my_num + 33 }" 263 | ); 264 | } 265 | 266 | #[export_tokens_no_emit] 267 | fn _non_compiling_fn() { 268 | compile_error!("this should not compile "); 269 | } 270 | 271 | // should not collide with above function since above function does not emit tokens locally and 272 | // so it does not exist locally 273 | fn _non_compiling_fn() -> usize { 274 | 3 275 | } 276 | 277 | #[cfg(feature = "proc_support")] 278 | #[test] 279 | fn test_export_tokens_no_emit_exportation() { 280 | import_tokens!(let tokens = _non_compiling_fn); 281 | assert_eq!( 282 | tokens.to_string(), 283 | "fn _non_compiling_fn () { compile_error ! (\"this should not compile \") ; }" 284 | ); 285 | assert_eq!(_non_compiling_fn(), 3); 286 | } 287 | --------------------------------------------------------------------------------