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

facet-rs / facet / 20134042527

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

Pull #1252

github

web-flow
Merge e3aaaf615 into c030d09c4
Pull Request #1252: Introduce the concept of truthiness

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

1 existing line in 1 file now uncovered.

28969 of 50123 relevant lines covered (57.8%)

6281.9 hits per line

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

83.77
/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,374✔
83
        Self {
2,374✔
84
            declared_traits: attrs.declared_traits.as_ref(),
2,374✔
85
            auto_traits: attrs.auto_traits,
2,374✔
86
            facet_default: attrs.has_builtin("default"),
2,374✔
87
        }
2,374✔
88
    }
2,374✔
89

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

95
    /// Check if we should use auto-detection for this trait
96
    fn should_auto(&self) -> bool {
4,883✔
97
        self.auto_traits
4,883✔
98
    }
4,883✔
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,374✔
115
    facet_crate: &TokenStream,
2,374✔
116
    type_name_fn: &TokenStream,
2,374✔
117
    sources: &TraitSources<'_>,
2,374✔
118
    transparent: Option<&TransparentInfo<'_>>,
2,374✔
119
    struct_type: &TokenStream,
2,374✔
120
    invariants_fn: Option<&TokenStream>,
2,374✔
121
) -> TokenStream {
2,374✔
122
    // If auto_traits is enabled, use VTableIndirect with runtime trait detection.
123
    if sources.auto_traits {
2,374✔
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,345✔
132

133
    // Otherwise, use VTableDirect with compile-time trait resolution.
134
    gen_vtable_direct(
2,345✔
135
        facet_crate,
2,345✔
136
        type_name_fn,
2,345✔
137
        sources,
2,345✔
138
        transparent,
2,345✔
139
        struct_type,
2,345✔
140
        invariants_fn,
2,345✔
141
    )
142
}
2,374✔
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,345✔
149
    facet_crate: &TokenStream,
2,345✔
150
    _type_name_fn: &TokenStream,
2,345✔
151
    sources: &TraitSources<'_>,
2,345✔
152
    transparent: Option<&TransparentInfo<'_>>,
2,345✔
153
    struct_type: &TokenStream,
2,345✔
154
    invariants_fn: Option<&TokenStream>,
2,345✔
155
) -> TokenStream {
2,345✔
156
    // Display: check declared
157
    let display_call = if sources.has_declared(|d| d.display) {
2,345✔
158
        quote! { .display(<Self as ::core::fmt::Display>::fmt) }
×
159
    } else {
160
        quote! {}
2,345✔
161
    };
162

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

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

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

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

191
    // Hash: check declared
192
    let hash_call = if sources.has_declared(|d| d.hash) {
2,345✔
193
        quote! { .hash(<Self as ::core::hash::Hash>::hash::<#facet_crate::HashProxy>) }
×
194
    } else {
195
        quote! {}
2,345✔
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,345✔
202
        if info.inner_is_opaque {
59✔
203
            // Opaque inner field - no borrow possible
204
            quote! {}
2✔
205
        } else if info.is_zst {
57✔
206
            // ZST case - no inner value to borrow
207
            quote! {}
×
208
        } else if let Some(inner_ty) = info.inner_field_type {
57✔
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! {
57✔
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,286✔
229
    };
230

231
    // Invariants: container-level invariants function
232
    let invariants_call = if let Some(inv_fn) = invariants_fn {
2,345✔
233
        quote! { .invariants(#inv_fn) }
3✔
234
    } else {
235
        quote! {}
2,342✔
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,345✔
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,345✔
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✔
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✔
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✔
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✔
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✔
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,374✔
519
    facet_crate: &TokenStream,
2,374✔
520
    sources: &TraitSources<'_>,
2,374✔
521
    struct_type: &TokenStream,
2,374✔
522
    has_type_or_const_generics: bool,
2,374✔
523
    truthy_fn: Option<&TokenStream>,
2,374✔
524
) -> Option<TokenStream> {
2,374✔
525
    // Only use TypeOpsIndirect when there are actual type or const generics.
526
    // For auto_traits WITHOUT generics, we can still use TypeOpsDirect since
527
    // the helper functions can use `Self` which resolves to the concrete type.
528
    if has_type_or_const_generics {
2,374✔
529
        return gen_type_ops_indirect(facet_crate, sources, struct_type, truthy_fn);
26✔
530
    }
2,348✔
531

532
    // Use TypeOpsDirect for non-generic types (including auto_traits without generics)
533
    gen_type_ops_direct(facet_crate, sources, struct_type, truthy_fn)
2,348✔
534
}
2,374✔
535

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

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

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

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

615
    // Generate TypeOpsDirect struct literal
616
    // Uses transmute to convert typed fn pointers to erased fn(*mut ()) etc.
617
    // Uses Self inside the const block which resolves to the implementing type
618
    let truthy_field = if let Some(truthy) = truthy_fn {
2,348✔
NEW
619
        quote! {
×
620
            is_truthy: ::core::option::Option::Some({
621
                unsafe fn __truthy(value: #facet_crate::PtrConst) -> bool {
622
                    let this: &#struct_type = unsafe { value.get::<#struct_type>() };
623
                    #truthy(this)
624
                }
625
                __truthy
626
            }),
627
        }
628
    } else {
629
        quote! { is_truthy: ::core::option::Option::None, }
2,348✔
630
    };
631

632
    Some(quote! {
2,348✔
633
        #facet_crate::TypeOps::Direct(&const {
2,348✔
634
            #facet_crate::TypeOpsDirect {
2,348✔
635
                drop_in_place: unsafe { ::core::mem::transmute(::core::ptr::drop_in_place::<Self> as unsafe fn(*mut Self)) },
2,348✔
636
                #default_field
2,348✔
637
                #clone_field
2,348✔
638
                #truthy_field
2,348✔
639
            }
2,348✔
640
        })
2,348✔
641
    })
2,348✔
642
}
2,348✔
643

644
/// Generates TypeOpsIndirect for generic types with auto_traits.
645
/// Returns Some(TokenStream) if any ops are needed, None otherwise.
646
///
647
/// Uses helper functions that take a type parameter to avoid the "can't use Self
648
/// from outer item" error in function items.
649
fn gen_type_ops_indirect(
26✔
650
    facet_crate: &TokenStream,
26✔
651
    sources: &TraitSources<'_>,
26✔
652
    _struct_type: &TokenStream,
26✔
653
    truthy_fn: Option<&TokenStream>,
26✔
654
) -> Option<TokenStream> {
26✔
655
    // For TypeOpsIndirect, we always need drop_in_place
656
    // default_in_place and clone_into are optional based on available traits
657
    // Note: We use helper functions 𝟋indirect_*_for::<Self>() which have their own
658
    // generic parameter, avoiding the "can't use Self from outer item" issue.
659

660
    // Check if Default is available
661
    // Note: For auto_traits, we could use specialization but it's complex.
662
    // For now, only generate default_in_place when Default is explicitly known.
663
    let default_field = if sources.has_declared(|d| d.default) || sources.facet_default {
26✔
664
        quote! {
×
665
            default_in_place: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_default_for::<Self>()),
666
        }
667
    } else {
668
        // For auto_traits or no default, set to None
669
        // Runtime detection of Default not supported in TypeOps yet
670
        quote! { default_in_place: ::core::option::Option::None, }
26✔
671
    };
672

673
    // Check if Clone is available
674
    // Note: For auto_traits, we could use specialization but it's complex.
675
    // For now, only generate clone_into when Clone is explicitly known.
676
    let clone_field = if sources.has_declared(|d| d.clone) {
26✔
677
        quote! {
×
678
            clone_into: ::core::option::Option::Some(#facet_crate::𝟋::𝟋indirect_clone_for::<Self>()),
679
        }
680
    } else {
681
        // For auto_traits or no clone, set to None
682
        // Runtime detection of Clone not supported in TypeOps yet
683
        quote! { clone_into: ::core::option::Option::None, }
26✔
684
    };
685

686
    let truthy_field = if let Some(truthy) = truthy_fn {
26✔
NEW
687
        quote! {
×
688
            is_truthy: ::core::option::Option::Some({
689
                unsafe fn __truthy(value: #facet_crate::PtrConst) -> bool {
690
                    let this: &Self = unsafe { value.get::<Self>() };
691
                    #truthy(this)
692
                }
693
                __truthy
694
            }),
695
        }
696
    } else {
697
        quote! { is_truthy: ::core::option::Option::None, }
26✔
698
    };
699

700
    Some(quote! {
26✔
701
        #facet_crate::TypeOps::Indirect(&const {
26✔
702
            #facet_crate::TypeOpsIndirect {
26✔
703
                drop_in_place: #facet_crate::𝟋::𝟋indirect_drop_for::<Self>(),
26✔
704
                #default_field
26✔
705
                #clone_field
26✔
706
                #truthy_field
26✔
707
            }
26✔
708
        })
26✔
709
    })
26✔
710
}
26✔
711

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

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

