• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 20134750802

11 Dec 2025 01:29PM UTC coverage: 57.796% (-0.1%) from 57.906%
20134750802

push

github

fasterthanlime
feat: Add concept of truthiness

The easiest way to tell if something should be serialized or not!

61 of 197 new or added lines in 14 files covered. (30.96%)

1 existing line in 1 file now uncovered.

28969 of 50123 relevant lines covered (57.8%)

6281.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.7
/facet-macros-impl/src/process_enum.rs
1
use super::*;
2
use crate::process_struct::{
3
    TraitSources, gen_field_from_pfield, gen_trait_bounds, gen_type_ops, gen_vtable,
4
};
5
use proc_macro2::Literal;
6
use quote::{format_ident, quote, quote_spanned};
7

8
/// Generate a Variant using VariantBuilder for more compact output.
9
///
10
/// NOTE: This function generates code that uses short aliases from the 𝟋 prelude.
11
/// It MUST be called within a context where `use #facet_crate::𝟋::*` has been emitted.
12
fn gen_variant(
745✔
13
    name: impl quote::ToTokens,
745✔
14
    discriminant: impl quote::ToTokens,
745✔
15
    attributes: Option<impl quote::ToTokens>,
745✔
16
    struct_kind: impl quote::ToTokens,
745✔
17
    fields: impl quote::ToTokens,
745✔
18
    doc: Option<impl quote::ToTokens>,
745✔
19
) -> TokenStream {
745✔
20
    // Only emit .attributes() and .doc() calls when there's actual content
21
    let attributes_call = attributes.map(|a| quote! { .attributes(#a) });
745✔
22
    let doc_call = doc.map(|d| quote! { .doc(#d) });
745✔
23

24
    quote! {
745✔
25
        𝟋VarB::new(
26
            #name,
27
            𝟋STyB::new(#struct_kind, #fields).build()
28
        )
29
        .discriminant(#discriminant)
30
        #attributes_call
31
        #doc_call
32
        .build()
33
    }
34
}
745✔
35

36
/// Generate a unit variant using the pre-built StructType::UNIT constant.
37
/// NOTE: This function generates code that uses short aliases from the 𝟋 prelude.
38
/// It MUST be called within a context where `use #facet_crate::𝟋::*` has been emitted.
39
fn gen_unit_variant(
360✔
40
    name: impl quote::ToTokens,
360✔
41
    discriminant: impl quote::ToTokens,
360✔
42
    attributes: Option<impl quote::ToTokens>,
360✔
43
    doc: Option<impl quote::ToTokens>,
360✔
44
) -> TokenStream {
360✔
45
    // Only emit .attributes() and .doc() calls when there's actual content
46
    let attributes_call = attributes.map(|a| quote! { .attributes(#a) });
360✔
47
    let doc_call = doc.map(|d| quote! { .doc(#d) });
360✔
48

49
    quote! {
360✔
50
        𝟋VarB::new(#name, 𝟋STy::UNIT)
51
            .discriminant(#discriminant)
52
            #attributes_call
53
            #doc_call
54
            .build()
55
    }
56
}
360✔
57

58
/// Processes an enum to implement Facet
59
pub(crate) fn process_enum(parsed: Enum) -> TokenStream {
368✔
60
    // Use already-parsed PEnum, including container/variant/field attributes and rename rules
61
    let pe = PEnum::parse(&parsed);
368✔
62

63
    // Emit any collected errors as compile_error! with proper spans
64
    if !pe.container.attrs.errors.is_empty() {
368✔
65
        let errors = pe.container.attrs.errors.iter().map(|e| {
×
66
            let msg = &e.message;
×
67
            let span = e.span;
×
68
            quote_spanned! { span => compile_error!(#msg); }
×
69
        });
×
70
        return quote! { #(#errors)* };
×
71
    }
368✔
72

73
    let enum_name = &pe.container.name;
368✔
74
    let enum_name_str = enum_name.to_string();
368✔
75

76
    let opaque = pe
368✔
77
        .container
368✔
78
        .attrs
368✔
79
        .facet
368✔
80
        .iter()
368✔
81
        .any(|a| a.is_builtin() && a.key_str() == "opaque");
368✔
82

83
    let skip_all_unless_truthy = pe.container.attrs.has_builtin("skip_all_unless_truthy");
368✔
84

85
    let truthy_attr: Option<TokenStream> = pe.container.attrs.facet.iter().find_map(|attr| {
368✔
86
        if attr.is_builtin() && attr.key_str() == "truthy" {
76✔
NEW
87
            let args = &attr.args;
×
NEW
88
            if args.is_empty() {
×
NEW
89
                return None;
×
NEW
90
            }
×
NEW
91
            let args_str = args.to_string();
×
NEW
92
            let fn_name_str = args_str.trim_start_matches('=').trim();
×
NEW
93
            let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
×
NEW
94
            Some(fn_name)
×
95
        } else {
96
            None
76✔
97
        }
98
    });
76✔
99

100
    // Get the facet crate path (custom or default ::facet)
101
    let facet_crate = pe.container.attrs.facet_crate();
368✔
102

103
    let type_name_fn =
368✔
104
        generate_type_name_fn(enum_name, parsed.generics.as_ref(), opaque, &facet_crate);
368✔
105

106
    // Determine trait sources and generate vtable accordingly
107
    // Enums don't support transparent semantics, so pass None
108
    let trait_sources = TraitSources::from_attrs(&pe.container.attrs);
368✔
109
    let bgp_for_vtable = pe.container.bgp.display_without_bounds();
368✔
110
    let enum_type_for_vtable = quote! { #enum_name #bgp_for_vtable };
368✔
111
    let vtable_code = gen_vtable(
368✔
112
        &facet_crate,
368✔
113
        &type_name_fn,
368✔
114
        &trait_sources,
368✔
115
        None,
368✔
116
        &enum_type_for_vtable,
368✔
117
        None, // enums don't support container-level invariants yet
368✔
118
    );
119
    // Note: vtable_code already contains &const { ... } for the VTableDirect,
120
    // no need for an extra const { } wrapper around VTableErased
121
    let vtable_init = vtable_code;
368✔
122

123
    // Generate TypeOps for drop/default/clone operations
124
    // Check if enum has type or const generics (not just lifetimes)
125
    let has_type_or_const_generics = pe.container.bgp.params.iter().any(|p| {
368✔
126
        matches!(
4✔
127
            p.param,
14✔
128
            facet_macro_parse::GenericParamName::Type(_)
129
                | facet_macro_parse::GenericParamName::Const(_)
130
        )
131
    });
14✔
132
    let type_ops_init = gen_type_ops(
368✔
133
        &facet_crate,
368✔
134
        &trait_sources,
368✔
135
        &enum_type_for_vtable,
368✔
136
        has_type_or_const_generics,
368✔
137
        truthy_attr.as_ref(),
368✔
138
    );
139

140
    let bgp = pe.container.bgp.clone();
368✔
141
    // Use the AST directly for where clauses and generics, as PContainer/PEnum doesn't store them
142
    let where_clauses_tokens = build_where_clauses(
368✔
143
        parsed.clauses.as_ref(),
368✔
144
        parsed.generics.as_ref(),
368✔
145
        opaque,
368✔
146
        &facet_crate,
368✔
147
    );
148
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
368✔
149

150
    // Container-level docs - returns builder call only if there are doc comments and doc feature is enabled
151
    #[cfg(feature = "doc")]
152
    let doc_call = match &pe.container.attrs.doc[..] {
368✔
153
        [] => quote! {},
368✔
154
        doc_lines => quote! { .doc(&[#(#doc_lines),*]) },
19✔
155
    };
156
    #[cfg(not(feature = "doc"))]
157
    let doc_call = quote! {};
×
158

159
    // Container attributes - returns builder call only if there are attributes
160
    let attributes_call = {
368✔
161
        let mut attribute_tokens: Vec<TokenStream> = Vec::new();
368✔
162
        for attr in &pe.container.attrs.facet {
368✔
163
            // These attributes are handled specially and not emitted to runtime:
164
            // - crate: sets the facet crate path
165
            // - traits: compile-time directive for vtable generation
166
            // - auto_traits: compile-time directive for vtable generation
167
            if attr.is_builtin() {
76✔
168
                let key = attr.key_str();
71✔
169
                if matches!(
5✔
170
                    key.as_str(),
71✔
171
                    "crate"
71✔
172
                        | "traits"
70✔
173
                        | "auto_traits"
70✔
174
                        | "proxy"
67✔
175
                        | "truthy"
66✔
176
                        | "skip_all_unless_truthy"
66✔
177
                ) {
178
                    continue;
5✔
179
                }
66✔
180
            }
5✔
181
            // All attributes go through grammar dispatch
182
            let ext_attr = emit_attr(attr, &facet_crate);
71✔
183
            attribute_tokens.push(quote! { #ext_attr });
71✔
184
        }
185

186
        if attribute_tokens.is_empty() {
368✔
187
            quote! {}
305✔
188
        } else {
189
            quote! { .attributes(&const {[#(#attribute_tokens),*]}) }
63✔
190
        }
191
    };
192

193
    // Type tag - returns builder call only if present
194
    let type_tag_call = {
368✔
195
        if let Some(type_tag) = pe.container.attrs.get_builtin_args("type_tag") {
368✔
196
            quote! { .type_tag(#type_tag) }
×
197
        } else {
198
            quote! {}
368✔
199
        }
200
    };
201

202
    // Tag field name for internally/adjacently tagged enums - returns builder call only if present
203
    let tag_call = {
368✔
204
        if let Some(tag) = pe.container.attrs.get_builtin_args("tag") {
368✔
205
            quote! { .tag(#tag) }
18✔
206
        } else {
207
            quote! {}
350✔
208
        }
209
    };
210

211
    // Content field name for adjacently tagged enums - returns builder call only if present
212
    let content_call = {
368✔
213
        if let Some(content) = pe.container.attrs.get_builtin_args("content") {
368✔
214
            quote! { .content(#content) }
8✔
215
        } else {
216
            quote! {}
360✔
217
        }
218
    };
219

220
    // Untagged flag - returns builder call only if present
221
    let untagged_call = {
368✔
222
        if pe.container.attrs.has_builtin("untagged") {
368✔
223
            quote! { .untagged() }
36✔
224
        } else {
225
            quote! {}
332✔
226
        }
227
    };
228

229
    // Container-level proxy from PEnum - generates ProxyDef with conversion functions
230
    let proxy_call = {
368✔
231
        if let Some(attr) = pe
368✔
232
            .container
368✔
233
            .attrs
368✔
234
            .facet
368✔
235
            .iter()
368✔
236
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
368✔
237
        {
238
            let proxy_type = &attr.args;
1✔
239
            let enum_type = &enum_name;
1✔
240
            let bgp_display = pe.container.bgp.display_without_bounds();
1✔
241

242
            quote! {
1✔
243
                .proxy(&const {
244
                    extern crate alloc as __alloc;
245

246
                    unsafe fn __proxy_convert_in(
247
                        proxy_ptr: #facet_crate::PtrConst,
248
                        field_ptr: #facet_crate::PtrUninit,
249
                    ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
250
                        let proxy: #proxy_type = proxy_ptr.read();
251
                        match <#enum_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
252
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
253
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
254
                        }
255
                    }
256

257
                    unsafe fn __proxy_convert_out(
258
                        field_ptr: #facet_crate::PtrConst,
259
                        proxy_ptr: #facet_crate::PtrUninit,
260
                    ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
261
                        let field_ref: &#enum_type #bgp_display = field_ptr.get();
262
                        match <#proxy_type as ::core::convert::TryFrom<&#enum_type #bgp_display>>::try_from(field_ref) {
263
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
264
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
265
                        }
266
                    }
267

268
                    #facet_crate::ProxyDef {
269
                        shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
270
                        convert_in: __proxy_convert_in,
271
                        convert_out: __proxy_convert_out,
272
                    }
273
                })
274
            }
275
        } else {
276
            quote! {}
367✔
277
        }
278
    };
279

280
    // Determine enum repr (already resolved by PEnum::parse())
281
    let valid_repr = &pe.repr;
368✔
282

283
    // Are these relevant for enums? Or is it always `repr(C)` if a `PrimitiveRepr` is present?
284
    let repr = match &valid_repr {
368✔
285
        PRepr::Transparent => unreachable!("this should be caught by PRepr::parse"),
×
286
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
276✔
287
        PRepr::C(_) => quote! { 𝟋Repr::C },
92✔
288
        PRepr::RustcWillCatch => {
289
            // rustc will emit the error - return empty TokenStream
290
            return quote! {};
×
291
        }
292
    };
293

294
    // Helper for EnumRepr TS (token stream) generation for primitives
295
    // Uses prelude alias 𝟋ERpr for compact output
296
    let enum_repr_ts_from_primitive = |primitive_repr: PrimitiveRepr| -> TokenStream {
368✔
297
        let type_name_str = primitive_repr.type_name().to_string();
278✔
298
        let enum_repr_variant_ident = format_ident!("{}", type_name_str.to_uppercase());
278✔
299
        quote! { 𝟋ERpr::#enum_repr_variant_ident }
278✔
300
    };
278✔
301

302
    // --- Processing code for shadow struct/fields/variant_expressions ---
303
    // A. C-style enums have shadow-discriminant, shadow-union, shadow-struct
304
    // B. Primitive enums have simpler layout.
305
    let (shadow_struct_defs, variant_expressions, enum_repr_type_tokenstream) = match valid_repr {
368✔
306
        PRepr::C(prim_opt) => {
92✔
307
            // Shadow discriminant
308
            let shadow_discriminant_name = quote::format_ident!("_D");
92✔
309
            let all_variant_names: Vec<Ident> = pe
92✔
310
                .variants
92✔
311
                .iter()
92✔
312
                .map(|pv| match &pv.name.raw {
336✔
313
                    IdentOrLiteral::Ident(id) => id.clone(),
336✔
314
                    IdentOrLiteral::Literal(n) => format_ident!("_{}", n), // Should not happen for enums
×
315
                })
336✔
316
                .collect();
92✔
317

318
            let repr_attr_content = match prim_opt {
92✔
319
                Some(p) => p.type_name(),
2✔
320
                None => quote! { C },
90✔
321
            };
322
            let mut shadow_defs = vec![quote! {
92✔
323
                #[repr(#repr_attr_content)]
324
                #[allow(dead_code)]
325
                enum #shadow_discriminant_name { #(#all_variant_names),* }
326
            }];
327

328
            // Shadow union
329
            let shadow_union_name = quote::format_ident!("_U");
92✔
330
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
92✔
331
            let bgp_with_bounds = facet_bgp.display_with_bounds();
92✔
332
            let bgp_without_bounds = facet_bgp.display_without_bounds();
92✔
333
            let phantom_data = facet_bgp.display_as_phantom_data();
92✔
334
            let all_union_fields: Vec<TokenStream> = pe.variants.iter().map(|pv| {
336✔
335
                // Each field is named after the variant, struct for its fields.
336
                let variant_ident = match &pv.name.raw {
336✔
337
                    IdentOrLiteral::Ident(id) => id.clone(),
336✔
338
                     IdentOrLiteral::Literal(idx) => format_ident!("_{}", idx), // Should not happen
×
339
                };
340
                let shadow_field_name_ident = quote::format_ident!("_F{}", variant_ident);
336✔
341
                quote! {
336✔
342
                    #variant_ident: ::core::mem::ManuallyDrop<#shadow_field_name_ident #bgp_without_bounds>
343
                }
344
            }).collect();
336✔
345

346
            shadow_defs.push(quote! {
92✔
347
                #[repr(C)]
348
                #[allow(non_snake_case, dead_code)]
349
                union #shadow_union_name #bgp_with_bounds #where_clauses_tokens { #(#all_union_fields),* }
350
            });
351

352
            // Shadow repr struct for enum as a whole
353
            let shadow_repr_name = quote::format_ident!("_R");
92✔
354
            shadow_defs.push(quote! {
92✔
355
                #[repr(C)]
356
                #[allow(non_snake_case)]
357
                #[allow(dead_code)]
358
                struct #shadow_repr_name #bgp_with_bounds #where_clauses_tokens {
359
                    _discriminant: #shadow_discriminant_name,
360
                    _phantom: #phantom_data,
361
                    _fields: #shadow_union_name #bgp_without_bounds,
362
                }
363
            });
364

365
            // Generate variant_expressions
366
            let mut discriminant: Option<&TokenStream> = None;
92✔
367
            let mut discriminant_offset: i64 = 0;
92✔
368
            let mut exprs = Vec::new();
92✔
369

370
            for pv in pe.variants.iter() {
336✔
371
                if let Some(dis) = &pv.discriminant {
336✔
372
                    discriminant = Some(dis);
×
373
                    discriminant_offset = 0;
×
374
                }
336✔
375

376
                // Only cast to i64 when we have a user-provided discriminant expression
377
                let discriminant_ts = if let Some(discriminant) = discriminant {
336✔
378
                    if discriminant_offset > 0 {
×
379
                        let offset_lit = Literal::i64_unsuffixed(discriminant_offset);
×
380
                        quote! { (#discriminant + #offset_lit) as i64 }
×
381
                    } else {
382
                        quote! { #discriminant as i64 }
×
383
                    }
384
                } else {
385
                    // Simple unsuffixed literal
386
                    let lit = Literal::i64_unsuffixed(discriminant_offset);
336✔
387
                    quote! { #lit }
336✔
388
                };
389

390
                let display_name = pv.name.effective.clone();
336✔
391
                let name_token = TokenTree::Literal(Literal::string(&display_name));
336✔
392
                let variant_attributes: Option<TokenStream> = if pv.attrs.facet.is_empty() {
336✔
393
                    None
327✔
394
                } else {
395
                    let attrs_list: Vec<TokenStream> = pv
9✔
396
                        .attrs
9✔
397
                        .facet
9✔
398
                        .iter()
9✔
399
                        .map(|attr| {
9✔
400
                            let ext_attr = emit_attr(attr, &facet_crate);
9✔
401
                            quote! { #ext_attr }
9✔
402
                        })
9✔
403
                        .collect();
9✔
404
                    Some(quote! { &const {[#(#attrs_list),*]} })
9✔
405
                };
406

407
                #[cfg(feature = "doc")]
408
                let variant_doc: Option<TokenStream> = match &pv.attrs.doc[..] {
336✔
409
                    [] => None,
336✔
410
                    doc_lines => Some(quote! { &[#(#doc_lines),*] }),
×
411
                };
412
                #[cfg(not(feature = "doc"))]
413
                let variant_doc: Option<TokenStream> = None;
×
414

415
                let shadow_struct_name = match &pv.name.raw {
336✔
416
                    IdentOrLiteral::Ident(id) => quote::format_ident!("_F{}", id),
336✔
417
                    IdentOrLiteral::Literal(idx) => quote::format_ident!("_F{}", idx),
×
418
                };
419

420
                let variant_offset = quote! {
336✔
421
                    ::core::mem::offset_of!(#shadow_repr_name #bgp_without_bounds, _fields)
422
                };
423

424
                // Determine field structure for the variant
425
                match &pv.kind {
336✔
426
                    PVariantKind::Unit => {
155✔
427
                        // Generate unit shadow struct for the variant
155✔
428
                        shadow_defs.push(quote! {
155✔
429
                            #[repr(C)]
155✔
430
                            #[allow(non_snake_case, dead_code)]
155✔
431
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens { _phantom: #phantom_data }
155✔
432
                        });
155✔
433
                        let variant = gen_unit_variant(
155✔
434
                            &name_token,
155✔
435
                            &discriminant_ts,
155✔
436
                            variant_attributes.as_ref(),
155✔
437
                            variant_doc.as_ref(),
155✔
438
                        );
155✔
439
                        exprs.push(variant);
155✔
440
                    }
155✔
441
                    PVariantKind::Tuple { fields } => {
120✔
442
                        // Tuple shadow struct
443
                        let fields_with_types: Vec<TokenStream> = fields
120✔
444
                            .iter()
120✔
445
                            .enumerate()
120✔
446
                            .map(|(idx, pf)| {
132✔
447
                                let field_ident = format_ident!("_{}", idx);
132✔
448
                                let typ = &pf.ty;
132✔
449
                                quote! { #field_ident: #typ }
132✔
450
                            })
132✔
451
                            .collect();
120✔
452
                        shadow_defs.push(quote! {
120✔
453
                            #[repr(C)]
454
                            #[allow(non_snake_case, dead_code)]
455
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
456
                                #(#fields_with_types),* ,
457
                                _phantom: #phantom_data
458
                            }
459
                        });
460
                        let field_defs: Vec<TokenStream> = fields
120✔
461
                            .iter()
120✔
462
                            .enumerate()
120✔
463
                            .map(|(idx, pf)| {
132✔
464
                                let mut pfield = pf.clone();
132✔
465
                                let field_ident = format_ident!("_{}", idx);
132✔
466
                                pfield.name.raw = IdentOrLiteral::Ident(field_ident);
132✔
467
                                gen_field_from_pfield(
132✔
468
                                    &pfield,
132✔
469
                                    &shadow_struct_name,
132✔
470
                                    &facet_bgp,
132✔
471
                                    Some(variant_offset.clone()),
132✔
472
                                    &facet_crate,
132✔
473
                                    skip_all_unless_truthy,
132✔
474
                                )
475
                            })
132✔
476
                            .collect();
120✔
477
                        let kind = quote! { 𝟋Sk::Tuple };
120✔
478
                        let variant = gen_variant(
120✔
479
                            &name_token,
120✔
480
                            &discriminant_ts,
120✔
481
                            variant_attributes.as_ref(),
120✔
482
                            &kind,
120✔
483
                            &quote! { fields },
120✔
484
                            variant_doc.as_ref(),
120✔
485
                        );
486
                        exprs.push(quote! {{
120✔
487
                            let fields: &'static [𝟋Fld] = &const {[
488
                                #(#field_defs),*
489
                            ]};
490
                            #variant
491
                        }});
492
                    }
493
                    PVariantKind::Struct { fields } => {
61✔
494
                        let fields_with_types: Vec<TokenStream> = fields
61✔
495
                            .iter()
61✔
496
                            .map(|pf| {
94✔
497
                                // Use raw name for struct field definition
498
                                let field_name = match &pf.name.raw {
94✔
499
                                    IdentOrLiteral::Ident(id) => quote! { #id },
94✔
500
                                    IdentOrLiteral::Literal(_) => {
501
                                        panic!("Struct variant cannot have literal field names")
×
502
                                    }
503
                                };
504
                                let typ = &pf.ty;
94✔
505
                                quote! { #field_name: #typ }
94✔
506
                            })
94✔
507
                            .collect();
61✔
508

509
                        // Handle empty fields case explicitly
510
                        let struct_fields = if fields_with_types.is_empty() {
61✔
511
                            // Only add phantom data for empty struct variants
512
                            quote! { _phantom: #phantom_data }
×
513
                        } else {
514
                            // Add fields plus phantom data for non-empty struct variants
515
                            quote! { #(#fields_with_types),*, _phantom: #phantom_data }
61✔
516
                        };
517
                        shadow_defs.push(quote! {
61✔
518
                            #[repr(C)]
519
                            #[allow(non_snake_case, dead_code)]
520
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
521
                                #struct_fields
522
                            }
523
                        });
524

525
                        let field_defs: Vec<TokenStream> = fields
61✔
526
                            .iter()
61✔
527
                            .map(|pf| {
94✔
528
                                gen_field_from_pfield(
94✔
529
                                    pf,
94✔
530
                                    &shadow_struct_name,
94✔
531
                                    &facet_bgp,
94✔
532
                                    Some(variant_offset.clone()),
94✔
533
                                    &facet_crate,
94✔
534
                                    skip_all_unless_truthy,
94✔
535
                                )
536
                            })
94✔
537
                            .collect();
61✔
538

539
                        let kind = quote! { 𝟋Sk::Struct };
61✔
540
                        let variant = gen_variant(
61✔
541
                            &name_token,
61✔
542
                            &discriminant_ts,
61✔
543
                            variant_attributes.as_ref(),
61✔
544
                            &kind,
61✔
545
                            &quote! { fields },
61✔
546
                            variant_doc.as_ref(),
61✔
547
                        );
548
                        exprs.push(quote! {{
61✔
549
                            let fields: &'static [𝟋Fld] = &const {[
550
                                #(#field_defs),*
551
                            ]};
552
                            #variant
553
                        }});
554
                    }
555
                };
556

557
                // C-style enums increment discriminant unless explicitly set
558
                discriminant_offset += 1;
336✔
559
            }
560

561
            // Generate the EnumRepr token stream (uses prelude alias 𝟋ERpr)
562
            let repr_type_ts = match prim_opt {
92✔
563
                None => {
564
                    quote! { 𝟋ERpr::from_discriminant_size::<#shadow_discriminant_name>() }
90✔
565
                }
566
                Some(p) => enum_repr_ts_from_primitive(*p),
2✔
567
            };
568

569
            (shadow_defs, exprs, repr_type_ts)
92✔
570
        }
571
        PRepr::Rust(Some(prim)) => {
276✔
572
            // Treat as primitive repr
573
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
276✔
574
            let bgp_with_bounds = facet_bgp.display_with_bounds();
276✔
575
            let phantom_data = facet_bgp.display_as_phantom_data();
276✔
576
            let discriminant_rust_type = prim.type_name();
276✔
577
            let mut shadow_defs = Vec::new();
276✔
578

579
            // Generate variant_expressions
580
            let mut discriminant: Option<&TokenStream> = None;
276✔
581
            let mut discriminant_offset: i64 = 0;
276✔
582

583
            let mut exprs = Vec::new();
276✔
584

585
            for pv in pe.variants.iter() {
769✔
586
                if let Some(dis) = &pv.discriminant {
769✔
587
                    discriminant = Some(dis);
26✔
588
                    discriminant_offset = 0;
26✔
589
                }
743✔
590

591
                // Only cast to i64 when we have a user-provided discriminant expression
592
                let discriminant_ts = if let Some(discriminant) = discriminant {
769✔
593
                    if discriminant_offset > 0 {
26✔
594
                        let offset_lit = Literal::i64_unsuffixed(discriminant_offset);
×
595
                        quote! { (#discriminant + #offset_lit) as i64 }
×
596
                    } else {
597
                        quote! { #discriminant as i64 }
26✔
598
                    }
599
                } else {
600
                    // Simple unsuffixed literal
601
                    let lit = Literal::i64_unsuffixed(discriminant_offset);
743✔
602
                    quote! { #lit }
743✔
603
                };
604

605
                let display_name = pv.name.effective.clone();
769✔
606
                let name_token = TokenTree::Literal(Literal::string(&display_name));
769✔
607
                let variant_attributes: Option<TokenStream> = if pv.attrs.facet.is_empty() {
769✔
608
                    None
688✔
609
                } else {
610
                    let attrs_list: Vec<TokenStream> = pv
81✔
611
                        .attrs
81✔
612
                        .facet
81✔
613
                        .iter()
81✔
614
                        .map(|attr| {
81✔
615
                            let ext_attr = emit_attr(attr, &facet_crate);
81✔
616
                            quote! { #ext_attr }
81✔
617
                        })
81✔
618
                        .collect();
81✔
619
                    Some(quote! { &const {[#(#attrs_list),*]} })
81✔
620
                };
621

622
                #[cfg(feature = "doc")]
623
                let variant_doc: Option<TokenStream> = match &pv.attrs.doc[..] {
769✔
624
                    [] => None,
769✔
625
                    doc_lines => Some(quote! { &[#(#doc_lines),*] }),
113✔
626
                };
627
                #[cfg(not(feature = "doc"))]
628
                let variant_doc: Option<TokenStream> = None;
×
629

630
                match &pv.kind {
769✔
631
                    PVariantKind::Unit => {
205✔
632
                        let variant = gen_unit_variant(
205✔
633
                            &name_token,
205✔
634
                            &discriminant_ts,
205✔
635
                            variant_attributes.as_ref(),
205✔
636
                            variant_doc.as_ref(),
205✔
637
                        );
205✔
638
                        exprs.push(variant);
205✔
639
                    }
205✔
640
                    PVariantKind::Tuple { fields } => {
340✔
641
                        let shadow_struct_name = match &pv.name.raw {
340✔
642
                            IdentOrLiteral::Ident(id) => {
340✔
643
                                quote::format_ident!("_T{}", id)
340✔
644
                            }
645
                            IdentOrLiteral::Literal(_) => {
646
                                panic!(
×
647
                                    "Enum variant names cannot be literals for tuple variants in #[repr(Rust)]"
648
                                )
649
                            }
650
                        };
651
                        let fields_with_types: Vec<TokenStream> = fields
340✔
652
                            .iter()
340✔
653
                            .enumerate()
340✔
654
                            .map(|(idx, pf)| {
377✔
655
                                let field_ident = format_ident!("_{}", idx);
377✔
656
                                let typ = &pf.ty;
377✔
657
                                quote! { #field_ident: #typ }
377✔
658
                            })
377✔
659
                            .collect();
340✔
660
                        shadow_defs.push(quote! {
340✔
661
                            #[repr(C)] // Layout variants like C structs
662
                            #[allow(non_snake_case, dead_code)]
663
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
664
                                _discriminant: #discriminant_rust_type,
665
                                _phantom: #phantom_data,
666
                                #(#fields_with_types),*
667
                            }
668
                        });
669
                        let field_defs: Vec<TokenStream> = fields
340✔
670
                            .iter()
340✔
671
                            .enumerate()
340✔
672
                            .map(|(idx, pf)| {
377✔
673
                                let mut pf = pf.clone();
377✔
674
                                let field_ident = format_ident!("_{}", idx);
377✔
675
                                pf.name.raw = IdentOrLiteral::Ident(field_ident);
377✔
676
                                gen_field_from_pfield(
377✔
677
                                    &pf,
377✔
678
                                    &shadow_struct_name,
377✔
679
                                    &facet_bgp,
377✔
680
                                    None,
377✔
681
                                    &facet_crate,
377✔
682
                                    skip_all_unless_truthy,
377✔
683
                                )
684
                            })
377✔
685
                            .collect();
340✔
686
                        let kind = quote! { 𝟋Sk::Tuple };
340✔
687
                        let variant = gen_variant(
340✔
688
                            &name_token,
340✔
689
                            &discriminant_ts,
340✔
690
                            variant_attributes.as_ref(),
340✔
691
                            &kind,
340✔
692
                            &quote! { fields },
340✔
693
                            variant_doc.as_ref(),
340✔
694
                        );
695
                        exprs.push(quote! {{
340✔
696
                            let fields: &'static [𝟋Fld] = &const {[
697
                                #(#field_defs),*
698
                            ]};
699
                            #variant
700
                        }});
701
                    }
702
                    PVariantKind::Struct { fields } => {
224✔
703
                        let shadow_struct_name = match &pv.name.raw {
224✔
704
                            IdentOrLiteral::Ident(id) => {
224✔
705
                                quote::format_ident!("_S{}", id)
224✔
706
                            }
707
                            IdentOrLiteral::Literal(_) => {
708
                                panic!(
×
709
                                    "Enum variant names cannot be literals for struct variants in #[repr(Rust)]"
710
                                )
711
                            }
712
                        };
713
                        let fields_with_types: Vec<TokenStream> = fields
224✔
714
                            .iter()
224✔
715
                            .map(|pf| {
454✔
716
                                let field_name = match &pf.name.raw {
454✔
717
                                    IdentOrLiteral::Ident(id) => quote! { #id },
454✔
718
                                    IdentOrLiteral::Literal(_) => {
719
                                        panic!("Struct variant cannot have literal field names")
×
720
                                    }
721
                                };
722
                                let typ = &pf.ty;
454✔
723
                                quote! { #field_name: #typ }
454✔
724
                            })
454✔
725
                            .collect();
224✔
726
                        shadow_defs.push(quote! {
224✔
727
                            #[repr(C)] // Layout variants like C structs
728
                            #[allow(non_snake_case, dead_code)]
729
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
730
                                _discriminant: #discriminant_rust_type,
731
                                _phantom: #phantom_data,
732
                                #(#fields_with_types),*
733
                            }
734
                        });
735
                        let field_defs: Vec<TokenStream> = fields
224✔
736
                            .iter()
224✔
737
                            .map(|pf| {
454✔
738
                                gen_field_from_pfield(
454✔
739
                                    pf,
454✔
740
                                    &shadow_struct_name,
454✔
741
                                    &facet_bgp,
454✔
742
                                    None,
454✔
743
                                    &facet_crate,
454✔
744
                                    skip_all_unless_truthy,
454✔
745
                                )
746
                            })
454✔
747
                            .collect();
224✔
748
                        let kind = quote! { 𝟋Sk::Struct };
224✔
749
                        let variant = gen_variant(
224✔
750
                            &name_token,
224✔
751
                            &discriminant_ts,
224✔
752
                            variant_attributes.as_ref(),
224✔
753
                            &kind,
224✔
754
                            &quote! { fields },
224✔
755
                            variant_doc.as_ref(),
224✔
756
                        );
757
                        exprs.push(quote! {{
224✔
758
                            let fields: &'static [𝟋Fld] = &const {[
759
                                #(#field_defs),*
760
                            ]};
761
                            #variant
762
                        }});
763
                    }
764
                }
765
                // Rust-style enums increment discriminant unless explicitly set
766
                discriminant_offset += 1;
769✔
767
            }
768
            let repr_type_ts = enum_repr_ts_from_primitive(*prim);
276✔
769
            (shadow_defs, exprs, repr_type_ts)
276✔
770
        }
771
        PRepr::Transparent => {
772
            return quote! {
×
773
                compile_error!("#[repr(transparent)] is not supported on enums by Facet");
774
            };
775
        }
776
        PRepr::Rust(None) => {
777
            return quote! {
×
778
                compile_error!("Facet requires enums to have an explicit representation (e.g., #[repr(C)], #[repr(u8)])");
779
            };
780
        }
781
        PRepr::RustcWillCatch => {
782
            // rustc will emit an error for the invalid repr (e.g., conflicting hints).
783
            // Return empty TokenStream so we don't add misleading errors.
784
            return quote! {};
×
785
        }
786
    };
787

788
    // Static decl removed - the TYPENAME_SHAPE static was redundant since
789
    // <T as Facet>::SHAPE is already accessible and nobody was using the static
790

791
    // Set up generics for impl blocks
792
    let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
368✔
793
    let bgp_def = facet_bgp.display_with_bounds();
368✔
794
    let bgp_without_bounds = bgp.display_without_bounds();
368✔
795

796
    let (ty_field, fields) = if opaque {
368✔
797
        (
×
798
            quote! {
×
799
                𝟋Ty::User(𝟋UTy::Opaque)
×
800
            },
×
801
            quote! {},
×
802
        )
×
803
    } else {
804
        // Inline the const block directly into the builder call
805
        (
806
            quote! {
368✔
807
                𝟋Ty::User(𝟋UTy::Enum(
808
                    𝟋ETyB::new(#enum_repr_type_tokenstream, &const {[
809
                        #(#variant_expressions),*
810
                    ]})
811
                        .repr(#repr)
812
                        .build()
813
                ))
814
            },
815
            quote! {},
368✔
816
        )
817
    };
818

819
    // Generate constructor expressions to suppress dead_code warnings on enum variants.
820
    // When variants are constructed via reflection (e.g., facet_args::from_std_args()),
821
    // the compiler doesn't see them being used and warns about dead code.
822
    // This ensures all variants are "constructed" from the compiler's perspective.
823
    // We use explicit type annotations to help inference with const generics and
824
    // unused type parameters.
825
    let variant_constructors: Vec<TokenStream> = pe
368✔
826
        .variants
368✔
827
        .iter()
368✔
828
        .map(|pv| {
1,105✔
829
            let variant_ident = match &pv.name.raw {
1,105✔
830
                IdentOrLiteral::Ident(id) => id.clone(),
1,105✔
831
                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
832
            };
833
            match &pv.kind {
1,105✔
834
                PVariantKind::Unit => quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident },
360✔
835
                PVariantKind::Tuple { fields } => {
460✔
836
                    let loops = fields.iter().map(|_| quote! { loop {} });
509✔
837
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident(#(#loops),*) }
460✔
838
                }
839
                PVariantKind::Struct { fields } => {
285✔
840
                    let field_inits: Vec<TokenStream> = fields
285✔
841
                        .iter()
285✔
842
                        .map(|pf| {
548✔
843
                            let field_name = match &pf.name.raw {
548✔
844
                                IdentOrLiteral::Ident(id) => id.clone(),
548✔
845
                                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
846
                            };
847
                            quote! { #field_name: loop {} }
548✔
848
                        })
548✔
849
                        .collect();
285✔
850
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident { #(#field_inits),* } }
285✔
851
                }
852
            }
853
        })
1,105✔
854
        .collect();
368✔
855

856
    // Compute variance - delegate to Shape::computed_variance() at runtime
857
    let variance_call = if opaque {
368✔
858
        // Opaque types don't expose internals, use invariant for safety
859
        quote! { .variance(𝟋Vnc::INVARIANT) }
×
860
    } else {
861
        // Point to Shape::computed_variance - it takes &Shape and walks fields
862
        quote! { .variance(𝟋CV) }
368✔
863
    };
864

865
    // TypeOps for drop, default, clone - convert Option<TokenStream> to a call
866
    let type_ops_call = match type_ops_init {
368✔
867
        Some(ops) => quote! { .type_ops(#ops) },
368✔
868
        None => quote! {},
×
869
    };
870

871
    // Type name function - for generic types, this formats with type parameters
872
    let type_name_call = if parsed.generics.is_some() && !opaque {
368✔
873
        quote! { .type_name(#type_name_fn) }
10✔
874
    } else {
875
        quote! {}
358✔
876
    };
877

878
    // Generate static assertions for declared traits (catches lies at compile time)
879
    // We put this in a generic function outside the const block so it can reference generic parameters
880
    let facet_default = pe.container.attrs.has_builtin("default");
368✔
881
    let trait_assertion_fn = if let Some(bounds) =
368✔
882
        gen_trait_bounds(pe.container.attrs.declared_traits.as_ref(), facet_default)
368✔
883
    {
884
        // Note: where_clauses_tokens already includes "where" keyword if non-empty
885
        // We need to add the trait bounds as an additional constraint
886
        quote! {
×
887
            const _: () = {
888
                #[allow(dead_code, clippy::multiple_bound_locations)]
889
                fn __facet_assert_traits #bgp_def (_: &#enum_name #bgp_without_bounds)
890
                where
891
                    #enum_name #bgp_without_bounds: #bounds
892
                {}
893
            };
894
        }
895
    } else {
896
        quote! {}
368✔
897
    };
898

899
    // Static declaration for release builds (pre-evaluates SHAPE)
900
    let static_decl =
368✔
901
        crate::derive::generate_static_decl(enum_name, &facet_crate, has_type_or_const_generics);
368✔
902

903
    // Generate the impl
904
    quote! {
368✔
905
        // Suppress dead_code warnings for enum variants constructed via reflection.
906
        // See: https://github.com/facet-rs/facet/issues/996
907
        const _: () = {
908
            #[allow(dead_code, unreachable_code, clippy::multiple_bound_locations, clippy::diverging_sub_expression)]
909
            fn __facet_construct_all_variants #bgp_def () -> #enum_name #bgp_without_bounds #where_clauses_tokens {
910
                loop {
911
                    #(#variant_constructors;)*
912
                }
913
            }
914
        };
915

916
        #trait_assertion_fn
917

918
        #[automatically_derived]
919
        #[allow(non_camel_case_types)]
920
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #enum_name #bgp_without_bounds #where_clauses_tokens {
921
            const SHAPE: &'static #facet_crate::Shape = &const {
922
                use #facet_crate::𝟋::*;
923
                #(#shadow_struct_defs)*
924
                #fields
925
                𝟋ShpB::for_sized::<Self>(#enum_name_str)
926
                    .vtable(#vtable_init)
927
                    #type_ops_call
928
                    .ty(#ty_field)
929
                    .def(𝟋Def::Undefined)
930
                    #type_params_call
931
                    #type_name_call
932
                    #doc_call
933
                    #attributes_call
934
                    #type_tag_call
935
                    #tag_call
936
                    #content_call
937
                    #untagged_call
938
                    #proxy_call
939
                    #variance_call
940
                    .build()
941
            };
942
        }
943

944
        #static_decl
945
    }
946
}
368✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc