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

facet-rs / facet / 20059345204

09 Dec 2025 09:57AM UTC coverage: 58.683% (-0.4%) from 59.082%
20059345204

push

github

fasterthanlime
Refactor VTable system: Direct/Indirect styles, OxRef/OxMut, builder patterns

Major architectural refactor of the facet vtable system for better code sharing
and reduced binary bloat.

- Remove ThinPtr/WidePtr in favor of PtrConst<'a>/PtrMut<'a>/PtrUninit<'a>
- Add OxRef<'a>/OxMut<'a> - shaped pointers bundling ptr + shape
- Add Ox<'a> enum for ownership tracking (like Cow for shaped pointers)

- VTableDirect: for concrete types, uses raw *const ()/*mut (), returns T directly
- VTableIndirect: for generic containers, uses OxRef/OxMut, returns Option<T>
- VTableErased enum wraps both styles
- Shape::call_* helpers dispatch to either style transparently

- vtable_direct! macro for sized types with compile-time known traits
- VTableIndirect::builder() for generic containers with runtime dispatch
- All container vtables (Array, Option, Result, List, Map, Set) use builders
- No more positional arguments - named builder methods only

- Hash trait is generic over H: Hasher, can't store directly in vtable
- HashProxy type-erases the hasher for vtable storage
- Enables hash support without wrapper function bloat

- impls_core/impls_alloc/impls_std -> impls/core/alloc/std/crates
- New internal/ module for facet's own types (Shape, Def, etc.)
- Cleaner separation of concerns

- Copy, Send, Sync, Eq, Unpin stored as bitflags on Shape
- Set via ShapeBuilder methods: .copy(), .send(), .sync(), .eq()

- ThinPtr -> *const () or PtrConst<'a>
- WidePtr -> OxRef<'a> or OxMut<'a>
- vtable_ref! -> vtable_direct! or VTableIndirect::builder()
- ValueVTable -> VTableDirect or VTableIndirect
- Closures in vtables -> named fn items

WIP: Some impl modules still need migration to new API.

4092 of 8007 new or added lines in 112 files covered. (51.11%)

117 existing lines in 26 files now uncovered.

26173 of 44601 relevant lines covered (58.68%)

635.02 hits per line

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

90.83
/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(
597✔
13
    name: impl quote::ToTokens,
597✔
14
    discriminant: impl quote::ToTokens,
597✔
15
    attributes: Option<impl quote::ToTokens>,
597✔
16
    struct_kind: impl quote::ToTokens,
597✔
17
    fields: impl quote::ToTokens,
597✔
18
    doc: Option<impl quote::ToTokens>,
597✔
19
) -> TokenStream {
597✔
20
    // Only emit .attributes() and .doc() calls when there's actual content
21
    let attributes_call = attributes.map(|a| quote! { .attributes(#a) });
597✔
22
    let doc_call = doc.map(|d| quote! { .doc(#d) });
597✔
23

24
    quote! {
597✔
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
}
597✔
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(
345✔
40
    name: impl quote::ToTokens,
345✔
41
    discriminant: impl quote::ToTokens,
345✔
42
    attributes: Option<impl quote::ToTokens>,
345✔
43
    doc: Option<impl quote::ToTokens>,
345✔
44
) -> TokenStream {
345✔
45
    // Only emit .attributes() and .doc() calls when there's actual content
46
    let attributes_call = attributes.map(|a| quote! { .attributes(#a) });
345✔
47
    let doc_call = doc.map(|d| quote! { .doc(#d) });
345✔
48

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

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

63
    // Emit any collected errors as compile_error! with proper spans
64
    if !pe.container.attrs.errors.is_empty() {
347✔
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
    }
347✔
72

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

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

83
    // Get the facet crate path (custom or default ::facet)
84
    let facet_crate = pe.container.attrs.facet_crate();
347✔
85

86
    let type_name_fn =
347✔
87
        generate_type_name_fn(enum_name, parsed.generics.as_ref(), opaque, &facet_crate);
347✔
88

89
    // Determine trait sources and generate vtable accordingly
90
    // Enums don't support transparent semantics, so pass None
91
    let trait_sources = TraitSources::from_attrs(&pe.container.attrs);
347✔
92
    let bgp_for_vtable = pe.container.bgp.display_without_bounds();
347✔
93
    let enum_type_for_vtable = quote! { #enum_name #bgp_for_vtable };
347✔
94
    let vtable_code = gen_vtable(
347✔
95
        &facet_crate,
347✔
96
        &type_name_fn,
347✔
97
        &trait_sources,
347✔
98
        None,
347✔
99
        &enum_type_for_vtable,
347✔
100
        None, // enums don't support container-level invariants yet
347✔
101
    );
102
    // Note: vtable_code already contains &const { ... } for the VTableDirect,
103
    // no need for an extra const { } wrapper around VTableErased
104
    let vtable_init = vtable_code;
347✔
105

106
    // Generate TypeOps for drop/default/clone operations
107
    // Check if enum has type or const generics (not just lifetimes)
108
    let has_type_or_const_generics = pe.container.bgp.params.iter().any(|p| {
347✔
109
        matches!(
4✔
110
            p.param,
14✔
111
            facet_macro_parse::GenericParamName::Type(_)
112
                | facet_macro_parse::GenericParamName::Const(_)
113
        )
114
    });
14✔
115
    let type_ops_init = gen_type_ops(
347✔
116
        &facet_crate,
347✔
117
        &trait_sources,
347✔
118
        &enum_type_for_vtable,
347✔
119
        has_type_or_const_generics,
347✔
120
    );
121

122
    let bgp = pe.container.bgp.clone();
347✔
123
    // Use the AST directly for where clauses and generics, as PContainer/PEnum doesn't store them
124
    let where_clauses_tokens = build_where_clauses(
347✔
125
        parsed.clauses.as_ref(),
347✔
126
        parsed.generics.as_ref(),
347✔
127
        opaque,
347✔
128
        &facet_crate,
347✔
129
    );
130
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
347✔
131

132
    // Container-level docs - returns builder call only if there are doc comments and doc feature is enabled
133
    #[cfg(feature = "doc")]
134
    let doc_call = match &pe.container.attrs.doc[..] {
347✔
135
        [] => quote! {},
347✔
136
        doc_lines => quote! { .doc(&[#(#doc_lines),*]) },
10✔
137
    };
138
    #[cfg(not(feature = "doc"))]
139
    let doc_call = quote! {};
×
140

141
    // Container attributes - returns builder call only if there are attributes
142
    let attributes_call = {
347✔
143
        let mut attribute_tokens: Vec<TokenStream> = Vec::new();
347✔
144
        for attr in &pe.container.attrs.facet {
347✔
145
            // These attributes are handled specially and not emitted to runtime:
146
            // - crate: sets the facet crate path
147
            // - traits: compile-time directive for vtable generation
148
            // - auto_traits: compile-time directive for vtable generation
149
            if attr.is_builtin() {
67✔
150
                let key = attr.key_str();
66✔
151
                if matches!(key.as_str(), "crate" | "traits" | "auto_traits" | "proxy") {
66✔
152
                    continue;
5✔
153
                }
61✔
154
            }
1✔
155
            // All attributes go through grammar dispatch
156
            let ext_attr = emit_attr(attr, &facet_crate);
62✔
157
            attribute_tokens.push(quote! { #ext_attr });
62✔
158
        }
159

160
        if attribute_tokens.is_empty() {
347✔
161
            quote! {}
293✔
162
        } else {
163
            quote! { .attributes(&const {[#(#attribute_tokens),*]}) }
54✔
164
        }
165
    };
166

167
    // Type tag - returns builder call only if present
168
    let type_tag_call = {
347✔
169
        if let Some(type_tag) = pe.container.attrs.get_builtin_args("type_tag") {
347✔
170
            quote! { .type_tag(#type_tag) }
×
171
        } else {
172
            quote! {}
347✔
173
        }
174
    };
175

176
    // Tag field name for internally/adjacently tagged enums - returns builder call only if present
177
    let tag_call = {
347✔
178
        if let Some(tag) = pe.container.attrs.get_builtin_args("tag") {
347✔
179
            quote! { .tag(#tag) }
18✔
180
        } else {
181
            quote! {}
329✔
182
        }
183
    };
184

185
    // Content field name for adjacently tagged enums - returns builder call only if present
186
    let content_call = {
347✔
187
        if let Some(content) = pe.container.attrs.get_builtin_args("content") {
347✔
188
            quote! { .content(#content) }
8✔
189
        } else {
190
            quote! {}
339✔
191
        }
192
    };
193

194
    // Untagged flag - returns builder call only if present
195
    let untagged_call = {
347✔
196
        if pe.container.attrs.has_builtin("untagged") {
347✔
197
            quote! { .untagged() }
31✔
198
        } else {
199
            quote! {}
316✔
200
        }
201
    };
202

203
    // Container-level proxy from PEnum - generates ProxyDef with conversion functions
204
    let proxy_call = {
347✔
205
        if let Some(attr) = pe
347✔
206
            .container
347✔
207
            .attrs
347✔
208
            .facet
347✔
209
            .iter()
347✔
210
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
347✔
211
        {
212
            let proxy_type = &attr.args;
1✔
213
            let enum_type = &enum_name;
1✔
214
            let bgp_display = pe.container.bgp.display_without_bounds();
1✔
215

216
            quote! {
1✔
217
                .proxy(&const {
218
                    extern crate alloc as __alloc;
219

220
                    unsafe fn __proxy_convert_in(
221
                        proxy_ptr: #facet_crate::PtrConst,
222
                        field_ptr: #facet_crate::PtrUninit,
223
                    ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
224
                        let proxy: #proxy_type = proxy_ptr.read();
225
                        match <#enum_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
226
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
227
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
228
                        }
229
                    }
230

231
                    unsafe fn __proxy_convert_out(
232
                        field_ptr: #facet_crate::PtrConst,
233
                        proxy_ptr: #facet_crate::PtrUninit,
234
                    ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
235
                        let field_ref: &#enum_type #bgp_display = field_ptr.get();
236
                        match <#proxy_type as ::core::convert::TryFrom<&#enum_type #bgp_display>>::try_from(field_ref) {
237
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
238
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
239
                        }
240
                    }
241

242
                    #facet_crate::ProxyDef {
243
                        shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
244
                        convert_in: __proxy_convert_in,
245
                        convert_out: __proxy_convert_out,
246
                    }
247
                })
248
            }
249
        } else {
250
            quote! {}
346✔
251
        }
252
    };
253

254
    // Determine enum repr (already resolved by PEnum::parse())
255
    let valid_repr = &pe.repr;
347✔
256

257
    // Are these relevant for enums? Or is it always `repr(C)` if a `PrimitiveRepr` is present?
258
    let repr = match &valid_repr {
347✔
259
        PRepr::Transparent => unreachable!("this should be caught by PRepr::parse"),
×
260
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
253✔
261
        PRepr::C(_) => quote! { 𝟋Repr::C },
94✔
262
        PRepr::RustcWillCatch => {
263
            // rustc will emit the error - return empty TokenStream
264
            return quote! {};
×
265
        }
266
    };
267

268
    // Helper for EnumRepr TS (token stream) generation for primitives
269
    // Uses prelude alias 𝟋ERpr for compact output
270
    let enum_repr_ts_from_primitive = |primitive_repr: PrimitiveRepr| -> TokenStream {
347✔
271
        let type_name_str = primitive_repr.type_name().to_string();
255✔
272
        let enum_repr_variant_ident = format_ident!("{}", type_name_str.to_uppercase());
255✔
273
        quote! { 𝟋ERpr::#enum_repr_variant_ident }
255✔
274
    };
255✔
275

276
    // --- Processing code for shadow struct/fields/variant_expressions ---
277
    // A. C-style enums have shadow-discriminant, shadow-union, shadow-struct
278
    // B. Primitive enums have simpler layout.
279
    let (shadow_struct_defs, variant_expressions, enum_repr_type_tokenstream) = match valid_repr {
347✔
280
        PRepr::C(prim_opt) => {
94✔
281
            // Shadow discriminant
282
            let shadow_discriminant_name = quote::format_ident!("_D");
94✔
283
            let all_variant_names: Vec<Ident> = pe
94✔
284
                .variants
94✔
285
                .iter()
94✔
286
                .map(|pv| match &pv.name.raw {
340✔
287
                    IdentOrLiteral::Ident(id) => id.clone(),
340✔
288
                    IdentOrLiteral::Literal(n) => format_ident!("_{}", n), // Should not happen for enums
×
289
                })
340✔
290
                .collect();
94✔
291

292
            let repr_attr_content = match prim_opt {
94✔
293
                Some(p) => p.type_name(),
2✔
294
                None => quote! { C },
92✔
295
            };
296
            let mut shadow_defs = vec![quote! {
94✔
297
                #[repr(#repr_attr_content)]
298
                #[allow(dead_code)]
299
                enum #shadow_discriminant_name { #(#all_variant_names),* }
300
            }];
301

302
            // Shadow union
303
            let shadow_union_name = quote::format_ident!("_U");
94✔
304
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
94✔
305
            let bgp_with_bounds = facet_bgp.display_with_bounds();
94✔
306
            let bgp_without_bounds = facet_bgp.display_without_bounds();
94✔
307
            let phantom_data = facet_bgp.display_as_phantom_data();
94✔
308
            let all_union_fields: Vec<TokenStream> = pe.variants.iter().map(|pv| {
340✔
309
                // Each field is named after the variant, struct for its fields.
310
                let variant_ident = match &pv.name.raw {
340✔
311
                    IdentOrLiteral::Ident(id) => id.clone(),
340✔
312
                     IdentOrLiteral::Literal(idx) => format_ident!("_{}", idx), // Should not happen
×
313
                };
314
                let shadow_field_name_ident = quote::format_ident!("_F{}", variant_ident);
340✔
315
                quote! {
340✔
316
                    #variant_ident: ::core::mem::ManuallyDrop<#shadow_field_name_ident #bgp_without_bounds>
317
                }
318
            }).collect();
340✔
319

320
            shadow_defs.push(quote! {
94✔
321
                #[repr(C)]
322
                #[allow(non_snake_case, dead_code)]
323
                union #shadow_union_name #bgp_with_bounds #where_clauses_tokens { #(#all_union_fields),* }
324
            });
325

326
            // Shadow repr struct for enum as a whole
327
            let shadow_repr_name = quote::format_ident!("_R");
94✔
328
            shadow_defs.push(quote! {
94✔
329
                #[repr(C)]
330
                #[allow(non_snake_case)]
331
                #[allow(dead_code)]
332
                struct #shadow_repr_name #bgp_with_bounds #where_clauses_tokens {
333
                    _discriminant: #shadow_discriminant_name,
334
                    _phantom: #phantom_data,
335
                    _fields: #shadow_union_name #bgp_without_bounds,
336
                }
337
            });
338

339
            // Generate variant_expressions
340
            let mut discriminant: Option<&TokenStream> = None;
94✔
341
            let mut discriminant_offset: i64 = 0;
94✔
342
            let mut exprs = Vec::new();
94✔
343

344
            for pv in pe.variants.iter() {
340✔
345
                if let Some(dis) = &pv.discriminant {
340✔
346
                    discriminant = Some(dis);
×
347
                    discriminant_offset = 0;
×
348
                }
340✔
349

350
                // Only cast to i64 when we have a user-provided discriminant expression
351
                let discriminant_ts = if let Some(discriminant) = discriminant {
340✔
352
                    if discriminant_offset > 0 {
×
NEW
353
                        let offset_lit = Literal::i64_unsuffixed(discriminant_offset);
×
NEW
354
                        quote! { (#discriminant + #offset_lit) as i64 }
×
355
                    } else {
NEW
356
                        quote! { #discriminant as i64 }
×
357
                    }
358
                } else {
359
                    // Simple unsuffixed literal
360
                    let lit = Literal::i64_unsuffixed(discriminant_offset);
340✔
361
                    quote! { #lit }
340✔
362
                };
363

364
                let display_name = pv.name.effective.clone();
340✔
365
                let name_token = TokenTree::Literal(Literal::string(&display_name));
340✔
366
                let variant_attributes: Option<TokenStream> = if pv.attrs.facet.is_empty() {
340✔
367
                    None
331✔
368
                } else {
369
                    let attrs_list: Vec<TokenStream> = pv
9✔
370
                        .attrs
9✔
371
                        .facet
9✔
372
                        .iter()
9✔
373
                        .map(|attr| {
9✔
374
                            let ext_attr = emit_attr(attr, &facet_crate);
9✔
375
                            quote! { #ext_attr }
9✔
376
                        })
9✔
377
                        .collect();
9✔
378
                    Some(quote! { &const {[#(#attrs_list),*]} })
9✔
379
                };
380

381
                #[cfg(feature = "doc")]
382
                let variant_doc: Option<TokenStream> = match &pv.attrs.doc[..] {
340✔
383
                    [] => None,
340✔
NEW
384
                    doc_lines => Some(quote! { &[#(#doc_lines),*] }),
×
385
                };
386
                #[cfg(not(feature = "doc"))]
NEW
387
                let variant_doc: Option<TokenStream> = None;
×
388

389
                let shadow_struct_name = match &pv.name.raw {
340✔
390
                    IdentOrLiteral::Ident(id) => quote::format_ident!("_F{}", id),
340✔
391
                    IdentOrLiteral::Literal(idx) => quote::format_ident!("_F{}", idx),
×
392
                };
393

394
                let variant_offset = quote! {
340✔
395
                    ::core::mem::offset_of!(#shadow_repr_name #bgp_without_bounds, _fields)
396
                };
397

398
                // Determine field structure for the variant
399
                match &pv.kind {
340✔
400
                    PVariantKind::Unit => {
156✔
401
                        // Generate unit shadow struct for the variant
156✔
402
                        shadow_defs.push(quote! {
156✔
403
                            #[repr(C)]
156✔
404
                            #[allow(non_snake_case, dead_code)]
156✔
405
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens { _phantom: #phantom_data }
156✔
406
                        });
156✔
407
                        let variant = gen_unit_variant(
156✔
408
                            &name_token,
156✔
409
                            &discriminant_ts,
156✔
410
                            variant_attributes.as_ref(),
156✔
411
                            variant_doc.as_ref(),
156✔
412
                        );
156✔
413
                        exprs.push(variant);
156✔
414
                    }
156✔
415
                    PVariantKind::Tuple { fields } => {
125✔
416
                        // Tuple shadow struct
417
                        let fields_with_types: Vec<TokenStream> = fields
125✔
418
                            .iter()
125✔
419
                            .enumerate()
125✔
420
                            .map(|(idx, pf)| {
137✔
421
                                let field_ident = format_ident!("_{}", idx);
137✔
422
                                let typ = &pf.ty;
137✔
423
                                quote! { #field_ident: #typ }
137✔
424
                            })
137✔
425
                            .collect();
125✔
426
                        shadow_defs.push(quote! {
125✔
427
                            #[repr(C)]
428
                            #[allow(non_snake_case, dead_code)]
429
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
430
                                #(#fields_with_types),* ,
431
                                _phantom: #phantom_data
432
                            }
433
                        });
434
                        let field_defs: Vec<TokenStream> = fields
125✔
435
                            .iter()
125✔
436
                            .enumerate()
125✔
437
                            .map(|(idx, pf)| {
137✔
438
                                let mut pfield = pf.clone();
137✔
439
                                let field_ident = format_ident!("_{}", idx);
137✔
440
                                pfield.name.raw = IdentOrLiteral::Ident(field_ident);
137✔
441
                                gen_field_from_pfield(
137✔
442
                                    &pfield,
137✔
443
                                    &shadow_struct_name,
137✔
444
                                    &facet_bgp,
137✔
445
                                    Some(variant_offset.clone()),
137✔
446
                                    &facet_crate,
137✔
447
                                )
448
                            })
137✔
449
                            .collect();
125✔
450
                        let kind = quote! { 𝟋Sk::Tuple };
125✔
451
                        let variant = gen_variant(
125✔
452
                            &name_token,
125✔
453
                            &discriminant_ts,
125✔
454
                            variant_attributes.as_ref(),
125✔
455
                            &kind,
125✔
456
                            &quote! { fields },
125✔
457
                            variant_doc.as_ref(),
125✔
458
                        );
459
                        exprs.push(quote! {{
125✔
460
                            let fields: &'static [𝟋Fld] = &const {[
461
                                #(#field_defs),*
462
                            ]};
463
                            #variant
464
                        }});
465
                    }
466
                    PVariantKind::Struct { fields } => {
59✔
467
                        let fields_with_types: Vec<TokenStream> = fields
59✔
468
                            .iter()
59✔
469
                            .map(|pf| {
93✔
470
                                // Use raw name for struct field definition
471
                                let field_name = match &pf.name.raw {
93✔
472
                                    IdentOrLiteral::Ident(id) => quote! { #id },
93✔
473
                                    IdentOrLiteral::Literal(_) => {
474
                                        panic!("Struct variant cannot have literal field names")
×
475
                                    }
476
                                };
477
                                let typ = &pf.ty;
93✔
478
                                quote! { #field_name: #typ }
93✔
479
                            })
93✔
480
                            .collect();
59✔
481

482
                        // Handle empty fields case explicitly
483
                        let struct_fields = if fields_with_types.is_empty() {
59✔
484
                            // Only add phantom data for empty struct variants
485
                            quote! { _phantom: #phantom_data }
×
486
                        } else {
487
                            // Add fields plus phantom data for non-empty struct variants
488
                            quote! { #(#fields_with_types),*, _phantom: #phantom_data }
59✔
489
                        };
490
                        shadow_defs.push(quote! {
59✔
491
                            #[repr(C)]
492
                            #[allow(non_snake_case, dead_code)]
493
                            struct #shadow_struct_name #bgp_with_bounds #where_clauses_tokens {
494
                                #struct_fields
495
                            }
496
                        });
497

498
                        let field_defs: Vec<TokenStream> = fields
59✔
499
                            .iter()
59✔
500
                            .map(|pf| {
93✔
501
                                gen_field_from_pfield(
93✔
502
                                    pf,
93✔
503
                                    &shadow_struct_name,
93✔
504
                                    &facet_bgp,
93✔
505
                                    Some(variant_offset.clone()),
93✔
506
                                    &facet_crate,
93✔
507
                                )
508
                            })
93✔
509
                            .collect();
59✔
510

511
                        let kind = quote! { 𝟋Sk::Struct };
59✔
512
                        let variant = gen_variant(
59✔
513
                            &name_token,
59✔
514
                            &discriminant_ts,
59✔
515
                            variant_attributes.as_ref(),
59✔
516
                            &kind,
59✔
517
                            &quote! { fields },
59✔
518
                            variant_doc.as_ref(),
59✔
519
                        );
520
                        exprs.push(quote! {{
59✔
521
                            let fields: &'static [𝟋Fld] = &const {[
522
                                #(#field_defs),*
523
                            ]};
524
                            #variant
525
                        }});
526
                    }
527
                };
528

529
                // C-style enums increment discriminant unless explicitly set
530
                discriminant_offset += 1;
340✔
531
            }
532

533
            // Generate the EnumRepr token stream (uses prelude alias 𝟋ERpr)
534
            let repr_type_ts = match prim_opt {
94✔
535
                None => {
536
                    quote! { 𝟋ERpr::from_discriminant_size::<#shadow_discriminant_name>() }
92✔
537
                }
538
                Some(p) => enum_repr_ts_from_primitive(*p),
2✔
539
            };
540

541
            (shadow_defs, exprs, repr_type_ts)
94✔
542
        }
543
        PRepr::Rust(Some(prim)) => {
253✔
544
            // Treat as primitive repr
545
            let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
253✔
546
            let bgp_with_bounds = facet_bgp.display_with_bounds();
253✔
547
            let phantom_data = facet_bgp.display_as_phantom_data();
253✔
548
            let discriminant_rust_type = prim.type_name();
253✔
549
            let mut shadow_defs = Vec::new();
253✔
550

551
            // Generate variant_expressions
552
            let mut discriminant: Option<&TokenStream> = None;
253✔
553
            let mut discriminant_offset: i64 = 0;
253✔
554

555
            let mut exprs = Vec::new();
253✔
556

557
            for pv in pe.variants.iter() {
602✔
558
                if let Some(dis) = &pv.discriminant {
602✔
559
                    discriminant = Some(dis);
26✔
560
                    discriminant_offset = 0;
26✔
561
                }
576✔
562

563
                // Only cast to i64 when we have a user-provided discriminant expression
564
                let discriminant_ts = if let Some(discriminant) = discriminant {
602✔
565
                    if discriminant_offset > 0 {
26✔
NEW
566
                        let offset_lit = Literal::i64_unsuffixed(discriminant_offset);
×
NEW
567
                        quote! { (#discriminant + #offset_lit) as i64 }
×
568
                    } else {
569
                        quote! { #discriminant as i64 }
26✔
570
                    }
571
                } else {
572
                    // Simple unsuffixed literal
573
                    let lit = Literal::i64_unsuffixed(discriminant_offset);
576✔
574
                    quote! { #lit }
576✔
575
                };
576

577
                let display_name = pv.name.effective.clone();
602✔
578
                let name_token = TokenTree::Literal(Literal::string(&display_name));
602✔
579
                let variant_attributes: Option<TokenStream> = if pv.attrs.facet.is_empty() {
602✔
580
                    None
587✔
581
                } else {
582
                    let attrs_list: Vec<TokenStream> = pv
15✔
583
                        .attrs
15✔
584
                        .facet
15✔
585
                        .iter()
15✔
586
                        .map(|attr| {
15✔
587
                            let ext_attr = emit_attr(attr, &facet_crate);
15✔
588
                            quote! { #ext_attr }
15✔
589
                        })
15✔
590
                        .collect();
15✔
591
                    Some(quote! { &const {[#(#attrs_list),*]} })
15✔
592
                };
593

594
                #[cfg(feature = "doc")]
595
                let variant_doc: Option<TokenStream> = match &pv.attrs.doc[..] {
602✔
596
                    [] => None,
602✔
597
                    doc_lines => Some(quote! { &[#(#doc_lines),*] }),
27✔
598
                };
599
                #[cfg(not(feature = "doc"))]
NEW
600
                let variant_doc: Option<TokenStream> = None;
×
601

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

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

761
    // Set up generics for impl blocks
762
    let facet_bgp = bgp.with_lifetime(LifetimeName(format_ident!("ʄ")));
347✔
763
    let bgp_def = facet_bgp.display_with_bounds();
347✔
764
    let bgp_without_bounds = bgp.display_without_bounds();
347✔
765

766
    let (ty_field, fields) = if opaque {
347✔
767
        (
×
768
            quote! {
×
769
                𝟋Ty::User(𝟋UTy::Opaque)
×
770
            },
×
771
            quote! {},
×
772
        )
×
773
    } else {
774
        // Inline the const block directly into the builder call
775
        (
776
            quote! {
347✔
777
                𝟋Ty::User(𝟋UTy::Enum(
778
                    𝟋ETyB::new(#enum_repr_type_tokenstream, &const {[
779
                        #(#variant_expressions),*
780
                    ]})
781
                        .repr(#repr)
782
                        .build()
783
                ))
784
            },
785
            quote! {},
347✔
786
        )
787
    };
788

789
    // Generate constructor expressions to suppress dead_code warnings on enum variants.
790
    // When variants are constructed via reflection (e.g., facet_args::from_std_args()),
791
    // the compiler doesn't see them being used and warns about dead code.
792
    // This ensures all variants are "constructed" from the compiler's perspective.
793
    // We use explicit type annotations to help inference with const generics and
794
    // unused type parameters.
795
    let variant_constructors: Vec<TokenStream> = pe
347✔
796
        .variants
347✔
797
        .iter()
347✔
798
        .map(|pv| {
942✔
799
            let variant_ident = match &pv.name.raw {
942✔
800
                IdentOrLiteral::Ident(id) => id.clone(),
942✔
801
                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
802
            };
803
            match &pv.kind {
942✔
804
                PVariantKind::Unit => quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident },
345✔
805
                PVariantKind::Tuple { fields } => {
393✔
806
                    let loops = fields.iter().map(|_| quote! { loop {} });
442✔
807
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident(#(#loops),*) }
393✔
808
                }
809
                PVariantKind::Struct { fields } => {
204✔
810
                    let field_inits: Vec<TokenStream> = fields
204✔
811
                        .iter()
204✔
812
                        .map(|pf| {
308✔
813
                            let field_name = match &pf.name.raw {
308✔
814
                                IdentOrLiteral::Ident(id) => id.clone(),
308✔
815
                                IdentOrLiteral::Literal(n) => format_ident!("_{}", n),
×
816
                            };
817
                            quote! { #field_name: loop {} }
308✔
818
                        })
308✔
819
                        .collect();
204✔
820
                    quote! { let _: #enum_name #bgp_without_bounds = #enum_name::#variant_ident { #(#field_inits),* } }
204✔
821
                }
822
            }
823
        })
942✔
824
        .collect();
347✔
825

826
    // Compute variance - delegate to Shape::computed_variance() at runtime
827
    let variance_call = if opaque {
347✔
828
        // Opaque types don't expose internals, use invariant for safety
NEW
829
        quote! { .variance(𝟋Vnc::INVARIANT) }
×
830
    } else {
831
        // Point to Shape::computed_variance - it takes &Shape and walks fields
832
        quote! { .variance(𝟋CV) }
347✔
833
    };
834

835
    // TypeOps for drop, default, clone - convert Option<TokenStream> to a call
836
    let type_ops_call = match type_ops_init {
347✔
837
        Some(ops) => quote! { .type_ops(#ops) },
347✔
NEW
838
        None => quote! {},
×
839
    };
840

841
    // Type name function - for generic types, this formats with type parameters
842
    let type_name_call = if parsed.generics.is_some() && !opaque {
347✔
843
        quote! { .type_name(#type_name_fn) }
10✔
844
    } else {
845
        quote! {}
337✔
846
    };
847

848
    // Generate static assertions for declared traits (catches lies at compile time)
849
    // We put this in a generic function outside the const block so it can reference generic parameters
850
    let facet_default = pe.container.attrs.has_builtin("default");
347✔
851
    let trait_assertion_fn = if let Some(bounds) =
347✔
852
        gen_trait_bounds(pe.container.attrs.declared_traits.as_ref(), facet_default)
347✔
853
    {
854
        // Note: where_clauses_tokens already includes "where" keyword if non-empty
855
        // We need to add the trait bounds as an additional constraint
856
        quote! {
×
857
            const _: () = {
858
                #[allow(dead_code, clippy::multiple_bound_locations)]
859
                fn __facet_assert_traits #bgp_def (_: &#enum_name #bgp_without_bounds)
860
                where
861
                    #enum_name #bgp_without_bounds: #bounds
862
                {}
863
            };
864
        }
865
    } else {
866
        quote! {}
347✔
867
    };
868

869
    // Static declaration for release builds (pre-evaluates SHAPE)
870
    let static_decl = crate::derive::generate_static_decl(enum_name, &facet_crate);
347✔
871

872
    // Generate the impl
873
    quote! {
347✔
874
        // Suppress dead_code warnings for enum variants constructed via reflection.
875
        // See: https://github.com/facet-rs/facet/issues/996
876
        const _: () = {
877
            #[allow(dead_code, unreachable_code, clippy::multiple_bound_locations, clippy::diverging_sub_expression)]
878
            fn __facet_construct_all_variants #bgp_def () -> #enum_name #bgp_without_bounds #where_clauses_tokens {
879
                loop {
880
                    #(#variant_constructors;)*
881
                }
882
            }
883
        };
884

885
        #trait_assertion_fn
886

887
        #[automatically_derived]
888
        #[allow(non_camel_case_types)]
889
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #enum_name #bgp_without_bounds #where_clauses_tokens {
890
            const SHAPE: &'static #facet_crate::Shape = &const {
891
                use #facet_crate::𝟋::*;
892
                #(#shadow_struct_defs)*
893
                #fields
894
                𝟋ShpB::for_sized::<Self>(#enum_name_str)
895
                    .vtable(#vtable_init)
896
                    #type_ops_call
897
                    .ty(#ty_field)
898
                    .def(𝟋Def::Undefined)
899
                    #type_params_call
900
                    #type_name_call
901
                    #doc_call
902
                    #attributes_call
903
                    #type_tag_call
904
                    #tag_call
905
                    #content_call
906
                    #untagged_call
907
                    #proxy_call
908
                    #variance_call
909
                    .build()
910
            };
911
        }
912

913
        #static_decl
914
    }
915
}
347✔
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