765
    // #[facet(default)] implies Default trait
766
    if facet_default && !declared.is_some_and(|d| d.default) {
2,374✔
767
        bounds.push(quote! { core::default::Default });
4✔
768
    }
2,370✔
769

770
    if bounds.is_empty() {
2,374✔
771
        None
2,356✔
772
    } else {
773
        Some(quote! { #(#bounds)+* })
18✔
774
    }
775
}
2,374✔
776

777
/// Generates the `::facet::Field` definition `TokenStream` from a `PStructField`.
778
pub(crate) fn gen_field_from_pfield(
4,754✔
779
    field: &PStructField,
4,754✔
780
    struct_name: &Ident,
4,754✔
781
    bgp: &BoundedGenericParams,
4,754✔
782
    base_offset: Option<TokenStream>,
4,754✔
783
    facet_crate: &TokenStream,
4,754✔
784
    skip_all_unless_truthy: bool,
4,754✔
785
) -> TokenStream {
4,754✔
786
    let field_name_effective = &field.name.effective;
4,754✔
787
    let field_name_raw = &field.name.raw;
4,754✔
788
    let field_type = &field.ty;
4,754✔
789

790
    let bgp_without_bounds = bgp.display_without_bounds();
4,754✔
791

792
    #[cfg(feature = "doc")]
793
    let doc_lines: Vec<String> = field
4,754✔
794
        .attrs
4,754✔
795
        .doc
4,754✔
796
        .iter()
4,754✔
797
        .map(|doc| doc.as_str().replace("\\\"", "\""))
4,754✔
798
        .collect();
4,754✔
799
    #[cfg(not(feature = "doc"))]
800
    let doc_lines: Vec<String> = Vec::new();
×
801

802
    // Check if this field is marked as a recursive type
803
    let is_recursive = field.attrs.has_builtin("recursive_type");
4,754✔
804

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

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

823
    // Track what kind of default was specified
824
    enum DefaultKind {
825
        FromTrait,
826
        Custom(TokenStream),
827
    }
828

829
    let mut flags: Vec<TokenStream> = Vec::new();
4,754✔
830
    let mut rename_value: Option<TokenStream> = None;
4,754✔
831
    let mut alias_value: Option<TokenStream> = None;
4,754✔
832
    let mut default_value: Option<DefaultKind> = None;
4,754✔
833
    let mut skip_serializing_if_value: Option<TokenStream> = None;
4,754✔
834
    let mut invariants_value: Option<TokenStream> = None;
4,754✔
835
    let mut proxy_value: Option<TokenStream> = None;
4,754✔
836
    let mut metadata_value: Option<String> = None;
4,754✔
837
    let mut attribute_list: Vec<TokenStream> = Vec::new();
4,754✔
838

839
    let mut want_truthy_skip = skip_all_unless_truthy;
4,754✔
840

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

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

94✔
953
                            unsafe fn __proxy_convert_out(
94✔
954
                                field_ptr: #facet_crate::PtrConst,
94✔
955
                                proxy_ptr: #facet_crate::PtrUninit,
94✔
956
                            ) -> ::core::result::Result<#facet_crate::PtrMut, __alloc::string::String> {
94✔
957
                                let field_ref: &#field_type = field_ptr.get();
94✔
958
                                match <#proxy_type as ::core::convert::TryFrom<&#field_type>>::try_from(field_ref) {
94✔
959
                                    ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
94✔
960
                                    ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
94✔
961
                                }
94✔
962
                            }
94✔
963

94✔
964
                            #facet_crate::ProxyDef {
94✔
965
                                shape: <#proxy_type as #facet_crate::Facet>::SHAPE,
94✔
966
                                convert_in: __proxy_convert_in,
94✔
967
                                convert_out: __proxy_convert_out,
94✔
968
                            }
94✔
969
                        }
94✔
970
                    });
94✔
971
                }
972
                // Everything else goes to attributes slice
