macro_magic_core/
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
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8use const_random::const_random;
9use derive_syn_parse::Parse;
10use macro_magic_core_macros::*;
11use proc_macro2::{Delimiter, Group, Punct, Spacing, Span, TokenStream as TokenStream2, TokenTree};
12use quote::{format_ident, quote, ToTokens, TokenStreamExt};
13use syn::{
14    parse::{Nothing, ParseStream},
15    parse2, parse_quote,
16    spanned::Spanned,
17    token::{Brace, Comma},
18    Attribute, Error, Expr, FnArg, Ident, Item, ItemFn, Pat, Path, Result, Token, Visibility,
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`].
25pub const MACRO_MAGIC_ROOT: &str = get_macro_magic_root!();
26
27/// A global counter, can be used to generate a relatively unique identifier.
28static 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`].
34const COMPILATION_TAG: u32 = const_random!(u32);
35
36/// Private module containing custom keywords used for parsing in this crate
37mod 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)]
53pub struct ForwardTokensExtraArg {
54    #[brace]
55    _brace: Brace,
56    /// Contains the underlying [`TokenStream2`] inside the brace.
57    #[inside(_brace)]
58    pub stream: TokenStream2,
59}
60
61impl 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)]
72pub 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<Comma>,
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<Path>,
82    _comma3: Option<Comma>,
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<ForwardTokensExtraArg>,
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)]
94pub 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<Comma>,
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<ForwardTokensExtraArg>,
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)]
113pub 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)]
153pub 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)]
167pub 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)]
178pub 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
187impl 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/// ```
234pub 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)]
243pub 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<Ident>,
255}
256
257impl ProcMacro {
258    /// Constructs a [`ProcMacro`] from anything compatible with [`TokenStream2`].
259    pub fn from<T: Into<TokenStream2>>(tokens: T) -> Result<Self> {
260        let proc_fn = parse2::<ItemFn>(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<ProcMacroType> = None;
265        if proc_fn
266            .attrs
267            .iter()
268            .find(|attr| {
269                if syn::parse2::<keywords::proc_macro>(attr.path().to_token_stream()).is_ok() {
270                    macro_type = Some(ProcMacroType::Normal);
271                } else if syn::parse2::<keywords::proc_macro_attribute>(
272                    attr.path().to_token_stream(),
273                )
274                .is_ok()
275                {
276                    macro_type = Some(ProcMacroType::Attribute);
277                } else if syn::parse2::<keywords::proc_macro>(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`
324pub fn parse_proc_macro_variant<T: Into<TokenStream2>>(
325    tokens: T,
326    macro_type: ProcMacroType,
327) -> Result<ProcMacro> {
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`
346pub fn macro_magic_root() -> Path {
347    parse2::<Path>(
348        MACRO_MAGIC_ROOT
349            .parse::<TokenStream2>()
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`
356pub fn private_path<T: Into<TokenStream2> + 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`
363pub fn macro_magic_path<T: Into<TokenStream2> + 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
372pub fn to_snake_case(input: impl Into<String>) -> 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<char> = 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::<String>()
411}
412
413/// "Flattens" an [`struct@Ident`] by converting it to snake case.
414///
415/// Used by [`export_tokens_macro_ident`].
416pub 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.
424pub 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.
434pub 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
445fn 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.
465pub fn export_tokens_internal<T: Into<TokenStream2>, E: Into<TokenStream2>>(
466    attr: T,
467    tokens: E,
468    emit: bool,
469    hide_exported_ident: bool,
470) -> Result<TokenStream2> {
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::<Nothing>(attr.clone()).is_ok() {
495                ident
496            } else {
497                parse2::<Ident>(attr)?
498            }
499        }
500        None => parse2::<Ident>(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]`
547pub fn export_tokens_alias_internal<T: Into<TokenStream2>>(
548    tokens: T,
549    emit: bool,
550    hide_exported_ident: bool,
551) -> Result<TokenStream2> {
552    let alias = parse2::<Ident>(tokens.into())?;
553    let export_tokens_internal_path = macro_magic_path(&quote!(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`.
592pub fn import_tokens_internal<T: Into<TokenStream2>>(tokens: T) -> Result<TokenStream2> {
593    let args = parse2::<ImportTokensArgs>(tokens.into())?;
594    let source_path = export_tokens_macro_path(&args.source_path);
595    let inner_macro_path = private_path(&quote!(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.
605pub fn import_tokens_inner_internal<T: Into<TokenStream2>>(tokens: T) -> Result<TokenStream2> {
606    let parsed = parse2::<ImportedTokens>(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(&quote!(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.
618pub fn forward_tokens_internal<T: Into<TokenStream2>>(
619    tokens: T,
620    hidden_source_path: bool,
621) -> Result<TokenStream2> {
622    let args = parse2::<ForwardTokensArgs>(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`].
649pub fn forward_tokens_inner_internal<T: Into<TokenStream2>>(tokens: T) -> Result<TokenStream2> {
650    let parsed = parse2::<ForwardedTokens>(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.
686pub fn with_custom_parsing_internal<T1: Into<TokenStream2>, T2: Into<TokenStream2>>(
687    attr: T1,
688    tokens: T2,
689    import_tokens_attr_name: &'static str,
690) -> Result<TokenStream2> {
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::<Path>(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)`.
748enum OverridePath {
749    Path(Path),
750    Expr(Expr),
751}
752
753impl syn::parse::Parse for OverridePath {
754    fn parse(input: ParseStream) -> Result<Self> {
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::<TokenTree>()?.to_token_stream());
761        }
762        if let Ok(path) = parse2::<Path>(remaining.clone()) {
763            return Ok(OverridePath::Path(path));
764        }
765        match parse2::<Expr>(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<String>`."
771                ));
772                Err(err)
773            }
774        }
775    }
776}
777
778impl 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.
794pub fn import_tokens_attr_internal<T1: Into<TokenStream2>, T2: Into<TokenStream2>>(
795    attr: T1,
796    tokens: T2,
797    hidden_source_path: bool,
798) -> Result<TokenStream2> {
799    let attr = attr.into();
800    let mm_override_path = parse2::<OverridePath>(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::<syn::Path>(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.
931pub fn import_tokens_proc_internal<T1: Into<TokenStream2>, T2: Into<TokenStream2>>(
932    attr: T1,
933    tokens: T2,
934) -> Result<TokenStream2> {
935    let attr = attr.into();
936    let mm_override_path = parse2::<OverridePath>(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::<syn::Path>(#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::<syn::Path>(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)]
1013mod 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!(export_tokens_internal(
1027            quote!(),
1028            quote!(
1029                struct MyStruct {}
1030            ),
1031            true,
1032            true
1033        )
1034        .unwrap()
1035        .to_string()
1036        .contains("my_struct"));
1037    }
1038
1039    #[test]
1040    fn export_tokens_internal_normal_ident() {
1041        assert!(export_tokens_internal(
1042            quote!(some_name),
1043            quote!(
1044                struct Something {}
1045            ),
1046            true,
1047            true
1048        )
1049        .unwrap()
1050        .to_string()
1051        .contains("some_name"));
1052    }
1053
1054    #[test]
1055    fn export_tokens_internal_generics_no_ident() {
1056        assert!(export_tokens_internal(
1057            quote!(),
1058            quote!(
1059                struct MyStruct<T> {}
1060            ),
1061            true,
1062            true
1063        )
1064        .unwrap()
1065        .to_string()
1066        .contains("__export_tokens_tt_my_struct"));
1067    }
1068
1069    #[test]
1070    fn export_tokens_internal_bad_ident() {
1071        assert!(export_tokens_internal(
1072            quote!(Something<T>),
1073            quote!(
1074                struct MyStruct {}
1075            ),
1076            true,
1077            true
1078        )
1079        .is_err());
1080        assert!(export_tokens_internal(
1081            quote!(some::path),
1082            quote!(
1083                struct MyStruct {}
1084            ),
1085            true,
1086            true
1087        )
1088        .is_err());
1089    }
1090
1091    #[test]
1092    fn test_export_tokens_no_emit() {
1093        assert!(export_tokens_internal(
1094            quote!(some_name),
1095            quote!(
1096                struct Something {}
1097            ),
1098            false,
1099            true
1100        )
1101        .unwrap()
1102        .to_string()
1103        .contains("some_name"));
1104    }
1105
1106    #[test]
1107    fn export_tokens_internal_verbatim_ident() {
1108        assert!(export_tokens_internal(
1109            quote!(),
1110            quote!(
1111                struct MyStruct<T> {}
1112            ),
1113            true,
1114            false
1115        )
1116        .unwrap()
1117        .to_string()
1118        .contains("MyStruct"));
1119    }
1120
1121    #[test]
1122    fn import_tokens_internal_simple_path() {
1123        assert!(
1124            import_tokens_internal(quote!(let tokens = my_crate::SomethingCool))
1125                .unwrap()
1126                .to_string()
1127                .contains("__export_tokens_tt_something_cool")
1128        );
1129    }
1130
1131    #[test]
1132    fn import_tokens_internal_flatten_long_paths() {
1133        assert!(import_tokens_internal(
1134            quote!(let tokens = my_crate::some_mod::complex::SomethingElse)
1135        )
1136        .unwrap()
1137        .to_string()
1138        .contains("__export_tokens_tt_something_else"));
1139    }
1140
1141    #[test]
1142    fn import_tokens_internal_invalid_token_ident() {
1143        assert!(import_tokens_internal(quote!(let 3 * 2 = my_crate::something)).is_err());
1144    }
1145
1146    #[test]
1147    fn import_tokens_internal_invalid_path() {
1148        assert!(import_tokens_internal(quote!(let my_tokens = 2 - 2)).is_err());
1149    }
1150
1151    #[test]
1152    fn import_tokens_inner_internal_basic() {
1153        assert!(import_tokens_inner_internal(quote! {
1154            my_ident,
1155            fn my_function() -> u32 {
1156                33
1157            }
1158        })
1159        .unwrap()
1160        .to_string()
1161        .contains("my_ident"));
1162    }
1163
1164    #[test]
1165    fn import_tokens_inner_internal_impl() {
1166        assert!(import_tokens_inner_internal(quote! {
1167            another_ident,
1168            impl Something for MyThing {
1169                fn something() -> CoolStuff {
1170                    CoolStuff {}
1171                }
1172            }
1173        })
1174        .unwrap()
1175        .to_string()
1176        .contains("something ()"));
1177    }
1178
1179    #[test]
1180    fn import_tokens_inner_internal_missing_comma() {
1181        assert!(import_tokens_inner_internal(quote! {
1182            {
1183                another_ident
1184                impl Something for MyThing {
1185                    fn something() -> CoolStuff {
1186                        CoolStuff {}
1187                    }
1188                }
1189            }
1190        })
1191        .is_err());
1192    }
1193
1194    #[test]
1195    fn import_tokens_inner_internal_non_item() {
1196        assert!(import_tokens_inner_internal(quote! {
1197            {
1198                another_ident,
1199                2 + 2
1200            }
1201        })
1202        .is_err());
1203    }
1204
1205    #[test]
1206    fn test_snake_case() {
1207        assert_eq!(to_snake_case("ThisIsATriumph"), "this_is_a_triumph");
1208        assert_eq!(
1209            to_snake_case("IAmMakingANoteHere"),
1210            "i_am_making_a_note_here"
1211        );
1212        assert_eq!(to_snake_case("huge_success"), "huge_success");
1213        assert_eq!(
1214            to_snake_case("It's hard to   Overstate my satisfaction!!!"),
1215            "its_hard_to_overstate_my_satisfaction"
1216        );
1217        assert_eq!(
1218            to_snake_case("__aperature_science__"),
1219            "__aperature_science__"
1220        );
1221        assert_eq!(
1222            to_snake_case("WeDoWhatWeMustBecause!<We, Can>()"),
1223            "we_do_what_we_must_because_we_can"
1224        );
1225        assert_eq!(
1226            to_snake_case("For_The_Good_of_all_of_us_Except_TheOnes_Who Are Dead".to_string()),
1227            "for_the_good_of_all_of_us_except_the_ones_who_are_dead"
1228        );
1229        assert_eq!(to_snake_case("".to_string()), "");
1230    }
1231}