magic_static_macro/
lib.rs

1#![allow(clippy::needless_doctest_main)]
2
3use proc_macro::TokenStream;
4use quote::ToTokens;
5
6#[proc_macro_attribute]
7/// An attribute that can be attached to your main function which initializes magic statics **in the specified order**.
8///
9/// Does nothing to a magic static if it has already been initialized.
10///
11/// # Safety
12///
13/// The following behaviour is considered undefined:
14///
15/// * Initializing magic statics from multiple threads concurrently.
16/// * Spawning new threads and accessing magic statics during initialization from them.
17/// * Interior mutability of magic statics where the mutability is not synchronized across multiple threads (e.g. with a Mutex or RwLock.) This is not a problem for single-threaded applications.
18///
19/// # Example
20///
21/// ```rust
22/// mod foo {
23///     magic_static! {
24///         pub(super) static ref MAGIC: usize = {
25///             println!("Magic!");
26///             42
27///         };
28///
29///         pub(super) static ref BAR: std::sync::Mutex::<()> = std::sync::Mutex::new(());
30///     }
31/// }
32///
33/// // You can also modularize your magic statics like so:
34/// mod baz {
35///     magic_static! {
36///         pub(super) static ref MAGIC: usize = {
37///             println!("Magic!");
38///             42
39///         };
40///
41///         pub(super) static ref BAR: std::sync::Mutex<()> = std::sync::Mutex::new(());
42///     }
43///
44///     #[magic_static::main(
45///         MAGIC,
46///         BAR
47///     )]
48///     // The `magic_statics!` macro (NOT `magic_static!`) can generate this function for you
49///     pub fn magic_static() {}
50/// }
51///
52/// #[magic_static::main(
53///     foo::MAGIC,
54///     foo::BAR,
55///     mod baz // This will initialize all magic statics in `baz`
56/// )]
57/// fn main() {
58///     println!("Hello, world!");
59/// }
60/// ```
61pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
62	let mut func = syn::parse_macro_input!(item as syn::ItemFn);
63	let attr = attr.to_string();
64
65	enum MagicStatic {
66		Module(syn::Path),
67		Item(syn::Path),
68	}
69	impl quote::ToTokens for MagicStatic {
70		fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
71			match self {
72				MagicStatic::Module(path) => tokens.extend(quote::quote! { #path::magic_static() }),
73				MagicStatic::Item(path) => tokens.extend(quote::quote! { #path.__init() }),
74			}
75		}
76	}
77
78	let mut magic_statics = vec![];
79	for item in attr.split(',').map(|path| path.trim()) {
80		if let Some(item) = item.strip_prefix("mod ").map(str::trim) {
81			if item.contains("::") {
82				magic_statics.push(MagicStatic::Module(syn::parse_str(item).expect("Expected path to a module containing an accessible `magic_static` function")));
83			} else {
84				magic_statics.push(MagicStatic::Module(syn::parse_str(&format!("self::{}", item)).expect("Expected path to a module containing an accessible `magic_static` function")));
85			}
86		} else {
87			magic_statics.push(MagicStatic::Item(syn::parse_str(item).expect("Expected path to magic static")));
88		}
89	}
90
91	func.block.stmts.insert(
92		0,
93		syn::parse(quote::quote! {
94			{
95				#(#magic_statics;)*
96			}
97		}.into()).expect("Internal error"),
98	);
99
100	func.into_token_stream().into()
101}
102
103#[proc_macro_attribute]
104pub fn magic_static(_attr: TokenStream, item: TokenStream) -> TokenStream {
105	let mut func = syn::parse_macro_input!(item as syn::ItemStatic);
106
107	let ty = func.ty;
108	let expr = func.expr;
109
110	func.ty = Box::new(syn::parse_quote! { ::magic_static::MagicStatic<#ty> });
111	func.expr = Box::new(syn::parse_quote! {
112		::magic_static::MagicStatic {
113			initialized: ::magic_static::__magic_static_initialized!(),
114			value: ::core::cell::UnsafeCell::new(::core::mem::MaybeUninit::uninit()),
115			init: || #expr
116		}
117	});
118
119	func.into_token_stream().into()
120}