973
                _ => {
33✔
974
                    let ext_attr =
33✔
975
                        emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
33✔
976
                    attribute_list.push(quote! { #ext_attr });
33✔
977
                }
33✔
978
            }
979
        } else {
1,435✔
980
            // Non-builtin (namespaced) attrs always go to attributes slice
1,435✔
981
            let ext_attr = emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
1,435✔
982
            attribute_list.push(quote! { #ext_attr });
1,435✔
983
        }
1,435✔
984
    }
985

986
    if skip_serializing_if_value.is_none() && want_truthy_skip {
4,754✔
NEW
987
        skip_serializing_if_value = Some(quote! {
×
NEW
988
            {
×
NEW
989
                unsafe fn __truthiness_with_fallback(
×
NEW
990
                    shape: &'static #facet_crate::Shape,
×
NEW
991
                    ptr: #facet_crate::PtrConst,
×
NEW
992
                ) -> Option<bool> {
×
NEW
993
                    if let Some(truthy) = shape.truthiness_fn() {
×
NEW
994
                        return Some(unsafe { truthy(ptr) });
×
NEW
995
                    }
×
NEW
996
                    if let #facet_crate::Def::Pointer(ptr_def) = shape.def {
×
NEW
997
                        if let (Some(inner_shape), Some(borrow)) =
×
NEW
998
                            (ptr_def.pointee(), ptr_def.vtable.borrow_fn)
×
NEW
999
                        {
×
NEW
1000
                            let inner_ptr = unsafe { borrow(ptr) };
×
NEW
1001
                            return __truthiness_with_fallback(inner_shape, inner_ptr);
×
NEW
1002
                        }
×
NEW
1003
                    }
×
NEW
1004
                    if let #facet_crate::Type::User(#facet_crate::UserType::Struct(st)) = shape.ty
×
NEW
1005
                        && matches!(st.kind, #facet_crate::StructKind::Tuple)
×
NEW
1006
                    {
×
NEW
1007
                        for field in st.fields {
×
NEW
1008
                            if field.shape.get().layout.sized_layout().is_err() {
×
NEW
1009
                                continue;
×
NEW
1010
                            }
×
NEW
1011
                            let field_ptr = #facet_crate::PtrConst::new(unsafe {
×
NEW
1012
                                ptr.as_byte_ptr().add(field.offset)
×
NEW
1013
                            } as *const ());
×
NEW
1014
                            if let Some(true) = __truthiness_with_fallback(field.shape.get(), field_ptr) {
×
NEW
1015
                                return Some(true);
×
NEW
1016
                            }
×
NEW
1017
                        }
×
NEW
1018
                        return Some(false);
×
NEW
1019
                    }
×
NEW
1020
                    None
×
NEW
1021
                }
×
NEW
1022

×
NEW
1023
                unsafe fn __skip_unless_truthy(ptr: #facet_crate::PtrConst) -> bool {
×
NEW
1024
                    let shape = <#field_type as #facet_crate::Facet>::SHAPE;
×
NEW
1025
                    match __truthiness_with_fallback(shape, ptr) {
×
NEW
1026
                        Some(result) => !result,
×
NEW
1027
                        None => false,
×
NEW
1028
                    }
×
NEW
1029
                }
×
NEW
1030
                __skip_unless_truthy
×
NEW
1031
            }
×
NEW
1032
        });
×
1033
    }
4,754✔
1034

