1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, TypePath};
use macro_magic_core::*;
/// This attribute can be attached to any [`syn::Item`]-compatible source code item without
/// specifying any arguments, with the exception of [`ForeignMod`](`syn::ItemForeignMod`),
/// [`Impl`](`syn::ItemImpl`), [`Macro`](`syn::ItemMacro`) (except when it has a defined
/// [`syn::Ident`]), [`Use`](`syn::ItemUse`), and [`Verbatim`](`syn::Item::Verbatim`) which all
/// require a disambiguation path to be specified as an argument. Attaching to an item will
/// "export" that item so that it can be imported elsewhere by name via the [`import_tokens!`]
/// macro.
///
/// For example, this would export a function named `foo`:
/// ```ignore
/// use macro_magic::export_tokens;
///
/// #[export_tokens]
/// fn foo(a: i64, b: i64) -> i64 {
/// a * a + b * b
/// }
/// ```
///
/// and this would export a module named `bar`:
/// ```ignore
/// use macro_magic::export_tokens;
///
/// #[export_tokens]
/// mod bar {
/// // ...
/// }
/// ```
///
/// You can also specify a path as an argument to [`#[export_tokens]`](`macro@export_tokens`)
/// as follows:
/// ```ignore
/// use macro_magic::export_tokens;
///
/// #[export_tokens(foo::bar::fizz_buzz)]
/// fn fizz_buzz() {
/// // ...
/// }
/// ```
///
/// This path will be used to disambiguate `fizz_buzz` from other `fizz_buzz` items that may
/// have been exported from different paths when you use [`import_tokens!`] to perform an
/// indirect import. Direct imports only make use of the last segment of this name, if it is
/// specified, while indirect imports will use the whole path. If indirect imports are
/// disabled, you will get a compiler error if you try to specify a multi-level path, since
/// there is no way for direct imports to make use of anything but the last segment.
///
/// Also note that direct imports are subject to visibility restrictions (i.e. they won't work
/// if you aren't in a public module), whereas indirect imports completely bypass visibility
/// restrictions because of how they are implemented internally.
///
/// ## Expansion
///
/// ```ignore
/// #[export_tokens]
/// fn foo_bar(a: u32) -> u32 {
/// a * 2
/// }
/// ```
///
/// expands to:
///
/// ```ignore
/// #[allow(dead_code)]
/// fn foo_bar(a: u32) -> u32 {
/// a * 2
/// }
/// #[allow(dead_code)]
/// #[doc(hidden)]
/// pub const __EXPORT_TOKENS__FOO_BAR: &'static str = "fn foo_bar(a : u32) -> u32 { a * 2 }";
/// ```
///
/// See the documentation for [`import_tokens!`] for more information and a full example of
/// exporting and importing tokens. README.md also contains valuable information.
#[proc_macro_attribute]
pub fn export_tokens(attr: TokenStream, tokens: TokenStream) -> TokenStream {
let (item, const_decl) = match export_tokens_internal(tokens, attr, "#[export_tokens]") {
Ok((item, const_decl)) => (item, const_decl),
Err(e) => return e.to_compile_error().into(),
};
quote! {
#[allow(dead_code)]
#item
#[doc(hidden)]
#[allow(dead_code)]
#const_decl
}
.into()
}
/// This macro is the primary way to bring exported tokens into scope in your proc macros
/// (though it can also be used in non-proc-macro contexts, and is based on
/// [`TokenStream2`](syn::__private::TokenStream2) for this purpose).
///
/// This approach is called a "direct import" and requires the source and target to be in the
/// same crate, or requires that the source crate is a dependency of the target crate. For a
/// less restrictive approach, see [`import_tokens_indirect!`].
///
/// Suppose you have exported tokens using the [`#[export_tokens]`](`macro@export_tokens`)
/// attribute macro as follows:
///
/// ```ignore
/// pub mod my_module {
/// use macro_magic::*;
///
/// #[export_tokens]
/// struct MyCoolStruct {
/// foo: u32,
/// bar: usize,
/// fizz: bool,
/// }
/// }
/// ```
///
/// You can now call `import_tokens!(MyCoolStruct)` anywhere that `my_module::*` has been
/// brought into scope (via a `use`), and you can call
/// `import_tokens!(my_module::MyCoolStruct)` anywhere that `my_module` is accessible,
/// including elsewhere in the same crate, or in crates that have the crate containing
/// `my_module` as a dependency.
///
/// In some other file in the same crate:
/// ```ignore
/// let tokens = import_tokens!(my_module::MyCoolStruct);
/// ```
///
/// In a proc macro crate that has the `my_module` crate as a dependency:
/// ```ignore
/// #[proc_macro]
/// pub fn my_macro(_tokens: TokenStream) -> TokenStream {
/// let struct_tokens = import_tokens!(some_crate::my_module::MyCoolStruct);
/// }
/// ```
///
/// With a `use` statement:
/// ```ignore
/// use some_crate::my_module::*;
///
/// #[proc_macro]
/// pub fn my_macro(_tokens: TokenStream) -> TokenStream {
/// let struct_tokens = import_tokens!(MyCoolStruct);
/// }
/// ```
///
/// Note that you must be able to import the module that contains the item where you called
/// `#[export_tokens]` for this to work properly.
///
/// ## Expansion
///
/// An invocation like this:
/// ```ignore
/// let tokens = import_tokens!(my_module::MyCoolStruct);
/// ```
/// would expand to this:
/// ```ignore
/// let tokens: TokenStream2 = my_module::MyCoolStruct::__EXPORT_TOKENS__MYCOOLSTRUCT
/// .parse::<::macro_magic::__private::TokenStream2>()
/// .unwrap();
/// ```
///
/// The `.unwrap()` will never fail because for [`#[export_tokens]`](`macro@export_tokens`) to
/// compile, the item it is attached to must be a valid [`syn::Item`], so syntax errors cannot
/// make it into the `__EXPORT_TOKENS__MYCOOLSTRUCT` const.
///
/// Because the expansion of [`import_tokens!()`](`macro@import_tokens`) calls the non-const
/// function `.parse()`, you cannot use [`import_tokens!`](`macro@import_tokens`) in a const
/// context.
///
/// Note that the type of `__EXPORT_TOKENS__MYCOOLSTRUCT` is `&'static str`. The naming of
/// these constants is consistent and is defined by the `get_const_name` function. You should
/// never need to call this directly so it is not exported anywhere.
#[proc_macro]
pub fn import_tokens(tokens: TokenStream) -> TokenStream {
let path = parse_macro_input!(tokens as TypePath);
let path = match get_const_path(&path) {
Ok(path) => path,
Err(e) => return e.to_compile_error().into(),
};
quote!(#path.parse::<::macro_magic::__private::TokenStream2>().unwrap()).into()
}
/// This macro allows you to import tokens across crate boundaries
/// without strict dependency requirements and to use advanced features such as
/// [`namespacing`](`read_namespace!').
///
/// Calling `import_tokens_indirect!` is slightly different from calling [`import_tokens!`] in
/// that indirect imports will work even when the item whose tokens you are importing is
/// contained in a crate that is not a dependency of the current crate, so long as the
/// following requirements are met:
///
/// 1. The "indirect-read" feature must be enabled for `macro_magic`, otherwise the
/// `import_tokens_indirect!` macro will not be available. This is automatically enabled by
/// the "indirect" feature.
/// 2. The source crate and the target crate must be in the same
/// [cargo workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html). This is a
/// non-negotiable hard requirement when using indirect imports, however direct imports will
/// work fine across workspace boundaries (they just have other stricter requirements that
/// can be cumbersome).
/// 3. The source crate and the target crate must both use the same version of `macro_magic`
/// (this is not a hard requirement, but undefined behavior could occur with mixed
/// versions).
/// 4. Both the source crate and target crate must have their builds somehow triggered by the
/// compilation target of the current workspace such that they are both compiled. Unlike
/// with direct imports, where you explictily `use` the source crate as a dependency of the
/// target crate, there needs to be some reason to compile the source crate, or its exported
/// tokens will be unavailable.
/// 5. The export path declared by the source crate must exactly match the path you try to
/// import in the target crate. If you don't manually specify an export path, then your
/// import path should be the name of the item that
/// [`#[export_tokens]`](`macro@export_tokens`) was attached to (i.e. the `Ident`), however
/// this approach is not recommended since you can run into collisions if you are not
/// explicit about naming. For highly uniquely named items, however, this is fine. In other
/// words, if you don't specify a namespace, and you have an item named `foo` in two
/// different files, when you export these two items, they will collide.
/// 6. The target crate _must_ be a proc macro crate. If this requirement is violated, then the
/// build-order guarantees exploited by the indirect approach no longer hold true and you
/// may experience undefined behavior in the form of compile errors.
///
/// The vast majority of common use cases for `macro_magic` meet these criteria, but if you run
/// into any issues where exported tokens can't be found, make sure your source crate is
/// included as part of the compilation target and that it is in the current workspace.
/// Likewise watch out for collisions as these are easy to encounter if you don't
/// [`namespace`](`read_namespace!') your items.
///
/// Keep in mind that you can use the optional attribute, `#[export_tokens(my::path::Here)]` to
/// specify a disambiguation path for the tokens you are exporting. Otherwise the name of the item
/// the macro is attached to will be used, potentially causing collisions if you export items by
/// the same name from different contexts.
///
/// This situation will eventually be resolved when the machinery behind
/// [caller_modpath](https://crates.io/crates/caller_modpath) is stabilized, which will allow
/// `macro_magic` to automatically detect the path of the
/// [`#[export_tokens]`](`macro@export_tokens`) caller.
///
/// A peculiar aspect of how [`#[export_tokens(some_path)]`](`macro@export_tokens`) works is
/// the path you enter doesn't need to be a real path. You could do
/// `#[export_tokens(completely::made_up::path::MyItem)]` in one context and then
/// `import_tokens!(completely::made_up::path::MyItem)` in another context, and it will still
/// work as long as these two paths are the same. They need not actually exist, they are just
/// used for disambiguation so we can tell the difference between these tokens and other
/// potential exports of an item called `MyItem`. The last segment _does_ need to match the
/// name of the item you are exporting, however.
#[proc_macro]
pub fn import_tokens_indirect(tokens: TokenStream) -> TokenStream {
match import_tokens_indirect_internal(tokens) {
Ok(res) => res.into(),
Err(e) => e.to_compile_error().into(),
}
}
/// This macro allows you to group a number of [`#[export_tokens]`](`macro@export_tokens`)
/// calls and collect them into a `Result<Vec<(String, TokenStream2)>>`.
///
/// The first component of the tuple corresponds with the name of the item and the second
/// component contains the tokens for that item. The `Result` is a [`std::io::Result`] and any
/// `Err` variants that come back would indicate an internal error (i.e. something tampered
/// with the `target` directory at an unexpected time) or (more likely) that the specified
/// namespace does not exist.
///
/// The [`macro@export_tokens`] attribute automatically defines namespaces when you call it
/// with an argument. Namespaces function like directories, so if you define item A with the
/// path `foo::bar::fizz` and item B with path `foo::bar::buzz`, and you will get back both
/// items if you read the namespace `foo::bar` i.e.:
///
/// ```ignore
/// let namespace_items = read_namespace!(foo::bar).unwrap();
/// let (name, tokens) = namespace_items.first().unwrap();
/// // name = "buzz"
/// // tokens = tokens for `foo::bar::buzz` item
/// ```
///
/// Note that `read_namespace!` always returns results sorted by name, so you can rely on the
/// order to be consistent.
#[proc_macro]
pub fn read_namespace(tokens: TokenStream) -> TokenStream {
match read_namespace_internal(tokens) {
Ok(res) => res.into(),
Err(e) => e.to_compile_error().into(),
}
}
/// This convenient macro can be used to publicly re-export an item that has been exported via
/// [`#[export_tokens]`](`macro@export_tokens`) when doing direct imports.
///
/// Note that this macro *will not work* with indirect imports. See the documentation for
/// [`#[export_tokens]`](`macro@export_tokens`) and [`import_tokens!`] for more information.
///
/// For example, assume in the module `my::cool_module` you have the following code:
/// ```ignore
/// pub mod cool_module {
/// use macro_magic::*;
///
/// #[export_tokens]
/// trait MyTrait {
/// fn some_behavior() -> String;
/// type SomeType;
/// }
/// }
/// ```
///
/// In another module or crate that has `my::cool_module` as a dependency, you could then do
/// something like this:
/// ```ignore
/// use macro_magic::re_export_tokens_const;
///
/// re_export_tokens_const!(my::cool_module::MyTrait);
/// ```
///
/// Now the `MyTrait` tokens will be available from the context where you called
/// `re_export_tokens_const` and can be accessed using direct imports via [`import_tokens!`],
/// for example if the re-export module/crate was called `other_crate::re_exports`, you could
/// then add that as a dependency to another crate and import the `my_item` tokens from that
/// crate like so:
///
/// ```ignore
/// use macro_magic::import_tokens;
/// use other_crate::re_exports::*; // this brings the re-exports into scope
/// use proc_macro2::TokenStream as TokenStream2;
///
/// #[proc_macro]
/// pub fn my_proc_macro(_tokens: TokenStream) -> TokenStream {
/// // ...
///
/// let my_trait_tokens: TokenStream2 = import_tokens!(MyTrait);
///
/// // can also do it this way if we wanted to avoid the `use` statement above:
/// let my_trait_tokens: TokenStream2 = import_tokens!(other_crate::re_exports::MyTrait);
///
/// // ...
///
/// my_item_tokens.into()
/// }
/// ```
///
/// So in other words, this is just a way of cleanly re-exporting the internal token constants
/// that are created by [`macro@export_tokens`] to make them accessible elsewhere.
///
/// ## Expansion
///
/// This code:
/// ```ignore
/// use macro_magic::re_export_tokens_const;
///
/// re_export_tokens_const!(my::cool_module::MyTrait);
/// ```
///
/// Would expand to the following:
/// ```ignore
/// use macro_magic::re_export_tokens_const;
/// #[doc(hidden)]
/// pub use mm_example_crate::cool_module::__EXPORT_TOKENS__MYTRAIT;
/// ```
///
/// Notice that the actual item under the hood is a `const`. &'static str`.
#[proc_macro]
pub fn re_export_tokens_const(tokens: TokenStream) -> TokenStream {
let path = match get_const_path(&parse_macro_input!(tokens as TypePath)) {
Ok(path) => path,
Err(e) => return e.to_compile_error().into(),
};
quote! {
#[doc(hidden)]
pub use #path;
}
.into()
}