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

facet-rs / facet / 20068363674

09 Dec 2025 03:11PM UTC coverage: 58.733% (+0.003%) from 58.73%
20068363674

push

github

fasterthanlime
refactor: remove dead derive-parsing code from facet-macros-impl

Rust strips #[derive(...)] attributes before passing to derive macros,
so any code attempting to parse them will never see them. This removes:

- KnownDerives struct that was meant to track detected derives
- parse_derive_attr function that attempted to parse #[derive(...)]
- has_derive method and all its call sites in process_struct.rs
- related documentation referring to "derive detection"

Users must use #[facet(traits(...))] to explicitly declare trait
implementations, as this was always the fallback path that worked.

Closes #1199

17 of 19 new or added lines in 2 files covered. (89.47%)

8 existing lines in 1 file now uncovered.

26171 of 44559 relevant lines covered (58.73%)

635.66 hits per line

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

89.89
/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 two ways these can be populated:
7
//!
8
//! ## 1. Explicit Declaration (No Specialization)
9
//!
10
//! Use `#[facet(traits(...))]` to declare which traits are implemented:
11
//!
12
//! ```ignore
13
//! #[derive(Debug, Clone, Facet)]
14
//! #[facet(traits(Debug, Clone))]  // Explicit declaration
15
//! struct Foo { ... }
16
//! ```
17
//!
18
//! This generates compile-time assertions to verify the traits are actually implemented.
19
//!
20
//! **Note:** Rust strips `#[derive(...)]` attributes before passing to derive macros,
21
//! so the Facet macro cannot automatically detect derived traits. You must explicitly
22
//! declare them via `#[facet(traits(...))]`.
23
//!
24
//! ## 2. Auto-Detection (Uses Specialization)
25
//!
26
//! For backward compatibility or when you don't want to list traits manually, use
27
//! `#[facet(auto_traits)]`. This uses the `impls!` macro to detect traits at compile
28
//! time via specialization tricks:
29
//!
30
//! ```ignore
31
//! #[derive(Debug, Facet)]
32
//! #[facet(auto_traits)]  // Auto-detect all traits
33
//! struct Foo { ... }
34
//! ```
35
//!
36
//! **Note:** Auto-detection is slower to compile because it generates specialization
37
//! code for each trait. Use explicit declaration when possible.
38
//!
39
//! ## Layered Resolution
40
//!
41
//! For each vtable entry, the macro checks sources in order:
42
//! 1. Is the trait in `#[facet(traits(...))]`? → Use direct impl
43
//! 2. Is `#[facet(auto_traits)]` present? → Use `impls!` detection
44
//! 3. Otherwise → Set to `None`
45
//!
46
//! Note: `#[facet(traits(...))]` and `#[facet(auto_traits)]` are mutually exclusive.
47

48
use quote::{format_ident, quote, quote_spanned};
49

50
use super::*;
51

52
/// Information about transparent type for vtable generation.
53
///
54
/// This is used to generate `try_borrow_inner` functions for transparent/newtype wrappers.
55
pub(crate) struct TransparentInfo<'a> {
56
    /// The inner field type (for tuple struct with one field)
57
    pub inner_field_type: Option<&'a TokenStream>,
58
    /// Whether the inner field is opaque
59
    pub inner_is_opaque: bool,
60
    /// Whether this is a ZST (unit-like transparent struct)
61
    pub is_zst: bool,
62
}
63

64
/// Sources of trait information for vtable generation.
65
///
66
/// The vtable generation uses a layered approach:
67
/// 1. **Declared** - traits explicitly listed in `#[facet(traits(...))]`
68
/// 2. **Implied** - traits implied by other attributes (e.g., `#[facet(default)]` implies Default)
69
/// 3. **Auto** - if `#[facet(auto_traits)]` is present, use `impls!` for remaining traits
70
/// 4. **None** - if none of the above apply, emit `None` for that trait
71
pub(crate) struct TraitSources<'a> {
72
    /// Traits explicitly declared via #[facet(traits(...))]
73
    pub declared_traits: Option<&'a DeclaredTraits>,
74
    /// Whether to auto-detect remaining traits via specialization
75
    pub auto_traits: bool,
76
    /// Whether `#[facet(default)]` is present (implies Default trait)
77
    pub facet_default: bool,
78
}
79

80
impl<'a> TraitSources<'a> {
81
    /// Create trait sources from parsed attributes
82
    pub fn from_attrs(attrs: &'a PAttrs) -> Self {
2,204✔
83
        Self {
2,204✔
84
            declared_traits: attrs.declared_traits.as_ref(),
2,204✔
85
            auto_traits: attrs.auto_traits,
2,204✔
86
            facet_default: attrs.has_builtin("default"),
2,204✔
87
        }
2,204✔
88
    }
2,204✔
89

90
    /// Check if a trait is explicitly declared
91
    fn has_declared(&self, check: impl FnOnce(&DeclaredTraits) -> bool) -> bool {
17,632✔
92
        self.declared_traits.is_some_and(check)
17,632✔
93
    }
17,632✔
94

95
    /// Check if we should use auto-detection for this trait
96
    fn should_auto(&self) -> bool {
4,557✔
97
        self.auto_traits
4,557✔
98
    }
4,557✔
99
}
100

101
/// Generates the vtable for a type based on trait sources.
102
///
103
/// Uses a layered approach for each trait:
104
/// 1. If explicitly declared → direct impl (no specialization)
105
/// 2. If auto_traits enabled → use `impls!` macro for detection
106
/// 3. Otherwise → None
107
///
108
/// When `auto_traits` is NOT enabled, generates `ValueVTableThinInstant` using
109
/// helper functions like `debug_for::<Self>()`. This avoids closures that would
110
/// require `T: 'static` bounds.
111
///
112
/// When `auto_traits` IS enabled, falls back to `ValueVTable::builder()` pattern
113
/// (ThinDelayed) which uses closures for runtime trait detection.
114
pub(crate) fn gen_vtable(
2,204✔
115
    facet_crate: &TokenStream,
2,204✔
116
    type_name_fn: &TokenStream,
2,204✔
117
    sources: &TraitSources<'_>,
2,204✔
118
    transparent: Option<&TransparentInfo<'_>>,
2,204✔
119
    struct_type: &TokenStream,
2,204✔
120
    invariants_fn: Option<&TokenStream>,
2,204✔
121
) -> TokenStream {
2,204✔
122
    // If auto_traits is enabled, use VTableIndirect with runtime trait detection.
123
    if sources.auto_traits {
2,204✔
124
        return gen_vtable_indirect(
29✔
125
            facet_crate,
29✔
126
            type_name_fn,
29✔
127
            sources,
29✔
128
            struct_type,
29✔
129
            invariants_fn,
29✔
130
        );
131
    }
2,175✔
132

133
    // Otherwise, use VTableDirect with compile-time trait resolution.
134
    gen_vtable_direct(
2,175✔
135
        facet_crate,
2,175✔
136
        type_name_fn,
2,175✔
137
        sources,
2,175✔
138
        transparent,
2,175✔
139
        struct_type,
2,175✔
140
        invariants_fn,
2,175✔
141
    )
142
}
2,204✔
143

144
/// Generates a VTableDirect using direct trait method references.
145
/// This avoids closures and uses the pattern from vtable_direct! macro.
146
/// Uses `Self` inside the const block, which properly resolves to the implementing type
147
/// without requiring that lifetime parameters outlive 'static.
148
fn gen_vtable_direct(
2,175✔
149
    facet_crate: &TokenStream,
2,175✔
150
    _type_name_fn: &TokenStream,
2,175✔
151
    sources: &TraitSources<'_>,
2,175✔
152
    transparent: Option<&TransparentInfo<'_>>,
2,175✔
153
    struct_type: &TokenStream,
2,175✔
154
    invariants_fn: Option<&TokenStream>,
2,175✔
155
) -> TokenStream {
2,175✔
156
    // Display: check declared
157
    let display_call = if sources.has_declared(|d| d.display) {
2,175✔
158
        quote! { .display(<Self as ::core::fmt::Display>::fmt) }
×
159
    } else {
160
        quote! {}
2,175✔
161
    };
162

163
    // Debug: check declared
164
    let debug_call = if sources.has_declared(|d| d.debug) {
2,175✔
165
        quote! { .debug(<Self as ::core::fmt::Debug>::fmt) }
2✔
166
    } else {
167
        quote! {}
2,173✔
168
    };
169

170
    // PartialEq: check declared
171
    let partial_eq_call = if sources.has_declared(|d| d.partial_eq) {
2,175✔
NEW
172
        quote! { .partial_eq(<Self as ::core::cmp::PartialEq>::eq) }
×
173
    } else {
174
        quote! {}
2,175✔
175
    };
176

177
    // PartialOrd: check declared
178
    let partial_ord_call = if sources.has_declared(|d| d.partial_ord) {
2,175✔
NEW
179
        quote! { .partial_cmp(<Self as ::core::cmp::PartialOrd>::partial_cmp) }
×
180
    } else {
181
        quote! {}
2,175✔
182
    };
183

184
    // Ord: check declared
185
    let ord_call = if sources.has_declared(|d| d.ord) {
2,175✔
UNCOV
186
        quote! { .cmp(<Self as ::core::cmp::Ord>::cmp) }
×
187
    } else {
188
        quote! {}
2,175✔
189
    };
190

191
    // Hash: check declared
192
    let hash_call = if sources.has_declared(|d| d.hash) {
2,175✔
UNCOV
193
        quote! { .hash(<Self as ::core::hash::Hash>::hash::<#facet_crate::HashProxy>) }
×
194
    } else {
195
        quote! {}
2,175✔
196
    };
197

198
    // Transparent type functions: try_borrow_inner
199
    // For transparent types (newtypes), we generate a function to borrow the inner value
200
    // Note: We still need the concrete struct_type here because we're dealing with field access
201
    let try_borrow_inner_call = if let Some(info) = transparent {
2,175✔
202
        if info.inner_is_opaque {
49✔
203
            // Opaque inner field - no borrow possible
204
            quote! {}
2✔
205
        } else if info.is_zst {
47✔
206
            // ZST case - no inner value to borrow
207
            quote! {}
×
208
        } else if let Some(inner_ty) = info.inner_field_type {
47✔
209
            // Transparent struct with one field - generate try_borrow_inner
210
            // The function signature for VTableDirect is: unsafe fn(*const T) -> Result<Ptr, String>
211
            quote! {
47✔
212
                .try_borrow_inner({
213
                    unsafe fn __try_borrow_inner(src: *const #struct_type) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
214
                        // src points to the wrapper (tuple struct), field 0 is the inner value
215
                        // We cast away const because try_borrow_inner returns PtrMut for flexibility
216
                        // (caller can downgrade to PtrConst if needed)
217
                        let wrapper_ptr = src as *mut #struct_type;
218
                        let inner_ptr: *mut #inner_ty = unsafe { &raw mut (*wrapper_ptr).0 };
219
                        ::core::result::Result::Ok(#facet_crate::PtrMut::new(inner_ptr as *mut u8))
220
                    }
221
                    __try_borrow_inner
222
                })
223
            }
224
        } else {
225
            quote! {}
×
226
        }
227
    } else {
228
        quote! {}
2,126✔
229
    };
230

231
    // Invariants: container-level invariants function
232
    let invariants_call = if let Some(inv_fn) = invariants_fn {
2,175✔
233
        quote! { .invariants(#inv_fn) }
3✔
234
    } else {
235
        quote! {}
2,172✔
236
    };
237

238
    // Generate VTableErased::Direct with a static VTableDirect
239
    // Uses prelude aliases for compact output (𝟋VtE, 𝟋VtD)
240
    // NOTE: drop_in_place, default_in_place, clone_into are now in TypeOps, not VTable
241
    quote! {
2,175✔
242
        𝟋VtE::Direct(&const {
243
            𝟋VtD::builder_for::<Self>()
244
                #display_call
245
                #debug_call
246
                #partial_eq_call
247
                #partial_ord_call
248
                #ord_call
249
                #hash_call
250
                #invariants_call
251
                #try_borrow_inner_call
252
                .build()
253
        })
254
    }
255
}
2,175✔
256

257
/// Generates a VTableIndirect using the specialization-based auto_traits approach.
258
/// Used when `#[facet(auto_traits)]` is enabled for runtime trait detection.
259
///
260
/// This generates functions that use `OxRef`/`OxMut` and return `Option<T>` to indicate
261
/// whether the trait is implemented.
262
fn gen_vtable_indirect(
29✔
263
    facet_crate: &TokenStream,
29✔
264
    _type_name_fn: &TokenStream,
29✔
265
    sources: &TraitSources<'_>,
29✔
266
    struct_type: &TokenStream,
29✔
267
    invariants_fn: Option<&TokenStream>,
29✔
268
) -> TokenStream {
29✔
269
    // For VTableIndirect, functions take OxRef/OxMut and return Option<T>
270
    // The Option layer allows returning None when trait is not implemented
271

272
    // Display: check declared then auto
273
    let display_field = if sources.has_declared(|d| d.display) {
29✔
274
        quote! {
×
275
            display: ::core::option::Option::Some({
276
                unsafe fn __display(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
277
                    let data: &#struct_type = data.ptr().get();
278
                    ::core::option::Option::Some(::core::fmt::Display::fmt(data, f))
279
                }
280
                __display
281
            }),
282
        }
283
    } else if sources.should_auto() {
29✔
284
        quote! {
29✔
285
            display: ::core::option::Option::Some({
286
                unsafe fn __display(data: #facet_crate::OxPtrConst, f: &mut ::core::fmt::Formatter<'_>) -> ::core::option::Option<::core::fmt::Result> {
287
                    if impls!(#struct_type: ::core::fmt::Display) {
288
                        let data: &#struct_type = data.ptr().get();
289
                        ::core::option::Option::Some((&&Spez(data)).spez_display(f))
290
                    } else {
291
                        ::core::option::Option::None
292
                    }
293
                }
294
                __display
295
            }),
296
        }
297
    } else {
298
        quote! { display: ::core::option::Option::None, }
×
299
    };
300

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

330
    // PartialEq: check declared, then auto
331
    let partial_eq_field = if sources.has_declared(|d| d.partial_eq) {
29✔
UNCOV
332
        quote! {
×
333
            partial_eq: ::core::option::Option::Some({
334
                unsafe fn __partial_eq(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<bool> {
335
                    let left: &#struct_type = left.ptr().get();
336
                    let right: &#struct_type = right.ptr().get();
337
                    ::core::option::Option::Some(<#struct_type as ::core::cmp::PartialEq>::eq(left, right))
338
                }
339
                __partial_eq
340
            }),
341
        }
342
    } else if sources.should_auto() {
29✔
343
        quote! {
29✔
344
            partial_eq: ::core::option::Option::Some({
345
                unsafe fn __partial_eq(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<bool> {
346
                    if impls!(#struct_type: ::core::cmp::PartialEq) {
347
                        let left: &#struct_type = left.ptr().get();
348
                        let right: &#struct_type = right.ptr().get();
349
                        ::core::option::Option::Some((&&Spez(left)).spez_partial_eq(&&Spez(right)))
350
                    } else {
351
                        ::core::option::Option::None
352
                    }
353
                }
354
                __partial_eq
355
            }),
356
        }
357
    } else {
358
        quote! { partial_eq: ::core::option::Option::None, }
×
359
    };
360

361
    // PartialOrd: check declared, then auto
362
    let partial_cmp_field = if sources.has_declared(|d| d.partial_ord) {
29✔
UNCOV
363
        quote! {
×
364
            partial_cmp: ::core::option::Option::Some({
365
                unsafe fn __partial_cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::option::Option<::core::cmp::Ordering>> {
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::PartialOrd>::partial_cmp(left, right))
369
                }
370
                __partial_cmp
371
            }),
372
        }
373
    } else if sources.should_auto() {
29✔
374
        quote! {
29✔
375
            partial_cmp: ::core::option::Option::Some({
376
                unsafe fn __partial_cmp(left: #facet_crate::OxPtrConst, right: #facet_crate::OxPtrConst) -> ::core::option::Option<::core::option::Option<::core::cmp::Ordering>> {
377
                    if impls!(#struct_type: ::core::cmp::PartialOrd) {
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_cmp(&&Spez(right)))
381
                    } else {
382
                        ::core::option::Option::None
383
                    }
384
                }
385
                __partial_cmp
386
            }),
387
        }
388
    } else {
389
        quote! { partial_cmp: ::core::option::Option::None, }
×
390
    };
391

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

423
    // Hash: check declared, then auto
424
    let hash_field = if sources.has_declared(|d| d.hash) {
29✔
UNCOV
425
        quote! {
×
426
            hash: ::core::option::Option::Some({
427
                unsafe fn __hash(value: #facet_crate::OxPtrConst, hasher: &mut #facet_crate::HashProxy<'_>) -> ::core::option::Option<()> {
428
                    let value: &#struct_type = value.ptr().get();
429
                    <#struct_type as ::core::hash::Hash>::hash(value, hasher);
430
                    ::core::option::Option::Some(())
431
                }
432
                __hash
433
            }),
434
        }
435
    } else if sources.should_auto() {
29✔
436
        quote! {
29✔
437
            hash: ::core::option::Option::Some({
438
                unsafe fn __hash(value: #facet_crate::OxPtrConst, hasher: &mut #facet_crate::HashProxy<'_>) -> ::core::option::Option<()> {
439
                    if impls!(#struct_type: ::core::hash::Hash) {
440
                        let value: &#struct_type = value.ptr().get();
441
                        (&&Spez(value)).spez_hash(hasher);
442
                        ::core::option::Option::Some(())
443
                    } else {
444
                        ::core::option::Option::None
445
                    }
446
                }
447
                __hash
448
            }),
449
        }
450
    } else {
451
        quote! { hash: ::core::option::Option::None, }
×
452
    };
453

454
    // Parse (FromStr): no derive exists, only auto-detect if enabled
455
    let parse_field = if sources.should_auto() {
29✔
456
        quote! {
29✔
457
            parse: ::core::option::Option::Some({
458
                unsafe fn __parse(s: &str, target: #facet_crate::OxPtrMut) -> ::core::option::Option<::core::result::Result<(), #facet_crate::ParseError>> {
459
                    if impls!(#struct_type: ::core::str::FromStr) {
460
                        ::core::option::Option::Some(
461
                            match (&&SpezEmpty::<#struct_type>::SPEZ).spez_parse(s, target.ptr().as_uninit()) {
462
                                ::core::result::Result::Ok(_) => ::core::result::Result::Ok(()),
463
                                ::core::result::Result::Err(e) => ::core::result::Result::Err(e),
464
                            }
465
                        )
466
                    } else {
467
                        ::core::option::Option::None
468
                    }
469
                }
470
                __parse
471
            }),
472
        }
473
    } else {
474
        quote! { parse: ::core::option::Option::None, }
×
475
    };
476

477
    // Invariants: container-level invariants function (wrapped for OxRef signature)
478
    let invariants_field = if let Some(inv_fn) = invariants_fn {
29✔
479
        quote! {
×
480
            invariants: ::core::option::Option::Some({
481
                unsafe fn __invariants(data: #facet_crate::OxPtrConst) -> ::core::option::Option<#facet_crate::𝟋::𝟋Result<(), #facet_crate::𝟋::𝟋Str>> {
482
                    let value: &#struct_type = data.ptr().get();
483
                    ::core::option::Option::Some(#inv_fn(value))
484
                }
485
                __invariants
486
            }),
487
        }
488
    } else {
489
        quote! { invariants: ::core::option::Option::None, }
29✔
490
    };
491

492
    // Return VTableErased::Indirect wrapping a VTableIndirect using struct literal syntax
493
    // Uses prelude aliases for compact output (𝟋VtE)
494
    // NOTE: drop_in_place, default_in_place, clone_into are now in TypeOps, not VTable
495
    quote! {
29✔
496
        𝟋VtE::Indirect(&const {
497
            #facet_crate::VTableIndirect {
498
                #display_field
499
                #debug_field
500
                #hash_field
501
                #invariants_field
502
                #parse_field
503
                try_from: ::core::option::Option::None,
504
                try_into_inner: ::core::option::Option::None,
505
                try_borrow_inner: ::core::option::Option::None,
506
                #partial_eq_field
507
                #partial_cmp_field
508
                #cmp_field
509
            }
510
        })
511
    }
512
}
29✔
513

514
/// Generates TypeOps for per-type operations (drop, default, clone).
515
/// Returns `Option<TokenStream>` - Some if any TypeOps is needed, None if no ops.
516
///
517
/// Uses TypeOpsDirect for non-generic types, TypeOpsIndirect for generic types.
518
pub(crate) fn gen_type_ops(
2,204✔
519
    facet_crate: &TokenStream,
2,204✔
520
    sources: &TraitSources<'_>,
2,204✔
521
    struct_type: &TokenStream,
2,204✔
522
    has_type_or_const_generics: bool,
2,204✔
523
) -> Option<TokenStream> {
2,204✔
524
    // Only use TypeOpsIndirect when there are actual type or const generics.
525
    // For auto_traits WITHOUT generics, we can still use TypeOpsDirect since
526
    // the helper functions can use `Self` which resolves to the concrete type.
527
    if has_type_or_const_generics {
2,204✔
528
        return gen_type_ops_indirect(facet_crate, sources, struct_type);
23✔
529
    }
2,181✔
530

531
    // Use TypeOpsDirect for non-generic types (including auto_traits without generics)
532
    gen_type_ops_direct(facet_crate, sources, struct_type)
2,181✔
533
}
2,204✔
534

535
/// Generates TypeOpsDirect for non-generic types.
536
/// Returns Some(TokenStream) if any ops are needed, None otherwise.
537
///
538
/// Uses raw pointers (`*mut ()`, `*const ()`) for type-erased function signatures,
539
/// matching VTableDirect's approach. The `Self` type is used inside the const block
540
/// which properly resolves without capturing lifetime parameters.
541
fn gen_type_ops_direct(
2,181✔
542
    facet_crate: &TokenStream,
2,181✔
543
    sources: &TraitSources<'_>,
2,181✔
544
    struct_type: &TokenStream,
2,181✔
545
) -> Option<TokenStream> {
2,181✔
546
    // Check if Default is available (from declared traits or #[facet(default)])
547
    let has_default = sources.has_declared(|d| d.default) || sources.facet_default;
2,181✔
548

549
    // Check if Clone is available (from declared traits)
550
    let has_clone = sources.has_declared(|d| d.clone);
2,181✔
551

552
    // Generate default_in_place field
553
    // Uses helper function 𝟋default_for::<Self>() which returns fn(*mut Self),
554
    // then transmutes to fn(*mut ()) for the erased signature
555
    let default_field = if has_default {
2,181✔
556
        quote! {
8✔
557
            default_in_place: ::core::option::Option::Some(
558
                unsafe { ::core::mem::transmute(#facet_crate::𝟋::𝟋default_for::<Self>() as unsafe fn(*mut Self)) }
559
            ),
560
        }
561
    } else if sources.should_auto() {
2,173✔
562
        // For auto_traits, generate an inline function that uses the Spez pattern.
563
        // The function hardcodes struct_type, so specialization resolves correctly.
564
        // The impls! check determines whether we return Some or None at const-eval time.
565
        quote! {
29✔
566
            default_in_place: if #facet_crate::𝟋::impls!(#struct_type: ::core::default::Default) {
567
                ::core::option::Option::Some({
568
                    unsafe fn __default_in_place(ptr: *mut ()) {
569
                        let target = #facet_crate::PtrUninit::new(ptr as *mut u8);
570
                        unsafe { (&&&#facet_crate::𝟋::SpezEmpty::<#struct_type>::SPEZ).spez_default_in_place(target) };
571
                    }
572
                    __default_in_place
573
                })
574
            } else {
575
                ::core::option::Option::None
576
            },
577
        }
578
    } else {
579
        quote! { default_in_place: ::core::option::Option::None, }
2,144✔
580
    };
581

582
    // Generate clone_into field
583
    // Uses helper function 𝟋clone_for::<Self>() which returns fn(*const Self, *mut Self),
584
    // then transmutes to fn(*const (), *mut ()) for the erased signature
585
    let clone_field = if has_clone {
2,181✔
586
        quote! {
×
587
            clone_into: ::core::option::Option::Some(
588
                unsafe { ::core::mem::transmute(#facet_crate::𝟋::𝟋clone_for::<Self>() as unsafe fn(*const Self, *mut Self)) }
589
            ),
590
        }
591
    } else if sources.should_auto() {
2,181✔
592
        // For auto_traits, generate an inline function that uses the Spez pattern.
593
        // The function hardcodes struct_type, so specialization resolves correctly.
594
        // The impls! check determines whether we return Some or None at const-eval time.
595
        quote! {
29✔
596
            clone_into: if #facet_crate::𝟋::impls!(#struct_type: ::core::clone::Clone) {
597
                ::core::option::Option::Some({
598
                    unsafe fn __clone_into(src: *const (), dst: *mut ()) {
599
                        let src_ref: &#struct_type = unsafe { &*(src as *const #struct_type) };
600
                        let target = #facet_crate::PtrUninit::new(dst as *mut u8);
601
                        unsafe { (&&&#facet_crate::𝟋::Spez(src_ref)).spez_clone_into(target) };
602
                    }
603
                    __clone_into
604
                })
605
            } else {
606
                ::core::option::Option::None
607
            },
608
        }
609
    } else {
610
        quote! { clone_into: ::core::option::Option::None, }
2,152✔
611
    };
612

613
    // Generate TypeOpsDirect struct literal
614
    // Uses transmute to convert typed fn pointers to erased fn(*mut ()) etc.
615
    // Uses Self inside the const block which resolves to the implementing type
616
    Some(quote! {
2,181✔
617
        #facet_crate::TypeOps::Direct(&const {
2,181✔
618
            #facet_crate::TypeOpsDirect {
2,181✔
619
                drop_in_place: unsafe { ::core::mem::transmute(::core::ptr::drop_in_place::<Self> as unsafe fn(*mut Self)) },
2,181✔
620
                #default_field
2,181✔
621
                #clone_field
2,181✔
622
            }
2,181✔
623
        })
2,181✔
624
    })
2,181✔
625
}
2,181✔
626

627
/// Generates TypeOpsIndirect for generic types with auto_traits.
628
/// Returns Some(TokenStream) if any ops are needed, None otherwise.
629
///
630
/// Uses helper functions that take a type parameter to avoid the "can't use Self
631
/// from outer item" error in function items.
632
fn gen_type_ops_indirect(
23✔
633
    facet_crate: &TokenStream,
23✔
634
    sources: &TraitSources<'_>,
23✔
635
    _struct_type: &TokenStream,
23✔
636
) -> Option<TokenStream> {
23✔
637
    // For TypeOpsIndirect, we always need drop_in_place
638
    // default_in_place and clone_into are optional based on available traits
639
    // Note: We use helper functions 𝟋indirect_*_for::<Self>() which have their own
640
    // generic parameter, avoiding the "can't use Self from outer item" issue.
641

642
    // Check if Default is available
643
    // Note: For auto_traits, we could use specialization but it's complex.
644
    // For now, only generate default_in_place when Default is explicitly known.
645
    let default_field = if sources.has_declared(|d| d.default) || sources.facet_default {
23✔
UNCOV
646
        quote! {
×
647
            default_in_place: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_default_for::<Self>()),
648
        }
649
    } else {
650
        // For auto_traits or no default, set to None
651
        // Runtime detection of Default not supported in TypeOps yet
652
        quote! { default_in_place: ::core::option::Option::None, }
23✔
653
    };
654

655
    // Check if Clone is available
656
    // Note: For auto_traits, we could use specialization but it's complex.
657
    // For now, only generate clone_into when Clone is explicitly known.
658
    let clone_field = if sources.has_declared(|d| d.clone) {
23✔
659
        quote! {
×
660
            clone_into: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_clone_for::<Self>()),
661
        }
662
    } else {
663
        // For auto_traits or no clone, set to None
664
        // Runtime detection of Clone not supported in TypeOps yet
665
        quote! { clone_into: ::core::option::Option::None, }
23✔
666
    };
667

668
    Some(quote! {
23✔
669
        #facet_crate::TypeOps::Indirect(&const {
23✔
670
            #facet_crate::TypeOpsIndirect {
23✔
671
                drop_in_place: #facet_crate::𝟋::𝟋indirect_drop_for::<Self>(),
23✔
672
                #default_field
23✔
673
                #clone_field
23✔
674
            }
23✔
675
        })
23✔
676
    })
23✔
677
}
23✔
678

679
/// Generate trait bounds for static assertions.
680
/// Returns a TokenStream of bounds like `core::fmt::Debug + core::clone::Clone`
681
/// that can be used in a where clause.
682
///
683
/// `facet_default` is true when `#[facet(default)]` is present, which implies Default.
684
pub(crate) fn gen_trait_bounds(
2,204✔
685
    declared: Option<&DeclaredTraits>,
2,204✔
686
    facet_default: bool,
2,204✔
687
) -> Option<TokenStream> {
2,204✔
688
    let mut bounds = Vec::new();
2,204✔
689

690
    if let Some(declared) = declared {
2,204✔
691
        if declared.display {
6✔
692
            bounds.push(quote! { core::fmt::Display });
×
693
        }
6✔
694
        if declared.debug {
6✔
695
            bounds.push(quote! { core::fmt::Debug });
2✔
696
        }
4✔
697
        if declared.clone {
6✔
698
            bounds.push(quote! { core::clone::Clone });
×
699
        }
6✔
700
        if declared.copy {
6✔
701
            bounds.push(quote! { core::marker::Copy });
×
702
        }
6✔
703
        if declared.partial_eq {
6✔
704
            bounds.push(quote! { core::cmp::PartialEq });
×
705
        }
6✔
706
        if declared.eq {
6✔
707
            bounds.push(quote! { core::cmp::Eq });
×
708
        }
6✔
709
        if declared.partial_ord {
6✔
710
            bounds.push(quote! { core::cmp::PartialOrd });
×
711
        }
6✔
712
        if declared.ord {
6✔
713
            bounds.push(quote! { core::cmp::Ord });
×
714
        }
6✔
715
        if declared.hash {
6✔
716
            bounds.push(quote! { core::hash::Hash });
×
717
        }
6✔
718
        if declared.default {
6✔
719
            bounds.push(quote! { core::default::Default });
4✔
720
        }
4✔
721
        if declared.send {
6✔
722
            bounds.push(quote! { core::marker::Send });
×
723
        }
6✔
724
        if declared.sync {
6✔
725
            bounds.push(quote! { core::marker::Sync });
×
726
        }
6✔
727
        if declared.unpin {
6✔
728
            bounds.push(quote! { core::marker::Unpin });
×
729
        }
6✔
730
    }
2,198✔
731

732
    // #[facet(default)] implies Default trait
733
    if facet_default && !declared.is_some_and(|d| d.default) {
2,204✔
734
        bounds.push(quote! { core::default::Default });
4✔
735
    }
2,200✔
736

737
    if bounds.is_empty() {
2,204✔
738
        None
2,194✔
739
    } else {
740
        Some(quote! { #(#bounds)+* })
10✔
741
    }
742
}
2,204✔
743

744
/// Generates the `::facet::Field` definition `TokenStream` from a `PStructField`.
745
pub(crate) fn gen_field_from_pfield(
3,900✔
746
    field: &PStructField,
3,900✔
747
    struct_name: &Ident,
3,900✔
748
    bgp: &BoundedGenericParams,
3,900✔
749
    base_offset: Option<TokenStream>,
3,900✔
750
    facet_crate: &TokenStream,
3,900✔
751
) -> TokenStream {
3,900✔
752
    let field_name_effective = &field.name.effective;
3,900✔
753
    let field_name_raw = &field.name.raw;
3,900✔
754
    let field_type = &field.ty;
3,900✔
755

756
    let bgp_without_bounds = bgp.display_without_bounds();
3,900✔
757

758
    #[cfg(feature = "doc")]
759
    let doc_lines: Vec<String> = field
3,900✔
760
        .attrs
3,900✔
761
        .doc
3,900✔
762
        .iter()
3,900✔
763
        .map(|doc| doc.as_str().replace("\\\"", "\""))
3,900✔
764
        .collect();
3,900✔
765
    #[cfg(not(feature = "doc"))]
766
    let doc_lines: Vec<String> = Vec::new();
×
767

768
    // Check if this field is marked as a recursive type
769
    let is_recursive = field.attrs.has_builtin("recursive_type");
3,900✔
770

771
    // Generate the shape expression directly using the field type
772
    // For opaque fields, wrap in Opaque<T>
773
    // NOTE: Uses short alias from `use #facet_crate::𝟋::*` in the enclosing const block
774
    let shape_expr = if field.attrs.has_builtin("opaque") {
3,900✔
775
        quote! { <#facet_crate::Opaque<#field_type> as 𝟋Fct>::SHAPE }
33✔
776
    } else {
777
        quote! { <#field_type as 𝟋Fct>::SHAPE }
3,867✔
778
    };
779

780
    // Process attributes, separating flag attrs and field attrs from the attribute slice.
781
    // Attributes with #[storage(flag)] go into FieldFlags for O(1) access.
782
    // Attributes with #[storage(field)] go into dedicated Field struct fields.
783
    // Everything else goes into the attributes slice.
784
    //
785
    // Flag attrs: sensitive, flatten, child, skip, skip_serializing, skip_deserializing
786
    // Field attrs: rename, alias
787
    // Note: default also sets HAS_DEFAULT flag (handled below)
788

789
    // Track what kind of default was specified
790
    enum DefaultKind {
791
        FromTrait,
792
        Custom(TokenStream),
793
    }
794

795
    let mut flags: Vec<TokenStream> = Vec::new();
3,900✔
796
    let mut rename_value: Option<TokenStream> = None;
3,900✔
797
    let mut alias_value: Option<TokenStream> = None;
3,900✔
798
    let mut default_value: Option<DefaultKind> = None;
3,900✔
799
    let mut skip_serializing_if_value: Option<TokenStream> = None;
3,900✔
800
    let mut invariants_value: Option<TokenStream> = None;
3,900✔
801
    let mut proxy_value: Option<TokenStream> = None;
3,900✔
802
    let mut attribute_list: Vec<TokenStream> = Vec::new();
3,900✔
803

804
    for attr in &field.attrs.facet {
3,900✔
805
        if attr.is_builtin() {
1,588✔
806
            let key = attr.key_str();
585✔
807
            match key.as_str() {
585✔
808
                // Flag attrs - set bit in FieldFlags, don't add to attribute_list
809
                "sensitive" => {
585✔
810
                    flags.push(quote! { 𝟋FF::SENSITIVE });
2✔
811
                }
2✔
812
                "flatten" => {
583✔
813
                    flags.push(quote! { 𝟋FF::FLATTEN });
94✔
814
                }
94✔
815
                "child" => {
489✔
816
                    flags.push(quote! { 𝟋FF::CHILD });
×
817
                }
×
818
                "skip" => {
489✔
819
                    flags.push(quote! { 𝟋FF::SKIP });
3✔
820
                }
3✔
821
                "skip_serializing" => {
486✔
822
                    flags.push(quote! { 𝟋FF::SKIP_SERIALIZING });
4✔
823
                }
4✔
824
                "skip_deserializing" => {
482✔
825
                    flags.push(quote! { 𝟋FF::SKIP_DESERIALIZING });
1✔
826
                }
1✔
827
                "default" => {
481✔
828
                    // Default goes into dedicated field, not attributes
829
                    let args = &attr.args;
235✔
830
                    if args.is_empty() {
235✔
831
                        // #[facet(default)] - use Default trait
217✔
832
                        default_value = Some(DefaultKind::FromTrait);
217✔
833
                    } else {
217✔
834
                        // #[facet(default = expr)] - use custom expression
835
                        // Parse `= expr` to get just the expr
836
                        let args_str = args.to_string();
18✔
837
                        let expr_str = args_str.trim_start_matches('=').trim();
18✔
838
                        let expr: TokenStream = expr_str.parse().unwrap_or_else(|_| args.clone());
18✔
839
                        default_value = Some(DefaultKind::Custom(expr));
18✔
840
                    }
841
                }
842
                "recursive_type" => {
246✔
843
                    // recursive_type sets a flag
77✔
844
                    flags.push(quote! { 𝟋FF::RECURSIVE_TYPE });
77✔
845
                }
77✔
846
                // Field attrs - store in dedicated field, don't add to attribute_list
847
                "rename" => {
169✔
848
                    // Extract the string literal from args
80✔
849
                    let args = &attr.args;
80✔
850
                    rename_value = Some(quote! { #args });
80✔
851
                }
80✔
852
                "alias" => {
89✔
853
                    // Extract the string literal from args
×
854
                    let args = &attr.args;
×
855
                    alias_value = Some(quote! { #args });
×
856
                }
×
857
                "skip_serializing_if" => {
89✔
858
                    // User provides a function name: #[facet(skip_serializing_if = fn_name)]
859
                    // We need to wrap it in a type-erased function that takes PtrConst
860
                    let args = &attr.args;
4✔
861
                    let args_str = args.to_string();
4✔
862
                    let fn_name_str = args_str.trim_start_matches('=').trim();
4✔
863
                    let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
4✔
864
                    // Generate a wrapper function that converts PtrConst to the expected type
865
                    skip_serializing_if_value = Some(quote! {
4✔
866
                        {
4✔
867
                            unsafe fn __skip_ser_if_wrapper(ptr: #facet_crate::PtrConst) -> bool {
4✔
868
                                let value: &#field_type = unsafe { ptr.get() };
4✔
869
                                #fn_name(value)
4✔
870
                            }
4✔
871
                            __skip_ser_if_wrapper
4✔
872
                        }
4✔
873
                    });
4✔
874
                }
875
                "invariants" => {
85✔
876
                    // User provides a function name: #[facet(invariants = fn_name)]
877
                    let args = &attr.args;
×
878
                    let args_str = args.to_string();
×
879
                    let fn_name_str = args_str.trim_start_matches('=').trim();
×
880
                    let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
×
881
                    invariants_value = Some(quote! { #fn_name });
×
882
                }
883
                "proxy" => {
85✔
884
                    // User provides a type: #[facet(proxy = ProxyType)]
885
                    let args = &attr.args;
52✔
886
                    let args_str = args.to_string();
52✔
887
                    let type_str = args_str.trim_start_matches('=').trim();
52✔
888
                    let proxy_type: TokenStream = type_str.parse().unwrap_or_else(|_| args.clone());
52✔
889
                    // Generate a full ProxyDef with convert functions for field-level proxy
890
                    proxy_value = Some(quote! {
52✔
891
                        &const {
52✔
892
                            extern crate alloc as __alloc;
52✔
893

52✔
894
                            unsafe fn __proxy_convert_in(
52✔
895
                                proxy_ptr: #facet_crate::PtrConst,
52✔
896
                                field_ptr: #facet_crate::PtrUninit,
52✔
897
                            ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
52✔
898
                                let proxy: #proxy_type = proxy_ptr.read();
52✔
899
                                match <#field_type as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
52✔
900
                                    ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
52✔
901
                                    ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
52✔
902
                                }
52✔
903
                            }
52✔
904

52✔
905
                            unsafe fn __proxy_convert_out(
52✔
906
                                field_ptr: #facet_crate::PtrConst,
52✔
907
                                proxy_ptr: #facet_crate::PtrUninit,
52✔
908
                            ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
52✔
909
                                let field_ref: &#field_type = field_ptr.get();
52✔
910
                                match <#proxy_type as ::core::convert::TryFrom<&#field_type>>::try_from(field_ref) {
52✔
911
                                    ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
52✔
912
                                    ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
52✔
913
                                }
52✔
914
                            }
52✔
915

52✔
916
                            #facet_crate::ProxyDef {
52✔
917
                                shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
52✔
918
                                convert_in: __proxy_convert_in,
52✔
919
                                convert_out: __proxy_convert_out,
52✔
920
                            }
52✔
921
                        }
52✔
922
                    });
52✔
923
                }
924
                // Everything else goes to attributes slice
925
                _ => {
33✔
926
                    let ext_attr =
33✔
927
                        emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
33✔
928
                    attribute_list.push(quote! { #ext_attr });
33✔
929
                }
33✔
930
            }
931
        } else {
1,003✔
932
            // Non-builtin (namespaced) attrs always go to attributes slice
1,003✔
933
            let ext_attr = emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
1,003✔
934
            attribute_list.push(quote! { #ext_attr });
1,003✔
935
        }
1,003✔
936
    }
937

938
    let maybe_attributes = if attribute_list.is_empty() {
3,900✔
939
        quote! { &[] }
2,943✔
940
    } else {
941
        quote! { &const {[#(#attribute_list),*]} }
957✔
942
    };
943

944
    let maybe_field_doc = if doc_lines.is_empty() {
3,900✔
945
        quote! { &[] }
3,836✔
946
    } else {
947
        quote! { &[#(#doc_lines),*] }
64✔
948
    };
949

950
    // Calculate the final offset, incorporating the base_offset if present
951
    let final_offset = match base_offset {
3,900✔
952
        Some(base) => {
230✔
953
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
230✔
954
        }
955
        None => {
956
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
3,670✔
957
        }
958
    };
959

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

963
    // Shape reference: always use a function for lazy evaluation
964
    // This moves const eval from compile time to runtime, improving compile times
965
    // ShapeRef is a tuple struct: ShapeRef(fn() -> &'static Shape)
966
    let is_opaque = field.attrs.has_builtin("opaque");
3,900✔
967
    let shape_ref_expr = if is_recursive {
3,900✔
968
        // Recursive types need a closure to break the cycle
969
        quote! { 𝟋ShpR(|| #shape_expr) }
77✔
970
    } else if is_opaque {
3,823✔
971
        // Opaque fields use Opaque<T> wrapper
972
        quote! { 𝟋ShpR(𝟋shp::<#facet_crate::Opaque<#field_type>>) }
33✔
973
    } else {
974
        // Normal fields use shape_of::<T> which is monomorphized per type
975
        quote! { 𝟋ShpR(𝟋shp::<#field_type>) }
3,790✔
976
    };
977

978
    // Flags: combine all flags or use empty
979
    let flags_expr = if flags.is_empty() {
3,900✔
980
        quote! { 𝟋NOFL }
3,719✔
981
    } else if flags.len() == 1 {
181✔
982
        let f = &flags[0];
181✔
983
        quote! { #f }
181✔
984
    } else {
985
        let first = &flags[0];
×
986
        let rest = &flags[1..];
×
987
        quote! { #first #(.union(#rest))* }
×
988
    };
989

990
    // Rename: Option
991
    let rename_expr = match &rename_value {
3,900✔
992
        Some(rename) => quote! { ::core::option::Option::Some(#rename) },
80✔
993
        None => quote! { ::core::option::Option::None },
3,820✔
994
    };
995

996
    // Alias: Option
997
    let alias_expr = match &alias_value {
3,900✔
998
        Some(alias) => quote! { ::core::option::Option::Some(#alias) },
×
999
        None => quote! { ::core::option::Option::None },
3,900✔
1000
    };
1001

1002
    // Default: Option<DefaultSource>
1003
    let default_expr = match &default_value {
3,900✔
1004
        Some(DefaultKind::FromTrait) => {
1005
            // When a field has 'opaque' attribute, the field shape doesn't have Default vtable
1006
            // because Opaque<T> doesn't expose T's vtable. Instead, generate a custom default
1007
            // function. Special case: Option<T> always defaults to None regardless of T's traits.
1008
            if field.attrs.has_builtin("opaque") {
217✔
1009
                // Check if the field type looks like Option<...>
1010
                let type_str = field_type.to_token_stream().to_string();
2✔
1011
                let is_option = type_str.starts_with("Option") || type_str.contains(":: Option");
2✔
1012

1013
                if is_option {
2✔
1014
                    // Option<T> always defaults to None
1015
                    quote! {
2✔
1016
                        ::core::option::Option::Some(𝟋DS::Custom({
1017
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1018
                                __ptr.put(<#field_type>::None)
1019
                            }
1020
                            __default
1021
                        }))
1022
                    }
1023
                } else {
1024
                    // For non-Option opaque types, call Default::default()
1025
                    quote! {
×
1026
                        ::core::option::Option::Some(𝟋DS::Custom({
1027
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1028
                                __ptr.put(<#field_type as ::core::default::Default>::default())
1029
                            }
1030
                            __default
1031
                        }))
1032
                    }
1033
                }
1034
            } else {
1035
                quote! { ::core::option::Option::Some(𝟋DS::FromTrait) }
215✔
1036
            }
1037
        }
1038
        Some(DefaultKind::Custom(expr)) => {
18✔
1039
            quote! {
18✔
1040
                ::core::option::Option::Some(𝟋DS::Custom({
1041
                    unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1042
                        __ptr.put(#expr)
1043
                    }
1044
                    __default
1045
                }))
1046
            }
1047
        }
1048
        None => quote! { ::core::option::Option::None },
3,665✔
1049
    };
1050

1051
    // Skip serializing if: Option
1052
    let skip_ser_if_expr = match &skip_serializing_if_value {
3,900✔
1053
        Some(skip_ser_if) => quote! { ::core::option::Option::Some(#skip_ser_if) },
4✔
1054
        None => quote! { ::core::option::Option::None },
3,896✔
1055
    };
1056

1057
    // Invariants: Option
1058
    let invariants_expr = match &invariants_value {
3,900✔
1059
        Some(inv) => quote! { ::core::option::Option::Some(#inv) },
×
1060
        None => quote! { ::core::option::Option::None },
3,900✔
1061
    };
1062

1063
    // Proxy: Option (requires alloc feature in facet-core)
1064
    // We always emit this field since we can't check facet-core's features from generated code.
1065
    // If facet-core was built without alloc, this will cause a compile error (acceptable trade-off).
1066
    let proxy_expr = match &proxy_value {
3,900✔
1067
        Some(proxy) => quote! { ::core::option::Option::Some(#proxy) },
52✔
1068
        None => quote! { ::core::option::Option::None },
3,848✔
1069
    };
1070

1071
    // Direct Field struct literal
1072
    quote! {
3,900✔
1073
        𝟋Fld {
1074
            name: #field_name_effective,
1075
            shape: #shape_ref_expr,
1076
            offset: #final_offset,
1077
            flags: #flags_expr,
1078
            rename: #rename_expr,
1079
            alias: #alias_expr,
1080
            attributes: #maybe_attributes,
1081
            doc: #maybe_field_doc,
1082
            default: #default_expr,
1083
            skip_serializing_if: #skip_ser_if_expr,
1084
            invariants: #invariants_expr,
1085
            proxy: #proxy_expr,
1086
        }
1087
    }
1088
}
3,900✔
1089

1090
/// Processes a regular struct to implement Facet
1091
///
1092
/// Example input:
1093
/// ```rust
1094
/// struct Blah {
1095
///     foo: u32,
1096
///     bar: String,
1097
/// }
1098
/// ```
1099
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
1,856✔
1100
    let ps = PStruct::parse(&parsed); // Use the parsed representation
1,856✔
1101

1102
    // Emit any collected errors as compile_error! with proper spans
1103
    if !ps.container.attrs.errors.is_empty() {
1,856✔
1104
        let errors = ps.container.attrs.errors.iter().map(|e| {
×
1105
            let msg = &e.message;
×
1106
            let span = e.span;
×
1107
            quote_spanned! { span => compile_error!(#msg); }
×
1108
        });
×
1109
        return quote! { #(#errors)* };
×
1110
    }
1,856✔
1111

1112
    let struct_name_ident = format_ident!("{}", ps.container.name);
1,856✔
1113
    let struct_name = &ps.container.name;
1,856✔
1114
    let struct_name_str = struct_name.to_string();
1,856✔
1115

1116
    let opaque = ps.container.attrs.has_builtin("opaque");
1,856✔
1117

1118
    // Get the facet crate path (custom or default ::facet)
1119
    let facet_crate = ps.container.attrs.facet_crate();
1,856✔
1120

1121
    let type_name_fn =
1,856✔
1122
        generate_type_name_fn(struct_name, parsed.generics.as_ref(), opaque, &facet_crate);
1,856✔
1123

1124
    // Determine if this struct should use transparent semantics (needed for vtable generation)
1125
    // Transparent is enabled if:
1126
    // 1. #[facet(transparent)] is explicitly set, OR
1127
    // 2. #[repr(transparent)] is set AND the struct is a tuple struct with exactly 0 or 1 field
1128
    let has_explicit_facet_transparent = ps.container.attrs.has_builtin("transparent");
1,856✔
1129
    let has_repr_transparent = ps.container.attrs.is_repr_transparent();
1,856✔
1130

1131
    let repr_implies_facet_transparent = if has_repr_transparent && !has_explicit_facet_transparent
1,856✔
1132
    {
1133
        match &ps.kind {
7✔
1134
            PStructKind::TupleStruct { fields } => fields.len() <= 1,
7✔
1135
            _ => false,
×
1136
        }
1137
    } else {
1138
        false
1,849✔
1139
    };
1140

1141
    let use_transparent_semantics =
1,856✔
1142
        has_explicit_facet_transparent || repr_implies_facet_transparent;
1,856✔
1143

1144
    // For transparent types, get the inner field info
1145
    let inner_field: Option<PStructField> = if use_transparent_semantics {
1,856✔
1146
        match &ps.kind {
49✔
1147
            PStructKind::TupleStruct { fields } => {
49✔
1148
                if fields.len() > 1 {
49✔
1149
                    return quote! {
×
1150
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
1151
                    };
1152
                }
49✔
1153
                fields.first().cloned()
49✔
1154
            }
1155
            _ => {
1156
                return quote! {
×
1157
                    compile_error!("Transparent structs must be tuple structs");
1158
                };
1159
            }
1160
        }
1161
    } else {
1162
        None
1,807✔
1163
    };
1164

1165
    // Build transparent info for vtable generation
1166
    let transparent_info = if use_transparent_semantics {
1,856✔
1167
        Some(TransparentInfo {
1168
            inner_field_type: inner_field.as_ref().map(|f| &f.ty),
49✔
1169
            inner_is_opaque: inner_field
49✔
1170
                .as_ref()
49✔
1171
                .is_some_and(|f| f.attrs.has_builtin("opaque")),
49✔
1172
            is_zst: inner_field.is_none(),
49✔
1173
        })
1174
    } else {
1175
        None
1,807✔
1176
    };
1177

1178
    // Determine trait sources and generate vtable accordingly
1179
    let trait_sources = TraitSources::from_attrs(&ps.container.attrs);
1,856✔
1180
    // Build the struct type token stream (e.g., `MyStruct` or `MyStruct<T, U>`)
1181
    // We need this because `Self` is not available inside `&const { }` blocks
1182
    let bgp_for_vtable = ps.container.bgp.display_without_bounds();
1,856✔
1183
    let struct_type_for_vtable = quote! { #struct_name_ident #bgp_for_vtable };
1,856✔
1184

1185
    // Extract container-level invariants and generate wrapper function
1186
    let invariants_wrapper: Option<TokenStream> = {
1,856✔
1187
        let invariant_exprs: Vec<&TokenStream> = ps
1,856✔
1188
            .container
1,856✔
1189
            .attrs
1,856✔
1190
            .facet
1,856✔
1191
            .iter()
1,856✔
1192
            .filter(|attr| attr.is_builtin() && attr.key_str() == "invariants")
1,856✔
1193
            .map(|attr| &attr.args)
1,856✔
1194
            .collect();
1,856✔
1195

1196
        if !invariant_exprs.is_empty() {
1,856✔
1197
            let tests = invariant_exprs.iter().map(|expr| {
3✔
1198
                quote! {
3✔
1199
                    if !#expr(value) {
1200
                        return 𝟋Result::Err(𝟋Str::from("invariant check failed"));
1201
                    }
1202
                }
1203
            });
3✔
1204

1205
            Some(quote! {
3✔
1206
                {
1207
                    fn __invariants_wrapper(value: &#struct_type_for_vtable) -> 𝟋Result<(), 𝟋Str> {
1208
                        use #facet_crate::𝟋::*;
1209
                        #(#tests)*
1210
                        𝟋Result::Ok(())
1211
                    }
1212
                    __invariants_wrapper
1213
                }
1214
            })
1215
        } else {
1216
            None
1,853✔
1217
        }
1218
    };
1219

1220
    let vtable_code = gen_vtable(
1,856✔
1221
        &facet_crate,
1,856✔
1222
        &type_name_fn,
1,856✔
1223
        &trait_sources,
1,856✔
1224
        transparent_info.as_ref(),
1,856✔
1225
        &struct_type_for_vtable,
1,856✔
1226
        invariants_wrapper.as_ref(),
1,856✔
1227
    );
1228
    // Note: vtable_code already contains &const { ... } for the VTableDirect,
1229
    // no need for an extra const { } wrapper around VTableErased
1230
    let vtable_init = vtable_code;
1,856✔
1231

1232
    // Generate TypeOps for drop, default, clone operations
1233
    // Check if the type has any type or const generics (NOT lifetimes)
1234
    // Lifetimes don't affect layout, so types like RawJson<'a> can use TypeOpsDirect
1235
    // Only types like Vec<T> need TypeOpsIndirect
1236
    let has_type_or_const_generics = ps.container.bgp.params.iter().any(|p| {
1,856✔
1237
        matches!(
59✔
1238
            p.param,
72✔
1239
            facet_macro_parse::GenericParamName::Type(_)
1240
                | facet_macro_parse::GenericParamName::Const(_)
1241
        )
1242
    });
72✔
1243
    let type_ops_init = gen_type_ops(
1,856✔
1244
        &facet_crate,
1,856✔
1245
        &trait_sources,
1,856✔
1246
        &struct_type_for_vtable,
1,856✔
1247
        has_type_or_const_generics,
1,856✔
1248
    );
1249

1250
    // TODO: I assume the `PrimitiveRepr` is only relevant for enums, and does not need to be preserved?
1251
    // NOTE: Uses short aliases from `use #facet_crate::𝟋::*` in the const block
1252
    let repr = match &ps.container.attrs.repr {
1,856✔
1253
        PRepr::Transparent => quote! { 𝟋Repr::TRANSPARENT },
7✔
1254
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
1,772✔
1255
        PRepr::C(_) => quote! { 𝟋Repr::C },
77✔
1256
        PRepr::RustcWillCatch => {
1257
            // rustc will emit an error for the invalid repr.
1258
            // Return empty TokenStream so we don't add misleading errors.
1259
            return quote! {};
×
1260
        }
1261
    };
1262

1263
    // Use PStruct for kind and fields
1264
    let (kind, fields_vec) = match &ps.kind {
1,856✔
1265
        PStructKind::Struct { fields } => {
1,742✔
1266
            let kind = quote!(𝟋Sk::Struct);
1,742✔
1267
            let fields_vec = fields
1,742✔
1268
                .iter()
1,742✔
1269
                .map(|field| {
3,020✔
1270
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
3,020✔
1271
                })
3,020✔
1272
                .collect::<Vec<_>>();
1,742✔
1273
            (kind, fields_vec)
1,742✔
1274
        }
1275
        PStructKind::TupleStruct { fields } => {
106✔
1276
            let kind = quote!(𝟋Sk::TupleStruct);
106✔
1277
            let fields_vec = fields
106✔
1278
                .iter()
106✔
1279
                .map(|field| {
129✔
1280
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
129✔
1281
                })
129✔
1282
                .collect::<Vec<_>>();
106✔
1283
            (kind, fields_vec)
106✔
1284
        }
1285
        PStructKind::UnitStruct => {
1286
            let kind = quote!(𝟋Sk::Unit);
8✔
1287
            (kind, vec![])
8✔
1288
        }
1289
    };
1290

1291
    // Compute variance - delegate to Shape::computed_variance() at runtime
1292
    let variance_call = if opaque {
1,856✔
1293
        // Opaque types don't expose internals, use invariant for safety
1294
        quote! { .variance(𝟋Vnc::INVARIANT) }
2✔
1295
    } else {
1296
        // Point to Shape::computed_variance - it takes &Shape and walks fields
1297
        quote! { .variance(𝟋CV) }
1,854✔
1298
    };
1299

1300
    // Still need original AST for where clauses and type params for build_ helpers
1301
    let where_clauses_ast = match &parsed.kind {
1,856✔
1302
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
1,742✔
1303
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
106✔
1304
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
8✔
1305
    };
1306
    let where_clauses = build_where_clauses(
1,856✔
1307
        where_clauses_ast,
1,856✔
1308
        parsed.generics.as_ref(),
1,856✔
1309
        opaque,
1,856✔
1310
        &facet_crate,
1,856✔
1311
    );
1312
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
1,856✔
1313

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

1317
    // Doc comments from PStruct - returns value for struct literal
1318
    // doc call - only emit if there are doc comments and doc feature is enabled
1319
    #[cfg(feature = "doc")]
1320
    let doc_call = if ps.container.attrs.doc.is_empty() {
1,856✔
1321
        quote! {}
1,796✔
1322
    } else {
1323
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
150✔
1324
        quote! { .doc(&[#(#doc_lines),*]) }
60✔
1325
    };
1326
    #[cfg(not(feature = "doc"))]
1327
    let doc_call = quote! {};
×
1328

1329
    // Container attributes - most go through grammar dispatch
1330
    // Filter out `invariants` and `crate` since they're handled specially
1331
    // Returns builder call only if there are attributes
1332
    let attributes_call = {
1,856✔
1333
        let items: Vec<TokenStream> = ps
1,856✔
1334
            .container
1,856✔
1335
            .attrs
1,856✔
1336
            .facet
1,856✔
1337
            .iter()
1,856✔
1338
            .filter(|attr| {
1,856✔
1339
                // These attributes are handled specially and not emitted to runtime:
1340
                // - invariants: populates vtable.invariants
1341
                // - crate: sets the facet crate path
1342
                // - traits: compile-time directive for vtable generation
1343
                // - auto_traits: compile-time directive for vtable generation
1344
                // - proxy: sets Shape::proxy for container-level proxy
1345
                if attr.is_builtin() {
183✔
1346
                    let key = attr.key_str();
168✔
1347
                    !matches!(
46✔
1348
                        key.as_str(),
168✔
1349
                        "invariants" | "crate" | "traits" | "auto_traits" | "proxy"
168✔
1350
                    )
1351
                } else {
1352
                    true
15✔
1353
                }
1354
            })
183✔
1355
            .map(|attr| {
1,856✔
1356
                let ext_attr = emit_attr(attr, &facet_crate);
137✔
1357
                quote! { #ext_attr }
137✔
1358
            })
137✔
1359
            .collect();
1,856✔
1360

1361
        if items.is_empty() {
1,856✔
1362
            quote! {}
1,739✔
1363
        } else {
1364
            quote! { .attributes(&const {[#(#items),*]}) }
117✔
1365
        }
1366
    };
1367

1368
    // Type tag from PStruct - returns builder call only if present
1369
    let type_tag_call = {
1,856✔
1370
        if let Some(type_tag) = ps.container.attrs.get_builtin_args("type_tag") {
1,856✔
1371
            quote! { .type_tag(#type_tag) }
8✔
1372
        } else {
1373
            quote! {}
1,848✔
1374
        }
1375
    };
1376

1377
    // Container-level proxy from PStruct - generates ProxyDef with conversion functions
1378
    //
1379
    // The challenge: Generic type parameters aren't available inside `const { }` blocks.
1380
    // Solution: We define the proxy functions as inherent methods on the type (outside const),
1381
    // then reference them via Self::method inside the Facet impl. This works because:
1382
    // 1. Inherent impl methods CAN use generic parameters from their impl block
1383
    // 2. Inside the Facet impl's const SHAPE, `Self` refers to the concrete monomorphized type
1384
    // 3. Function pointers to Self::method get properly monomorphized
1385
    let (proxy_inherent_impl, proxy_call) = {
1,856✔
1386
        if let Some(attr) = ps
1,856✔
1387
            .container
1,856✔
1388
            .attrs
1,856✔
1389
            .facet
1,856✔
1390
            .iter()
1,856✔
1391
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
1,856✔
1392
        {
1393
            let proxy_type = &attr.args;
8✔
1394
            let struct_type = &struct_name_ident;
8✔
1395
            let bgp_display = ps.container.bgp.display_without_bounds();
8✔
1396
            // Compute bgp locally for the inherent impl
1397
            let helper_bgp = ps
8✔
1398
                .container
8✔
1399
                .bgp
8✔
1400
                .with_lifetime(LifetimeName(format_ident!("ʄ")));
8✔
1401
            let bgp_def_for_helper = helper_bgp.display_with_bounds();
8✔
1402

1403
            // Define an inherent impl with the proxy helper methods
1404
            // These are NOT in a const block, so generic params ARE available
1405
            // We need where clauses for:
1406
            // 1. The proxy type must implement Facet (for __facet_proxy_shape)
1407
            // 2. The TryFrom conversions (checked when methods are called)
1408
            // Compute the where_clauses for the helper impl by adding the proxy Facet bound
1409
            // Build the combined where clause - we need to add proxy: Facet to existing clauses
1410
            let proxy_where = {
8✔
1411
                // Build additional clause tokens (comma-separated)
1412
                let additional_clauses = quote! { #proxy_type: #facet_crate::Facet<'ʄ> };
8✔
1413

1414
                // where_clauses is either empty or "where X: Y, ..."
1415
                // We need to append our clause
1416
                if where_clauses.is_empty() {
8✔
1417
                    quote! { where #additional_clauses }
7✔
1418
                } else {
1419
                    quote! { #where_clauses, #additional_clauses }
1✔
1420
                }
1421
            };
1422

1423
            let proxy_impl = quote! {
8✔
1424
                #[doc(hidden)]
1425
                impl #bgp_def_for_helper #struct_type #bgp_display
1426
                #proxy_where
1427
                {
1428
                    #[doc(hidden)]
1429
                    unsafe fn __facet_proxy_convert_in(
1430
                        proxy_ptr: #facet_crate::PtrConst,
1431
                        field_ptr: #facet_crate::PtrUninit,
1432
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1433
                        extern crate alloc as __alloc;
1434
                        let proxy: #proxy_type = proxy_ptr.read();
1435
                        match <#struct_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
1436
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
1437
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1438
                        }
1439
                    }
1440

1441
                    #[doc(hidden)]
1442
                    unsafe fn __facet_proxy_convert_out(
1443
                        field_ptr: #facet_crate::PtrConst,
1444
                        proxy_ptr: #facet_crate::PtrUninit,
1445
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1446
                        extern crate alloc as __alloc;
1447
                        let field_ref: &#struct_type #bgp_display = field_ptr.get();
1448
                        match <#proxy_type as ::core::convert::TryFrom<&#struct_type #bgp_display>>::try_from(field_ref) {
1449
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
1450
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1451
                        }
1452
                    }
1453

1454
                    #[doc(hidden)]
1455
                    const fn __facet_proxy_shape() -> &'static #facet_crate::Shape {
1456
                        <#proxy_type as #facet_crate::Facet>::SHAPE
1457
                    }
1458
                }
1459
            };
1460

1461
            // Reference the inherent methods from within the SHAPE const block.
1462
            // We use <Self> syntax which works inside &const { } blocks and properly
1463
            // refers to the monomorphized type from the enclosing impl.
1464
            let proxy_ref = quote! {
8✔
1465
                .proxy(&const {
1466
                    #facet_crate::ProxyDef {
1467
                        shape: <Self>::__facet_proxy_shape(),
1468
                        convert_in: <Self>::__facet_proxy_convert_in,
1469
                        convert_out: <Self>::__facet_proxy_convert_out,
1470
                    }
1471
                })
1472
            };
1473

1474
            (proxy_impl, proxy_ref)
8✔
1475
        } else {
1476
            (quote! {}, quote! {})
1,848✔
1477
        }
1478
    };
1479

1480
    // Generate the inner shape field value for transparent types
1481
    // inner call - only emit for transparent types
1482
    let inner_call = if use_transparent_semantics {
1,856✔
1483
        let inner_shape_val = if let Some(inner_field) = &inner_field {
49✔
1484
            let ty = &inner_field.ty;
49✔
1485
            if inner_field.attrs.has_builtin("opaque") {
49✔
1486
                quote! { <#facet_crate::Opaque<#ty> as #facet_crate::Facet>::SHAPE }
2✔
1487
            } else {
1488
                quote! { <#ty as #facet_crate::Facet>::SHAPE }
47✔
1489
            }
1490
        } else {
1491
            // Transparent ZST case
1492
            quote! { <() as #facet_crate::Facet>::SHAPE }
×
1493
        };
1494
        quote! { .inner(#inner_shape_val) }
49✔
1495
    } else {
1496
        quote! {}
1,807✔
1497
    };
1498

1499
    // Type name function - for generic types, this formats with type parameters
1500
    let type_name_call = if parsed.generics.is_some() && !opaque {
1,856✔
1501
        quote! { .type_name(#type_name_fn) }
68✔
1502
    } else {
1503
        quote! {}
1,788✔
1504
    };
1505

1506
    // Generics from PStruct
1507
    let facet_bgp = ps
1,856✔
1508
        .container
1,856✔
1509
        .bgp
1,856✔
1510
        .with_lifetime(LifetimeName(format_ident!("ʄ")));
1,856✔
1511
    let bgp_def = facet_bgp.display_with_bounds();
1,856✔
1512
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
1,856✔
1513

1514
    // Generate ty_field and optionally a hoisted __FIELDS const
1515
    // Hoisting avoids &const { [...] } which causes 12+ promotions per struct
1516
    let (ty_field, fields_const) = if opaque {
1,856✔
1517
        (
2✔
1518
            quote! {
2✔
1519
                #facet_crate::Type::User(#facet_crate::UserType::Opaque)
2✔
1520
            },
2✔
1521
            quote! {},
2✔
1522
        )
2✔
1523
    } else if fields_vec.is_empty() {
1,854✔
1524
        // Optimize: use &[] for empty fields to avoid const block overhead
1525
        (
19✔
1526
            quote! {
19✔
1527
                𝟋Ty::User(𝟋UTy::Struct(
19✔
1528
                    𝟋STyB::new(#kind, &[]).repr(#repr).build()
19✔
1529
                ))
19✔
1530
            },
19✔
1531
            quote! {},
19✔
1532
        )
19✔
1533
    } else {
1534
        // Hoist fields array to associated const to avoid promotions
1535
        let num_fields = fields_vec.len();
1,835✔
1536
        (
1537
            quote! {
1,835✔
1538
                𝟋Ty::User(𝟋UTy::Struct(
1539
                    𝟋STyB::new(#kind, &Self::__FIELDS).repr(#repr).build()
1540
                ))
1541
            },
1542
            quote! {
1,835✔
1543
                const __FIELDS: [#facet_crate::Field; #num_fields] = {
1544
                    use #facet_crate::𝟋::*;
1545
                    [#(#fields_vec),*]
1546
                };
1547
            },
1548
        )
1549
    };
1550

1551
    // Generate code to suppress dead_code warnings on structs constructed via reflection.
1552
    // When structs are constructed via reflection (e.g., facet_args::from_std_args()),
1553
    // the compiler doesn't see them being used and warns about dead code.
1554
    // This function ensures the struct type is "used" from the compiler's perspective.
1555
    // See: https://github.com/facet-rs/facet/issues/996
1556
    let dead_code_suppression = quote! {
1,856✔
1557
        const _: () = {
1558
            #[allow(dead_code, clippy::multiple_bound_locations)]
1559
            fn __facet_use_struct #bgp_def (__v: &#struct_name_ident #bgp_without_bounds) #where_clauses {
1560
                let _ = __v;
1561
            }
1562
        };
1563
    };
1564

1565
    // Generate static assertions for declared traits (catches lies at compile time)
1566
    // We put this in a generic function outside the const block so it can reference generic parameters
1567
    let facet_default = ps.container.attrs.has_builtin("default");
1,856✔
1568
    let trait_assertion_fn = if let Some(bounds) =
1,856✔
1569
        gen_trait_bounds(ps.container.attrs.declared_traits.as_ref(), facet_default)
1,856✔
1570
    {
1571
        // Note: where_clauses already includes "where" keyword if non-empty
1572
        // We need to add the trait bounds as an additional constraint
1573
        quote! {
10✔
1574
            const _: () = {
1575
                #[allow(dead_code, clippy::multiple_bound_locations)]
1576
                fn __facet_assert_traits #bgp_def (_: &#struct_name_ident #bgp_without_bounds)
1577
                where
1578
                    #struct_name_ident #bgp_without_bounds: #bounds
1579
                {}
1580
            };
1581
        }
1582
    } else {
1583
        quote! {}
1,846✔
1584
    };
1585

1586
    // Vtable is now fully built in gen_vtable, including invariants
1587
    let vtable_field = quote! { #vtable_init };
1,856✔
1588

1589
    // TypeOps for drop, default, clone - convert Option<TokenStream> to a call
1590
    let type_ops_call = match type_ops_init {
1,856✔
1591
        Some(ops) => quote! { .type_ops(#ops) },
1,856✔
1592
        None => quote! {},
×
1593
    };
1594

1595
    // Hoist the entire SHAPE construction to an inherent impl const
1596
    // This avoids &const {} promotions - the reference is to a plain const, not an inline const block
1597
    let shape_inherent_impl = quote! {
1,856✔
1598
        #[doc(hidden)]
1599
        impl #bgp_def #struct_name_ident #bgp_without_bounds #where_clauses {
1600
            #fields_const
1601

1602
            const __SHAPE_DATA: #facet_crate::Shape = {
1603
                use #facet_crate::𝟋::*;
1604

1605
                𝟋ShpB::for_sized::<Self>(#struct_name_str)
1606
                    .vtable(#vtable_field)
1607
                    #type_ops_call
1608
                    .ty(#ty_field)
1609
                    .def(𝟋Def::Undefined)
1610
                    #type_params_call
1611
                    #type_name_call
1612
                    #doc_call
1613
                    #attributes_call
1614
                    #type_tag_call
1615
                    #proxy_call
1616
                    #inner_call
1617
                    #variance_call
1618
                    .build()
1619
            };
1620
        }
1621
    };
1622

1623
    // Static declaration for release builds (pre-evaluates SHAPE)
1624
    let static_decl = crate::derive::generate_static_decl(&struct_name_ident, &facet_crate);
1,856✔
1625

1626
    // Final quote block using refactored parts
1627
    let result = quote! {
1,856✔
1628
        #dead_code_suppression
1629

1630
        #trait_assertion_fn
1631

1632
        // Proxy inherent impl (outside the Facet impl so generic params are in scope)
1633
        #proxy_inherent_impl
1634

1635
        // Hoisted SHAPE data const (avoids &const {} promotions)
1636
        #shape_inherent_impl
1637

1638
        #[automatically_derived]
1639
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #struct_name_ident #bgp_without_bounds #where_clauses {
1640
            const SHAPE: &'static #facet_crate::Shape = &Self::__SHAPE_DATA;
1641
        }
1642

1643
        #static_decl
1644
    };
1645

1646
    result
1,856✔
1647
}
1,856✔
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