1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse_macro_input;
4use syn::Item;
5use std::collections::HashMap;
6use std::sync::Mutex;
7use once_cell::sync::Lazy;
8
9use proc_macro2::TokenStream as TokenStream2;
10
11fn module_path_literal() -> TokenStream2 {
12 quote! {
13 module_path!()
14 }
15}
16
17static EXPORTS: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
19
20fn register_export(name: &str, path: &str) -> Result<(), String> {
21 let mut exports = EXPORTS.lock().unwrap();
22 if let Some(existing) = exports.get(name) {
23 if existing != path {
24 return Err(format!(
25 "Name collision detected: '{}' is already exported in '{}'",
26 name, existing
27 ));
28 }
29 }
30 exports.insert(name.to_string(), path.to_string());
31 Ok(())
32}
33
34#[proc_macro_attribute]
35pub fn export(_attr: TokenStream, item: TokenStream) -> TokenStream {
36 let item = parse_macro_input!(item as Item);
37 process_export(item, false)
38}
39
40#[proc_macro_attribute]
41pub fn export_fullpath(_attr: TokenStream, item: TokenStream) -> TokenStream {
42 let item = parse_macro_input!(item as Item);
43 process_export(item, true)
44}
45
46fn process_export(item: Item, use_fullpath: bool) -> TokenStream {
47 let (name, module_path) = match &item {
48 Item::Struct(s) => (&s.ident, module_path_literal()),
49 Item::Enum(e) => (&e.ident, module_path_literal()),
50 Item::Fn(f) => (&f.sig.ident, module_path_literal()),
51 _ => panic!("Export only supports structs, enums, and functions"),
52 };
53
54 let name_str = name.to_string();
55 let export_path = if use_fullpath {
56 quote! { concat!(#module_path, "::", stringify!(#name)) }
57 } else {
58 quote! { stringify!(#name) }
59 };
60
61 if let Err(err) = register_export(&name_str, &format!("{}::{}", module_path, name_str)) {
63 return syn::Error::new_spanned(&item, err)
64 .to_compile_error()
65 .into();
66 }
67
68 quote! {
69 #item
70
71 inventory::submit! {
72 crate::ExportItem {
73 name: #export_path,
74 module_path: #module_path,
75 full_path: concat!(#module_path, "::", stringify!(#name)),
76 is_fullpath: #use_fullpath
77 }
78 }
79 }.into()
80}