1035
    let maybe_attributes = if attribute_list.is_empty() {
4,754✔
1036
        quote! { &[] }
3,365✔
1037
    } else {
1038
        quote! { &const {[#(#attribute_list),*]} }
1,389✔
1039
    };
1040

1041
    let maybe_field_doc = if doc_lines.is_empty() {
4,754✔
1042
        quote! { &[] }
4,687✔
1043
    } else {
1044
        quote! { &[#(#doc_lines),*] }
67✔
1045
    };
1046

1047
    // Calculate the final offset, incorporating the base_offset if present
1048
    let final_offset = match base_offset {
4,754✔
1049
        Some(base) => {
226✔
1050
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
226✔
1051
        }
1052
        None => {
1053
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
4,528✔
1054
        }
1055
    };
1056

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

1060
    // Shape reference: always use a function for lazy evaluation
1061
    // This moves const eval from compile time to runtime, improving compile times
1062
    // ShapeRef is a tuple struct: ShapeRef(fn() -> &'static Shape)
1063
    let is_opaque = field.attrs.has_builtin("opaque");
4,754✔
1064
    let shape_ref_expr = if is_recursive {
4,754✔
1065
        // Recursive types need a closure to break the cycle
1066
        quote! { 𝟋ShpR(|| #shape_expr) }
77✔
1067
    } else if is_opaque {
4,677✔
1068
        // Opaque fields use Opaque<T> wrapper
1069
        quote! { 𝟋ShpR(𝟋shp::<#facet_crate::Opaque<#field_type>>) }
33✔
1070
    } else {
1071
        // Normal fields use shape_of::<T> which is monomorphized per type
1072
        quote! { 𝟋ShpR(𝟋shp::<#field_type>) }
4,644✔
1073
    };
1074

1075
    // Flags: combine all flags or use empty
1076
    let flags_expr = if flags.is_empty() {
4,754✔
1077
        quote! { 𝟋NOFL }
4,573✔
1078
    } else if flags.len() == 1 {
181✔
1079
        let f = &flags[0];
181✔
1080
        quote! { #f }
181✔
1081
    } else {
1082
        let first = &flags[0];
×
1083
        let rest = &flags[1..];
×
1084
        quote! { #first #(.union(#rest))* }
×
1085
    };
1086

1087
    // Rename: Option
1088
    let rename_expr = match &rename_value {
4,754✔
1089
        Some(rename) => quote! { ::core::option::Option::Some(#rename) },
91✔
1090
        None => quote! { ::core::option::Option::None },
4,663✔
1091
    };
1092

1093
    // Alias: Option
1094
    let alias_expr = match &alias_value {
4,754✔
1095
        Some(alias) => quote! { ::core::option::Option::Some(#alias) },
×
1096
        None => quote! { ::core::option::Option::None },
4,754✔
1097
    };
1098

1099
    // Default: Option<DefaultSource>
1100
    let default_expr = match &default_value {
4,754✔
1101
        Some(DefaultKind::FromTrait) => {
1102
            // When a field has 'opaque' attribute, the field shape doesn't have Default vtable
1103
            // because Opaque<T> doesn't expose T's vtable. Instead, generate a custom default
1104
            // function. Special case: Option<T> always defaults to None regardless of T's traits.
1105
            if field.attrs.has_builtin("opaque") {
526✔
1106
                // Check if the field type looks like Option<...>
1107
                let type_str = field_type.to_token_stream().to_string();
2✔
1108
                let is_option = type_str.starts_with("Option") || type_str.contains(":: Option");
2✔
1109

1110
                if is_option {
2✔
1111
                    // Option<T> always defaults to None
1112
                    quote! {
2✔
1113
                        ::core::option::Option::Some(𝟋DS::Custom({
1114
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1115
                                __ptr.put(<#field_type>::None)
1116
                            }
1117
                            __default
1118
                        }))
1119
                    }
1120
                } else {
1121
                    // For non-Option opaque types, call Default::default()
1122
                    quote! {
×
1123
                        ::core::option::Option::Some(𝟋DS::Custom({
1124
                            unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1125
                                __ptr.put(<#field_type as ::core::default::Default>::default())
1126
                            }
1127
                            __default
1128
                        }))
1129
                    }
1130
                }
1131
            } else {
1132
                quote! { ::core::option::Option::Some(𝟋DS::FromTrait) }
524✔
1133
            }
1134
        }
1135
        Some(DefaultKind::Custom(expr)) => {
22✔
1136
            // Use vtable's try_from to convert the expression to the field type.
1137
            // This allows `default = "foo"` to work for String fields,
1138
            // and `default = 42` to work for any integer type.
1139
            // If the types are the same, we just write directly.
1140
            quote! {
22✔
1141
                ::core::option::Option::Some(𝟋DS::Custom({
1142
                    unsafe fn __default(__ptr: #facet_crate::PtrUninit) -> #facet_crate::PtrMut {
1143
                        // Helper function to get shape from a value via type inference
1144
                        #[inline]
1145
                        fn __shape_of_val<'a, T: #facet_crate::Facet<'a>>(_: &T) -> &'static #facet_crate::Shape {
1146
                            T::SHAPE
1147
                        }
1148
                        // Create the source value
1149
                        let __src_value = #expr;
1150
                        // Get shapes for source and destination types
1151
                        let __src_shape = __shape_of_val(&__src_value);
1152
                        let __dst_shape = <#field_type as #facet_crate::Facet>::SHAPE;
1153

1154
                        // If types are the same (by shape id), just write directly
1155
                        if __src_shape.id == __dst_shape.id {
1156
                            return unsafe { __ptr.put(__src_value) };
1157
                        }
1158

1159
                        // Create a pointer to the source value
1160
                        let __src_ptr = #facet_crate::PtrConst::new(
1161
                            &__src_value as *const _ as *const u8
1162
                        );
1163
                        // Get destination pointer
1164
                        let __dst_ptr = #facet_crate::PtrMut::new(__ptr.as_byte_ptr() as *mut u8);
1165
                        // Call try_from via vtable
1166
                        match unsafe { __dst_shape.call_try_from(__src_shape, __src_ptr, __dst_ptr) } {
1167
                            Some(Ok(())) => {
1168
                                // Don't run destructor on source value since we consumed it
1169
                                ::core::mem::forget(__src_value);
1170
                                unsafe { __ptr.assume_init() }
1171
                            },
1172
                            Some(Err(e)) => panic!("default value conversion failed: {}", e),
1173
                            None => panic!("type {} does not support try_from", __dst_shape.type_identifier),
1174
                        }
1175
                    }
1176
                    __default
1177
                }))
1178
            }
1179
        }
1180
        None => quote! { ::core::option::Option::None },
4,206✔
1181
    };
1182

1183
    // Skip serializing if: Option
1184
    let skip_ser_if_expr = match &skip_serializing_if_value {
4,754✔
1185
        Some(skip_ser_if) => quote! { ::core::option::Option::Some(#skip_ser_if) },
344✔
1186
        None => quote! { ::core::option::Option::None },
4,410✔
1187
    };
1188

1189
    // Invariants: Option
1190
    let invariants_expr = match &invariants_value {
4,754✔
1191
        Some(inv) => quote! { ::core::option::Option::Some(#inv) },
×
1192
        None => quote! { ::core::option::Option::None },
4,754✔
1193
    };
1194

1195
    // Proxy: Option (requires alloc feature in facet-core)
1196
    // We always emit this field since we can't check facet-core's features from generated code.
1197
    // If facet-core was built without alloc, this will cause a compile error (acceptable trade-off).
1198
    let proxy_expr = match &proxy_value {
4,754✔
1199
        Some(proxy) => quote! { ::core::option::Option::Some(#proxy) },
94✔
1200
        None => quote! { ::core::option::Option::None },
4,660✔
1201
    };
1202

1203
    // Metadata: Option<&'static str>
1204
    let metadata_expr = match &metadata_value {
4,754✔
1205
        Some(kind) => quote! { ::core::option::Option::Some(#kind) },
2✔
1206
        None => quote! { ::core::option::Option::None },
4,752✔
1207
    };
1208

1209
    // Direct Field struct literal
1210
    quote! {
4,754✔
1211
        𝟋Fld {
1212
            name: #field_name_effective,
1213
            shape: #shape_ref_expr,
1214
            offset: #final_offset,
1215
            flags: #flags_expr,
1216
            rename: #rename_expr,
1217
            alias: #alias_expr,
1218
            attributes: #maybe_attributes,
1219
            doc: #maybe_field_doc,
1220
            default: #default_expr,
1221
            skip_serializing_if: #skip_ser_if_expr,
1222
            invariants: #invariants_expr,
1223
            proxy: #proxy_expr,
1224
            metadata: #metadata_expr,
1225
        }
1226
    }
1227
}
4,754✔
1228

1229
/// Processes a regular struct to implement Facet
1230
///
1231
/// Example input:
1232
/// ```rust
1233
/// struct Blah {
1234
///     foo: u32,
1235
///     bar: String,
1236
/// }
1237
/// ```
1238
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
2,006✔
1239
    let ps = PStruct::parse(&parsed); // Use the parsed representation
2,006✔
1240

1241
    // Emit any collected errors as compile_error! with proper spans
1242
    if !ps.container.attrs.errors.is_empty() {
2,006✔
1243
        let errors = ps.container.attrs.errors.iter().map(|e| {
×
1244
            let msg = &e.message;
×
1245
            let span = e.span;
×
1246
            quote_spanned! { span => compile_error!(#msg); }
×
1247
        });
×
1248
        return quote! { #(#errors)* };
×
1249
    }
2,006✔
1250

1251
    let struct_name_ident = format_ident!("{}", ps.container.name);
2,006✔
1252
    let struct_name = &ps.container.name;
2,006✔
1253
    let struct_name_str = struct_name.to_string();
2,006✔
1254

1255
    let opaque = ps.container.attrs.has_builtin("opaque");
2,006✔
1256

1257
    let skip_all_unless_truthy = ps.container.attrs.has_builtin("skip_all_unless_truthy");
2,006✔
1258

1259
    let truthy_attr: Option<TokenStream> = ps.container.attrs.facet.iter().find_map(|attr| {
2,006✔
1260
        if attr.is_builtin() && attr.key_str() == "truthy" {
318✔
NEW
1261
            let args = &attr.args;
×
NEW
1262
            if args.is_empty() {
×
NEW
1263
                return None;
×
NEW
1264
            }
×
NEW
1265
            let args_str = args.to_string();
×
NEW
1266
            let fn_name_str = args_str.trim_start_matches('=').trim();
×
NEW
1267
            let fn_name: TokenStream = fn_name_str.parse().unwrap_or_else(|_| args.clone());
×
NEW
1268
            Some(fn_name)
×
1269
        } else {
1270
            None
318✔
1271
        }
1272
    });
318✔
1273

1274
    // Get the facet crate path (custom or default ::facet)
1275
    let facet_crate = ps.container.attrs.facet_crate();
2,006✔
1276

1277
    let type_name_fn =
2,006✔
1278
        generate_type_name_fn(struct_name, parsed.generics.as_ref(), opaque, &facet_crate);
2,006✔
1279

1280
    // Determine if this struct should use transparent semantics (needed for vtable generation)
1281
    // Transparent is enabled if:
1282
    // 1. #[facet(transparent)] is explicitly set, OR
1283
    // 2. #[repr(transparent)] is set AND the struct is a tuple struct with exactly 0 or 1 field
1284
    let has_explicit_facet_transparent = ps.container.attrs.has_builtin("transparent");
2,006✔
1285
    let has_repr_transparent = ps.container.attrs.is_repr_transparent();
2,006✔
1286

1287
    let repr_implies_facet_transparent = if has_repr_transparent && !has_explicit_facet_transparent
2,006✔
1288
    {
1289
        match &ps.kind {
7✔
1290
            PStructKind::TupleStruct { fields } => fields.len() <= 1,
7✔
1291
            _ => false,
×
1292
        }
1293
    } else {
1294
        false
1,999✔
1295
    };
1296

1297
    let use_transparent_semantics =
2,006✔
1298
        has_explicit_facet_transparent || repr_implies_facet_transparent;
2,006✔
1299

1300
    // For transparent types, get the inner field info
1301
    let inner_field: Option<PStructField> = if use_transparent_semantics {
2,006✔
1302
        match &ps.kind {
59✔
1303
            PStructKind::TupleStruct { fields } => {
59✔
1304
                if fields.len() > 1 {
59✔
1305
                    return quote! {
×
1306
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
1307
                    };
1308
                }
59✔
1309
                fields.first().cloned()
59✔
1310
            }
1311
            _ => {
1312
                return quote! {
×
1313
                    compile_error!("Transparent structs must be tuple structs");
1314
                };
1315
            }
1316
        }
1317
    } else {
1318
        None
1,947✔
1319
    };
1320

1321
    // Build transparent info for vtable generation
1322
    let transparent_info = if use_transparent_semantics {
2,006✔
1323
        Some(TransparentInfo {
1324
            inner_field_type: inner_field.as_ref().map(|f| &f.ty),
59✔
1325
            inner_is_opaque: inner_field
59✔
1326
                .as_ref()
59✔
1327
                .is_some_and(|f| f.attrs.has_builtin("opaque")),
59✔
1328
            is_zst: inner_field.is_none(),
59✔
1329
        })
1330
    } else {
1331
        None
1,947✔
1332
    };
1333

1334
    // Determine trait sources and generate vtable accordingly
1335
    let trait_sources = TraitSources::from_attrs(&ps.container.attrs);
2,006✔
1336
    // Build the struct type token stream (e.g., `MyStruct` or `MyStruct<T, U>`)
1337
    // We need this because `Self` is not available inside `&const { }` blocks
1338
    let bgp_for_vtable = ps.container.bgp.display_without_bounds();
2,006✔
1339
    let struct_type_for_vtable = quote! { #struct_name_ident #bgp_for_vtable };
2,006✔
1340

1341
    // Extract container-level invariants and generate wrapper function
1342
    let invariants_wrapper: Option<TokenStream> = {
2,006✔
1343
        let invariant_exprs: Vec<&TokenStream> = ps
2,006✔
1344
            .container
2,006✔
1345
            .attrs
2,006✔
1346
            .facet
2,006✔
1347
            .iter()
2,006✔
1348
            .filter(|attr| attr.is_builtin() && attr.key_str() == "invariants")
2,006✔
1349
            .map(|attr| &attr.args)
2,006✔
1350
            .collect();
2,006✔
1351

1352
        if !invariant_exprs.is_empty() {
2,006✔
1353
            let tests = invariant_exprs.iter().map(|expr| {
3✔
1354
                quote! {
3✔
1355
                    if !#expr(value) {
1356
                        return 𝟋Result::Err(𝟋Str::from("invariant check failed"));
1357
                    }
1358
                }
1359
            });
3✔
1360

1361
            Some(quote! {
3✔
1362
                {
1363
                    fn __invariants_wrapper(value: &#struct_type_for_vtable) -> 𝟋Result<(), 𝟋Str> {
1364
                        use #facet_crate::𝟋::*;
1365
                        #(#tests)*
1366
                        𝟋Result::Ok(())
1367
                    }
1368
                    __invariants_wrapper
1369
                }
1370
            })
1371
        } else {
1372
            None
2,003✔
1373
        }
1374
    };
1375

1376
    let vtable_code = gen_vtable(
2,006✔
1377
        &facet_crate,
2,006✔
1378
        &type_name_fn,
2,006✔
1379
        &trait_sources,
2,006✔
1380
        transparent_info.as_ref(),
2,006✔
1381
        &struct_type_for_vtable,
2,006✔
1382
        invariants_wrapper.as_ref(),
2,006✔
1383
    );
1384
    // Note: vtable_code already contains &const { ... } for the VTableDirect,
1385
    // no need for an extra const { } wrapper around VTableErased
1386
    let vtable_init = vtable_code;
2,006✔
1387

1388
    // Generate TypeOps for drop, default, clone operations
1389
    // Check if the type has any type or const generics (NOT lifetimes)
1390
    // Lifetimes don't affect layout, so types like RawJson<'a> can use TypeOpsDirect
1391
    // Only types like Vec<T> need TypeOpsIndirect
1392
    let has_type_or_const_generics = ps.container.bgp.params.iter().any(|p| {
2,006✔
1393
        matches!(
55✔
1394
            p.param,
71✔
1395
            facet_macro_parse::GenericParamName::Type(_)
1396
                | facet_macro_parse::GenericParamName::Const(_)
1397
        )
1398
    });
71✔
1399
    let type_ops_init = gen_type_ops(
2,006✔
1400
        &facet_crate,
2,006✔
1401
        &trait_sources,
2,006✔
1402
        &struct_type_for_vtable,
2,006✔
1403
        has_type_or_const_generics,
2,006✔
1404
        truthy_attr.as_ref(),
2,006✔
1405
    );
1406

1407
    // TODO: I assume the `PrimitiveRepr` is only relevant for enums, and does not need to be preserved?
1408
    // NOTE: Uses short aliases from `use #facet_crate::𝟋::*` in the const block
1409
    let repr = match &ps.container.attrs.repr {
2,006✔
1410
        PRepr::Transparent => quote! { 𝟋Repr::TRANSPARENT },
8✔
1411
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
1,921✔
1412
        PRepr::C(_) => quote! { 𝟋Repr::C },
77✔
1413
        PRepr::RustcWillCatch => {
1414
            // rustc will emit an error for the invalid repr.
1415
            // Return empty TokenStream so we don't add misleading errors.
1416
            return quote! {};
×
1417
        }
1418
    };
1419

1420
    // Use PStruct for kind and fields
1421
    let (kind, fields_vec) = match &ps.kind {
2,006✔
1422
        PStructKind::Struct { fields } => {
1,881✔
1423
            let kind = quote!(𝟋Sk::Struct);
1,881✔
1424
            let fields_vec = fields
1,881✔
1425
                .iter()
1,881✔
1426
                .map(|field| {
3,557✔
1427
                    gen_field_from_pfield(
3,557✔
1428
                        field,
3,557✔
1429
                        struct_name,
3,557✔
1430
                        &ps.container.bgp,
3,557✔
1431
                        None,
3,557✔
1432
                        &facet_crate,
3,557✔
1433
                        skip_all_unless_truthy,
3,557✔
1434
                    )
1435
                })
3,557✔
1436
                .collect::<Vec<_>>();
1,881✔
1437
            (kind, fields_vec)
1,881✔
1438
        }
1439
        PStructKind::TupleStruct { fields } => {
117✔
1440
            let kind = quote!(𝟋Sk::TupleStruct);
117✔
1441
            let fields_vec = fields
117✔
1442
                .iter()
117✔
1443
                .map(|field| {
140✔
1444
                    gen_field_from_pfield(
140✔
1445
                        field,
140✔
1446
                        struct_name,
140✔
1447
                        &ps.container.bgp,
140✔
1448
                        None,
140✔
1449
                        &facet_crate,
140✔
1450
                        skip_all_unless_truthy,
140✔
1451
                    )
1452
                })
140✔
1453
                .collect::<Vec<_>>();
117✔
1454
            (kind, fields_vec)
117✔
1455
        }
1456
        PStructKind::UnitStruct => {
1457
            let kind = quote!(𝟋Sk::Unit);
8✔
1458
            (kind, vec![])
8✔
1459
        }
1460
    };
1461

1462
    // Compute variance - delegate to Shape::computed_variance() at runtime
1463
    let variance_call = if opaque {
2,006✔
1464
        // Opaque types don't expose internals, use invariant for safety
1465
        quote! { .variance(𝟋Vnc::INVARIANT) }
2✔
1466
    } else {
1467
        // Point to Shape::computed_variance - it takes &Shape and walks fields
1468
        quote! { .variance(𝟋CV) }
2,004✔
1469
    };
1470

1471
    // Still need original AST for where clauses and type params for build_ helpers
1472
    let where_clauses_ast = match &parsed.kind {
2,006✔
1473
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
1,881✔
1474
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
117✔
1475
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
8✔
1476
    };
1477
    let where_clauses = build_where_clauses(
2,006✔
1478
        where_clauses_ast,
2,006✔
1479
        parsed.generics.as_ref(),
2,006✔
1480
        opaque,
2,006✔
1481
        &facet_crate,
2,006✔
1482
    );
1483
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
2,006✔
1484

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

1488
    // Doc comments from PStruct - returns value for struct literal
1489
    // doc call - only emit if there are doc comments and doc feature is enabled
1490
    #[cfg(feature = "doc")]
1491
    let doc_call = if ps.container.attrs.doc.is_empty() {
2,006✔
1492
        quote! {}
1,859✔
1493
    } else {
1494
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
241✔
1495
        quote! { .doc(&[#(#doc_lines),*]) }
147✔
1496
    };
1497
    #[cfg(not(feature = "doc"))]
1498
    let doc_call = quote! {};
×
1499

1500
    // Container attributes - most go through grammar dispatch
1501
    // Filter out `invariants` and `crate` since they're handled specially
1502
    // Returns builder call only if there are attributes
1503
    let attributes_call = {
2,006✔
1504
        let items: Vec<TokenStream> = ps
2,006✔
1505
            .container
2,006✔
1506
            .attrs
2,006✔
1507
            .facet
2,006✔
1508
            .iter()
2,006✔
1509
            .filter(|attr| {
2,006✔
1510
                // These attributes are handled specially and not emitted to runtime:
1511
                // - invariants: populates vtable.invariants
1512
                // - crate: sets the facet crate path
1513
                // - traits: compile-time directive for vtable generation
1514
                // - auto_traits: compile-time directive for vtable generation
1515
                // - proxy: sets Shape::proxy for container-level proxy
1516
                if attr.is_builtin() {
318✔
1517
                    let key = attr.key_str();
238✔
1518
                    !matches!(
55✔
1519
                        key.as_str(),
238✔
1520
                        "invariants"
238✔
1521
                            | "crate"
235✔
1522
                            | "traits"
232✔
1523
                            | "auto_traits"
218✔
1524
                            | "proxy"
192✔
1525
                            | "truthy"
183✔
1526
                            | "skip_all_unless_truthy"
183✔
1527
                    )
1528
                } else {
1529
                    true
80✔
1530
                }
1531
            })
318✔
1532
            .map(|attr| {
2,006✔
1533
                let ext_attr = emit_attr(attr, &facet_crate);
263✔
1534
                quote! { #ext_attr }
263✔
1535
            })
263✔
1536
            .collect();
2,006✔
1537

1538
        if items.is_empty() {
2,006✔
1539
            quote! {}
1,809✔
1540
        } else {
1541
            quote! { .attributes(&const {[#(#items),*]}) }
197✔
1542
        }
1543
    };
1544

1545
    // Type tag from PStruct - returns builder call only if present
1546
    let type_tag_call = {
2,006✔
1547
        if let Some(type_tag) = ps.container.attrs.get_builtin_args("type_tag") {
2,006✔
1548
            quote! { .type_tag(#type_tag) }
8✔
1549
        } else {
1550
            quote! {}
1,998✔
1551
        }
1552
    };
1553

1554
    // Container-level proxy from PStruct - generates ProxyDef with conversion functions
1555
    //
1556
    // The challenge: Generic type parameters aren't available inside `const { }` blocks.
1557
    // Solution: We define the proxy functions as inherent methods on the type (outside const),
1558
    // then reference them via Self::method inside the Facet impl. This works because:
1559
    // 1. Inherent impl methods CAN use generic parameters from their impl block
1560
    // 2. Inside the Facet impl's const SHAPE, `Self` refers to the concrete monomorphized type
1561
    // 3. Function pointers to Self::method get properly monomorphized
1562
    let (proxy_inherent_impl, proxy_call) = {
2,006✔
1563
        if let Some(attr) = ps
2,006✔
1564
            .container
2,006✔
1565
            .attrs
2,006✔
1566
            .facet
2,006✔
1567
            .iter()
2,006✔
1568
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
2,006✔
1569
        {
1570
            let proxy_type = &attr.args;
9✔
1571
            let struct_type = &struct_name_ident;
9✔
1572
            let bgp_display = ps.container.bgp.display_without_bounds();
9✔
1573
            // Compute bgp locally for the inherent impl
1574
            let helper_bgp = ps
9✔
1575
                .container
9✔
1576
                .bgp
9✔
1577
                .with_lifetime(LifetimeName(format_ident!("ʄ")));
9✔
1578
            let bgp_def_for_helper = helper_bgp.display_with_bounds();
9✔
1579

1580
            // Define an inherent impl with the proxy helper methods
1581
            // These are NOT in a const block, so generic params ARE available
1582
            // We need where clauses for:
1583
            // 1. The proxy type must implement Facet (for __facet_proxy_shape)
1584
            // 2. The TryFrom conversions (checked when methods are called)
1585
            // Compute the where_clauses for the helper impl by adding the proxy Facet bound
1586
            // Build the combined where clause - we need to add proxy: Facet to existing clauses
1587
            let proxy_where = {
9✔
1588
                // Build additional clause tokens (comma-separated)
1589
                let additional_clauses = quote! { #proxy_type: #facet_crate::Facet<'ʄ> };
9✔
1590

1591
                // where_clauses is either empty or "where X: Y, ..."
1592
                // We need to append our clause
1593
                if where_clauses.is_empty() {
9✔
1594
                    quote! { where #additional_clauses }
7✔
1595
                } else {
1596
                    quote! { #where_clauses, #additional_clauses }
2✔
1597
                }
1598
            };
1599

1600
            let proxy_impl = quote! {
9✔
1601
                #[doc(hidden)]
1602
                impl #bgp_def_for_helper #struct_type #bgp_display
1603
                #proxy_where
1604
                {
1605
                    #[doc(hidden)]
1606
                    unsafe fn __facet_proxy_convert_in(
1607
                        proxy_ptr: #facet_crate::PtrConst,
1608
                        field_ptr: #facet_crate::PtrUninit,
1609
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1610
                        extern crate alloc as __alloc;
1611
                        let proxy: #proxy_type = proxy_ptr.read();
1612
                        match <#struct_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
1613
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
1614
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1615
                        }
1616
                    }
1617

1618
                    #[doc(hidden)]
1619
                    unsafe fn __facet_proxy_convert_out(
1620
                        field_ptr: #facet_crate::PtrConst,
1621
                        proxy_ptr: #facet_crate::PtrUninit,
1622
                    ) -> ::core::result::Result<#facet_crate::PtrMut, #facet_crate::𝟋::𝟋Str> {
1623
                        extern crate alloc as __alloc;
1624
                        let field_ref: &#struct_type #bgp_display = field_ptr.get();
1625
                        match <#proxy_type as ::core::convert::TryFrom<&#struct_type #bgp_display>>::try_from(field_ref) {
1626
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
1627
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1628
                        }
1629
                    }
1630

1631
                    #[doc(hidden)]
1632
                    const fn __facet_proxy_shape() -> &'static #facet_crate::Shape {
1633
                        <#proxy_type as #facet_crate::Facet>::SHAPE
1634
                    }
1635
                }
1636
            };
1637

1638
            // Reference the inherent methods from within the SHAPE const block.
1639
            // We use <Self> syntax which works inside &const { } blocks and properly
1640
            // refers to the monomorphized type from the enclosing impl.
1641
            let proxy_ref = quote! {
9✔
1642
                .proxy(&const {
1643
                    #facet_crate::ProxyDef {
1644
                        shape: <Self>::__facet_proxy_shape(),
1645
                        convert_in: <Self>::__facet_proxy_convert_in,
1646
                        convert_out: <Self>::__facet_proxy_convert_out,
1647
                    }
1648
                })
1649
            };
1650

1651
            (proxy_impl, proxy_ref)
9✔
1652
        } else {
1653
            (quote! {}, quote! {})
1,997✔
1654
        }
1655
    };
1656

1657
    // Generate the inner shape field value for transparent types
1658
    // inner call - only emit for transparent types
1659
    let inner_call = if use_transparent_semantics {
2,006✔
1660
        let inner_shape_val = if let Some(inner_field) = &inner_field {
59✔
1661
            let ty = &inner_field.ty;
59✔
1662
            if inner_field.attrs.has_builtin("opaque") {
59✔
1663
                quote! { <#facet_crate::Opaque<#ty> as #facet_crate::Facet>::SHAPE }
2✔
1664
            } else {
1665
                quote! { <#ty as #facet_crate::Facet>::SHAPE }
57✔
1666
            }
1667
        } else {
1668
            // Transparent ZST case
1669
            quote! { <() as #facet_crate::Facet>::SHAPE }
×
1670
        };
1671
        quote! { .inner(#inner_shape_val) }
59✔
1672
    } else {
1673
        quote! {}
1,947✔
1674
    };
1675

1676
    // Type name function - for generic types, this formats with type parameters
1677
    let type_name_call = if parsed.generics.is_some() && !opaque {
2,006✔
1678
        quote! { .type_name(#type_name_fn) }
67✔
1679
    } else {
1680
        quote! {}
1,939✔
1681
    };
1682

1683
    // Generics from PStruct
1684
    let facet_bgp = ps
2,006✔
1685
        .container
2,006✔
1686
        .bgp
2,006✔
1687
        .with_lifetime(LifetimeName(format_ident!("ʄ")));
2,006✔
1688
    let bgp_def = facet_bgp.display_with_bounds();
2,006✔
1689
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
2,006✔
1690

1691
    // Generate ty_field and optionally a hoisted __FIELDS const
1692
    // Hoisting avoids &const { [...] } which causes 12+ promotions per struct
1693
    let (ty_field, fields_const) = if opaque {
2,006✔
1694
        (
2✔
1695
            quote! {
2✔
1696
                #facet_crate::Type::User(#facet_crate::UserType::Opaque)
2✔
1697
            },
2✔
1698
            quote! {},
2✔
1699
        )
2✔
1700
    } else if fields_vec.is_empty() {
2,004✔
1701
        // Optimize: use &[] for empty fields to avoid const block overhead
1702
        (
19✔
1703
            quote! {
19✔
1704
                𝟋Ty::User(𝟋UTy::Struct(
19✔
1705
                    𝟋STyB::new(#kind, &[]).repr(#repr).build()
19✔
1706
                ))
19✔
1707
            },
19✔
1708
            quote! {},
19✔
1709
        )
19✔
1710
    } else {
1711
        // Hoist fields array to associated const to avoid promotions
1712
        let num_fields = fields_vec.len();
1,985✔
1713
        (
1714
            quote! {
1,985✔
1715
                𝟋Ty::User(𝟋UTy::Struct(
1716
                    𝟋STyB::new(#kind, &Self::__FIELDS).repr(#repr).build()
1717
                ))
1718
            },
1719
            quote! {
1,985✔
1720
                const __FIELDS: [#facet_crate::Field; #num_fields] = {
1721
                    use #facet_crate::𝟋::*;
1722
                    [#(#fields_vec),*]
1723
                };
1724
            },
1725
        )
1726
    };
1727

1728
    // Generate code to suppress dead_code warnings on structs constructed via reflection.
1729
    // When structs are constructed via reflection (e.g., facet_args::from_std_args()),
1730
    // the compiler doesn't see them being used and warns about dead code.
1731
    // This function ensures the struct type is "used" from the compiler's perspective.
1732
    // See: https://github.com/facet-rs/facet/issues/996
1733
    let dead_code_suppression = quote! {
2,006✔
1734
        const _: () = {
1735
            #[allow(dead_code, clippy::multiple_bound_locations)]
1736
            fn __facet_use_struct #bgp_def (__v: &#struct_name_ident #bgp_without_bounds) #where_clauses {
1737
                let _ = __v;
1738
            }
1739
        };
1740
    };
1741

1742
    // Generate static assertions for declared traits (catches lies at compile time)
1743
    // We put this in a generic function outside the const block so it can reference generic parameters
1744
    let facet_default = ps.container.attrs.has_builtin("default");
2,006✔
1745
    let trait_assertion_fn = if let Some(bounds) =
2,006✔
1746
        gen_trait_bounds(ps.container.attrs.declared_traits.as_ref(), facet_default)
2,006✔
1747
    {
1748
        // Note: where_clauses already includes "where" keyword if non-empty
1749
        // We need to add the trait bounds as an additional constraint
1750
        quote! {
18✔
1751
            const _: () = {
1752
                #[allow(dead_code, clippy::multiple_bound_locations)]
1753
                fn __facet_assert_traits #bgp_def (_: &#struct_name_ident #bgp_without_bounds)
1754
                where
1755
                    #struct_name_ident #bgp_without_bounds: #bounds
1756
                {}
1757
            };
1758
        }
1759
    } else {
1760
        quote! {}
1,988✔
1761
    };
1762

1763
    // Vtable is now fully built in gen_vtable, including invariants
1764
    let vtable_field = quote! { #vtable_init };
2,006✔
1765

1766
    // TypeOps for drop, default, clone - convert Option<TokenStream> to a call
1767
    let type_ops_call = match type_ops_init {
2,006✔
1768
        Some(ops) => quote! { .type_ops(#ops) },
2,006✔
1769
        None => quote! {},
×
1770
    };
1771

1772
    // Hoist the entire SHAPE construction to an inherent impl const
1773
    // This avoids &const {} promotions - the reference is to a plain const, not an inline const block
1774
    let shape_inherent_impl = quote! {
2,006✔
1775
        #[doc(hidden)]
1776
        impl #bgp_def #struct_name_ident #bgp_without_bounds #where_clauses {
1777
            #fields_const
1778

1779
            const __SHAPE_DATA: #facet_crate::Shape = {
1780
                use #facet_crate::𝟋::*;
1781

1782
                𝟋ShpB::for_sized::<Self>(#struct_name_str)
1783
                    .vtable(#vtable_field)
1784
                    #type_ops_call
1785
                    .ty(#ty_field)
1786
                    .def(𝟋Def::Undefined)
1787
                    #type_params_call
1788
                    #type_name_call
1789
                    #doc_call
1790
                    #attributes_call
1791
                    #type_tag_call
1792
                    #proxy_call
1793
                    #inner_call
1794
                    #variance_call
1795
                    .build()
1796
            };
1797
        }
1798
    };
1799

1800
    // Static declaration for release builds (pre-evaluates SHAPE)
1801
    let static_decl = crate::derive::generate_static_decl(
2,006✔
1802
        &struct_name_ident,
2,006✔
1803
        &facet_crate,
2,006✔
1804
        has_type_or_const_generics,
2,006✔
1805
    );
1806

1807
    // Final quote block using refactored parts
1808
    let result = quote! {
2,006✔
1809
        #dead_code_suppression
1810

1811
        #trait_assertion_fn
1812

1813
        // Proxy inherent impl (outside the Facet impl so generic params are in scope)
1814
        #proxy_inherent_impl
1815

1816
        // Hoisted SHAPE data const (avoids &const {} promotions)
1817
        #shape_inherent_impl
1818

1819
        #[automatically_derived]
1820
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #struct_name_ident #bgp_without_bounds #where_clauses {
1821
            const SHAPE: &'static #facet_crate::Shape = &Self::__SHAPE_DATA;
1822
        }
1823

1824
        #static_decl
1825
    };
1826

1827
    result
2,006✔
1828
}
2,006✔
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