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

facet-rs / facet / 20024290994

08 Dec 2025 10:08AM UTC coverage: 58.613% (+0.01%) from 58.6%
20024290994

push

github

fasterthanlime
feat: compute variance automatically from field types

This implements automatic variance computation for derived types:

- Change `Shape.variance` from `fn() -> Variance` to `fn(&'static Shape) -> Variance`
  to allow variance functions to walk type structure without capturing generics

- Add `Shape::computed_variance()` that walks struct fields, enum variants,
  and inner shapes (for Box, Vec, Option, etc.) to compute variance

- Use thread-local cycle detection to prevent stack overflow with recursive types.
  When a cycle is detected, returns `Covariant` (identity for `combine`)

- Update derive macros to use `.variance(Shape::computed_variance)`
  instead of generating per-type variance computation code

- Update `Box<T>` to use computed variance (depends on T's variance)
  instead of hardcoded `Invariant`

- Add variance tests for recursive types

Closes #1171

44 of 60 new or added lines in 3 files covered. (73.33%)

487 existing lines in 23 files now uncovered.

24730 of 42192 relevant lines covered (58.61%)

544.95 hits per line

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

90.7
/facet-macros-impl/src/process_enum.rs
1
use super::*;
2
use crate::process_struct::{TraitSources, gen_field_from_pfield, gen_trait_bounds, gen_vtable};
3
use quote::{format_ident, quote, quote_spanned};
4

5
/// Generate a Variant using VariantBuilder for more compact output.
6
///
7
/// NOTE: This function generates code that uses short aliases from the 𝟋 prelude.
8
/// It MUST be called within a context where `use #facet_crate::𝟋::*` has been emitted.
9
fn gen_variant(
543✔
10
    name: impl quote::ToTokens,
543✔
11
    discriminant: impl quote::ToTokens,
543✔
12
    attributes: impl quote::ToTokens,
543✔
13
    struct_kind: impl quote::ToTokens,
543✔
14
    fields: impl quote::ToTokens,
543✔
15
    doc: impl quote::ToTokens,
543✔
16
) -> TokenStream {
543✔
17
    quote! {
543✔
18
        𝟋VarB::new(
19
            #name,
20
            𝟋STyB::new(#struct_kind, #fields).build()
21
        )
22
        .discriminant(Some(#discriminant as _))
23
        .attributes(#attributes)
24
        .doc(#doc)
25
        .build()
26
    }
27
}
543✔
28

29
/// Generate a unit variant using the pre-built StructType::UNIT constant.
30
/// NOTE: This function generates code that uses short aliases from the 𝟋 prelude.
31
/// It MUST be called within a context where `use #facet_crate::𝟋::*` has been emitted.
32
fn gen_unit_variant(
344✔
33
    name: impl quote::ToTokens,
344✔
34
    discriminant: impl quote::ToTokens,
344✔
35
    attributes: impl quote::ToTokens,
344✔
36
    doc: impl quote::ToTokens,
344✔
37
) -> TokenStream {
344✔
38
    quote! {
344✔
39
        𝟋VarB::new(#name, 𝟋STy::UNIT)
40
            .discriminant(Some(#discriminant as _))
41
            .attributes(#attributes)
42
            .doc(#doc)
43
            .build()
44
    }
45
}
344✔
46

47
/// Processes an enum to implement Facet
48
pub(crate) fn process_enum(parsed: Enum) -> TokenStream {
321✔
49
    // Use already-parsed PEnum, including container/variant/field attributes and rename rules
50
    let pe = PEnum::parse(&parsed);
321✔
51

52
    // Emit any collected errors as compile_error! with proper spans
53
    if !pe.container.attrs.errors.is_empty() {
321✔
54
        let errors = pe.container.attrs.errors.iter().map(|e| {
×
55
            let msg = &e.message;
×
56
            let span = e.span;
×
57
            quote_spanned! { span => compile_error!(#msg); }
×
58
        });
×
59
        return quote! { #(#errors)* };
×
60
    }
321✔
61

62
    let enum_name = &pe.container.name;
321✔
63
    let enum_name_str = enum_name.to_string();
321✔
64

65
    let opaque = pe
321✔
66
        .container
321✔
67
        .attrs
321✔
68
        .facet
321✔
69
        .iter()
321✔
70
        .any(|a| a.is_builtin() && a.key_str() == "opaque");
321✔
71

72
    // Get the facet crate path (custom or default ::facet)
73
    let facet_crate = pe.container.attrs.facet_crate();
321✔
74

75
    let type_name_fn =
321✔
76
        generate_type_name_fn(enum_name, parsed.generics.as_ref(), opaque, &facet_crate);
321✔
77

78
    // Determine trait sources and generate vtable accordingly
79
    let trait_sources = TraitSources::from_attrs(&pe.container.attrs);
321✔
80
    let vtable_code = gen_vtable(&facet_crate, &type_name_fn, &trait_sources);
321✔
81
    let vtable_init = quote! { const { #vtable_code } };
321✔
82

83
    let bgp = pe.container.bgp.clone();
321✔
84
    // Use the AST directly for where clauses and generics, as PContainer/PEnum doesn't store them
85
    let where_clauses_tokens = build_where_clauses(
321✔
86
        parsed.clauses.as_ref(),
321✔
87
        parsed.generics.as_ref(),
321✔
88
        opaque,
321✔
89
        &facet_crate,
321✔
90
    );
91
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
321✔
92

93
    // Container-level docs - returns builder call only if there are doc comments and doc feature is enabled
94
    #[cfg(feature = "doc")]
95
    let doc_call = match &pe.container.attrs.doc[..] {
321✔
96
        [] => quote! {},
321✔
97
        doc_lines => quote! { .doc(&[#(#doc_lines),*]) },
10✔
98
    };
99
    #[cfg(not(feature = "doc"))]
100
    let doc_call = quote! {};
×
101

102
    // Container attributes - returns builder call only if there are attributes
103
    let attributes_call = {
321✔
104
        let mut attribute_tokens: Vec<TokenStream> = Vec::new();
321✔
105
        for attr in &pe.container.attrs.facet {
321✔
106
            // These attributes are handled specially and not emitted to runtime:
107
            // - crate: sets the facet crate path
108
            // - traits: compile-time directive for vtable generation
109
            // - auto_traits: compile-time directive for vtable generation
110
            if attr.is_builtin() {
43✔
111
                let key = attr.key_str();
42✔
112
                if matches!(key.as_str(), "crate" | "traits" | "auto_traits" | "proxy") {
42✔
113
                    continue;
5✔
114
                }
37✔
115
            }
1✔
116
            // All attributes go through grammar dispatch
117
            let ext_attr = emit_attr(attr, &facet_crate);
38✔
118
            attribute_tokens.push(quote! { #ext_attr });
38✔
119
        }
120

121
        if attribute_tokens.is_empty() {
321✔
122
            quote! {}
291✔
123
        } else {
124
            quote! { .attributes(&const {[#(#attribute_tokens),*]}) }
30✔
125
        }
126
    };
127

128
    // Type tag - returns builder call only if present
129
    let type_tag_call = {
321✔
130
        if let Some(type_tag) = pe.container.attrs.get_builtin_args("type_tag") {
321✔
131
            quote! { .type_tag(#type_tag) }
×
132
        } else {
133
            quote! {}
321✔
134
        }
135
    };
136

137
    // Container-level proxy from PEnum - generates ProxyDef with conversion functions
138
    let proxy_call = {
321✔
139
        if let Some(attr) = pe
321✔
140
            .container
321✔
141
            .attrs
321✔
142
            .facet
321✔
143
            .iter()
321✔
144
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
321✔
145
        {
146
            let proxy_type = &attr.args;
1✔
147
            let enum_type = &enum_name;
1✔
148
            let bgp_display = pe.container.bgp.display_without_bounds();
1✔
149

150
            quote! {
1✔
151
                .proxy(&const {
152
                    extern crate alloc as __alloc;
153

154
                    unsafe fn __proxy_convert_in<'mem>(
155
                        proxy_ptr: #facet_crate::PtrConst<'mem>,
156
                        field_ptr: #facet_crate::PtrUninit<'mem>,
157
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, __alloc::string::String> {
158
                        let proxy: #proxy_type = proxy_ptr.read();
159
                        match <#enum_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
160
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
161
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
162
                        }
163
                    }
164

165
                    unsafe fn __proxy_convert_out<'mem>(
166
                        field_ptr: #facet_crate::PtrConst<'mem>,
167
                        proxy_ptr: #facet_crate::PtrUninit<'mem>,
168
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, __alloc::string::String> {
169
                        let field_ref: &#enum_type #bgp_display = field_ptr.get();
170
                        match <#proxy_type as ::core::convert::TryFrom<&#enum_type #bgp_display>>::try_from(field_ref) {
171
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
172
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
173
                        }
174
                    }
175

176
                    #facet_crate::ProxyDef {
177
                        shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
178
                        convert_in: __proxy_convert_in,
179
                        convert_out: __proxy_convert_out,
180
                    }
181
                })
182
            }
183
        } else {
184
            quote! {}
320✔
185
        }
186
    };
187

188
    // Determine enum repr (already resolved by PEnum::parse())
189
    let valid_repr = &pe.repr;
321✔
190

191
    // Are these relevant for enums? Or is it always `repr(C)` if a `PrimitiveRepr` is present?
192
    let repr = match &valid_repr {
321✔
193
        PRepr::Transparent => unreachable!("this should be caught by PRepr::parse"),
×
194
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
237✔
195
        PRepr::C(_) => quote! { 𝟋Repr::C },
84✔
196
        PRepr::RustcWillCatch => {
197
            // rustc will emit the error - return empty TokenStream
198
            return quote! {};
×
199
        }
200
    };
201

202
    // Helper for EnumRepr TS (token stream) generation for primitives
203
    let enum_repr_ts_from_primitive = |primitive_repr: PrimitiveRepr| -> TokenStream {
321✔
204
        let type_name_str = primitive_repr.type_name().to_string();
239✔
205
        let enum_repr_variant_ident = format_ident!("{}", type_name_str.to_uppercase());
239✔
206
        quote! { #facet_crate::EnumRepr::#enum_repr_variant_ident }
239✔
207
    };
239✔
208

209
    // --- Processing code for shadow struct/fields/variant_expressions ---
210
    // A. C-style enums have shadow-discriminant, shadow-union, shadow-struct
211
    // B. Primitive enums have simpler layout.
212
    let (shadow_struct_defs, variant_expressions, enum_repr_type_tokenstream) = match valid_repr {
321✔
213
        PRepr::C(prim_opt) => {
84✔
214
            // Shadow discriminant
215
            let shadow_discriminant_name = quote::format_ident!("_D");
84✔
216
            let all_variant_names: Vec<Ident> = pe
84✔
217
                .variants
84✔
218
                .iter()
84✔
219
                .map(|pv| match &pv.name.raw {
316✔
220
                    IdentOrLiteral::Ident(id) => id.clone(),
316✔
221
                    IdentOrLiteral::Literal(n) => format_ident!("_{}", n), // Should not happen for enums
×
222
                })
316✔
223
                .collect();
84✔
224

225
            let repr_attr_content = match prim_opt {
84✔
226
                Some(p) => p.type_name(),
2✔
227
                None => quote! { C },
82✔
228
            };
229
            let mut shadow_defs = vec![quote! {
84✔
230
                #[repr(#repr_attr_content)]
231
                #[allow(dead_code)]
232
                enum #shadow_discriminant_name { #(#all_variant_names),* }
233
            }];
234

235
            // Shadow union
236
            let shadow_union_name = quote::format_ident!("_U");
84✔
237
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
84✔
238
            let bgp_with_bounds = facet_bgp.display_with_bounds();
84✔
239
            let bgp_without_bounds = facet_bgp.display_without_bounds();
84✔
240
            let phantom_data = facet_bgp.display_as_phantom_data();
84✔
241
            let all_union_fields: Vec<TokenStream> = pe.variants.iter().map(|pv| {
316✔
242
                // Each field is named after the variant, struct for its fields.
243
                let variant_ident = match &pv.name.raw {
316✔
244
                    IdentOrLiteral::Ident(id) => id.clone(),
316✔
245
                     IdentOrLiteral::Literal(idx) => format_ident!("_{}", idx), // Should not happen
×
246
                };
247
                let shadow_field_name_ident = quote::format_ident!("_F{}", variant_ident);
316✔
248
                quote! {
316✔
249
                    #variant_ident: ::core::mem::ManuallyDrop<#shadow_field_name_ident #bgp_without_bounds>
250
                }
251
            }).collect();
316✔
252

253
            shadow_defs.push(quote! {
84✔
254
                #[repr(C)]
255
                #[allow(non_snake_case, dead_code)]
256
                union #shadow_union_name #bgp_with_bounds #where_clauses_tokens { #(#all_union_fields),* }
257
            });
258

259
            // Shadow repr struct for enum as a whole
260
            let shadow_repr_name = quote::format_ident!("_R");
84✔
261
            shadow_defs.push(quote! {
84✔
262
                #[repr(C)]
263
                #[allow(non_snake_case)]
264
                #[allow(dead_code)]
265
                struct #shadow_repr_name #bgp_with_bounds #where_clauses_tokens {
266
                    _discriminant: #shadow_discriminant_name,
267
                    _phantom: #phantom_data,
268
                    _fields: #shadow_union_name #bgp_without_bounds,
269
                }
270
            });
271

272
            // Generate variant_expressions
273
            let mut discriminant: Option<&TokenStream> = None;
84✔
274
            let mut discriminant_offset: i64 = 0;
84✔
275
            let mut exprs = Vec::new();
84✔
276

277
            for pv in pe.variants.iter() {
316✔
278
                if let Some(dis) = &pv.discriminant {
316✔
279
                    discriminant = Some(dis);
×
280
                    discriminant_offset = 0;
×
281
                }
316✔
282

283
                let discriminant_ts = if let Some(discriminant) = discriminant {
316✔
284
                    if discriminant_offset > 0 {
×
285
                        quote! { #discriminant + #discriminant_offset }
×
286
                    } else {
287
                        quote! { #discriminant }
×
288
                    }
289
                } else {
290
                    quote! { #discriminant_offset }
316✔
291
                };
292

293
                let display_name = pv.name.effective.clone();
316✔
294
                let name_token = TokenTree::Literal(Literal::string(&display_name));
316✔
295
                let variant_attributes = {
316✔
296
                    if pv.attrs.facet.is_empty() {
316✔
297
                        quote! { &[] }
307✔
298
                    } else {
299
                        let attrs_list: Vec<TokenStream> = pv
9✔
300
                            .attrs
9✔
301
                            .facet
9✔
302
                            .iter()
9✔
303
                            .map(|attr| {
9✔
304
                                let ext_attr = emit_attr(attr, &facet_crate);
9✔
305
                                quote! { #ext_attr }
9✔
306
                            })
9✔
307
                            .collect();
9✔
308
                        quote! { &const {[#(#attrs_list),*]} }
9✔
309
                    }
310
                };
311

312
                #[cfg(feature = "doc")]
313
                let variant_doc = match &pv.attrs.doc[..] {
316✔
314
                    [] => quote! { &[] },
316✔
315
                    doc_lines => quote! { &[#(#doc_lines),*] },
×
316
                };
317
                #[cfg(not(feature = "doc"))]
318
                let variant_doc = quote! { &[] };
×
319

320
                let shadow_struct_name = match &pv.name.raw {
316✔
321
                    IdentOrLiteral::Ident(id) => quote::format_ident!("_F{}", id),
316✔
322
                    IdentOrLiteral::Literal(idx) => quote::format_ident!("_F{}", idx),
×
323
                };
324

325
                let variant_offset = quote! {
316✔
326
                    ::core::mem::offset_of!(#shadow_repr_name #bgp_without_bounds, _fields)
327
                };
328

329
                // Determine field structure for the variant
330
                match &pv.kind {
316✔
331
                    PVariantKind::Unit => {
153✔
332
                        // Generate unit shadow struct for the variant
153✔
333
                        shadow_defs.push(quote! {
153✔
334
                            #[repr(C)]
153✔
335
                            #[allow(non_snake_case, dead_code)]
153✔
336
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens { _phantom: #phantom_data }
153✔
337
                        });
153✔
338
                        let variant = gen_unit_variant(
153✔
339
                            &name_token,
153✔
340
                            &discriminant_ts,
153✔
341
                            &variant_attributes,
153✔
342
                            &variant_doc,
153✔
343
                        );
153✔
344
                        exprs.push(variant);
153✔
345
                    }
153✔
346
                    PVariantKind::Tuple { fields } => {
119✔
347
                        // Tuple shadow struct
348
                        let fields_with_types: Vec<TokenStream> = fields
119✔
349
                            .iter()
119✔
350
                            .enumerate()
119✔
351
                            .map(|(idx, pf)| {
131✔
352
                                let field_ident = format_ident!("_{}", idx);
131✔
353
                                let typ = &pf.ty;
131✔
354
                                quote! { #field_ident: #typ }
131✔
355
                            })
131✔
356
                            .collect();
119✔
357
                        shadow_defs.push(quote! {
119✔
358
                            #[repr(C)]
359
                            #[allow(non_snake_case, dead_code)]
360
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
361
                                #(#fields_with_types),* ,
362
                                _phantom: #phantom_data
363
                            }
364
                        });
365
                        let field_defs: Vec<TokenStream> = fields
119✔
366
                            .iter()
119✔
367
                            .enumerate()
119✔
368
                            .map(|(idx, pf)| {
131✔
369
                                let mut pfield = pf.clone();
131✔
370
                                let field_ident = format_ident!("_{}", idx);
131✔
371
                                pfield.name.raw = IdentOrLiteral::Ident(field_ident);
131✔
372
                                gen_field_from_pfield(
131✔
373
                                    &pfield,
131✔
374
                                    &shadow_struct_name,
131✔
375
                                    &facet_bgp,
131✔
376
                                    Some(variant_offset.clone()),
131✔
377
                                    &facet_crate,
131✔
378
                                )
379
                            })
131✔
380
                            .collect();
119✔
381
                        let kind = quote! { 𝟋Sk::Tuple };
119✔
382
                        let variant = gen_variant(
119✔
383
                            &name_token,
119✔
384
                            &discriminant_ts,
119✔
385
                            &variant_attributes,
119✔
386
                            &kind,
119✔
387
                            &quote! { fields },
119✔
388
                            &variant_doc,
119✔
389
                        );
390
                        exprs.push(quote! {{
119✔
391
                            let fields: &'static [𝟋Fld] = &const {[
392
                                #(#field_defs),*
393
                            ]};
394
                            #variant
395
                        }});
396
                    }
397
                    PVariantKind::Struct { fields } => {
44✔
398
                        let fields_with_types: Vec<TokenStream> = fields
44✔
399
                            .iter()
44✔
400
                            .map(|pf| {
72✔
401
                                // Use raw name for struct field definition
402
                                let field_name = match &pf.name.raw {
72✔
403
                                    IdentOrLiteral::Ident(id) => quote! { #id },
72✔
404
                                    IdentOrLiteral::Literal(_) => {
405
                                        panic!("Struct variant cannot have literal field names")
×
406
                                    }
407
                                };
408
                                let typ = &pf.ty;
72✔
409
                                quote! { #field_name: #typ }
72✔
410
                            })
72✔
411
                            .collect();
44✔
412

413
                        // Handle empty fields case explicitly
414
                        let struct_fields = if fields_with_types.is_empty() {
44✔
415
                            // Only add phantom data for empty struct variants
416
                            quote! { _phantom: #phantom_data }
×
417
                        } else {
418
                            // Add fields plus phantom data for non-empty struct variants
419
                            quote! { #(#fields_with_types),*, _phantom: #phantom_data }
44✔
420
                        };
421
                        shadow_defs.push(quote! {
44✔
422
                            #[repr(C)]
423
                            #[allow(non_snake_case, dead_code)]
424
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
425
                                #struct_fields
426
                            }
427
                        });
428

429
                        let field_defs: Vec<TokenStream> = fields
44✔
430
                            .iter()
44✔
431
                            .map(|pf| {
72✔
432
                                gen_field_from_pfield(
72✔
433
                                    pf,
72✔
434
                                    &shadow_struct_name,
72✔
435
                                    &facet_bgp,
72✔
436
                                    Some(variant_offset.clone()),
72✔
437
                                    &facet_crate,
72✔
438
                                )
439
                            })
72✔
440
                            .collect();
44✔
441

442
                        let kind = quote! { 𝟋Sk::Struct };
44✔
443
                        let variant = gen_variant(
44✔
444
                            &name_token,
44✔
445
                            &discriminant_ts,
44✔
446
                            &variant_attributes,
44✔
447
                            &kind,
44✔
448
                            &quote! { fields },
44✔
449
                            &variant_doc,
44✔
450
                        );
451
                        exprs.push(quote! {{
44✔
452
                            let fields: &'static [𝟋Fld] = &const {[
453
                                #(#field_defs),*
454
                            ]};
455
                            #variant
456
                        }});
457
                    }
458
                };
459

460
                // C-style enums increment discriminant unless explicitly set
461
                discriminant_offset += 1;
316✔
462
            }
463

464
            // Generate the EnumRepr token stream
465
            let repr_type_ts = match prim_opt {
84✔
466
                None => {
467
                    quote! { #facet_crate::EnumRepr::from_discriminant_size::<#shadow_discriminant_name>() }
82✔
468
                }
469
                Some(p) => enum_repr_ts_from_primitive(*p),
2✔
470
            };
471

472
            (shadow_defs, exprs, repr_type_ts)
84✔
473
        }
474
        PRepr::Rust(Some(prim)) => {
237✔
475
            // Treat as primitive repr
476
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
237✔
477
            let bgp_with_bounds = facet_bgp.display_with_bounds();
237✔
478
            let phantom_data = facet_bgp.display_as_phantom_data();
237✔
479
            let discriminant_rust_type = prim.type_name();
237✔
480
            let mut shadow_defs = Vec::new();
237✔
481

482
            // Generate variant_expressions
483
            let mut discriminant: Option<&TokenStream> = None;
237✔
484
            let mut discriminant_offset: i64 = 0;
237✔
485

486
            let mut exprs = Vec::new();
237✔
487

488
            for pv in pe.variants.iter() {
571✔
489
                if let Some(dis) = &pv.discriminant {
571✔
490
                    discriminant = Some(dis);
26✔
491
                    discriminant_offset = 0;
26✔
492
                }
545✔
493

494
                let discriminant_ts = if let Some(discriminant) = discriminant {
571✔
495
                    if discriminant_offset > 0 {
26✔
496
                        quote! { #discriminant + #discriminant_offset }
×
497
                    } else {
498
                        quote! { #discriminant }
26✔
499
                    }
500
                } else {
501
                    quote! { #discriminant_offset }
545✔
502
                };
503

504
                let display_name = pv.name.effective.clone();
571✔
505
                let name_token = TokenTree::Literal(Literal::string(&display_name));
571✔
506
                let variant_attributes = {
571✔
507
                    if pv.attrs.facet.is_empty() {
571✔
508
                        quote! { &[] }
556✔
509
                    } else {
510
                        let attrs_list: Vec<TokenStream> = pv
15✔
511
                            .attrs
15✔
512
                            .facet
15✔
513
                            .iter()
15✔
514
                            .map(|attr| {
15✔
515
                                let ext_attr = emit_attr(attr, &facet_crate);
15✔
516
                                quote! { #ext_attr }
15✔
517
                            })
15✔
518
                            .collect();
15✔
519
                        quote! { &const {[#(#attrs_list),*]} }
15✔
520
                    }
521
                };
522

523
                #[cfg(feature = "doc")]
524
                let variant_doc = match &pv.attrs.doc[..] {
571✔
525
                    [] => quote! { &[] },
571✔
526
                    doc_lines => quote! { &[#(#doc_lines),*] },
27✔
527
                };
528
                #[cfg(not(feature = "doc"))]
529
                let variant_doc = quote! { &[] };
×
530

531
                match &pv.kind {
571✔
532
                    PVariantKind::Unit => {
191✔
533
                        let variant = gen_unit_variant(
191✔
534
                            &name_token,
191✔
535
                            &discriminant_ts,
191✔
536
                            &variant_attributes,
191✔
537
                            &variant_doc,
191✔
538
                        );
191✔
539
                        exprs.push(variant);
191✔
540
                    }
191✔
541
                    PVariantKind::Tuple { fields } => {
236✔
542
                        let shadow_struct_name = match &pv.name.raw {
236✔
543
                            IdentOrLiteral::Ident(id) => {
236✔
544
                                quote::format_ident!("_T{}", id)
236✔
545
                            }
546
                            IdentOrLiteral::Literal(_) => {
547
                                panic!(
×
548
                                    "Enum variant names cannot be literals for tuple variants in #[repr(Rust)]"
549
                                )
550
                            }
551
                        };
552
                        let fields_with_types: Vec<TokenStream> = fields
236✔
553
                            .iter()
236✔
554
                            .enumerate()
236✔
555
                            .map(|(idx, pf)| {
260✔
556
                                let field_ident = format_ident!("_{}", idx);
260✔
557
                                let typ = &pf.ty;
260✔
558
                                quote! { #field_ident: #typ }
260✔
559
                            })
260✔
560
                            .collect();
236✔
561
                        shadow_defs.push(quote! {
236✔
562
                            #[repr(C)] // Layout variants like C structs
563
                            #[allow(non_snake_case, dead_code)]
564
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
565
                                _discriminant: #discriminant_rust_type,
566
                                _phantom: #phantom_data,
567
                                #(#fields_with_types),*
568
                            }
569
                        });
570
                        let field_defs: Vec<TokenStream> = fields
236✔
571
                            .iter()
236✔
572
                            .enumerate()
236✔
573
                            .map(|(idx, pf)| {
260✔
574
                                let mut pf = pf.clone();
260✔
575
                                let field_ident = format_ident!("_{}", idx);
260✔
576
                                pf.name.raw = IdentOrLiteral::Ident(field_ident);
260✔
577
                                gen_field_from_pfield(
260✔
578
                                    &pf,
260✔
579
                                    &shadow_struct_name,
260✔
580
                                    &facet_bgp,
260✔
581
                                    None,
260✔
582
                                    &facet_crate,
260✔
583
                                )
584
                            })
260✔
585
                            .collect();
236✔
586
                        let kind = quote! { 𝟋Sk::Tuple };
236✔
587
                        let variant = gen_variant(
236✔
588
                            &name_token,
236✔
589
                            &discriminant_ts,
236✔
590
                            &variant_attributes,
236✔
591
                            &kind,
236✔
592
                            &quote! { fields },
236✔
593
                            &variant_doc,
236✔
594
                        );
595
                        exprs.push(quote! {{
236✔
596
                            let fields: &'static [𝟋Fld] = &const {[
597
                                #(#field_defs),*
598
                            ]};
599
                            #variant
600
                        }});
601
                    }
602
                    PVariantKind::Struct { fields } => {
144✔
603
                        let shadow_struct_name = match &pv.name.raw {
144✔
604
                            IdentOrLiteral::Ident(id) => {
144✔
605
                                quote::format_ident!("_S{}", id)
144✔
606
                            }
607
                            IdentOrLiteral::Literal(_) => {
608
                                panic!(
×
609
                                    "Enum variant names cannot be literals for struct variants in #[repr(Rust)]"
610
                                )
611
                            }
612
                        };
613
                        let fields_with_types: Vec<TokenStream> = fields
144✔
614
                            .iter()
144✔
615
                            .map(|pf| {
213✔
616
                                let field_name = match &pf.name.raw {
213✔
617
                                    IdentOrLiteral::Ident(id) => quote! { #id },
213✔
618
                                    IdentOrLiteral::Literal(_) => {
619
                                        panic!("Struct variant cannot have literal field names")
×
620
                                    }
621
                                };
622
                                let typ = &pf.ty;
213✔
623
                                quote! { #field_name: #typ }
213✔
624
                            })
213✔
625
                            .collect();
144✔
626
                        shadow_defs.push(quote! {
144✔
627
                            #[repr(C)] // Layout variants like C structs
628
                            #[allow(non_snake_case, dead_code)]
629
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
630
                                _discriminant: #discriminant_rust_type,
631
                                _phantom: #phantom_data,
632
                                #(#fields_with_types),*
633
                            }
634
                        });
635
                        let field_defs: Vec<TokenStream> = fields
144✔
636
                            .iter()
144✔
637
                            .map(|pf| {
213✔
638
                                gen_field_from_pfield(
213✔
639
                                    pf,
213✔
640
                                    &shadow_struct_name,
213✔
641
                                    &facet_bgp,
213✔
642
                                    None,
213✔
643
                                    &facet_crate,
213✔
644
                                )
645
                            })
213✔
646
                            .collect();
144✔
647
                        let kind = quote! { 𝟋Sk::Struct };
144✔
648
                        let variant = gen_variant(
144✔
649
                            &name_token,
144✔
650
                            &discriminant_ts,
144✔
651
                            &variant_attributes,
144✔
652
                            &kind,
144✔
653
                            &quote! { fields },
144✔
654
                            &variant_doc,
144✔
655
                        );
656
                        exprs.push(quote! {{
144✔
657
                            let fields: &'static [𝟋Fld] = &const {[
658
                                #(#field_defs),*
659
                            ]};
660
                            #variant
661
                        }});
662
                    }
663
                }
664
                // Rust-style enums increment discriminant unless explicitly set
665
                discriminant_offset += 1;
571✔
666
            }
667
            let repr_type_ts = enum_repr_ts_from_primitive(*prim);
237✔
668
            (shadow_defs, exprs, repr_type_ts)
237✔
669
        }
670
        PRepr::Transparent => {
671
            return quote! {
×
672
                compile_error!("#[repr(transparent)] is not supported on enums by Facet");
673
            };
674
        }
675
        PRepr::Rust(None) => {
676
            return quote! {
×
677
                compile_error!("Facet requires enums to have an explicit representation (e.g., #[repr(C)], #[repr(u8)])");
678
            };
679
        }
680
        PRepr::RustcWillCatch => {
681
            // rustc will emit an error for the invalid repr (e.g., conflicting hints).
682
            // Return empty TokenStream so we don't add misleading errors.
683
            return quote! {};
×
684
        }
685
    };
686

687
    // Only make static_decl for non-generic enums
688
    let static_decl = if parsed.generics.is_none() {
321✔
689
        generate_static_decl(enum_name, &facet_crate)
311✔
690
    } else {
691
        quote! {}
10✔
692
    };
693

694
    // Set up generics for impl blocks
695
    let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
321✔
696
    let bgp_def = facet_bgp.display_with_bounds();
321✔
697
    let bgp_without_bounds = bgp.display_without_bounds();
321✔
698

699
    let (ty_field, fields) = if opaque {
321✔
700
        (
×
701
            quote! {
×
702
                𝟋Ty::User(𝟋UTy::Opaque)
×
703
            },
×
704
            quote! {},
×
705
        )
×
706
    } else {
707
        // Inline the const block directly into the builder call
708
        (
709
            quote! {
321✔
710
                𝟋Ty::User(𝟋UTy::Enum(
711
                    𝟋ETyB::new(#enum_repr_type_tokenstream, &const {[
712
                        #(#variant_expressions),*
713
                    ]})
714
                        .repr(#repr)
715
                        .build()
716
                ))
717
            },
718
            quote! {},
321✔
719
        )
720
    };
721

722
    // Generate constructor expressions to suppress dead_code warnings on enum variants.
723
    // When variants are constructed via reflection (e.g., facet_args::from_std_args()),
724
    // the compiler doesn't see them being used and warns about dead code.
725
    // This ensures all variants are "constructed" from the compiler's perspective.
726
    // We use explicit type annotations to help inference with const generics and
727
    // unused type parameters.
728
    let variant_constructors: Vec<TokenStream> = pe
321✔
729
        .variants
321✔
730
        .iter()
321✔
731
        .map(|pv| {
887✔
732
            let variant_ident = match &pv.name.raw {
887✔
733
                IdentOrLiteral::Ident(id) => id.clone(),
887✔
734
                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
735
            };
736
            match &pv.kind {
887✔
737
                PVariantKind::Unit => quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident },
344✔
738
                PVariantKind::Tuple { fields } => {
355✔
739
                    let loops = fields.iter().map(|_| quote! { loop {} });
391✔
740
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident(#(#loops),*) }
355✔
741
                }
742
                PVariantKind::Struct { fields } => {
188✔
743
                    let field_inits: Vec<TokenStream> = fields
188✔
744
                        .iter()
188✔
745
                        .map(|pf| {
285✔
746
                            let field_name = match &pf.name.raw {
285✔
747
                                IdentOrLiteral::Ident(id) => id.clone(),
285✔
748
                                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
749
                            };
750
                            quote! { #field_name: loop {} }
285✔
751
                        })
285✔
752
                        .collect();
188✔
753
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident { #(#field_inits),* } }
188✔
754
                }
755
            }
756
        })
887✔
757
        .collect();
321✔
758

759
    // Compute variance - delegate to Shape::computed_variance() at runtime
760
    let variance_call = if opaque {
321✔
761
        // Opaque types don't expose internals, use invariant for safety
NEW
762
        quote! { .variance(#facet_crate::Variance::INVARIANT) }
×
763
    } else {
764
        // Point to Shape::computed_variance - it takes &Shape and walks fields
765
        quote! {
321✔
766
            .variance(#facet_crate::Shape::computed_variance)
767
        }
768
    };
769

770
    // Generate static assertions for declared traits (catches lies at compile time)
771
    // We put this in a generic function outside the const block so it can reference generic parameters
772
    let facet_default = pe.container.attrs.has_builtin("default");
321✔
773
    let trait_assertion_fn = if let Some(bounds) =
321✔
774
        gen_trait_bounds(pe.container.attrs.declared_traits.as_ref(), facet_default)
321✔
775
    {
776
        // Note: where_clauses_tokens already includes "where" keyword if non-empty
777
        // We need to add the trait bounds as an additional constraint
778
        quote! {
×
779
            const _: () = {
780
                #[allow(dead_code, clippy::multiple_bound_locations)]
781
                fn __facet_assert_traits #bgp_def (_: &#enum_name #bgp_without_bounds)
782
                where
783
                    #enum_name #bgp_without_bounds: #bounds
784
                {}
785
            };
786
        }
787
    } else {
788
        quote! {}
321✔
789
    };
790

791
    // Generate the impl
792
    quote! {
321✔
793
        #static_decl
794

795
        // Suppress dead_code warnings for enum variants constructed via reflection.
796
        // See: https://github.com/facet-rs/facet/issues/996
797
        const _: () = {
798
            #[allow(dead_code, unreachable_code, clippy::multiple_bound_locations, clippy::diverging_sub_expression)]
799
            fn __facet_construct_all_variants #bgp_def () -> #enum_name #bgp_without_bounds #where_clauses_tokens {
800
                loop {
801
                    #(#variant_constructors;)*
802
                }
803
            }
804
        };
805

806
        #trait_assertion_fn
807

808
        #[automatically_derived]
809
        #[allow(non_camel_case_types)]
810
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #enum_name #bgp_without_bounds #where_clauses_tokens {
811
            const SHAPE: &'static #facet_crate::Shape = &const {
812
                use #facet_crate::𝟋::*;
813
                #(#shadow_struct_defs)*
814
                #fields
815
                𝟋ShpB::for_sized::<Self>(#type_name_fn, #enum_name_str)
816
                    .vtable(#vtable_init)
817
                    .ty(#ty_field)
818
                    .def(𝟋Def::Undefined)
819
                    #type_params_call
820
                    #doc_call
821
                    #attributes_call
822
                    #type_tag_call
823
                    #proxy_call
824
                    #variance_call
825
                    .build()
826
            };
827
        }
828
    }
829
}
321✔
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