Skip to main content

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 parsed_attrs = PAttrs::parse(attrs);
53                    parsed_attrs.facet_crate()
54                };
55
56                // Generate plugin chain
57                if let Some(chain) = generate_plugin_chain(&input, &plugins, &facet_crate) {
58                    return chain;
59                }
60            }
61
62            // No plugins, proceed with normal codegen
63            match it.first {
64                AdtDecl::Struct(parsed) => process_struct::process_struct(parsed),
65                AdtDecl::Enum(parsed) => process_enum::process_enum(parsed),
66            }
67        }
68        Err(err) => {
69            panic!("Could not parse type declaration: {input}\nError: {err}");
70        }
71    }
72}
73
74pub(crate) fn build_where_clauses(
75    where_clauses: Option<&WhereClauses>,
76    generics: Option<&GenericParams>,
77    opaque: bool,
78    facet_crate: &TokenStream,
79    custom_bounds: &[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    // Add custom bounds from #[facet(bound = "...")]
128    for bound in custom_bounds {
129        if has_clauses {
130            where_clause_tokens.extend(quote! { , });
131        }
132        where_clause_tokens.extend(bound.clone());
133        has_clauses = true;
134    }
135
136    if !has_clauses {
137        quote! {}
138    } else {
139        quote! { where #where_clause_tokens }
140    }
141}
142
143/// Build the `.type_params(...)` builder call, returning empty if no type params.
144pub(crate) fn build_type_params_call(
145    generics: Option<&GenericParams>,
146    opaque: bool,
147    facet_crate: &TokenStream,
148) -> TokenStream {
149    if opaque {
150        return quote! {};
151    }
152
153    let mut type_params = Vec::new();
154    if let Some(generics) = generics {
155        for p in generics.params.iter() {
156            match &p.value {
157                GenericParam::Lifetime { .. } => {
158                    // ignore for now
159                }
160                GenericParam::Const { .. } => {
161                    // ignore for now
162                }
163                GenericParam::Type { name, .. } => {
164                    let name_str = name.to_string();
165                    type_params.push(quote! {
166                        #facet_crate::TypeParam {
167                            name: #name_str,
168                            shape: <#name as #facet_crate::Facet>::SHAPE
169                        }
170                    });
171                }
172            }
173        }
174    }
175
176    if type_params.is_empty() {
177        quote! {}
178    } else {
179        quote! { .type_params(&[#(#type_params),*]) }
180    }
181}
182
183/// Generate the `type_name` function for the `ValueVTable`,
184/// displaying realized generics if present.
185pub(crate) fn generate_type_name_fn(
186    type_name: &Ident,
187    generics: Option<&GenericParams>,
188    opaque: bool,
189    facet_crate: &TokenStream,
190) -> TokenStream {
191    let type_name_str = type_name.to_string();
192
193    let write_generics = (!opaque)
194        .then_some(generics)
195        .flatten()
196        .and_then(|generics| {
197            let params = generics.params.iter();
198            let write_each = params.filter_map(|param| match &param.value {
199                // Lifetimes not shown by `std::any::type_name`, this is parity.
200                GenericParam::Lifetime { .. } => None,
201                GenericParam::Const { name, .. } => Some(quote! {
202                    write!(f, "{:?}", #name)?;
203                }),
204                GenericParam::Type { name, .. } => Some(quote! {
205                    <#name as #facet_crate::Facet>::SHAPE.write_type_name(f, opts)?;
206                }),
207            });
208            // TODO: is there a way to construct a DelimitedVec from an iterator?
209            let mut tokens = TokenStream::new();
210            tokens.append_separated(write_each, quote! { write!(f, ", ")?; });
211            if tokens.is_empty() {
212                None
213            } else {
214                Some(tokens)
215            }
216        });
217
218    match write_generics {
219        Some(write_generics) => {
220            quote! {
221                |_shape, f, opts| {
222                    write!(f, #type_name_str)?;
223                    if let Some(opts) = opts.for_children() {
224                        write!(f, "<")?;
225                        #write_generics
226                        write!(f, ">")?;
227                    } else {
228                        write!(f, "<…>")?;
229                    }
230                    Ok(())
231                }
232            }
233        }
234        None => quote! { |_shape, f, _opts| ::core::fmt::Write::write_str(f, #type_name_str) },
235    }
236}