1#![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
21pub const MACRO_MAGIC_ROOT: &str = get_macro_magic_root!();
26
27static COUNTER: AtomicUsize = AtomicUsize::new(0);
29
30const COMPILATION_TAG: u32 = const_random!(u32);
35
36mod 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 custom_keyword!(__private_macro_magic_tokens_forwarded);
46}
47
48#[derive(Parse)]
53pub struct ForwardTokensExtraArg {
54 #[brace]
55 _brace: Brace,
56 #[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#[derive(Parse)]
72pub struct ForwardTokensArgs {
73 pub source: Path,
75 _comma1: Comma,
76 pub target: Path,
78 _comma2: Option<Comma>,
79 #[parse_if(_comma2.is_some())]
81 pub mm_path: Option<Path>,
82 _comma3: Option<Comma>,
83 #[parse_if(_comma3.is_some())]
87 pub extra: Option<ForwardTokensExtraArg>,
88}
89
90#[derive(Parse)]
94pub struct ForwardedTokens {
95 pub target_path: Path,
97 _comma1: Comma,
98 pub item: Item,
100 _comma2: Option<Comma>,
101 #[parse_if(_comma2.is_some())]
105 pub extra: Option<ForwardTokensExtraArg>,
106}
107
108#[derive(Parse)]
113pub struct AttrItemWithExtra {
114 pub imported_item: Item,
117 _comma1: Comma,
118 #[brace]
119 _brace: Brace,
120 #[brace]
121 #[inside(_brace)]
122 _tokens_ident_brace: Brace,
123 #[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 #[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 #[inside(_custom_tokens_brace)]
146 pub custom_tokens: TokenStream2,
147}
148
149#[derive(Parse)]
153pub struct ImportTokensArgs {
154 _let: Token![let],
155 pub tokens_var_ident: Ident,
158 _eq: Token![=],
159 pub source_path: Path,
161}
162
163#[derive(Parse)]
167pub struct ImportedTokens {
168 pub tokens_var_ident: Ident,
171 _comma: Comma,
172 pub item: Item,
174}
175
176#[derive(Copy, Clone, Eq, PartialEq, Debug)]
178pub enum ProcMacroType {
179 Normal,
181 Attribute,
183 Derive,
185}
186
187impl ProcMacroType {
188 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 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
207pub trait ForeignPath {
235 fn foreign_path(&self) -> &syn::Path;
239}
240
241#[derive(Clone)]
243pub struct ProcMacro {
244 pub proc_fn: ItemFn,
246 pub macro_type: ProcMacroType,
248 pub tokens_ident: Ident,
252 pub attr_ident: Option<Ident>,
255}
256
257impl ProcMacro {
258 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 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 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
323pub 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
343pub 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
355pub 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
362pub 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
371pub 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
413pub fn flatten_ident(ident: &Ident) -> Ident {
417 Ident::new(to_snake_case(ident.to_string()).as_str(), ident.span())
418}
419
420pub 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
430pub 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
444fn 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
452pub 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, 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 _ => 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 (
521 $(::)?$($tokens_var:ident)::*,
522 $(::)?$($callback:ident)::*,
523 { $( $extra:tt )* }
524 ) => {
525 $($callback)::*! {
526 $($tokens_var)::*,
527 #item,
528 { $( $extra )* }
529 }
530 };
531 ($(::)?$($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
545pub 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("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
565pub 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("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
602pub 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("e!(TokenStream2));
610 Ok(quote! {
611 let #ident = #tokens_string.parse::<#token_stream_2>().expect("failed to parse quoted tokens");
612 })
613}
614
615pub 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
648pub 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) => Ok(quote! {
658 #pound [#target_path(
659 #tokens_forwarded_keyword
660 #imported_tokens,
661 #extra
662 )] type __Discarded = ();
663 }),
664 None => Ok(quote! {
666 #target_path! {
667 #tokens_forwarded_keyword
668 #imported_tokens
669 }
670 }),
671 }
672}
673
674pub 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 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 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 let custom_path = parse2::<Path>(attr.into())?;
735
736 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
745enum 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
790pub 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 let attr_ident = proc_macro.attr_ident.unwrap();
806 let tokens_ident = proc_macro.tokens_ident;
807
808 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 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 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 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 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
927pub 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 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 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 let tokens_ident = proc_macro.tokens_ident;
954
955 let pound = Punct::new('#', Spacing::Alone);
956
957 Ok(quote! {
960 #(#orig_attrs)
961 *
962 pub #orig_sig {
963 #inner_sig {
964 #(#orig_stmts)
965 *
966 }
967
968 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}