• 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.08
/facet-macros-impl/src/process_struct.rs
1
//! Struct processing and vtable generation for the Facet derive macro.
2
//!
3
//! # Vtable Trait Detection
4
//!
5
//! The vtable contains function pointers for various trait implementations (Debug, Clone,
6
//! PartialEq, etc.). There are three ways these can be populated:
7
//!
8
//! ## 1. Derive Detection (Fastest - No Specialization)
9
//!
10
//! When `#[derive(Debug, Clone, Facet)]` is used, the macro can see the other derives
11
//! and knows those traits are implemented. It generates direct function pointers without
12
//! any specialization overhead.
13
//!
14
//! ```ignore
15
//! #[derive(Debug, Clone, Facet)]  // Debug and Clone detected from derives
16
//! struct Foo { ... }
17
//! ```
18
//!
19
//! ## 2. Explicit Declaration (No Specialization)
20
//!
21
//! For traits that are implemented manually (not via derive), use `#[facet(traits(...))]`:
22
//!
23
//! ```ignore
24
//! #[derive(Facet)]
25
//! #[facet(traits(Debug, PartialEq))]  // Explicit declaration
26
//! struct Foo { ... }
27
//!
28
//! impl Debug for Foo { ... }  // Manual implementation
29
//! impl PartialEq for Foo { ... }
30
//! ```
31
//!
32
//! This generates compile-time assertions to verify the traits are actually implemented.
33
//!
34
//! ## 3. Auto-Detection (Uses Specialization)
35
//!
36
//! For backward compatibility or when you don't want to list traits manually, use
37
//! `#[facet(auto_traits)]`. This uses the `impls!` macro to detect traits at compile
38
//! time via specialization tricks:
39
//!
40
//! ```ignore
41
//! #[derive(Debug, Facet)]
42
//! #[facet(auto_traits)]  // Auto-detect all other traits
43
//! struct Foo { ... }
44
//! ```
45
//!
46
//! **Note:** Auto-detection is slower to compile because it generates specialization
47
//! code for each trait. Use derive detection or explicit declaration when possible.
48
//!
49
//! ## Layered Resolution
50
//!
51
//! For each vtable entry, the macro checks sources in order:
52
//! 1. Is the trait in `#[derive(...)]`? → Use direct impl
53
//! 2. Is the trait in `#[facet(traits(...))]`? → Use direct impl
54
//! 3. Is `#[facet(auto_traits)]` present? → Use `impls!` detection
55
//! 4. Otherwise → Set to `None`
56
//!
57
//! Note: `#[facet(traits(...))]` and `#[facet(auto_traits)]` are mutually exclusive.
58
//! You can combine derives with either one:
59
//! ```ignore
60
//! #[derive(Debug, Clone, Facet)]  // Debug, Clone detected from derives
61
//! #[facet(traits(Display))]       // Display declared explicitly (manual impl)
62
//! struct Foo { ... }
63
//! ```
64

65
use quote::{format_ident, quote, quote_spanned};
66

67
use super::*;
68

69
/// Information about transparent type for vtable generation.
70
///
71
/// This is used to generate `try_borrow_inner` functions for transparent/newtype wrappers.
72
pub(crate) struct TransparentInfo<'a> {
73
    /// The inner field type (for tuple struct with one field)
74
    pub inner_field_type: Option<&'a TokenStream>,
75
    /// Whether the inner field is opaque
76
    pub inner_is_opaque: bool,
77
    /// Whether this is a ZST (unit-like transparent struct)
78
    pub is_zst: bool,
79
}
80

81
/// Sources of trait information for vtable generation.
82
///
83
/// The vtable generation uses a layered approach:
84
/// 1. **Derives** - traits detected from `#[derive(...)]` next to `#[derive(Facet)]`
85
/// 2. **Declared** - traits explicitly listed in `#[facet(traits(...))]`
86
/// 3. **Implied** - traits implied by other attributes (e.g., `#[facet(default)]` implies Default)
87
/// 4. **Auto** - if `#[facet(auto_traits)]` is present, use `impls!` for remaining traits
88
/// 5. **None** - if none of the above apply, emit `None` for that trait
89
pub(crate) struct TraitSources<'a> {
90
    /// Traits detected from #[derive(...)] attributes
91
    pub known_derives: &'a KnownDerives,
92
    /// Traits explicitly declared via #[facet(traits(...))]
93
    pub declared_traits: Option<&'a DeclaredTraits>,
94
    /// Whether to auto-detect remaining traits via specialization
95
    pub auto_traits: bool,
96
    /// Whether `#[facet(default)]` is present (implies Default trait)
97
    pub facet_default: bool,
98
}
99

100
impl<'a> TraitSources<'a> {
101
    /// Create trait sources from parsed attributes
102
    pub fn from_attrs(attrs: &'a PAttrs) -> Self {
2,188✔
103
        Self {
2,188✔
104
            known_derives: &attrs.known_derives,
2,188✔
105
            declared_traits: attrs.declared_traits.as_ref(),
2,188✔
106
            auto_traits: attrs.auto_traits,
2,188✔
107
            facet_default: attrs.has_builtin("default"),
2,188✔
108
        }
2,188✔
109
    }
2,188✔
110

111
    /// Check if a trait is known from derives
112
    fn has_derive(&self, check: impl FnOnce(&KnownDerives) -> bool) -> bool {
15,316✔
113
        check(self.known_derives)
15,316✔
114
    }
15,316✔
115

116
    /// Check if a trait is explicitly declared
117
    fn has_declared(&self, check: impl FnOnce(&DeclaredTraits) -> bool) -> bool {
17,504✔
118
        self.declared_traits.is_some_and(check)
17,504✔
119
    }
17,504✔
120

121
    /// Check if we should use auto-detection for this trait
122
    fn should_auto(&self) -> bool {
4,529✔
123
        self.auto_traits
4,529✔
124
    }
4,529✔
125
}
126

127
/// Generates the vtable for a type based on trait sources.
128
///
129
/// Uses a layered approach for each trait:
130
/// 1. If known from derives → direct impl (no specialization)
131
/// 2. If explicitly declared → direct impl (no specialization)
132
/// 3. If auto_traits enabled → use `impls!` macro for detection
133
/// 4. Otherwise → None
134
///
135
/// When `auto_traits` is NOT enabled, generates `ValueVTableThinInstant` using
136
/// helper functions like `debug_for::<Self>()`. This avoids closures that would
137
/// require `T: 'static` bounds.
138
///
139
/// When `auto_traits` IS enabled, falls back to `ValueVTable::builder()` pattern
140
/// (ThinDelayed) which uses closures for runtime trait detection.
141
pub(crate) fn gen_vtable(
2,188✔
142
    facet_crate: &TokenStream,
2,188✔
143
    type_name_fn: &TokenStream,
2,188✔
144
    sources: &TraitSources<'_>,
2,188✔
145
    transparent: Option<&TransparentInfo<'_>>,
2,188✔
146
    struct_type: &TokenStream,
2,188✔
147
    invariants_fn: Option<&TokenStream>,
2,188✔
148
) -> TokenStream {
2,188✔
149
    // If auto_traits is enabled, use VTableIndirect with runtime trait detection.
150
    if sources.auto_traits {
2,188✔
151
        return gen_vtable_indirect(
29✔
152
            facet_crate,
29✔
153
            type_name_fn,
29✔
154
            sources,
29✔
155
            struct_type,
29✔
156
            invariants_fn,
29✔
157
        );
158
    }
2,159✔
159

160
    // Otherwise, use VTableDirect with compile-time trait resolution.
161
    gen_vtable_direct(
2,159✔
162
        facet_crate,
2,159✔
163
        type_name_fn,
2,159✔
164
        sources,
2,159✔
165
        transparent,
2,159✔
166
        struct_type,
2,159✔
167
        invariants_fn,
2,159✔
168
    )
169
}
2,188✔
170

171
/// Generates a VTableDirect using direct trait method references.
172
/// This avoids closures and uses the pattern from vtable_direct! macro.
173
/// Uses `Self` inside the const block, which properly resolves to the implementing type
174
/// without requiring that lifetime parameters outlive 'static.
175
fn gen_vtable_direct(
2,159✔
176
    facet_crate: &TokenStream,
2,159✔
177
    _type_name_fn: &TokenStream,
2,159✔
178
    sources: &TraitSources<'_>,
2,159✔
179
    transparent: Option<&TransparentInfo<'_>>,
2,159✔
180
    struct_type: &TokenStream,
2,159✔
181
    invariants_fn: Option<&TokenStream>,
2,159✔
182
) -> TokenStream {
2,159✔
183
    // Display: no standard derive, only check declared
184
    let display_call = if sources.has_declared(|d| d.display) {
2,159✔
NEW
185
        quote! { .display(<Self as ::core::fmt::Display>::fmt) }
×
186
    } else {
187
        quote! {}
2,159✔
188
    };
189

190
    // Debug: check derive, then declared
191
    let debug_call = if sources.has_derive(|d| d.debug) || sources.has_declared(|d| d.debug) {
2,159✔
192
        quote! { .debug(<Self as ::core::fmt::Debug>::fmt) }
2✔
193
    } else {
194
        quote! {}
2,157✔
195
    };
196

197
    // PartialEq: check derive, then declared
198
    let partial_eq_call =
2,159✔
199
        if sources.has_derive(|d| d.partial_eq) || sources.has_declared(|d| d.partial_eq) {
2,159✔
NEW
200
            quote! { .partial_eq(<Self as ::core::cmp::PartialEq>::eq) }
×
201
        } else {
202
            quote! {}
2,159✔
203
        };
204

205
    // PartialOrd: check derive, then declared
206
    let partial_ord_call =
2,159✔
207
        if sources.has_derive(|d| d.partial_ord) || sources.has_declared(|d| d.partial_ord) {
2,159✔
NEW
208
            quote! { .partial_cmp(<Self as ::core::cmp::PartialOrd>::partial_cmp) }
×
209
        } else {
210
            quote! {}
2,159✔
211
        };
212

213
    // Ord: check derive, then declared
214
    let ord_call = if sources.has_derive(|d| d.ord) || sources.has_declared(|d| d.ord) {
2,159✔
NEW
215
        quote! { .cmp(<Self as ::core::cmp::Ord>::cmp) }
×
216
    } else {
217
        quote! {}
2,159✔
218
    };
219

220
    // Hash: check derive, then declared
221
    let hash_call = if sources.has_derive(|d| d.hash) || sources.has_declared(|d| d.hash) {
2,159✔
NEW
222
        quote! { .hash(<Self as ::core::hash::Hash>::hash::<#facet_crate::HashProxy>) }
×
223
    } else {
224
        quote! {}
2,159✔
225
    };
226

227
    // Transparent type functions: try_borrow_inner
228
    // For transparent types (newtypes), we generate a function to borrow the inner value
229
    // Note: We still need the concrete struct_type here because we're dealing with field access
230
    let try_borrow_inner_call = if let Some(info) = transparent {
2,159✔
231
        if info.inner_is_opaque {
47✔
232
            // Opaque inner field - no borrow possible
233
            quote! {}
2✔
234
        } else if info.is_zst {
45✔
235
            // ZST case - no inner value to borrow
NEW
236
            quote! {}
×
237
        } else if let Some(inner_ty) = info.inner_field_type {
45✔
238
            // Transparent struct with one field - generate try_borrow_inner
239
            // The function signature for VTableDirect is: unsafe fn(*const T) -> Result<Ptr, String>
240
            quote! {
45✔
241
                .try_borrow_inner({
242
                    unsafe fn __try_borrow_inner(src: *const #struct_type) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
243
                        // src points to the wrapper (tuple struct), field 0 is the inner value
244
                        // We cast away const because try_borrow_inner returns PtrMut for flexibility
245
                        // (caller can downgrade to PtrConst if needed)
246
                        let wrapper_ptr = src as *mut #struct_type;
247
                        let inner_ptr: *mut #inner_ty = unsafe { &raw mut (*wrapper_ptr).0 };
248
                        ::core::result::Result::Ok(#facet_crate::PtrMut::new(inner_ptr as *mut u8))
249
                    }
250
                    __try_borrow_inner
251
                })
252
            }
253
        } else {
NEW
254
            quote! {}
×
255
        }
256
    } else {
257
        quote! {}
2,112✔
258
    };
259

260
    // Invariants: container-level invariants function
261
    let invariants_call = if let Some(inv_fn) = invariants_fn {
2,159✔
262
        quote! { .invariants(#inv_fn) }
3✔
263
    } else {
264
        quote! {}
2,156✔
265
    };
266

267
    // Generate VTableErased::Direct with a static VTableDirect
268
    // Uses prelude aliases for compact output (𝟋VtE, 𝟋VtD)
269
    // NOTE: drop_in_place, default_in_place, clone_into are now in TypeOps, not VTable
270
    quote! {
2,159✔
271
        𝟋VtE::Direct(&const {
272
            𝟋VtD::builder_for::<Self>()
273
                #display_call
274
                #debug_call
275
                #partial_eq_call
276
                #partial_ord_call
277
                #ord_call
278
                #hash_call
279
                #invariants_call
280
                #try_borrow_inner_call
281
                .build()
282
        })
283
    }
284
}
2,159✔
285

286
/// Generates a VTableIndirect using the specialization-based auto_traits approach.
287
/// Used when `#[facet(auto_traits)]` is enabled for runtime trait detection.
288
///
289
/// This generates functions that use `OxRef`/`OxMut` and return `Option<T>` to indicate
290
/// whether the trait is implemented.
291
fn gen_vtable_indirect(
29✔
292
    facet_crate: &TokenStream,
29✔
293
    _type_name_fn: &TokenStream,
29✔
294
    sources: &TraitSources<'_>,
29✔
295
    struct_type: &TokenStream,
29✔
296
    invariants_fn: Option<&TokenStream>,
29✔
297
) -> TokenStream {
29✔
298
    // For VTableIndirect, functions take OxRef/OxMut and return Option<T>
299
    // The Option layer allows returning None when trait is not implemented
300

301
    // Display: check declared then auto
302
    let display_field = if sources.has_declared(|d| d.display) {
29✔
NEW
303
        quote! {
×
304
            display: ::core::option::Option::Some({
305
                unsafe fn __display(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
306
                    let data: &#struct_type = data.ptr().get();
307
                    ::core::option::Option::Some(::core::fmt::Display::fmt(data, f))
308
                }
309
                __display
310
            }),
311
        }
312
    } else if sources.should_auto() {
29✔
313
        quote! {
29✔
314
            display: ::core::option::Option::Some({
315
                unsafe fn __display(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
316
                    if impls!(#struct_type: ::core::fmt::Display) {
317
                        let data: &#struct_type = data.ptr().get();
318
                        ::core::option::Option::Some((&&Spez(data)).spez_display(f))
319
                    } else {
320
                        ::core::option::Option::None
321
                    }
322
                }
323
                __display
324
            }),
325
        }
326
    } else {
NEW
327
        quote! { display: ::core::option::Option::None, }
×
328
    };
329

330
    // Debug: check derive, then declared, then auto
331
    let debug_field = if sources.has_derive(|d| d.debug) || sources.has_declared(|d| d.debug) {
29✔
NEW
332
        quote! {
×
333
            debug: ::core::option::Option::Some({
334
                unsafe fn __debug(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
335
                    let data: &#struct_type = data.ptr().get();
336
                    ::core::option::Option::Some(::core::fmt::Debug::fmt(data, f))
337
                }
338
                __debug
339
            }),
340
        }
341
    } else if sources.should_auto() {
29✔
342
        quote! {
29✔
343
            debug: ::core::option::Option::Some({
344
                unsafe fn __debug(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
345
                    if impls!(#struct_type: ::core::fmt::Debug) {
346
                        let data: &#struct_type = data.ptr().get();
347
                        ::core::option::Option::Some((&&Spez(data)).spez_debug(f))
348
                    } else {
349
                        ::core::option::Option::None
350
                    }
351
                }
352
                __debug
353
            }),
354
        }
355
    } else {
NEW
356
        quote! { debug: ::core::option::Option::None, }
×
357
    };
358

359
    // PartialEq: check derive, then declared, then auto
360
    let partial_eq_field = if sources.has_derive(|d| d.partial_eq)
29✔
361
        || sources.has_declared(|d| d.partial_eq)
29✔
362
    {
NEW
363
        quote! {
×
364
            partial_eq: ::core::option::Option::Some({
365
                unsafe fn __partial_eq(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<bool> {
366
                    let left: &#struct_type = left.ptr().get();
367
                    let right: &#struct_type = right.ptr().get();
368
                    ::core::option::Option::Some(<#struct_type as ::core::cmp::PartialEq>::eq(left, right))
369
                }
370
                __partial_eq
371
            }),
372
        }
373
    } else if sources.should_auto() {
29✔
374
        quote! {
29✔
375
            partial_eq: ::core::option::Option::Some({
376
                unsafe fn __partial_eq(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<bool> {
377
                    if impls!(#struct_type: ::core::cmp::PartialEq) {
378
                        let left: &#struct_type = left.ptr().get();
379
                        let right: &#struct_type = right.ptr().get();
380
                        ::core::option::Option::Some((&&Spez(left)).spez_partial_eq(&&Spez(right)))
381
                    } else {
382
                        ::core::option::Option::None
383
                    }
384
                }
385
                __partial_eq
386
            }),
387
        }
388
    } else {
NEW
389
        quote! { partial_eq: ::core::option::Option::None, }
×
390
    };
391

392
    // PartialOrd: check derive, then declared, then auto
393
    let partial_cmp_field = if sources.has_derive(|d| d.partial_ord)
29✔
394
        || sources.has_declared(|d| d.partial_ord)
29✔
395
    {
NEW
396
        quote! {
×
397
            partial_cmp: ::core::option::Option::Some({
398
                unsafe fn __partial_cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::option::Option<::core::cmp::Ordering>> {
399
                    let left: &#struct_type = left.ptr().get();
400
                    let right: &#struct_type = right.ptr().get();
401
                    ::core::option::Option::Some(<#struct_type as ::core::cmp::PartialOrd>::partial_cmp(left, right))
402
                }
403
                __partial_cmp
404
            }),
405
        }
406
    } else if sources.should_auto() {
29✔
407
        quote! {
29✔
408
            partial_cmp: ::core::option::Option::Some({
409
                unsafe fn __partial_cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::option::Option<::core::cmp::Ordering>> {
410
                    if impls!(#struct_type: ::core::cmp::PartialOrd) {
411
                        let left: &#struct_type = left.ptr().get();
412
                        let right: &#struct_type = right.ptr().get();
413
                        ::core::option::Option::Some((&&Spez(left)).spez_partial_cmp(&&Spez(right)))
414
                    } else {
415
                        ::core::option::Option::None
416
                    }
417
                }
418
                __partial_cmp
419
            }),
420
        }
421
    } else {
NEW
422
        quote! { partial_cmp: ::core::option::Option::None, }
×
423
    };
424

425
    // Ord: check derive, then declared, then auto
426
    let cmp_field = if sources.has_derive(|d| d.ord) || sources.has_declared(|d| d.ord) {
29✔
NEW
427
        quote! {
×
428
            cmp: ::core::option::Option::Some({
429
                unsafe fn __cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::cmp::Ordering> {
430
                    let left: &#struct_type = left.ptr().get();
431
                    let right: &#struct_type = right.ptr().get();
432
                    ::core::option::Option::Some(<#struct_type as ::core::cmp::Ord>::cmp(left, right))
433
                }
434
                __cmp
435
            }),
436
        }
437
    } else if sources.should_auto() {
29✔
438
        quote! {
29✔
439
            cmp: ::core::option::Option::Some({
440
                unsafe fn __cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::cmp::Ordering> {
441
                    if impls!(#struct_type: ::core::cmp::Ord) {
442
                        let left: &#struct_type = left.ptr().get();
443
                        let right: &#struct_type = right.ptr().get();
444
                        ::core::option::Option::Some((&&Spez(left)).spez_cmp(&&Spez(right)))
445
                    } else {
446
                        ::core::option::Option::None
447
                    }
448
                }
449
                __cmp
450
            }),
451
        }
452
    } else {
NEW
453
        quote! { cmp: ::core::option::Option::None, }
×
454
    };
455

456
    // Hash: check derive, then declared, then auto
457
    let hash_field = if sources.has_derive(|d| d.hash) || sources.has_declared(|d| d.hash) {
29✔
NEW
458
        quote! {
×
459
            hash: ::core::option::Option::Some({
460
                unsafe fn __hash(value: #facet_crate::OxPtrConst, hasher: &mut #facet_crate::HashProxy<'_>) -> ::core::option::Option<()> {
461
                    let value: &#struct_type = value.ptr().get();
462
                    <#struct_type as ::core::hash::Hash>::hash(value, hasher);
463
                    ::core::option::Option::Some(())
464
                }
465
                __hash
466
            }),
467
        }
468
    } else if sources.should_auto() {
29✔
469
        quote! {
29✔
470
            hash: ::core::option::Option::Some({
471
                unsafe fn __hash(value: #facet_crate::OxPtrConst, hasher: &mut #facet_crate::HashProxy<'_>) -> ::core::option::Option<()> {
472
                    if impls!(#struct_type: ::core::hash::Hash) {
473
                        let value: &#struct_type = value.ptr().get();
474
                        (&&Spez(value)).spez_hash(hasher);
475
                        ::core::option::Option::Some(())
476
                    } else {
477
                        ::core::option::Option::None
478
                    }
479
                }
480
                __hash
481
            }),
482
        }
483
    } else {
NEW
484
        quote! { hash: ::core::option::Option::None, }
×
485
    };
486

487
    // Parse (FromStr): no derive exists, only auto-detect if enabled
488
    let parse_field = if sources.should_auto() {
29✔
489
        quote! {
29✔
490
            parse: ::core::option::Option::Some({
491
                unsafe fn __parse(s: &str, target: #facet_crate::OxPtrMut) -> ::core::option::Option<::core::result::Result<(), #facet_crate::ParseError>> {
492
                    if impls!(#struct_type: ::core::str::FromStr) {
493
                        ::core::option::Option::Some(
494
                            match (&&SpezEmpty::<#struct_type>::SPEZ).spez_parse(s, target.ptr().as_uninit()) {
495
                                ::core::result::Result::Ok(_) => ::core::result::Result::Ok(()),
496
                                ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
497
                            }
498
                        )
499
                    } else {
500
                        ::core::option::Option::None
501
                    }
502
                }
503
                __parse
504
            }),
505
        }
506
    } else {
NEW
507
        quote! { parse: ::core::option::Option::None, }
×
508
    };
509

510
    // Invariants: container-level invariants function (wrapped for OxRef signature)
511
    let invariants_field = if let Some(inv_fn) = invariants_fn {
29✔
NEW
512
        quote! {
×
513
            invariants: ::core::option::Option::Some({
514
                unsafe fn __invariants(data: #facet_crate::OxPtrConst) -> ::core::option::Option<#facet_crate::𝟋::𝟋Result<(), #facet_crate::𝟋::𝟋Str>> {
515
                    let value: &#struct_type = data.ptr().get();
516
                    ::core::option::Option::Some(#inv_fn(value))
517
                }
518
                __invariants
519
            }),
520
        }
521
    } else {
522
        quote! { invariants: ::core::option::Option::None, }
29✔
523
    };
524

525
    // Return VTableErased::Indirect wrapping a VTableIndirect using struct literal syntax
526
    // Uses prelude aliases for compact output (𝟋VtE)
527
    // NOTE: drop_in_place, default_in_place, clone_into are now in TypeOps, not VTable
528
    quote! {
29✔
529
        𝟋VtE::Indirect(&const {
530
            #facet_crate::VTableIndirect {
531
                #display_field
532
                #debug_field
533
                #hash_field
534
                #invariants_field
535
                #parse_field
536
                try_from: ::core::option::Option::None,
537
                try_into_inner: ::core::option::Option::None,
538
                try_borrow_inner: ::core::option::Option::None,
539
                #partial_eq_field
540
                #partial_cmp_field
541
                #cmp_field
542
            }
543
        })
544
    }
545
}
29✔
546

547
/// Generates TypeOps for per-type operations (drop, default, clone).
548
/// Returns `Option<TokenStream>` - Some if any TypeOps is needed, None if no ops.
549
///
550
/// Uses TypeOpsDirect for non-generic types, TypeOpsIndirect for generic types.
551
pub(crate) fn gen_type_ops(
2,188✔
552
    facet_crate: &TokenStream,
2,188✔
553
    sources: &TraitSources<'_>,
2,188✔
554
    struct_type: &TokenStream,
2,188✔
555
    has_type_or_const_generics: bool,
2,188✔
556
) -> Option<TokenStream> {
2,188✔
557
    // Only use TypeOpsIndirect when there are actual type or const generics.
558
    // For auto_traits WITHOUT generics, we can still use TypeOpsDirect since
559
    // the helper functions can use `Self` which resolves to the concrete type.
560
    if has_type_or_const_generics {
2,188✔
561
        return gen_type_ops_indirect(facet_crate, sources, struct_type);
23✔
562
    }
2,165✔
563

564
    // Use TypeOpsDirect for non-generic types (including auto_traits without generics)
565
    gen_type_ops_direct(facet_crate, sources, struct_type)
2,165✔
566
}
2,188✔
567

568
/// Generates TypeOpsDirect for non-generic types.
569
/// Returns Some(TokenStream) if any ops are needed, None otherwise.
570
///
571
/// Uses raw pointers (`*mut ()`, `*const ()`) for type-erased function signatures,
572
/// matching VTableDirect's approach. The `Self` type is used inside the const block
573
/// which properly resolves without capturing lifetime parameters.
574
fn gen_type_ops_direct(
2,165✔
575
    facet_crate: &TokenStream,
2,165✔
576
    sources: &TraitSources<'_>,
2,165✔
577
    struct_type: &TokenStream,
2,165✔
578
) -> Option<TokenStream> {
2,165✔
579
    // Check if Default is available (from derive or declared traits or #[facet(default)])
580
    let has_default = sources.has_derive(|d| d.default)
2,165✔
581
        || sources.has_declared(|d| d.default)
2,165✔
582
        || sources.facet_default;
2,164✔
583

584
    // Check if Clone is available (from derive or declared traits)
585
    let has_clone = sources.has_derive(|d| d.clone) || sources.has_declared(|d| d.clone);
2,165✔
586

587
    // Generate default_in_place field
588
    // Uses helper function 𝟋default_for::<Self>() which returns fn(*mut Self),
589
    // then transmutes to fn(*mut ()) for the erased signature
590
    let default_field = if has_default {
2,165✔
591
        quote! {
4✔
592
            default_in_place: ::core::option::Option::Some(
593
                unsafe { ::core::mem::transmute(#facet_crate::𝟋::𝟋default_for::<Self>() as unsafe fn(*mut Self)) }
594
            ),
595
        }
596
    } else if sources.should_auto() {
2,161✔
597
        // For auto_traits, generate an inline function that uses the Spez pattern.
598
        // The function hardcodes struct_type, so specialization resolves correctly.
599
        // The impls! check determines whether we return Some or None at const-eval time.
600
        quote! {
29✔
601
            default_in_place: if #facet_crate::𝟋::impls!(#struct_type: ::core::default::Default) {
602
                ::core::option::Option::Some({
603
                    unsafe fn __default_in_place(ptr: *mut ()) {
604
                        let target = #facet_crate::PtrUninit::new(ptr as *mut u8);
605
                        unsafe { (&&&#facet_crate::𝟋::SpezEmpty::<#struct_type>::SPEZ).spez_default_in_place(target) };
606
                    }
607
                    __default_in_place
608
                })
609
            } else {
610
                ::core::option::Option::None
611
            },
612
        }
613
    } else {
614
        quote! { default_in_place: ::core::option::Option::None, }
2,132✔
615
    };
616

617
    // Generate clone_into field
618
    // Uses helper function 𝟋clone_for::<Self>() which returns fn(*const Self, *mut Self),
619
    // then transmutes to fn(*const (), *mut ()) for the erased signature
620
    let clone_field = if has_clone {
2,165✔
NEW
621
        quote! {
×
622
            clone_into: ::core::option::Option::Some(
623
                unsafe { ::core::mem::transmute(#facet_crate::𝟋::𝟋clone_for::<Self>() as unsafe fn(*const Self, *mut Self)) }
624
            ),
625
        }
626
    } else if sources.should_auto() {
2,165✔
627
        // For auto_traits, generate an inline function that uses the Spez pattern.
628
        // The function hardcodes struct_type, so specialization resolves correctly.
629
        // The impls! check determines whether we return Some or None at const-eval time.
630
        quote! {
29✔
631
            clone_into: if #facet_crate::𝟋::impls!(#struct_type: ::core::clone::Clone) {
632
                ::core::option::Option::Some({
633
                    unsafe fn __clone_into(src: *const (), dst: *mut ()) {
634
                        let src_ref: &#struct_type = unsafe { &*(src as *const #struct_type) };
635
                        let target = #facet_crate::PtrUninit::new(dst as *mut u8);
636
                        unsafe { (&&&#facet_crate::𝟋::Spez(src_ref)).spez_clone_into(target) };
637
                    }
638
                    __clone_into
639
                })
640
            } else {
641
                ::core::option::Option::None
642
            },
643
        }
644
    } else {
645
        quote! { clone_into: ::core::option::Option::None, }
2,136✔
646
    };
647

648
    // Generate TypeOpsDirect struct literal
649
    // Uses transmute to convert typed fn pointers to erased fn(*mut ()) etc.
650
    // Uses Self inside the const block which resolves to the implementing type
651
    Some(quote! {
2,165✔
652
        #facet_crate::TypeOps::Direct(&const {
2,165✔
653
            #facet_crate::TypeOpsDirect {
2,165✔
654
                drop_in_place: unsafe { ::core::mem::transmute(::core::ptr::drop_in_place::<Self> as unsafe fn(*mut Self)) },
2,165✔
655
                #default_field
2,165✔
656
                #clone_field
2,165✔
657
            }
2,165✔
658
        })
2,165✔
659
    })
2,165✔
660
}
2,165✔
661

662
/// Generates TypeOpsIndirect for generic types with auto_traits.
663
/// Returns Some(TokenStream) if any ops are needed, None otherwise.
664
///
665
/// Uses helper functions that take a type parameter to avoid the "can't use Self
666
/// from outer item" error in function items.
667
fn gen_type_ops_indirect(
23✔
668
    facet_crate: &TokenStream,
23✔
669
    sources: &TraitSources<'_>,
23✔
670
    _struct_type: &TokenStream,
23✔
671
) -> Option<TokenStream> {
23✔
672
    // For TypeOpsIndirect, we always need drop_in_place
673
    // default_in_place and clone_into are optional based on available traits
674
    // Note: We use helper functions 𝟋indirect_*_for::<Self>() which have their own
675
    // generic parameter, avoiding the "can't use Self from outer item" issue.
676

677
    // Check if Default is available
678
    // Note: For auto_traits, we could use specialization but it's complex.
679
    // For now, only generate default_in_place when Default is explicitly known.
680
    let default_field = if sources.has_derive(|d| d.default)
23✔
681
        || sources.has_declared(|d| d.default)
23✔
682
        || sources.facet_default
23✔
683
    {
NEW
684
        quote! {
×
685
            default_in_place: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_default_for::<Self>()),
686
        }
687
    } else {
688
        // For auto_traits or no default, set to None
689
        // Runtime detection of Default not supported in TypeOps yet
690
        quote! { default_in_place: ::core::option::Option::None, }
23✔
691
    };
692

693
    // Check if Clone is available
694
    // Note: For auto_traits, we could use specialization but it's complex.
695
    // For now, only generate clone_into when Clone is explicitly known.
696
    let clone_field = if sources.has_derive(|d| d.clone) || sources.has_declared(|d| d.clone) {
23✔
NEW
697
        quote! {
×
698
            clone_into: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_clone_for::<Self>()),
699
        }
700
    } else {
701
        // For auto_traits or no clone, set to None
702
        // Runtime detection of Clone not supported in TypeOps yet
703
        quote! { clone_into: ::core::option::Option::None, }
23✔
704
    };
705

706
    Some(quote! {
23✔
707
        #facet_crate::TypeOps::Indirect(&const {
23✔
708
            #facet_crate::TypeOpsIndirect {
23✔
709
                drop_in_place: #facet_crate::𝟋::𝟋indirect_drop_for::<Self>(),
23✔
710
                #default_field
23✔
711
                #clone_field
23✔
712
            }
23✔
713
        })
23✔
714
    })
23✔
715
}
23✔
716

717
/// Generate trait bounds for static assertions.
718
/// Returns a TokenStream of bounds like `core::fmt::Debug + core::clone::Clone`
719
/// that can be used in a where clause.
720
///
721
/// `facet_default` is true when `#[facet(default)]` is present, which implies Default.
722
pub(crate) fn gen_trait_bounds(
2,188✔
723
    declared: Option<&DeclaredTraits>,
2,188✔
724
    facet_default: bool,
2,188✔
725
) -> Option<TokenStream> {
2,188✔
726
    let mut bounds = Vec::new();
2,188✔
727

728
    if let Some(declared) = declared {
2,188✔
729
        if declared.display {
3✔
730
            bounds.push(quote! { core::fmt::Display });
×
731
        }
3✔
732
        if declared.debug {
3✔
733
            bounds.push(quote! { core::fmt::Debug });
2✔
734
        }
2✔
735
        if declared.clone {
3✔
736
            bounds.push(quote! { core::clone::Clone });
×
737
        }
3✔
738
        if declared.copy {
3✔
739
            bounds.push(quote! { core::marker::Copy });
×
740
        }
3✔
741
        if declared.partial_eq {
3✔
742
            bounds.push(quote! { core::cmp::PartialEq });
×
743
        }
3✔
744
        if declared.eq {
3✔
745
            bounds.push(quote! { core::cmp::Eq });
×
746
        }
3✔
747
        if declared.partial_ord {
3✔
748
            bounds.push(quote! { core::cmp::PartialOrd });
×
749
        }
3✔
750
        if declared.ord {
3✔
751
            bounds.push(quote! { core::cmp::Ord });
×
752
        }
3✔
753
        if declared.hash {
3✔
754
            bounds.push(quote! { core::hash::Hash });
×
755
        }
3✔
756
        if declared.default {
3✔
757
            bounds.push(quote! { core::default::Default });
1✔
758
        }
2✔
759
        if declared.send {
3✔
760
            bounds.push(quote! { core::marker::Send });
×
761
        }
3✔
762
        if declared.sync {
3✔
763
            bounds.push(quote! { core::marker::Sync });
×
764
        }
3✔
765
        if declared.unpin {
3✔
766
            bounds.push(quote! { core::marker::Unpin });
×
767
        }
3✔
768
    }
2,185✔
769

770
    // #[facet(default)] implies Default trait
771
    if facet_default && !declared.is_some_and(|d| d.default) {
2,188✔
772
        bounds.push(quote! { core::default::Default });
3✔
773
    }
2,185✔
774

775
    if bounds.is_empty() {
2,188✔
776
        None
2,182✔
777
    } else {
778
        Some(quote! { #(#bounds)+* })
6✔
779
    }
780
}
2,188✔
781

782
/// Generates the `::facet::Field` definition `TokenStream` from a `PStructField`.
783
pub(crate) fn gen_field_from_pfield(
3,880✔
784
    field: &PStructField,
3,880✔
785
    struct_name: &Ident,
3,880✔
786
    bgp: &BoundedGenericParams,
3,880✔
787
    base_offset: Option<TokenStream>,
3,880✔
788
    facet_crate: &TokenStream,
3,880✔
789
) -> TokenStream {
3,880✔
790
    let field_name_effective = &field.name.effective;
3,880✔
791
    let field_name_raw = &field.name.raw;
3,880✔
792
    let field_type = &field.ty;
3,880✔
793

794
    let bgp_without_bounds = bgp.display_without_bounds();
3,880✔
795

796
    #[cfg(feature = "doc")]
797
    let doc_lines: Vec<String> = field
3,880✔
798
        .attrs
3,880✔
799
        .doc
3,880✔
800
        .iter()
3,880✔
801
        .map(|doc| doc.as_str().replace("\\\"", "\""))
3,880✔
802
        .collect();
3,880✔
803
    #[cfg(not(feature = "doc"))]
804
    let doc_lines: Vec<String> = Vec::new();
×
805

806
    // Check if this field is marked as a recursive type
807
    let is_recursive = field.attrs.has_builtin("recursive_type");
3,880✔
808

809
    // Generate the shape expression directly using the field type
810
    // For opaque fields, wrap in Opaque<T>
811
    // NOTE: Uses short alias from `use #facet_crate::𝟋::*` in the enclosing const block
812
    let shape_expr = if field.attrs.has_builtin("opaque") {
3,880✔
813
        quote! { <#facet_crate::Opaque<#field_type> as 𝟋Fct>::SHAPE }
33✔
814
    } else {
815
        quote! { <#field_type as 𝟋Fct>::SHAPE }
3,847✔
816
    };
817

818
    // Process attributes, separating flag attrs and field attrs from the attribute slice.
819
    // Attributes with #[storage(flag)] go into FieldFlags for O(1) access.
820
    // Attributes with #[storage(field)] go into dedicated Field struct fields.
821
    // Everything else goes into the attributes slice.
822
    //
823
    // Flag attrs: sensitive, flatten, child, skip, skip_serializing, skip_deserializing
824
    // Field attrs: rename, alias
825
    // Note: default also sets HAS_DEFAULT flag (handled below)
826

827
    // Track what kind of default was specified
828
    enum DefaultKind {
829
        FromTrait,
830
        Custom(TokenStream),
831
    }
832

833
    let mut flags: Vec<TokenStream> = Vec::new();
3,880✔
834
    let mut rename_value: Option<TokenStream> = None;
3,880✔
835
    let mut alias_value: Option<TokenStream> = None;
3,880✔
836
    let mut default_value: Option<DefaultKind> = None;
3,880✔
837
    let mut skip_serializing_if_value: Option<TokenStream> = None;
3,880✔
838
    let mut invariants_value: Option<TokenStream> = None;
3,880✔
839
    let mut proxy_value: Option<TokenStream> = None;
3,880✔
840
    let mut attribute_list: Vec<TokenStream> = Vec::new();
3,880✔
841

842
    for attr in &field.attrs.facet {
3,880✔
843
        if attr.is_builtin() {
1,559✔
844
            let key = attr.key_str();
571✔
845
            match key.as_str() {
571✔
846
                // Flag attrs - set bit in FieldFlags, don't add to attribute_list
847
                "sensitive" => {
571✔
848
                    flags.push(quote! { 𝟋FF::SENSITIVE });
2✔
849
                }
2✔
850
                "flatten" => {
569✔
851
                    flags.push(quote! { 𝟋FF::FLATTEN });
94✔
852
                }
94✔
853
                "child" => {
475✔
854
                    flags.push(quote! { 𝟋FF::CHILD });
×
855
                }
×
856
                "skip" => {
475✔
857
                    flags.push(quote! { 𝟋FF::SKIP });
3✔
858
                }
3✔
859
                "skip_serializing" => {
472✔
860
                    flags.push(quote! { 𝟋FF::SKIP_SERIALIZING });
4✔
861
                }
4✔
862
                "skip_deserializing" => {
468✔
863
                    flags.push(quote! { 𝟋FF::SKIP_DESERIALIZING });
1✔
864
                }
1✔
865
                "default" => {
467✔
866
                    // Default goes into dedicated field, not attributes
867
                    let args = &attr.args;
232✔
868
                    if args.is_empty() {
232✔
869
                        // #[facet(default)] - use Default trait
214✔
870
                        default_value = Some(DefaultKind::FromTrait);
214✔
871
                    } else {
214✔
872
                        // #[facet(default = expr)] - use custom expression
873
                        // Parse `= expr` to get just the expr
874
                        let args_str = args.to_string();
18✔
875
                        let expr_str = args_str.trim_start_matches('=').trim();
18✔
876
                        let expr: TokenStream = expr_str.parse().unwrap_or_else(|_| args.clone());
18✔
877
                        default_value = Some(DefaultKind::Custom(expr));
18✔
878
                    }
879
                }
880
                "recursive_type" => {
235✔
881
                    // recursive_type sets a flag
77✔
882
                    flags.push(quote! { 𝟋FF::RECURSIVE_TYPE });
77✔
883
                }
77✔
884
                // Field attrs - store in dedicated field, don't add to attribute_list
885
                "rename" => {
158✔
886
                    // Extract the string literal from args
80✔
887
                    let args = &attr.args;
80✔
888
                    rename_value = Some(quote! { #args });
80✔
889
                }
80✔
890
                "alias" => {
78✔
891
                    // Extract the string literal from args
×
892
                    let args = &attr.args;
×
893
                    alias_value = Some(quote! { #args });
×
894
                }
×
895
                "skip_serializing_if" => {
78✔
896
                    // User provides a function name: #[facet(skip_serializing_if = fn_name)]
897
                    // We need to wrap it in a type-erased function that takes PtrConst
898
                    let args = &attr.args;
4✔
899
                    let args_str = args.to_string();
4✔
900
                    let fn_name_str = args_str.trim_start_matches('=').trim();
4✔
901
                    let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
4✔
902
                    // Generate a wrapper function that converts PtrConst to the expected type
903
                    skip_serializing_if_value = Some(quote! {
4✔
904
                        {
4✔
905
                            unsafe fn __skip_ser_if_wrapper(ptr: #facet_crate::PtrConst) -> bool {
4✔
906
                                let value: &#field_type = unsafe { ptr.get() };
4✔
907
                                #fn_name(value)
4✔
908
                            }
4✔
909
                            __skip_ser_if_wrapper
4✔
910
                        }
4✔
911
                    });
4✔
912
                }
913
                "invariants" => {
74✔
914
                    // User provides a function name: #[facet(invariants = fn_name)]
NEW
915
                    let args = &attr.args;
×
NEW
916
                    let args_str = args.to_string();
×
NEW
917
                    let fn_name_str = args_str.trim_start_matches('=').trim();
×
NEW
918
                    let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
×
NEW
919
                    invariants_value = Some(quote! { #fn_name });
×
920
                }
921
                "proxy" => {
74✔
922
                    // User provides a type: #[facet(proxy = ProxyType)]
923
                    let args = &attr.args;
41✔
924
                    let args_str = args.to_string();
41✔
925
                    let type_str = args_str.trim_start_matches('=').trim();
41✔
926
                    let proxy_type: TokenStream = type_str.parse().unwrap_or_else(|_| args.clone());
41✔
927
                    // Generate a full ProxyDef with convert functions for field-level proxy
928
                    proxy_value = Some(quote! {
41✔
929
                        &const {
41✔
930
                            extern crate alloc as __alloc;
41✔
931

41✔
932
                            unsafe fn __proxy_convert_in(
41✔
933
                                proxy_ptr: #facet_crate::PtrConst,
41✔
934
                                field_ptr: #facet_crate::PtrUninit,
41✔
935
                            ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
41✔
936
                                let proxy: #proxy_type = proxy_ptr.read();
41✔
937
                                match <#field_type as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
41✔
938
                                    ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
41✔
939
                                    ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
41✔
940
                                }
41✔
941
                            }
41✔
942

41✔
943
                            unsafe fn __proxy_convert_out(
41✔
944
                                field_ptr: #facet_crate::PtrConst,
41✔
945
                                proxy_ptr: #facet_crate::PtrUninit,
41✔
946
                            ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
41✔
947
                                let field_ref: &#field_type = field_ptr.get();
41✔
948
                                match <#proxy_type as ::core::convert::TryFrom<&#field_type>>::try_from(field_ref) {
41✔
949
                                    ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
41✔
950
                                    ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
41✔
951
                                }
41✔
952
                            }
41✔
953

41✔
954
                            #facet_crate::ProxyDef {
41✔
955
                                shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
41✔
956
                                convert_in: __proxy_convert_in,
41✔
957
                                convert_out: __proxy_convert_out,
41✔
958
                            }
41✔
959
                        }
41✔
960
                    });
41✔
961
                }
962
                // Everything else goes to attributes slice
963
                _ => {
33✔
964
                    let ext_attr =
33✔
965
                        emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
33✔
966
                    attribute_list.push(quote! { #ext_attr });
33✔
967
                }
33✔
968
            }
969
        } else {
988✔
970
            // Non-builtin (namespaced) attrs always go to attributes slice
988✔
971
            let ext_attr = emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
988✔
972
            attribute_list.push(quote! { #ext_attr });
988✔
973
        }
988✔
974
    }
975

976
    let maybe_attributes = if attribute_list.is_empty() {
3,880✔
977
        quote! { &[] }
2,938✔
978
    } else {
979
        quote! { &const {[#(#attribute_list),*]} }
942✔
980
    };
981

982
    let maybe_field_doc = if doc_lines.is_empty() {
3,880✔
983
        quote! { &[] }
3,816✔
984
    } else {
985
        quote! { &[#(#doc_lines),*] }
64✔
986
    };
987

988
    // Calculate the final offset, incorporating the base_offset if present
989
    let final_offset = match base_offset {
3,880✔
990
        Some(base) => {
230✔
991
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
230✔
992
        }
993
        None => {
994
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
3,650✔
995
        }
996
    };
997

998
    // === Direct Field construction (avoiding builder pattern for faster const eval) ===
999
    // Uses short aliases from `use #facet_crate::𝟋::*` in the enclosing const block
1000

1001
    // Shape reference: always use a function for lazy evaluation
1002
    // This moves const eval from compile time to runtime, improving compile times
1003
    // ShapeRef is a tuple struct: ShapeRef(fn() -> &'static Shape)
1004
    let is_opaque = field.attrs.has_builtin("opaque");
3,880✔
1005
    let shape_ref_expr = if is_recursive {
3,880✔
1006
        // Recursive types need a closure to break the cycle
1007
        quote! { 𝟋ShpR(|| #shape_expr) }
77✔
1008
    } else if is_opaque {
3,803✔
1009
        // Opaque fields use Opaque<T> wrapper
1010
        quote! { 𝟋ShpR(𝟋shp::<#facet_crate::Opaque<#field_type>>) }
33✔
1011
    } else {
1012
        // Normal fields use shape_of::<T> which is monomorphized per type
1013
        quote! { 𝟋ShpR(𝟋shp::<#field_type>) }
3,770✔
1014
    };
1015

1016
    // Flags: combine all flags or use empty
1017
    let flags_expr = if flags.is_empty() {
3,880✔
1018
        quote! { 𝟋NOFL }
3,699✔
1019
    } else if flags.len() == 1 {
181✔
1020
        let f = &flags[0];
181✔
1021
        quote! { #f }
181✔
1022
    } else {
NEW
1023
        let first = &flags[0];
×
NEW
1024
        let rest = &flags[1..];
×
NEW
1025
        quote! { #first #(.union(#rest))* }
×
1026
    };
1027

1028
    // Rename: Option
1029
    let rename_expr = match &rename_value {
3,880✔
1030
        Some(rename) => quote! { ::core::option::Option::Some(#rename) },
80✔
1031
        None => quote! { ::core::option::Option::None },
3,800✔
1032
    };
1033

1034
    // Alias: Option
1035
    let alias_expr = match &alias_value {
3,880✔
NEW
1036
        Some(alias) => quote! { ::core::option::Option::Some(#alias) },
×
1037
        None => quote! { ::core::option::Option::None },
3,880✔
1038
    };
1039

1040
    // Default: Option<DefaultSource>
1041
    let default_expr = match &default_value {
3,880✔
1042
        Some(DefaultKind::FromTrait) => {
1043
            // When a field has 'opaque' attribute, the field shape doesn't have Default vtable
1044
            // because Opaque<T> doesn't expose T's vtable. Instead, generate a custom default
1045
            // function. Special case: Option<T> always defaults to None regardless of T's traits.
1046
            if field.attrs.has_builtin("opaque") {
214✔
1047
                // Check if the field type looks like Option<...>
1048
                let type_str = field_type.to_token_stream().to_string();
2✔
1049
                let is_option = type_str.starts_with("Option") || type_str.contains(":: Option");
2✔
1050

1051
                if is_option {
2✔
1052
                    // Option<T> always defaults to None
1053
                    quote! {
2✔
1054
                        ::core::option::Option::Some(𝟋DS::Custom({
1055
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1056
                                __ptr.put(<#field_type>::None)
1057
                            }
1058
                            __default
1059
                        }))
1060
                    }
1061
                } else {
1062
                    // For non-Option opaque types, call Default::default()
NEW
1063
                    quote! {
×
1064
                        ::core::option::Option::Some(𝟋DS::Custom({
1065
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1066
                                __ptr.put(<#field_type as ::core::default::Default>::default())
1067
                            }
1068
                            __default
1069
                        }))
1070
                    }
1071
                }
1072
            } else {
1073
                quote! { ::core::option::Option::Some(𝟋DS::FromTrait) }
212✔
1074
            }
1075
        }
1076
        Some(DefaultKind::Custom(expr)) => {
18✔
1077
            quote! {
18✔
1078
                ::core::option::Option::Some(𝟋DS::Custom({
1079
                    unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1080
                        __ptr.put(#expr)
1081
                    }
1082
                    __default
1083
                }))
1084
            }
1085
        }
1086
        None => quote! { ::core::option::Option::None },
3,648✔
1087
    };
1088

1089
    // Skip serializing if: Option
1090
    let skip_ser_if_expr = match &skip_serializing_if_value {
3,880✔
1091
        Some(skip_ser_if) => quote! { ::core::option::Option::Some(#skip_ser_if) },
4✔
1092
        None => quote! { ::core::option::Option::None },
3,876✔
1093
    };
1094

1095
    // Invariants: Option
1096
    let invariants_expr = match &invariants_value {
3,880✔
NEW
1097
        Some(inv) => quote! { ::core::option::Option::Some(#inv) },
×
1098
        None => quote! { ::core::option::Option::None },
3,880✔
1099
    };
1100

1101
    // Proxy: Option (requires alloc feature in facet-core)
1102
    // We always emit this field since we can't check facet-core's features from generated code.
1103
    // If facet-core was built without alloc, this will cause a compile error (acceptable trade-off).
1104
    let proxy_expr = match &proxy_value {
3,880✔
1105
        Some(proxy) => quote! { ::core::option::Option::Some(#proxy) },
41✔
1106
        None => quote! { ::core::option::Option::None },
3,839✔
1107
    };
1108

1109
    // Direct Field struct literal
1110
    quote! {
3,880✔
1111
        𝟋Fld {
1112
            name: #field_name_effective,
1113
            shape: #shape_ref_expr,
1114
            offset: #final_offset,
1115
            flags: #flags_expr,
1116
            rename: #rename_expr,
1117
            alias: #alias_expr,
1118
            attributes: #maybe_attributes,
1119
            doc: #maybe_field_doc,
1120
            default: #default_expr,
1121
            skip_serializing_if: #skip_ser_if_expr,
1122
            invariants: #invariants_expr,
1123
            proxy: #proxy_expr,
1124
        }
1125
    }
1126
}
3,880✔
1127

1128
/// Processes a regular struct to implement Facet
1129
///
1130
/// Example input:
1131
/// ```rust
1132
/// struct Blah {
1133
///     foo: u32,
1134
///     bar: String,
1135
/// }
1136
/// ```
1137
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
1,841✔
1138
    let ps = PStruct::parse(&parsed); // Use the parsed representation
1,841✔
1139

1140
    // Emit any collected errors as compile_error! with proper spans
1141
    if !ps.container.attrs.errors.is_empty() {
1,841✔
1142
        let errors = ps.container.attrs.errors.iter().map(|e| {
×
1143
            let msg = &e.message;
×
1144
            let span = e.span;
×
1145
            quote_spanned! { span => compile_error!(#msg); }
×
1146
        });
×
1147
        return quote! { #(#errors)* };
×
1148
    }
1,841✔
1149

1150
    let struct_name_ident = format_ident!("{}", ps.container.name);
1,841✔
1151
    let struct_name = &ps.container.name;
1,841✔
1152
    let struct_name_str = struct_name.to_string();
1,841✔
1153

1154
    let opaque = ps.container.attrs.has_builtin("opaque");
1,841✔
1155

1156
    // Get the facet crate path (custom or default ::facet)
1157
    let facet_crate = ps.container.attrs.facet_crate();
1,841✔
1158

1159
    let type_name_fn =
1,841✔
1160
        generate_type_name_fn(struct_name, parsed.generics.as_ref(), opaque, &facet_crate);
1,841✔
1161

1162
    // Determine if this struct should use transparent semantics (needed for vtable generation)
1163
    // Transparent is enabled if:
1164
    // 1. #[facet(transparent)] is explicitly set, OR
1165
    // 2. #[repr(transparent)] is set AND the struct is a tuple struct with exactly 0 or 1 field
1166
    let has_explicit_facet_transparent = ps.container.attrs.has_builtin("transparent");
1,841✔
1167
    let has_repr_transparent = ps.container.attrs.is_repr_transparent();
1,841✔
1168

1169
    let repr_implies_facet_transparent = if has_repr_transparent && !has_explicit_facet_transparent
1,841✔
1170
    {
1171
        match &ps.kind {
7✔
1172
            PStructKind::TupleStruct { fields } => fields.len() <= 1,
7✔
NEW
1173
            _ => false,
×
1174
        }
1175
    } else {
1176
        false
1,834✔
1177
    };
1178

1179
    let use_transparent_semantics =
1,841✔
1180
        has_explicit_facet_transparent || repr_implies_facet_transparent;
1,841✔
1181

1182
    // For transparent types, get the inner field info
1183
    let inner_field: Option<PStructField> = if use_transparent_semantics {
1,841✔
1184
        match &ps.kind {
47✔
1185
            PStructKind::TupleStruct { fields } => {
47✔
1186
                if fields.len() > 1 {
47✔
NEW
1187
                    return quote! {
×
1188
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
1189
                    };
1190
                }
47✔
1191
                fields.first().cloned()
47✔
1192
            }
1193
            _ => {
NEW
1194
                return quote! {
×
1195
                    compile_error!("Transparent structs must be tuple structs");
1196
                };
1197
            }
1198
        }
1199
    } else {
1200
        None
1,794✔
1201
    };
1202

1203
    // Build transparent info for vtable generation
1204
    let transparent_info = if use_transparent_semantics {
1,841✔
1205
        Some(TransparentInfo {
1206
            inner_field_type: inner_field.as_ref().map(|f| &f.ty),
47✔
1207
            inner_is_opaque: inner_field
47✔
1208
                .as_ref()
47✔
1209
                .is_some_and(|f| f.attrs.has_builtin("opaque")),
47✔
1210
            is_zst: inner_field.is_none(),
47✔
1211
        })
1212
    } else {
1213
        None
1,794✔
1214
    };
1215

1216
    // Determine trait sources and generate vtable accordingly
1217
    let trait_sources = TraitSources::from_attrs(&ps.container.attrs);
1,841✔
1218
    // Build the struct type token stream (e.g., `MyStruct` or `MyStruct<T, U>`)
1219
    // We need this because `Self` is not available inside `&const { }` blocks
1220
    let bgp_for_vtable = ps.container.bgp.display_without_bounds();
1,841✔
1221
    let struct_type_for_vtable = quote! { #struct_name_ident #bgp_for_vtable };
1,841✔
1222

1223
    // Extract container-level invariants and generate wrapper function
1224
    let invariants_wrapper: Option<TokenStream> = {
1,841✔
1225
        let invariant_exprs: Vec<&TokenStream> = ps
1,841✔
1226
            .container
1,841✔
1227
            .attrs
1,841✔
1228
            .facet
1,841✔
1229
            .iter()
1,841✔
1230
            .filter(|attr| attr.is_builtin() && attr.key_str() == "invariants")
1,841✔
1231
            .map(|attr| &attr.args)
1,841✔
1232
            .collect();
1,841✔
1233

1234
        if !invariant_exprs.is_empty() {
1,841✔
1235
            let tests = invariant_exprs.iter().map(|expr| {
3✔
1236
                quote! {
3✔
1237
                    if !#expr(value) {
1238
                        return 𝟋Result::Err(𝟋Str::from("invariant check failed"));
1239
                    }
1240
                }
1241
            });
3✔
1242

1243
            Some(quote! {
3✔
1244
                {
1245
                    fn __invariants_wrapper(value: &#struct_type_for_vtable) -> 𝟋Result<(), 𝟋Str> {
1246
                        use #facet_crate::𝟋::*;
1247
                        #(#tests)*
1248
                        𝟋Result::Ok(())
1249
                    }
1250
                    __invariants_wrapper
1251
                }
1252
            })
1253
        } else {
1254
            None
1,838✔
1255
        }
1256
    };
1257

1258
    let vtable_code = gen_vtable(
1,841✔
1259
        &facet_crate,
1,841✔
1260
        &type_name_fn,
1,841✔
1261
        &trait_sources,
1,841✔
1262
        transparent_info.as_ref(),
1,841✔
1263
        &struct_type_for_vtable,
1,841✔
1264
        invariants_wrapper.as_ref(),
1,841✔
1265
    );
1266
    // Note: vtable_code already contains &const { ... } for the VTableDirect,
1267
    // no need for an extra const { } wrapper around VTableErased
1268
    let vtable_init = vtable_code;
1,841✔
1269

1270
    // Generate TypeOps for drop, default, clone operations
1271
    // Check if the type has any type or const generics (NOT lifetimes)
1272
    // Lifetimes don't affect layout, so types like RawJson<'a> can use TypeOpsDirect
1273
    // Only types like Vec<T> need TypeOpsIndirect
1274
    let has_type_or_const_generics = ps.container.bgp.params.iter().any(|p| {
1,841✔
1275
        matches!(
59✔
1276
            p.param,
72✔
1277
            facet_macro_parse::GenericParamName::Type(_)
1278
                | facet_macro_parse::GenericParamName::Const(_)
1279
        )
1280
    });
72✔
1281
    let type_ops_init = gen_type_ops(
1,841✔
1282
        &facet_crate,
1,841✔
1283
        &trait_sources,
1,841✔
1284
        &struct_type_for_vtable,
1,841✔
1285
        has_type_or_const_generics,
1,841✔
1286
    );
1287

1288
    // TODO: I assume the `PrimitiveRepr` is only relevant for enums, and does not need to be preserved?
1289
    // NOTE: Uses short aliases from `use #facet_crate::𝟋::*` in the const block
1290
    let repr = match &ps.container.attrs.repr {
1,841✔
1291
        PRepr::Transparent => quote! { 𝟋Repr::TRANSPARENT },
7✔
1292
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
1,757✔
1293
        PRepr::C(_) => quote! { 𝟋Repr::C },
77✔
1294
        PRepr::RustcWillCatch => {
1295
            // rustc will emit an error for the invalid repr.
1296
            // Return empty TokenStream so we don't add misleading errors.
1297
            return quote! {};
×
1298
        }
1299
    };
1300

1301
    // Use PStruct for kind and fields
1302
    let (kind, fields_vec) = match &ps.kind {
1,841✔
1303
        PStructKind::Struct { fields } => {
1,729✔
1304
            let kind = quote!(𝟋Sk::Struct);
1,729✔
1305
            let fields_vec = fields
1,729✔
1306
                .iter()
1,729✔
1307
                .map(|field| {
3,003✔
1308
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
3,003✔
1309
                })
3,003✔
1310
                .collect::<Vec<_>>();
1,729✔
1311
            (kind, fields_vec)
1,729✔
1312
        }
1313
        PStructKind::TupleStruct { fields } => {
104✔
1314
            let kind = quote!(𝟋Sk::TupleStruct);
104✔
1315
            let fields_vec = fields
104✔
1316
                .iter()
104✔
1317
                .map(|field| {
127✔
1318
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
127✔
1319
                })
127✔
1320
                .collect::<Vec<_>>();
104✔
1321
            (kind, fields_vec)
104✔
1322
        }
1323
        PStructKind::UnitStruct => {
1324
            let kind = quote!(𝟋Sk::Unit);
8✔
1325
            (kind, vec![])
8✔
1326
        }
1327
    };
1328

1329
    // Compute variance - delegate to Shape::computed_variance() at runtime
1330
    let variance_call = if opaque {
1,841✔
1331
        // Opaque types don't expose internals, use invariant for safety
1332
        quote! { .variance(𝟋Vnc::INVARIANT) }
2✔
1333
    } else {
1334
        // Point to Shape::computed_variance - it takes &Shape and walks fields
1335
        quote! { .variance(𝟋CV) }
1,839✔
1336
    };
1337

1338
    // Still need original AST for where clauses and type params for build_ helpers
1339
    let where_clauses_ast = match &parsed.kind {
1,841✔
1340
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
1,729✔
1341
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
104✔
1342
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
8✔
1343
    };
1344
    let where_clauses = build_where_clauses(
1,841✔
1345
        where_clauses_ast,
1,841✔
1346
        parsed.generics.as_ref(),
1,841✔
1347
        opaque,
1,841✔
1348
        &facet_crate,
1,841✔
1349
    );
1350
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
1,841✔
1351

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

1355
    // Doc comments from PStruct - returns value for struct literal
1356
    // doc call - only emit if there are doc comments and doc feature is enabled
1357
    #[cfg(feature = "doc")]
1358
    let doc_call = if ps.container.attrs.doc.is_empty() {
1,841✔
1359
        quote! {}
1,785✔
1360
    } else {
1361
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
146✔
1362
        quote! { .doc(&[#(#doc_lines),*]) }
56✔
1363
    };
1364
    #[cfg(not(feature = "doc"))]
1365
    let doc_call = quote! {};
×
1366

1367
    // Container attributes - most go through grammar dispatch
1368
    // Filter out `invariants` and `crate` since they're handled specially
1369
    // Returns builder call only if there are attributes
1370
    let attributes_call = {
1,841✔
1371
        let items: Vec<TokenStream> = ps
1,841✔
1372
            .container
1,841✔
1373
            .attrs
1,841✔
1374
            .facet
1,841✔
1375
            .iter()
1,841✔
1376
            .filter(|attr| {
1,841✔
1377
                // These attributes are handled specially and not emitted to runtime:
1378
                // - invariants: populates vtable.invariants
1379
                // - crate: sets the facet crate path
1380
                // - traits: compile-time directive for vtable generation
1381
                // - auto_traits: compile-time directive for vtable generation
1382
                // - proxy: sets Shape::proxy for container-level proxy
1383
                if attr.is_builtin() {
177✔
1384
                    let key = attr.key_str();
162✔
1385
                    !matches!(
43✔
1386
                        key.as_str(),
162✔
1387
                        "invariants" | "crate" | "traits" | "auto_traits" | "proxy"
162✔
1388
                    )
1389
                } else {
1390
                    true
15✔
1391
                }
1392
            })
177✔
1393
            .map(|attr| {
1,841✔
1394
                let ext_attr = emit_attr(attr, &facet_crate);
134✔
1395
                quote! { #ext_attr }
134✔
1396
            })
134✔
1397
            .collect();
1,841✔
1398

1399
        if items.is_empty() {
1,841✔
1400
            quote! {}
1,727✔
1401
        } else {
1402
            quote! { .attributes(&const {[#(#items),*]}) }
114✔
1403
        }
1404
    };
1405

1406
    // Type tag from PStruct - returns builder call only if present
1407
    let type_tag_call = {
1,841✔
1408
        if let Some(type_tag) = ps.container.attrs.get_builtin_args("type_tag") {
1,841✔
1409
            quote! { .type_tag(#type_tag) }
8✔
1410
        } else {
1411
            quote! {}
1,833✔
1412
        }
1413
    };
1414

1415
    // Container-level proxy from PStruct - generates ProxyDef with conversion functions
1416
    //
1417
    // The challenge: Generic type parameters aren't available inside `const { }` blocks.
1418
    // Solution: We define the proxy functions as inherent methods on the type (outside const),
1419
    // then reference them via Self::method inside the Facet impl. This works because:
1420
    // 1. Inherent impl methods CAN use generic parameters from their impl block
1421
    // 2. Inside the Facet impl's const SHAPE, `Self` refers to the concrete monomorphized type
1422
    // 3. Function pointers to Self::method get properly monomorphized
1423
    let (proxy_inherent_impl, proxy_call) = {
1,841✔
1424
        if let Some(attr) = ps
1,841✔
1425
            .container
1,841✔
1426
            .attrs
1,841✔
1427
            .facet
1,841✔
1428
            .iter()
1,841✔
1429
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
1,841✔
1430
        {
1431
            let proxy_type = &attr.args;
8✔
1432
            let struct_type = &struct_name_ident;
8✔
1433
            let bgp_display = ps.container.bgp.display_without_bounds();
8✔
1434
            // Compute bgp locally for the inherent impl
1435
            let helper_bgp = ps
8✔
1436
                .container
8✔
1437
                .bgp
8✔
1438
                .with_lifetime(LifetimeName(format_ident!("ʄ")));
8✔
1439
            let bgp_def_for_helper = helper_bgp.display_with_bounds();
8✔
1440

1441
            // Define an inherent impl with the proxy helper methods
1442
            // These are NOT in a const block, so generic params ARE available
1443
            // We need where clauses for:
1444
            // 1. The proxy type must implement Facet (for __facet_proxy_shape)
1445
            // 2. The TryFrom conversions (checked when methods are called)
1446
            // Compute the where_clauses for the helper impl by adding the proxy Facet bound
1447
            // Build the combined where clause - we need to add proxy: Facet to existing clauses
1448
            let proxy_where = {
8✔
1449
                // Build additional clause tokens (comma-separated)
1450
                let additional_clauses = quote! { #proxy_type: #facet_crate::Facet<'ʄ> };
8✔
1451

1452
                // where_clauses is either empty or "where X: Y, ..."
1453
                // We need to append our clause
1454
                if where_clauses.is_empty() {
8✔
1455
                    quote! { where #additional_clauses }
7✔
1456
                } else {
1457
                    quote! { #where_clauses, #additional_clauses }
1✔
1458
                }
1459
            };
1460

1461
            let proxy_impl = quote! {
8✔
1462
                #[doc(hidden)]
1463
                impl #bgp_def_for_helper #struct_type #bgp_display
1464
                #proxy_where
1465
                {
1466
                    #[doc(hidden)]
1467
                    unsafe fn __facet_proxy_convert_in(
1468
                        proxy_ptr: #facet_crate::PtrConst,
1469
                        field_ptr: #facet_crate::PtrUninit,
1470
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1471
                        extern crate alloc as __alloc;
1472
                        let proxy: #proxy_type = proxy_ptr.read();
1473
                        match <#struct_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
1474
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
1475
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1476
                        }
1477
                    }
1478

1479
                    #[doc(hidden)]
1480
                    unsafe fn __facet_proxy_convert_out(
1481
                        field_ptr: #facet_crate::PtrConst,
1482
                        proxy_ptr: #facet_crate::PtrUninit,
1483
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1484
                        extern crate alloc as __alloc;
1485
                        let field_ref: &#struct_type #bgp_display = field_ptr.get();
1486
                        match <#proxy_type as ::core::convert::TryFrom<&#struct_type #bgp_display>>::try_from(field_ref) {
1487
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
1488
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1489
                        }
1490
                    }
1491

1492
                    #[doc(hidden)]
1493
                    const fn __facet_proxy_shape() -> &'static #facet_crate::Shape {
1494
                        <#proxy_type as #facet_crate::Facet>::SHAPE
1495
                    }
1496
                }
1497
            };
1498

1499
            // Reference the inherent methods from within the SHAPE const block.
1500
            // We use <Self> syntax which works inside &const { } blocks and properly
1501
            // refers to the monomorphized type from the enclosing impl.
1502
            let proxy_ref = quote! {
8✔
1503
                .proxy(&const {
1504
                    #facet_crate::ProxyDef {
1505
                        shape: <Self>::__facet_proxy_shape(),
1506
                        convert_in: <Self>::__facet_proxy_convert_in,
1507
                        convert_out: <Self>::__facet_proxy_convert_out,
1508
                    }
1509
                })
1510
            };
1511

1512
            (proxy_impl, proxy_ref)
8✔
1513
        } else {
1514
            (quote! {}, quote! {})
1,833✔
1515
        }
1516
    };
1517

1518
    // Generate the inner shape field value for transparent types
1519
    // inner call - only emit for transparent types
1520
    let inner_call = if use_transparent_semantics {
1,841✔
1521
        let inner_shape_val = if let Some(inner_field) = &inner_field {
47✔
1522
            let ty = &inner_field.ty;
47✔
1523
            if inner_field.attrs.has_builtin("opaque") {
47✔
1524
                quote! { <#facet_crate::Opaque<#ty> as #facet_crate::Facet>::SHAPE }
2✔
1525
            } else {
1526
                quote! { <#ty as #facet_crate::Facet>::SHAPE }
45✔
1527
            }
1528
        } else {
1529
            // Transparent ZST case
1530
            quote! { <() as #facet_crate::Facet>::SHAPE }
×
1531
        };
1532
        quote! { .inner(#inner_shape_val) }
47✔
1533
    } else {
1534
        quote! {}
1,794✔
1535
    };
1536

1537
    // Type name function - for generic types, this formats with type parameters
1538
    let type_name_call = if parsed.generics.is_some() && !opaque {
1,841✔
1539
        quote! { .type_name(#type_name_fn) }
68✔
1540
    } else {
1541
        quote! {}
1,773✔
1542
    };
1543

1544
    // Generics from PStruct
1545
    let facet_bgp = ps
1,841✔
1546
        .container
1,841✔
1547
        .bgp
1,841✔
1548
        .with_lifetime(LifetimeName(format_ident!("ʄ")));
1,841✔
1549
    let bgp_def = facet_bgp.display_with_bounds();
1,841✔
1550
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
1,841✔
1551

1552
    // Generate ty_field and optionally a hoisted __FIELDS const
1553
    // Hoisting avoids &const { [...] } which causes 12+ promotions per struct
1554
    let (ty_field, fields_const) = if opaque {
1,841✔
1555
        (
2✔
1556
            quote! {
2✔
1557
                #facet_crate::Type::User(#facet_crate::UserType::Opaque)
2✔
1558
            },
2✔
1559
            quote! {},
2✔
1560
        )
2✔
1561
    } else if fields_vec.is_empty() {
1,839✔
1562
        // Optimize: use &[] for empty fields to avoid const block overhead
1563
        (
19✔
1564
            quote! {
19✔
1565
                𝟋Ty::User(𝟋UTy::Struct(
19✔
1566
                    𝟋STyB::new(#kind, &[]).repr(#repr).build()
19✔
1567
                ))
19✔
1568
            },
19✔
1569
            quote! {},
19✔
1570
        )
19✔
1571
    } else {
1572
        // Hoist fields array to associated const to avoid promotions
1573
        let num_fields = fields_vec.len();
1,820✔
1574
        (
1575
            quote! {
1,820✔
1576
                𝟋Ty::User(𝟋UTy::Struct(
1577
                    𝟋STyB::new(#kind, &Self::__FIELDS).repr(#repr).build()
1578
                ))
1579
            },
1580
            quote! {
1,820✔
1581
                const __FIELDS: [#facet_crate::Field; #num_fields] = {
1582
                    use #facet_crate::𝟋::*;
1583
                    [#(#fields_vec),*]
1584
                };
1585
            },
1586
        )
1587
    };
1588

1589
    // Generate code to suppress dead_code warnings on structs constructed via reflection.
1590
    // When structs are constructed via reflection (e.g., facet_args::from_std_args()),
1591
    // the compiler doesn't see them being used and warns about dead code.
1592
    // This function ensures the struct type is "used" from the compiler's perspective.
1593
    // See: https://github.com/facet-rs/facet/issues/996
1594
    let dead_code_suppression = quote! {
1,841✔
1595
        const _: () = {
1596
            #[allow(dead_code, clippy::multiple_bound_locations)]
1597
            fn __facet_use_struct #bgp_def (__v: &#struct_name_ident #bgp_without_bounds) #where_clauses {
1598
                let _ = __v;
1599
            }
1600
        };
1601
    };
1602

1603
    // Generate static assertions for declared traits (catches lies at compile time)
1604
    // We put this in a generic function outside the const block so it can reference generic parameters
1605
    let facet_default = ps.container.attrs.has_builtin("default");
1,841✔
1606
    let trait_assertion_fn = if let Some(bounds) =
1,841✔
1607
        gen_trait_bounds(ps.container.attrs.declared_traits.as_ref(), facet_default)
1,841✔
1608
    {
1609
        // Note: where_clauses already includes "where" keyword if non-empty
1610
        // We need to add the trait bounds as an additional constraint
1611
        quote! {
6✔
1612
            const _: () = {
1613
                #[allow(dead_code, clippy::multiple_bound_locations)]
1614
                fn __facet_assert_traits #bgp_def (_: &#struct_name_ident #bgp_without_bounds)
1615
                where
1616
                    #struct_name_ident #bgp_without_bounds: #bounds
1617
                {}
1618
            };
1619
        }
1620
    } else {
1621
        quote! {}
1,835✔
1622
    };
1623

1624
    // Vtable is now fully built in gen_vtable, including invariants
1625
    let vtable_field = quote! { #vtable_init };
1,841✔
1626

1627
    // TypeOps for drop, default, clone - convert Option<TokenStream> to a call
1628
    let type_ops_call = match type_ops_init {
1,841✔
1629
        Some(ops) => quote! { .type_ops(#ops) },
1,841✔
NEW
1630
        None => quote! {},
×
1631
    };
1632

1633
    // Hoist the entire SHAPE construction to an inherent impl const
1634
    // This avoids &const {} promotions - the reference is to a plain const, not an inline const block
1635
    let shape_inherent_impl = quote! {
1,841✔
1636
        #[doc(hidden)]
1637
        impl #bgp_def #struct_name_ident #bgp_without_bounds #where_clauses {
1638
            #fields_const
1639

1640
            const __SHAPE_DATA: #facet_crate::Shape = {
1641
                use #facet_crate::𝟋::*;
1642

1643
                𝟋ShpB::for_sized::<Self>(#struct_name_str)
1644
                    .vtable(#vtable_field)
1645
                    #type_ops_call
1646
                    .ty(#ty_field)
1647
                    .def(𝟋Def::Undefined)
1648
                    #type_params_call
1649
                    #type_name_call
1650
                    #doc_call
1651
                    #attributes_call
1652
                    #type_tag_call
1653
                    #proxy_call
1654
                    #inner_call
1655
                    #variance_call
1656
                    .build()
1657
            };
1658
        }
1659
    };
1660

1661
    // Static declaration for release builds (pre-evaluates SHAPE)
1662
    let static_decl = crate::derive::generate_static_decl(&struct_name_ident, &facet_crate);
1,841✔
1663

1664
    // Final quote block using refactored parts
1665
    let result = quote! {
1,841✔
1666
        #dead_code_suppression
1667

1668
        #trait_assertion_fn
1669

1670
        // Proxy inherent impl (outside the Facet impl so generic params are in scope)
1671
        #proxy_inherent_impl
1672

1673
        // Hoisted SHAPE data const (avoids &const {} promotions)
1674
        #shape_inherent_impl
1675

1676
        #[automatically_derived]
1677
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #struct_name_ident #bgp_without_bounds #where_clauses {
1678
            const SHAPE: &'static #facet_crate::Shape = &Self::__SHAPE_DATA;
1679
        }
1680

1681
        #static_decl
1682
    };
1683

1684
    result
1,841✔
1685
}
1,841✔
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