bronzite_macros/
lib.rs

1//! Proc-macros for compile-time type reflection using Bronzite.
2//!
3//! This crate provides proc-macros that query type information from a running
4//! Bronzite daemon. The daemon is automatically started if not already running.
5//!
6//! # Usage
7//!
8//! ```ignore
9//! // Get trait implementations as a const array of trait names
10//! const IMPLS: &[&str] = bronzite_trait_names!("my_crate", "MyStruct");
11//! ```
12//!
13//! # Note for Proc-Macro Authors
14//!
15//! If you're writing a proc-macro and want to query Bronzite programmatically,
16//! use the `bronzite-client` crate directly instead of this one:
17//!
18//! ```ignore
19//! use bronzite_client::{connect_or_start, BronziteClient};
20//!
21//! let mut client = connect_or_start(None)?;
22//! let impls = client.get_trait_impls("my_crate", "MyType")?;
23//! ```
24
25use proc_macro::TokenStream;
26use proc_macro2::TokenStream as TokenStream2;
27use quote::quote;
28use std::sync::OnceLock;
29
30use bronzite_types::{
31    FieldInfo, InherentImplDetails, TraitDetails, TraitImplDetails, TraitInfo, TypeDetails,
32    TypeSummary,
33};
34
35/// Global flag tracking daemon initialization.
36static DAEMON_INITIALIZED: OnceLock<bool> = OnceLock::new();
37
38/// Ensure the daemon is running. Called automatically by query functions.
39fn ensure_daemon() -> Result<(), String> {
40    DAEMON_INITIALIZED.get_or_init(|| {
41        match bronzite_client::ensure_daemon_running(None) {
42            Ok(()) => true,
43            Err(e) => {
44                // We can't return an error from get_or_init, so we store false
45                // and check it later
46                eprintln!("[bronzite] Warning: Failed to start daemon: {}", e);
47                false
48            }
49        }
50    });
51
52    if *DAEMON_INITIALIZED.get().unwrap_or(&false) {
53        Ok(())
54    } else {
55        Err("Failed to initialize bronzite daemon".to_string())
56    }
57}
58
59/// Get a connected client.
60fn get_client() -> Result<bronzite_client::BronziteClient, String> {
61    ensure_daemon()?;
62    bronzite_client::connect().map_err(|e| e.to_string())
63}
64
65// ============================================================================
66// Internal Query Functions
67// ============================================================================
68
69fn query_trait_impls(crate_name: &str, type_path: &str) -> Result<Vec<TraitImplDetails>, String> {
70    let mut client = get_client()?;
71    client
72        .get_trait_impls(crate_name, type_path)
73        .map_err(|e| e.to_string())
74}
75
76fn query_inherent_impls(
77    crate_name: &str,
78    type_path: &str,
79) -> Result<Vec<InherentImplDetails>, String> {
80    let mut client = get_client()?;
81    client
82        .get_inherent_impls(crate_name, type_path)
83        .map_err(|e| e.to_string())
84}
85
86fn query_fields(crate_name: &str, type_path: &str) -> Result<Vec<FieldInfo>, String> {
87    let mut client = get_client()?;
88    client
89        .get_fields(crate_name, type_path)
90        .map_err(|e| e.to_string())
91}
92
93#[allow(dead_code)]
94fn query_type(crate_name: &str, type_path: &str) -> Result<TypeDetails, String> {
95    let mut client = get_client()?;
96    client
97        .get_type(crate_name, type_path)
98        .map_err(|e| e.to_string())
99}
100
101fn query_check_impl(
102    crate_name: &str,
103    type_path: &str,
104    trait_path: &str,
105) -> Result<(bool, Option<TraitImplDetails>), String> {
106    let mut client = get_client()?;
107    client
108        .check_impl(crate_name, type_path, trait_path)
109        .map_err(|e| e.to_string())
110}
111
112fn query_traits(crate_name: &str) -> Result<Vec<TraitInfo>, String> {
113    let mut client = get_client()?;
114    client.get_traits(crate_name).map_err(|e| e.to_string())
115}
116
117#[allow(dead_code)]
118fn query_trait(crate_name: &str, trait_path: &str) -> Result<TraitDetails, String> {
119    let mut client = get_client()?;
120    client
121        .get_trait(crate_name, trait_path)
122        .map_err(|e| e.to_string())
123}
124
125#[allow(dead_code)]
126fn query_find_types(crate_name: &str, pattern: &str) -> Result<Vec<TypeSummary>, String> {
127    let mut client = get_client()?;
128    client
129        .find_types(crate_name, pattern)
130        .map_err(|e| e.to_string())
131}
132
133fn query_resolve_alias(
134    crate_name: &str,
135    alias_path: &str,
136) -> Result<(String, String, Vec<String>), String> {
137    let mut client = get_client()?;
138    client
139        .resolve_alias(crate_name, alias_path)
140        .map_err(|e| e.to_string())
141}
142
143fn query_implementors(crate_name: &str, trait_path: &str) -> Result<Vec<TypeSummary>, String> {
144    let mut client = get_client()?;
145    client
146        .get_implementors(crate_name, trait_path)
147        .map_err(|e| e.to_string())
148}
149
150// ============================================================================
151// Helper for parsing macro input
152// ============================================================================
153
154struct MacroArgs {
155    crate_name: String,
156    type_path: String,
157    trait_path: Option<String>,
158}
159
160fn parse_two_args(input: TokenStream) -> Result<MacroArgs, TokenStream2> {
161    let input_str = input.to_string();
162    let parts: Vec<&str> = input_str.split(',').map(|s| s.trim()).collect();
163
164    if parts.len() != 2 {
165        return Err(quote! {
166            compile_error!("Expected two arguments: crate_name, type_path")
167        });
168    }
169
170    let crate_name = parts[0].trim_matches('"').to_string();
171    let type_path = parts[1].trim_matches('"').to_string();
172
173    Ok(MacroArgs {
174        crate_name,
175        type_path,
176        trait_path: None,
177    })
178}
179
180fn parse_three_args(input: TokenStream) -> Result<MacroArgs, TokenStream2> {
181    let input_str = input.to_string();
182    let parts: Vec<&str> = input_str.split(',').map(|s| s.trim()).collect();
183
184    if parts.len() != 3 {
185        return Err(quote! {
186            compile_error!("Expected three arguments: crate_name, type_path, trait_path")
187        });
188    }
189
190    let crate_name = parts[0].trim_matches('"').to_string();
191    let type_path = parts[1].trim_matches('"').to_string();
192    let trait_path = parts[2].trim_matches('"').to_string();
193
194    Ok(MacroArgs {
195        crate_name,
196        type_path,
197        trait_path: Some(trait_path),
198    })
199}
200
201fn parse_one_arg(input: TokenStream) -> Result<String, TokenStream2> {
202    let input_str = input.to_string();
203    let crate_name = input_str.trim().trim_matches('"').to_string();
204
205    if crate_name.is_empty() {
206        return Err(quote! {
207            compile_error!("Expected one argument: crate_name")
208        });
209    }
210
211    Ok(crate_name)
212}
213
214// ============================================================================
215// Proc-Macros
216// ============================================================================
217
218/// Get the names of all traits implemented by a type as a const slice.
219///
220/// # Example
221///
222/// ```ignore
223/// const TRAIT_NAMES: &[&str] = bronzite_trait_names!("my_crate", "MyStruct");
224/// // Expands to: &["Debug", "Clone", "MyTrait", ...]
225/// ```
226#[proc_macro]
227pub fn bronzite_trait_names(input: TokenStream) -> TokenStream {
228    let args = match parse_two_args(input) {
229        Ok(a) => a,
230        Err(e) => return e.into(),
231    };
232
233    match query_trait_impls(&args.crate_name, &args.type_path) {
234        Ok(impls) => {
235            let names: Vec<String> = impls.iter().map(|i| i.trait_path.clone()).collect();
236
237            let output = quote! {
238                &[#(#names),*]
239            };
240            output.into()
241        }
242        Err(e) => {
243            let msg = format!("bronzite error: {}", e);
244            quote! { compile_error!(#msg) }.into()
245        }
246    }
247}
248
249/// Check if a type implements a trait at compile time.
250///
251/// Returns `true` or `false` as a literal.
252///
253/// # Example
254///
255/// ```ignore
256/// const IMPLEMENTS_DEBUG: bool = bronzite_implements!("my_crate", "MyStruct", "Debug");
257/// ```
258#[proc_macro]
259pub fn bronzite_implements(input: TokenStream) -> TokenStream {
260    let args = match parse_three_args(input) {
261        Ok(a) => a,
262        Err(e) => return e.into(),
263    };
264
265    let trait_path = args.trait_path.unwrap();
266
267    match query_check_impl(&args.crate_name, &args.type_path, &trait_path) {
268        Ok((implements, _)) => {
269            let output = if implements {
270                quote! { true }
271            } else {
272                quote! { false }
273            };
274            output.into()
275        }
276        Err(e) => {
277            let msg = format!("bronzite error: {}", e);
278            quote! { compile_error!(#msg) }.into()
279        }
280    }
281}
282
283/// Get field names of a struct as a const slice.
284///
285/// # Example
286///
287/// ```ignore
288/// const FIELD_NAMES: &[&str] = bronzite_field_names!("my_crate", "MyStruct");
289/// // Expands to: &["field1", "field2", ...]
290/// ```
291#[proc_macro]
292pub fn bronzite_field_names(input: TokenStream) -> TokenStream {
293    let args = match parse_two_args(input) {
294        Ok(a) => a,
295        Err(e) => return e.into(),
296    };
297
298    match query_fields(&args.crate_name, &args.type_path) {
299        Ok(fields) => {
300            let names: Vec<String> = fields.iter().filter_map(|f| f.name.clone()).collect();
301
302            let output = quote! {
303                &[#(#names),*]
304            };
305            output.into()
306        }
307        Err(e) => {
308            let msg = format!("bronzite error: {}", e);
309            quote! { compile_error!(#msg) }.into()
310        }
311    }
312}
313
314/// Get method names from a type's inherent impl as a const slice.
315///
316/// # Example
317///
318/// ```ignore
319/// const METHOD_NAMES: &[&str] = bronzite_method_names!("my_crate", "MyStruct");
320/// // Expands to: &["new", "do_thing", ...]
321/// ```
322#[proc_macro]
323pub fn bronzite_method_names(input: TokenStream) -> TokenStream {
324    let args = match parse_two_args(input) {
325        Ok(a) => a,
326        Err(e) => return e.into(),
327    };
328
329    match query_inherent_impls(&args.crate_name, &args.type_path) {
330        Ok(impls) => {
331            let names: Vec<String> = impls
332                .iter()
333                .flat_map(|i| i.methods.iter())
334                .map(|m| m.name.clone())
335                .collect();
336
337            let output = quote! {
338                &[#(#names),*]
339            };
340            output.into()
341        }
342        Err(e) => {
343            let msg = format!("bronzite error: {}", e);
344            quote! { compile_error!(#msg) }.into()
345        }
346    }
347}
348
349/// Get all trait names defined in a crate as a const slice.
350///
351/// # Example
352///
353/// ```ignore
354/// const CRATE_TRAITS: &[&str] = bronzite_crate_traits!("my_crate");
355/// ```
356#[proc_macro]
357pub fn bronzite_crate_traits(input: TokenStream) -> TokenStream {
358    let crate_name = match parse_one_arg(input) {
359        Ok(c) => c,
360        Err(e) => return e.into(),
361    };
362
363    match query_traits(&crate_name) {
364        Ok(traits) => {
365            let names: Vec<String> = traits.iter().map(|t| t.name.clone()).collect();
366
367            let output = quote! {
368                &[#(#names),*]
369            };
370            output.into()
371        }
372        Err(e) => {
373            let msg = format!("bronzite error: {}", e);
374            quote! { compile_error!(#msg) }.into()
375        }
376    }
377}
378
379/// Resolve a type alias and get the underlying type as a string literal.
380///
381/// # Example
382///
383/// ```ignore
384/// const RESOLVED: &str = bronzite_resolve_alias!("my_crate", "MyAlias");
385/// // Expands to: "actual::Type"
386/// ```
387#[proc_macro]
388pub fn bronzite_resolve_alias(input: TokenStream) -> TokenStream {
389    let args = match parse_two_args(input) {
390        Ok(a) => a,
391        Err(e) => return e.into(),
392    };
393
394    match query_resolve_alias(&args.crate_name, &args.type_path) {
395        Ok((_, resolved, _)) => {
396            let output = quote! { #resolved };
397            output.into()
398        }
399        Err(e) => {
400            let msg = format!("bronzite error: {}", e);
401            quote! { compile_error!(#msg) }.into()
402        }
403    }
404}
405
406/// Get types that implement a trait as a const slice of type path strings.
407///
408/// # Example
409///
410/// ```ignore
411/// const IMPLEMENTORS: &[&str] = bronzite_implementors!("my_crate", "MyTrait");
412/// ```
413#[proc_macro]
414pub fn bronzite_implementors(input: TokenStream) -> TokenStream {
415    let args = match parse_two_args(input) {
416        Ok(a) => a,
417        Err(e) => return e.into(),
418    };
419
420    match query_implementors(&args.crate_name, &args.type_path) {
421        Ok(types) => {
422            let paths: Vec<String> = types.iter().map(|t| t.path.clone()).collect();
423
424            let output = quote! {
425                &[#(#paths),*]
426            };
427            output.into()
428        }
429        Err(e) => {
430            let msg = format!("bronzite error: {}", e);
431            quote! { compile_error!(#msg) }.into()
432        }
433    }
434}