facet_macros_impl/
derive.rs

1use crate::{ToTokens, *};
2use quote::{TokenStreamExt as _, quote};
3
4use crate::plugin::{extract_derive_plugins, generate_plugin_chain};
5use crate::{LifetimeName, RenameRule, process_enum, process_struct};
6
7/// Generate a static declaration that pre-evaluates `<T as Facet>::SHAPE`.
8/// Only emitted in release builds to avoid slowing down debug compile times.
9/// Skipped for generic types since we can't create a static for an unmonomorphized type.
10pub(crate) fn generate_static_decl(
11    type_name: &Ident,
12    facet_crate: &TokenStream,
13    has_type_or_const_generics: bool,
14) -> TokenStream {
15    // Can't generate a static for generic types - the type parameters aren't concrete
16    if has_type_or_const_generics {
17        return quote! {};
18    }
19
20    let type_name_str = type_name.to_string();
21    let screaming_snake_name = RenameRule::ScreamingSnakeCase.apply(&type_name_str);
22
23    let static_name_ident = quote::format_ident!("{}_SHAPE", screaming_snake_name);
24
25    quote! {
26        #[cfg(not(debug_assertions))]
27        static #static_name_ident: &'static #facet_crate::Shape = <#type_name as #facet_crate::Facet>::SHAPE;
28    }
29}
30
31/// Main entry point for the `#[derive(Facet)]` macro. Parses type declarations and generates Facet trait implementations.
32///
33/// If `#[facet(derive(...))]` is present, chains to plugins before generating.
34pub fn facet_macros(input: TokenStream) -> TokenStream {
35    let mut i = input.clone().to_token_iter();
36
37    // Parse as TypeDecl
38    match i.parse::<Cons<AdtDecl, EndOfStream>>() {
39        Ok(it) => {
40            // Extract attributes to check for plugins
41            let attrs = match &it.first {
42                AdtDecl::Struct(s) => &s.attributes,
43                AdtDecl::Enum(e) => &e.attributes,
44            };
45
46            // Check for #[facet(derive(...))] plugins
47            let plugins = extract_derive_plugins(attrs);
48
49            if !plugins.is_empty() {
50                // Get the facet crate path from attributes
51                let facet_crate = {
52                    let mut display_name = String::new();
53                    let parsed_attrs = PAttrs::parse(attrs, &mut display_name);
54                    parsed_attrs.facet_crate()
55                };
56
57                // Generate plugin chain
58                if let Some(chain) = generate_plugin_chain(&input, &plugins, &facet_crate) {
59                    return chain;
60                }
61            }
62
63            // No plugins, proceed with normal codegen
64            match it.first {
65                AdtDecl::Struct(parsed) => process_struct::process_struct(parsed),
66                AdtDecl::Enum(parsed) => process_enum::process_enum(parsed),
67            }
68        }
69        Err(err) => {
70            panic!("Could not parse type declaration: {input}\nError: {err}");
71        }
72    }
73}
74
75pub(crate) fn build_where_clauses(
76    where_clauses: Option<&WhereClauses>,
77    generics: Option<&GenericParams>,
78    opaque: bool,
79    facet_crate: &TokenStream,
80) -> TokenStream {
81    let mut where_clause_tokens = TokenStream::new();
82    let mut has_clauses = false;
83
84    if let Some(wc) = where_clauses {
85        for c in wc.clauses.iter() {
86            if has_clauses {
87                where_clause_tokens.extend(quote! { , });
88            }
89            where_clause_tokens.extend(c.value.to_token_stream());
90            has_clauses = true;
91        }
92    }
93
94    if let Some(generics) = generics {
95        for p in generics.params.iter() {
96            match &p.value {
97                GenericParam::Lifetime { name, .. } => {
98                    let facet_lifetime = LifetimeName(quote::format_ident!("{}", "ʄ"));
99                    let lifetime = LifetimeName(name.name.clone());
100                    if has_clauses {
101                        where_clause_tokens.extend(quote! { , });
102                    }
103                    where_clause_tokens
104                        .extend(quote! { #lifetime: #facet_lifetime, #facet_lifetime: #lifetime });
105
106                    has_clauses = true;
107                }
108                GenericParam::Const { .. } => {
109                    // ignore for now
110                }
111                GenericParam::Type { name, .. } => {
112                    if has_clauses {
113                        where_clause_tokens.extend(quote! { , });
114                    }
115                    // Only specify lifetime bound for opaque containers
116                    if opaque {
117                        where_clause_tokens.extend(quote! { #name: });
118                    } else {
119                        where_clause_tokens.extend(quote! { #name: #facet_crate::Facet<> });
120                    }
121                    has_clauses = true;
122                }
123            }
124        }
125    }
126
127    if !has_clauses {
128        quote! {}
129    } else {
130        quote! { where #where_clause_tokens }
131    }
132}
133
134/// Build the `.type_params(...)` builder call, returning empty if no type params.
135pub(crate) fn build_type_params_call(
136    generics: Option<&GenericParams>,
137    opaque: bool,
138    facet_crate: &TokenStream,
139) -> TokenStream {
140    if opaque {
141        return quote! {};
142    }
143
144    let mut type_params = Vec::new();
145    if let Some(generics) = generics {
146        for p in generics.params.iter() {
147            match &p.value {
148                GenericParam::Lifetime { .. } => {
149                    // ignore for now
150                }
151                GenericParam::Const { .. } => {
152                    // ignore for now
153                }
154                GenericParam::Type { name, .. } => {
155                    let name_str = name.to_string();
156                    type_params.push(quote! {
157                        #facet_crate::TypeParam {
158                            name: #name_str,
159                            shape: <#name as #facet_crate::Facet>::SHAPE
160                        }
161                    });
162                }
163            }
164        }
165    }
166
167    if type_params.is_empty() {
168        quote! {}
169    } else {
170        quote! { .type_params(&[#(#type_params),*]) }
171    }
172}
173
174/// Generate the `type_name` function for the `ValueVTable`,
175/// displaying realized generics if present.
176pub(crate) fn generate_type_name_fn(
177    type_name: &Ident,
178    generics: Option<&GenericParams>,
179    opaque: bool,
180    facet_crate: &TokenStream,
181) -> TokenStream {
182    let type_name_str = type_name.to_string();
183
184    let write_generics = (!opaque)
185        .then_some(generics)
186        .flatten()
187        .and_then(|generics| {
188            let params = generics.params.iter();
189            let write_each = params.filter_map(|param| match &param.value {
190                // Lifetimes not shown by `std::any::type_name`, this is parity.
191                GenericParam::Lifetime { .. } => None,
192                GenericParam::Const { name, .. } => Some(quote! {
193                    write!(f, "{:?}", #name)?;
194                }),
195                GenericParam::Type { name, .. } => Some(quote! {
196                    <#name as #facet_crate::Facet>::SHAPE.write_type_name(f, opts)?;
197                }),
198            });
199            // TODO: is there a way to construct a DelimitedVec from an iterator?
200            let mut tokens = TokenStream::new();
201            tokens.append_separated(write_each, quote! { write!(f, ", ")?; });
202            if tokens.is_empty() {
203                None
204            } else {
205                Some(tokens)
206            }
207        });
208
209    match write_generics {
210        Some(write_generics) => {
211            quote! {
212                |_shape, f, opts| {
213                    write!(f, #type_name_str)?;
214                    if let Some(opts) = opts.for_children() {
215                        write!(f, "<")?;
216                        #write_generics
217                        write!(f, ">")?;
218                    } else {
219                        write!(f, "<…>")?;
220                    }
221                    Ok(())
222                }
223            }
224        }
225        None => quote! { |_shape, f, _opts| ::core::fmt::Write::write_str(f, #type_name_str) },
226    }
227}