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::{ToTokens, TokenStreamExt, format_ident, quote};
13use 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
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!(
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<T> {}
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<T>),
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<T> {}
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!<We, Can>()"),
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}