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

facet-rs / facet / 20020740731

08 Dec 2025 07:54AM UTC coverage: 58.6% (+0.02%) from 58.577%
20020740731

push

github

fasterthanlime
feat: make repr(transparent) imply facet(transparent) for single-field tuple structs

When a tuple struct has `#[repr(transparent)]` and exactly 0 or 1 fields,
the facet derive macro will now automatically use transparent semantics,
removing the need to also add `#[facet(transparent)]`.

This aligns with the common use case of newtype wrappers that use
`repr(transparent)` for memory layout optimization - they typically also
want transparent serialization behavior.

Note: For tuple structs with multiple fields (e.g., with PhantomData),
`repr(transparent)` does NOT automatically imply `facet(transparent)` since
facet's transparent semantics only support 0-1 fields. Users must explicitly
add `#[facet(transparent)]` in those cases.

Closes #1172

15 of 16 new or added lines in 2 files covered. (93.75%)

24676 of 42109 relevant lines covered (58.6%)

545.52 hits per line

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

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

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

67
use super::*;
68

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

88
impl<'a> TraitSources<'a> {
89
    /// Create trait sources from parsed attributes
90
    pub fn from_attrs(attrs: &'a PAttrs) -> Self {
2,123✔
91
        Self {
2,123✔
92
            known_derives: &attrs.known_derives,
2,123✔
93
            declared_traits: attrs.declared_traits.as_ref(),
2,123✔
94
            auto_traits: attrs.auto_traits,
2,123✔
95
            facet_default: attrs.has_builtin("default"),
2,123✔
96
        }
2,123✔
97
    }
2,123✔
98

99
    /// Check if a trait is known from derives
100
    fn has_derive(&self, check: impl FnOnce(&KnownDerives) -> bool) -> bool {
19,165✔
101
        check(self.known_derives)
19,165✔
102
    }
19,165✔
103

104
    /// Check if a trait is explicitly declared
105
    fn has_declared(&self, check: impl FnOnce(&DeclaredTraits) -> bool) -> bool {
27,744✔
106
        self.declared_traits.is_some_and(check)
27,744✔
107
    }
27,744✔
108

109
    /// Check if we should use auto-detection for this trait
110
    fn should_auto(&self) -> bool {
19,102✔
111
        self.auto_traits
19,102✔
112
    }
19,102✔
113
}
114

115
/// Generates the vtable for a type based on trait sources.
116
///
117
/// Uses a layered approach for each trait:
118
/// 1. If known from derives → direct impl (no specialization)
119
/// 2. If explicitly declared → direct impl (no specialization)
120
/// 3. If auto_traits enabled → use `impls!` macro for detection
121
/// 4. Otherwise → None
122
pub(crate) fn gen_vtable(
2,123✔
123
    facet_crate: &TokenStream,
2,123✔
124
    type_name_fn: &TokenStream,
2,123✔
125
    sources: &TraitSources<'_>,
2,123✔
126
) -> TokenStream {
2,123✔
127
    // Helper to generate a direct implementation (no specialization)
128
    let direct_display = quote! {
2,123✔
129
        Some(|data, f| {
130
            let data = unsafe { data.get::<Self>() };
131
            core::fmt::Display::fmt(data, f)
132
        })
133
    };
134
    let direct_debug = quote! {
2,123✔
135
        Some(|data, f| {
136
            let data = unsafe { data.get::<Self>() };
137
            core::fmt::Debug::fmt(data, f)
138
        })
139
    };
140
    let direct_default = quote! {
2,123✔
141
        Some(|target| unsafe {
142
            target.put(<Self as core::default::Default>::default())
143
        })
144
    };
145
    let direct_clone = quote! {
2,123✔
146
        Some(|src, dst| unsafe {
147
            let src = src.get::<Self>();
148
            dst.put(<Self as core::clone::Clone>::clone(src))
149
        })
150
    };
151
    let direct_partial_eq = quote! {
2,123✔
152
        Some(|left, right| {
153
            let left = unsafe { left.get::<Self>() };
154
            let right = unsafe { right.get::<Self>() };
155
            <Self as core::cmp::PartialEq>::eq(left, right)
156
        })
157
    };
158
    let direct_partial_ord = quote! {
2,123✔
159
        Some(|left, right| {
160
            let left = unsafe { left.get::<Self>() };
161
            let right = unsafe { right.get::<Self>() };
162
            <Self as core::cmp::PartialOrd>::partial_cmp(left, right)
163
        })
164
    };
165
    let direct_ord = quote! {
2,123✔
166
        Some(|left, right| {
167
            let left = unsafe { left.get::<Self>() };
168
            let right = unsafe { right.get::<Self>() };
169
            <Self as core::cmp::Ord>::cmp(left, right)
170
        })
171
    };
172
    let direct_hash = quote! {
2,123✔
173
        Some(|value, hasher| {
174
            let value = unsafe { value.get::<Self>() };
175
            <Self as core::hash::Hash>::hash(value, hasher)
176
        })
177
    };
178

179
    // Auto-detection versions using spez
180
    let auto_display = quote! {
2,123✔
181
        if #facet_crate::spez::impls!(Self: core::fmt::Display) {
182
            Some(|data, f| {
183
                let data = unsafe { data.get::<Self>() };
184
                use #facet_crate::spez::*;
185
                (&&Spez(data)).spez_display(f)
186
            })
187
        } else {
188
            None
189
        }
190
    };
191
    let auto_debug = quote! {
2,123✔
192
        if #facet_crate::spez::impls!(Self: core::fmt::Debug) {
193
            Some(|data, f| {
194
                let data = unsafe { data.get::<Self>() };
195
                use #facet_crate::spez::*;
196
                (&&Spez(data)).spez_debug(f)
197
            })
198
        } else {
199
            None
200
        }
201
    };
202
    let auto_default = quote! {
2,123✔
203
        if #facet_crate::spez::impls!(Self: core::default::Default) {
204
            Some(|target| unsafe {
205
                use #facet_crate::spez::*;
206
                (&&SpezEmpty::<Self>::SPEZ).spez_default_in_place(target)
207
            })
208
        } else {
209
            None
210
        }
211
    };
212
    let auto_clone = quote! {
2,123✔
213
        if #facet_crate::spez::impls!(Self: core::clone::Clone) {
214
            Some(|src, dst| unsafe {
215
                use #facet_crate::spez::*;
216
                let src = src.get::<Self>();
217
                (&&Spez(src)).spez_clone_into(dst)
218
            })
219
        } else {
220
            None
221
        }
222
    };
223
    let auto_partial_eq = quote! {
2,123✔
224
        if #facet_crate::spez::impls!(Self: core::cmp::PartialEq) {
225
            Some(|left, right| {
226
                let left = unsafe { left.get::<Self>() };
227
                let right = unsafe { right.get::<Self>() };
228
                use #facet_crate::spez::*;
229
                (&&Spez(left)).spez_partial_eq(&&Spez(right))
230
            })
231
        } else {
232
            None
233
        }
234
    };
235
    let auto_partial_ord = quote! {
2,123✔
236
        if #facet_crate::spez::impls!(Self: core::cmp::PartialOrd) {
237
            Some(|left, right| {
238
                let left = unsafe { left.get::<Self>() };
239
                let right = unsafe { right.get::<Self>() };
240
                use #facet_crate::spez::*;
241
                (&&Spez(left)).spez_partial_cmp(&&Spez(right))
242
            })
243
        } else {
244
            None
245
        }
246
    };
247
    let auto_ord = quote! {
2,123✔
248
        if #facet_crate::spez::impls!(Self: core::cmp::Ord) {
249
            Some(|left, right| {
250
                let left = unsafe { left.get::<Self>() };
251
                let right = unsafe { right.get::<Self>() };
252
                use #facet_crate::spez::*;
253
                (&&Spez(left)).spez_cmp(&&Spez(right))
254
            })
255
        } else {
256
            None
257
        }
258
    };
259
    let auto_hash = quote! {
2,123✔
260
        if #facet_crate::spez::impls!(Self: core::hash::Hash) {
261
            Some(|value, hasher| {
262
                let value = unsafe { value.get::<Self>() };
263
                use #facet_crate::spez::*;
264
                (&&Spez(value)).spez_hash(&mut { hasher })
265
            })
266
        } else {
267
            None
268
        }
269
    };
270
    let auto_parse = quote! {
2,123✔
271
        if #facet_crate::spez::impls!(Self: core::str::FromStr) {
272
            Some(|s, target| {
273
                use #facet_crate::spez::*;
274
                unsafe { (&&SpezEmpty::<Self>::SPEZ).spez_parse(s, target) }
275
            })
276
        } else {
277
            None
278
        }
279
    };
280

281
    // For each trait: derive > declared > auto > none
282
    // Only emit the builder call if we have a value (not None)
283

284
    // Display: no derive exists, so check declared then auto
285
    let display_call = if sources.has_declared(|d| d.display) {
2,123✔
286
        quote! { .display_opt(#direct_display) }
×
287
    } else if sources.should_auto() {
2,123✔
288
        quote! { .display_opt(#auto_display) }
29✔
289
    } else {
290
        quote! {}
2,094✔
291
    };
292

293
    // Debug: check derive, then declared, then auto
294
    let debug_call = if sources.has_derive(|d| d.debug) || sources.has_declared(|d| d.debug) {
2,123✔
295
        quote! { .debug_opt(#direct_debug) }
2✔
296
    } else if sources.should_auto() {
2,121✔
297
        quote! { .debug_opt(#auto_debug) }
29✔
298
    } else {
299
        quote! {}
2,092✔
300
    };
301

302
    // Default: check derive, then declared, then facet(default), then auto
303
    // Note: #[facet(default)] implies the type implements Default
304
    let default_call = if sources.has_derive(|d| d.default)
2,123✔
305
        || sources.has_declared(|d| d.default)
2,123✔
306
        || sources.facet_default
2,123✔
307
    {
308
        quote! { .default_in_place_opt(#direct_default) }
3✔
309
    } else if sources.should_auto() {
2,120✔
310
        quote! { .default_in_place_opt(#auto_default) }
29✔
311
    } else {
312
        quote! {}
2,091✔
313
    };
314

315
    // Clone: check derive (including Copy which implies Clone), then declared, then auto
316
    let clone_call = if sources.has_derive(|d| d.clone || d.copy)
2,123✔
317
        || sources.has_declared(|d| d.clone || d.copy)
2,123✔
318
    {
319
        quote! { .clone_into_opt(#direct_clone) }
×
320
    } else if sources.should_auto() {
2,123✔
321
        quote! { .clone_into_opt(#auto_clone) }
29✔
322
    } else {
323
        quote! {}
2,094✔
324
    };
325

326
    // PartialEq: check derive, then declared, then auto
327
    let partial_eq_call =
2,123✔
328
        if sources.has_derive(|d| d.partial_eq) || sources.has_declared(|d| d.partial_eq) {
2,123✔
329
            quote! { .partial_eq_opt(#direct_partial_eq) }
×
330
        } else if sources.should_auto() {
2,123✔
331
            quote! { .partial_eq_opt(#auto_partial_eq) }
29✔
332
        } else {
333
            quote! {}
2,094✔
334
        };
335

336
    // PartialOrd: check derive, then declared, then auto
337
    let partial_ord_call =
2,123✔
338
        if sources.has_derive(|d| d.partial_ord) || sources.has_declared(|d| d.partial_ord) {
2,123✔
339
            quote! { .partial_ord_opt(#direct_partial_ord) }
×
340
        } else if sources.should_auto() {
2,123✔
341
            quote! { .partial_ord_opt(#auto_partial_ord) }
29✔
342
        } else {
343
            quote! {}
2,094✔
344
        };
345

346
    // Ord: check derive, then declared, then auto
347
    let ord_call = if sources.has_derive(|d| d.ord) || sources.has_declared(|d| d.ord) {
2,123✔
348
        quote! { .ord_opt(#direct_ord) }
×
349
    } else if sources.should_auto() {
2,123✔
350
        quote! { .ord_opt(#auto_ord) }
29✔
351
    } else {
352
        quote! {}
2,094✔
353
    };
354

355
    // Hash: check derive, then declared, then auto
356
    let hash_call = if sources.has_derive(|d| d.hash) || sources.has_declared(|d| d.hash) {
2,123✔
357
        quote! { .hash_opt(#direct_hash) }
×
358
    } else if sources.should_auto() {
2,123✔
359
        quote! { .hash_opt(#auto_hash) }
29✔
360
    } else {
361
        quote! {}
2,094✔
362
    };
363

364
    // Parse (FromStr): no derive exists, only auto-detect if enabled
365
    let parse_call = if sources.should_auto() {
2,123✔
366
        quote! { .parse_opt(#auto_parse) }
29✔
367
    } else {
368
        quote! {}
2,094✔
369
    };
370

371
    // Marker traits - these set bitflags in MarkerTraits
372
    // Copy: derive (Copy implies Clone), declared, or auto
373
    let has_copy =
2,123✔
374
        sources.has_derive(|d| d.copy) || sources.has_declared(|d| d.copy) || sources.auto_traits;
2,123✔
375
    // Send: declared or auto (no standard derive for Send)
376
    let has_send = sources.has_declared(|d| d.send) || sources.auto_traits;
2,123✔
377
    // Sync: declared or auto (no standard derive for Sync)
378
    let has_sync = sources.has_declared(|d| d.sync) || sources.auto_traits;
2,123✔
379
    // Eq: derive (PartialEq + Eq), declared, or auto
380
    let has_eq =
2,123✔
381
        sources.has_derive(|d| d.eq) || sources.has_declared(|d| d.eq) || sources.auto_traits;
2,123✔
382
    // Unpin: declared or auto (no standard derive for Unpin)
383
    let has_unpin = sources.has_declared(|d| d.unpin) || sources.auto_traits;
2,123✔
384

385
    // Build markers expression
386
    let markers_entry = if has_copy || has_send || has_sync || has_eq || has_unpin {
2,123✔
387
        // At least one marker trait might be set
388
        let copy_check = if has_copy
29✔
389
            && sources.auto_traits
29✔
390
            && !sources.has_derive(|d| d.copy)
29✔
391
            && !sources.has_declared(|d| d.copy)
29✔
392
        {
393
            // Auto-detect Copy
394
            quote! {
29✔
395
                if #facet_crate::spez::impls!(Self: core::marker::Copy) {
396
                    markers = markers.with_copy();
397
                }
398
            }
399
        } else if sources.has_derive(|d| d.copy) || sources.has_declared(|d| d.copy) {
×
400
            // Directly known to be Copy
401
            quote! { markers = markers.with_copy(); }
×
402
        } else {
403
            quote! {}
×
404
        };
405

406
        let send_check = if has_send && sources.auto_traits && !sources.has_declared(|d| d.send) {
29✔
407
            quote! {
29✔
408
                if #facet_crate::spez::impls!(Self: core::marker::Send) {
409
                    markers = markers.with_send();
410
                }
411
            }
412
        } else if sources.has_declared(|d| d.send) {
×
413
            quote! { markers = markers.with_send(); }
×
414
        } else {
415
            quote! {}
×
416
        };
417

418
        let sync_check = if has_sync && sources.auto_traits && !sources.has_declared(|d| d.sync) {
29✔
419
            quote! {
29✔
420
                if #facet_crate::spez::impls!(Self: core::marker::Sync) {
421
                    markers = markers.with_sync();
422
                }
423
            }
424
        } else if sources.has_declared(|d| d.sync) {
×
425
            quote! { markers = markers.with_sync(); }
×
426
        } else {
427
            quote! {}
×
428
        };
429

430
        let eq_check = if has_eq
29✔
431
            && sources.auto_traits
29✔
432
            && !sources.has_derive(|d| d.eq)
29✔
433
            && !sources.has_declared(|d| d.eq)
29✔
434
        {
435
            quote! {
29✔
436
                if #facet_crate::spez::impls!(Self: core::cmp::Eq) {
437
                    markers = markers.with_eq();
438
                }
439
            }
440
        } else if sources.has_derive(|d| d.eq) || sources.has_declared(|d| d.eq) {
×
441
            quote! { markers = markers.with_eq(); }
×
442
        } else {
443
            quote! {}
×
444
        };
445

446
        let unpin_check = if has_unpin && sources.auto_traits && !sources.has_declared(|d| d.unpin)
29✔
447
        {
448
            quote! {
29✔
449
                if #facet_crate::spez::impls!(Self: core::marker::Unpin) {
450
                    markers = markers.with_unpin();
451
                }
452
            }
453
        } else if sources.has_declared(|d| d.unpin) {
×
454
            quote! { markers = markers.with_unpin(); }
×
455
        } else {
456
            quote! {}
×
457
        };
458

459
        quote! {{
29✔
460
            let mut markers = #facet_crate::MarkerTraits::EMPTY;
461
            #copy_check
462
            #send_check
463
            #sync_check
464
            #eq_check
465
            #unpin_check
466
            markers
467
        }}
468
    } else {
469
        quote! { #facet_crate::MarkerTraits::EMPTY }
2,094✔
470
    };
471

472
    quote! {
2,123✔
473
        #facet_crate::ValueVTable::builder(#type_name_fn)
474
            .drop_in_place(#facet_crate::ValueVTable::drop_in_place_for::<Self>())
475
            #display_call
476
            #debug_call
477
            #default_call
478
            #clone_call
479
            #partial_eq_call
480
            #partial_ord_call
481
            #ord_call
482
            #hash_call
483
            #parse_call
484
            .markers(#markers_entry)
485
            .build()
486
    }
487
}
2,123✔
488

489
/// Generate trait bounds for static assertions.
490
/// Returns a TokenStream of bounds like `core::fmt::Debug + core::clone::Clone`
491
/// that can be used in a where clause.
492
///
493
/// `facet_default` is true when `#[facet(default)]` is present, which implies Default.
494
pub(crate) fn gen_trait_bounds(
2,123✔
495
    declared: Option<&DeclaredTraits>,
2,123✔
496
    facet_default: bool,
2,123✔
497
) -> Option<TokenStream> {
2,123✔
498
    let mut bounds = Vec::new();
2,123✔
499

500
    if let Some(declared) = declared {
2,123✔
501
        if declared.display {
2✔
502
            bounds.push(quote! { core::fmt::Display });
×
503
        }
2✔
504
        if declared.debug {
2✔
505
            bounds.push(quote! { core::fmt::Debug });
2✔
506
        }
2✔
507
        if declared.clone {
2✔
508
            bounds.push(quote! { core::clone::Clone });
×
509
        }
2✔
510
        if declared.copy {
2✔
511
            bounds.push(quote! { core::marker::Copy });
×
512
        }
2✔
513
        if declared.partial_eq {
2✔
514
            bounds.push(quote! { core::cmp::PartialEq });
×
515
        }
2✔
516
        if declared.eq {
2✔
517
            bounds.push(quote! { core::cmp::Eq });
×
518
        }
2✔
519
        if declared.partial_ord {
2✔
520
            bounds.push(quote! { core::cmp::PartialOrd });
×
521
        }
2✔
522
        if declared.ord {
2✔
523
            bounds.push(quote! { core::cmp::Ord });
×
524
        }
2✔
525
        if declared.hash {
2✔
526
            bounds.push(quote! { core::hash::Hash });
×
527
        }
2✔
528
        if declared.default {
2✔
529
            bounds.push(quote! { core::default::Default });
×
530
        }
2✔
531
        if declared.send {
2✔
532
            bounds.push(quote! { core::marker::Send });
×
533
        }
2✔
534
        if declared.sync {
2✔
535
            bounds.push(quote! { core::marker::Sync });
×
536
        }
2✔
537
        if declared.unpin {
2✔
538
            bounds.push(quote! { core::marker::Unpin });
×
539
        }
2✔
540
    }
2,121✔
541

542
    // #[facet(default)] implies Default trait
543
    if facet_default && !declared.is_some_and(|d| d.default) {
2,123✔
544
        bounds.push(quote! { core::default::Default });
3✔
545
    }
2,120✔
546

547
    if bounds.is_empty() {
2,123✔
548
        None
2,118✔
549
    } else {
550
        Some(quote! { #(#bounds)+* })
5✔
551
    }
552
}
2,123✔
553

554
/// Generates the `::facet::Field` definition `TokenStream` from a `PStructField`.
555
pub(crate) fn gen_field_from_pfield(
3,709✔
556
    field: &PStructField,
3,709✔
557
    struct_name: &Ident,
3,709✔
558
    bgp: &BoundedGenericParams,
3,709✔
559
    base_offset: Option<TokenStream>,
3,709✔
560
    facet_crate: &TokenStream,
3,709✔
561
) -> TokenStream {
3,709✔
562
    let field_name_effective = &field.name.effective;
3,709✔
563
    let field_name_raw = &field.name.raw;
3,709✔
564
    let field_type = &field.ty;
3,709✔
565

566
    let bgp_without_bounds = bgp.display_without_bounds();
3,709✔
567

568
    #[cfg(feature = "doc")]
569
    let doc_lines: Vec<String> = field
3,709✔
570
        .attrs
3,709✔
571
        .doc
3,709✔
572
        .iter()
3,709✔
573
        .map(|doc| doc.as_str().replace("\\\"", "\""))
3,709✔
574
        .collect();
3,709✔
575
    #[cfg(not(feature = "doc"))]
576
    let doc_lines: Vec<String> = Vec::new();
×
577

578
    // Check if this field is marked as a recursive type
579
    let is_recursive = field.attrs.has_builtin("recursive_type");
3,709✔
580

581
    // Generate the shape expression directly using the field type
582
    // For opaque fields, wrap in Opaque<T>
583
    // NOTE: Uses short alias from `use #facet_crate::𝟋::*` in the enclosing const block
584
    let shape_expr = if field.attrs.has_builtin("opaque") {
3,709✔
585
        quote! { <#facet_crate::Opaque<#field_type> as 𝟋Fct>::SHAPE }
33✔
586
    } else {
587
        quote! { <#field_type as 𝟋Fct>::SHAPE }
3,676✔
588
    };
589

590
    // Process attributes, separating flag attrs and field attrs from the attribute slice.
591
    // Attributes with #[storage(flag)] go into FieldFlags for O(1) access.
592
    // Attributes with #[storage(field)] go into dedicated Field struct fields.
593
    // Everything else goes into the attributes slice.
594
    //
595
    // Flag attrs: sensitive, flatten, child, skip, skip_serializing, skip_deserializing
596
    // Field attrs: rename, alias
597
    // Note: default also sets HAS_DEFAULT flag (handled below)
598

599
    let mut flags: Vec<TokenStream> = Vec::new();
3,709✔
600
    let mut rename_value: Option<TokenStream> = None;
3,709✔
601
    let mut alias_value: Option<TokenStream> = None;
3,709✔
602
    let mut attribute_list: Vec<TokenStream> = Vec::new();
3,709✔
603

604
    for attr in &field.attrs.facet {
3,709✔
605
        if attr.is_builtin() {
1,560✔
606
            let key = attr.key_str();
573✔
607
            match key.as_str() {
573✔
608
                // Flag attrs - set bit in FieldFlags, don't add to attribute_list
609
                "sensitive" => {
573✔
610
                    flags.push(quote! { 𝟋FF::SENSITIVE });
2✔
611
                }
2✔
612
                "flatten" => {
571✔
613
                    flags.push(quote! { 𝟋FF::FLATTEN });
94✔
614
                }
94✔
615
                "child" => {
477✔
616
                    flags.push(quote! { 𝟋FF::CHILD });
×
617
                }
×
618
                "skip" => {
477✔
619
                    flags.push(quote! { 𝟋FF::SKIP });
3✔
620
                }
3✔
621
                "skip_serializing" => {
474✔
622
                    flags.push(quote! { 𝟋FF::SKIP_SERIALIZING });
4✔
623
                }
4✔
624
                "skip_deserializing" => {
470✔
625
                    flags.push(quote! { 𝟋FF::SKIP_DESERIALIZING });
1✔
626
                }
1✔
627
                "default" => {
469✔
628
                    // Default sets the HAS_DEFAULT flag AND goes into attributes
230✔
629
                    flags.push(quote! { 𝟋FF::HAS_DEFAULT });
230✔
630
                    let ext_attr =
230✔
631
                        emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
230✔
632
                    attribute_list.push(quote! { #ext_attr });
230✔
633
                }
230✔
634
                "recursive_type" => {
239✔
635
                    // recursive_type sets a flag
76✔
636
                    flags.push(quote! { 𝟋FF::RECURSIVE_TYPE });
76✔
637
                }
76✔
638
                // Field attrs - store in dedicated field, don't add to attribute_list
639
                "rename" => {
163✔
640
                    // Extract the string literal from args
86✔
641
                    let args = &attr.args;
86✔
642
                    rename_value = Some(quote! { #args });
86✔
643
                }
86✔
644
                "alias" => {
77✔
645
                    // Extract the string literal from args
×
646
                    let args = &attr.args;
×
647
                    alias_value = Some(quote! { #args });
×
648
                }
×
649
                // Everything else goes to attributes slice
650
                _ => {
77✔
651
                    let ext_attr =
77✔
652
                        emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
77✔
653
                    attribute_list.push(quote! { #ext_attr });
77✔
654
                }
77✔
655
            }
656
        } else {
987✔
657
            // Non-builtin (namespaced) attrs always go to attributes slice
987✔
658
            let ext_attr = emit_attr_for_field(attr, field_name_raw, field_type, facet_crate);
987✔
659
            attribute_list.push(quote! { #ext_attr });
987✔
660
        }
987✔
661
    }
662

663
    // Generate proxy conversion function pointers when proxy attribute is present
664
    if let Some(attr) = field
3,709✔
665
        .attrs
3,709✔
666
        .facet
3,709✔
667
        .iter()
3,709✔
668
        .find(|a| a.is_builtin() && a.key_str() == "proxy")
3,709✔
669
    {
40✔
670
        let proxy_type = &attr.args;
40✔
671

40✔
672
        // Generate __proxy_in: converts proxy -> field type via TryFrom
40✔
673
        attribute_list.push(quote! {
40✔
674
            #facet_crate::ExtensionAttr {
40✔
675
                ns: ::core::option::Option::None,
40✔
676
                key: "__proxy_in",
40✔
677
                data: &const {
40✔
678
                    extern crate alloc as __alloc;
40✔
679
                    unsafe fn __proxy_convert_in<'mem>(
40✔
680
                        proxy_ptr: #facet_crate::PtrConst<'mem>,
40✔
681
                        field_ptr: #facet_crate::PtrUninit<'mem>,
40✔
682
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, __alloc::string::String> {
40✔
683
                        let proxy: #proxy_type = proxy_ptr.read();
40✔
684
                        match <#field_type as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
40✔
685
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
40✔
686
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
40✔
687
                        }
40✔
688
                    }
40✔
689
                    __proxy_convert_in as #facet_crate::ProxyConvertInFn
40✔
690
                } as *const #facet_crate::ProxyConvertInFn as *const (),
40✔
691
                shape: <() as #facet_crate::Facet>::SHAPE,
40✔
692
            }
40✔
693
        });
40✔
694

40✔
695
        // Generate __proxy_out: converts &field type -> proxy via TryFrom
40✔
696
        attribute_list.push(quote! {
40✔
697
            #facet_crate::ExtensionAttr {
40✔
698
                ns: ::core::option::Option::None,
40✔
699
                key: "__proxy_out",
40✔
700
                data: &const {
40✔
701
                    extern crate alloc as __alloc;
40✔
702
                    unsafe fn __proxy_convert_out<'mem>(
40✔
703
                        field_ptr: #facet_crate::PtrConst<'mem>,
40✔
704
                        proxy_ptr: #facet_crate::PtrUninit<'mem>,
40✔
705
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, __alloc::string::String> {
40✔
706
                        let field_ref: &#field_type = field_ptr.get();
40✔
707
                        match <#proxy_type as ::core::convert::TryFrom<&#field_type>>::try_from(field_ref) {
40✔
708
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
40✔
709
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
40✔
710
                        }
40✔
711
                    }
40✔
712
                    __proxy_convert_out as #facet_crate::ProxyConvertOutFn
40✔
713
                } as *const #facet_crate::ProxyConvertOutFn as *const (),
40✔
714
                shape: <() as #facet_crate::Facet>::SHAPE,
40✔
715
            }
40✔
716
        });
40✔
717
    }
3,669✔
718

719
    let maybe_attributes = if attribute_list.is_empty() {
3,709✔
720
        quote! { &[] }
2,710✔
721
    } else {
722
        quote! { &const {[#(#attribute_list),*]} }
999✔
723
    };
724

725
    let maybe_field_doc = if doc_lines.is_empty() {
3,709✔
726
        quote! { &[] }
3,645✔
727
    } else {
728
        quote! { &[#(#doc_lines),*] }
64✔
729
    };
730

731
    // Calculate the final offset, incorporating the base_offset if present
732
    let final_offset = match base_offset {
3,709✔
733
        Some(base) => {
203✔
734
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
203✔
735
        }
736
        None => {
737
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
3,506✔
738
        }
739
    };
740

741
    // Use FieldBuilder for more compact generated code
742
    // NOTE: Uses short alias from `use #facet_crate::𝟋::*` in the enclosing const block
743
    //
744
    // For most fields, use `new` with a direct shape reference (more efficient).
745
    // For recursive type fields (marked with #[facet(recursive_type)]), use `new_lazy`
746
    // with a closure to break cycles.
747
    let builder = if is_recursive {
3,709✔
748
        quote! {
76✔
749
            𝟋FldB::new_lazy(
750
                #field_name_effective,
751
                || #shape_expr,
752
                #final_offset,
753
            )
754
        }
755
    } else {
756
        quote! {
3,633✔
757
            𝟋FldB::new(
758
                #field_name_effective,
759
                #shape_expr,
760
                #final_offset,
761
            )
762
        }
763
    };
764

765
    // Build the chain of builder method calls
766
    let mut builder_chain = builder;
3,709✔
767

768
    // Add flags if any were collected
769
    if !flags.is_empty() {
3,709✔
770
        let flags_expr = if flags.len() == 1 {
406✔
771
            let f = &flags[0];
402✔
772
            quote! { #f }
402✔
773
        } else {
774
            // Union multiple flags together
775
            let first = &flags[0];
4✔
776
            let rest = &flags[1..];
4✔
777
            quote! { #first #(.union(#rest))* }
4✔
778
        };
779
        builder_chain = quote! { #builder_chain.flags(#flags_expr) };
406✔
780
    }
3,303✔
781

782
    // Add rename if present
783
    if let Some(rename) = &rename_value {
3,709✔
784
        builder_chain = quote! { #builder_chain.rename(#rename) };
86✔
785
    }
3,623✔
786

787
    // Add alias if present
788
    if let Some(alias) = &alias_value {
3,709✔
789
        builder_chain = quote! { #builder_chain.alias(#alias) };
×
790
    }
3,709✔
791

792
    // Add attributes if any
793
    if !attribute_list.is_empty() {
3,709✔
794
        builder_chain = quote! { #builder_chain.attributes(#maybe_attributes) };
999✔
795
    }
2,710✔
796

797
    // Add doc if present
798
    if !doc_lines.is_empty() {
3,709✔
799
        builder_chain = quote! { #builder_chain.doc(#maybe_field_doc) };
64✔
800
    }
3,645✔
801

802
    // Finally call build
803
    quote! { #builder_chain.build() }
3,709✔
804
}
3,709✔
805

806
/// Processes a regular struct to implement Facet
807
///
808
/// Example input:
809
/// ```rust
810
/// struct Blah {
811
///     foo: u32,
812
///     bar: String,
813
/// }
814
/// ```
815
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
1,804✔
816
    let ps = PStruct::parse(&parsed); // Use the parsed representation
1,804✔
817

818
    // Emit any collected errors as compile_error! with proper spans
819
    if !ps.container.attrs.errors.is_empty() {
1,804✔
820
        let errors = ps.container.attrs.errors.iter().map(|e| {
×
821
            let msg = &e.message;
×
822
            let span = e.span;
×
823
            quote_spanned! { span => compile_error!(#msg); }
×
824
        });
×
825
        return quote! { #(#errors)* };
×
826
    }
1,804✔
827

828
    let struct_name_ident = format_ident!("{}", ps.container.name);
1,804✔
829
    let struct_name = &ps.container.name;
1,804✔
830
    let struct_name_str = struct_name.to_string();
1,804✔
831

832
    let opaque = ps.container.attrs.has_builtin("opaque");
1,804✔
833

834
    // Get the facet crate path (custom or default ::facet)
835
    let facet_crate = ps.container.attrs.facet_crate();
1,804✔
836

837
    let type_name_fn =
1,804✔
838
        generate_type_name_fn(struct_name, parsed.generics.as_ref(), opaque, &facet_crate);
1,804✔
839

840
    // Determine trait sources and generate vtable accordingly
841
    let trait_sources = TraitSources::from_attrs(&ps.container.attrs);
1,804✔
842
    let vtable_code = gen_vtable(&facet_crate, &type_name_fn, &trait_sources);
1,804✔
843
    let vtable_init = quote! { const { #vtable_code } };
1,804✔
844

845
    // TODO: I assume the `PrimitiveRepr` is only relevant for enums, and does not need to be preserved?
846
    // NOTE: Uses short aliases from `use #facet_crate::𝟋::*` in the const block
847
    let repr = match &ps.container.attrs.repr {
1,804✔
848
        PRepr::Transparent => quote! { 𝟋Repr::TRANSPARENT },
7✔
849
        PRepr::Rust(_) => quote! { 𝟋Repr::RUST },
1,720✔
850
        PRepr::C(_) => quote! { 𝟋Repr::C },
77✔
851
        PRepr::RustcWillCatch => {
852
            // rustc will emit an error for the invalid repr.
853
            // Return empty TokenStream so we don't add misleading errors.
854
            return quote! {};
×
855
        }
856
    };
857

858
    // Use PStruct for kind and fields
859
    let (kind, fields_vec) = match &ps.kind {
1,804✔
860
        PStructKind::Struct { fields } => {
1,696✔
861
            let kind = quote!(𝟋Sk::Struct);
1,696✔
862
            let fields_vec = fields
1,696✔
863
                .iter()
1,696✔
864
                .map(|field| {
2,916✔
865
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
2,916✔
866
                })
2,916✔
867
                .collect::<Vec<_>>();
1,696✔
868
            (kind, fields_vec)
1,696✔
869
        }
870
        PStructKind::TupleStruct { fields } => {
100✔
871
            let kind = quote!(𝟋Sk::TupleStruct);
100✔
872
            let fields_vec = fields
100✔
873
                .iter()
100✔
874
                .map(|field| {
123✔
875
                    gen_field_from_pfield(field, struct_name, &ps.container.bgp, None, &facet_crate)
123✔
876
                })
123✔
877
                .collect::<Vec<_>>();
100✔
878
            (kind, fields_vec)
100✔
879
        }
880
        PStructKind::UnitStruct => {
881
            let kind = quote!(𝟋Sk::Unit);
8✔
882
            (kind, vec![])
8✔
883
        }
884
    };
885

886
    // Still need original AST for where clauses and type params for build_ helpers
887
    let where_clauses_ast = match &parsed.kind {
1,804✔
888
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
1,696✔
889
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
100✔
890
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
8✔
891
    };
892
    let where_clauses = build_where_clauses(
1,804✔
893
        where_clauses_ast,
1,804✔
894
        parsed.generics.as_ref(),
1,804✔
895
        opaque,
1,804✔
896
        &facet_crate,
1,804✔
897
    );
898
    let type_params_call = build_type_params_call(parsed.generics.as_ref(), opaque, &facet_crate);
1,804✔
899

900
    // Static decl using PStruct BGP
901
    let static_decl = if ps.container.bgp.params.is_empty() {
1,804✔
902
        generate_static_decl(struct_name, &facet_crate)
1,735✔
903
    } else {
904
        TokenStream::new()
69✔
905
    };
906

907
    // Doc comments from PStruct - returns value for struct literal
908
    // doc call - only emit if there are doc comments and doc feature is enabled
909
    #[cfg(feature = "doc")]
910
    let doc_call = if ps.container.attrs.doc.is_empty() {
1,804✔
911
        quote! {}
1,757✔
912
    } else {
913
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
136✔
914
        quote! { .doc(&[#(#doc_lines),*]) }
47✔
915
    };
916
    #[cfg(not(feature = "doc"))]
917
    let doc_call = quote! {};
×
918

919
    // Container attributes - most go through grammar dispatch
920
    // Filter out `invariants` and `crate` since they're handled specially
921
    // Returns builder call only if there are attributes
922
    let attributes_call = {
1,804✔
923
        let items: Vec<TokenStream> = ps
1,804✔
924
            .container
1,804✔
925
            .attrs
1,804✔
926
            .facet
1,804✔
927
            .iter()
1,804✔
928
            .filter(|attr| {
1,804✔
929
                // These attributes are handled specially and not emitted to runtime:
930
                // - invariants: populates vtable.invariants
931
                // - crate: sets the facet crate path
932
                // - traits: compile-time directive for vtable generation
933
                // - auto_traits: compile-time directive for vtable generation
934
                // - proxy: sets Shape::proxy for container-level proxy
935
                if attr.is_builtin() {
170✔
936
                    let key = attr.key_str();
155✔
937
                    !matches!(
39✔
938
                        key.as_str(),
155✔
939
                        "invariants" | "crate" | "traits" | "auto_traits" | "proxy"
155✔
940
                    )
941
                } else {
942
                    true
15✔
943
                }
944
            })
170✔
945
            .map(|attr| {
1,804✔
946
                let ext_attr = emit_attr(attr, &facet_crate);
131✔
947
                quote! { #ext_attr }
131✔
948
            })
131✔
949
            .collect();
1,804✔
950

951
        if items.is_empty() {
1,804✔
952
            quote! {}
1,693✔
953
        } else {
954
            quote! { .attributes(&const {[#(#items),*]}) }
111✔
955
        }
956
    };
957

958
    // Type tag from PStruct - returns builder call only if present
959
    let type_tag_call = {
1,804✔
960
        if let Some(type_tag) = ps.container.attrs.get_builtin_args("type_tag") {
1,804✔
961
            quote! { .type_tag(#type_tag) }
8✔
962
        } else {
963
            quote! {}
1,796✔
964
        }
965
    };
966

967
    // Container-level proxy from PStruct - generates ProxyDef with conversion functions
968
    //
969
    // The challenge: Generic type parameters aren't available inside `const { }` blocks.
970
    // Solution: We define the proxy functions as inherent methods on the type (outside const),
971
    // then reference them via Self::method inside the Facet impl. This works because:
972
    // 1. Inherent impl methods CAN use generic parameters from their impl block
973
    // 2. Inside the Facet impl's const SHAPE, `Self` refers to the concrete monomorphized type
974
    // 3. Function pointers to Self::method get properly monomorphized
975
    let (proxy_inherent_impl, proxy_call) = {
1,804✔
976
        if let Some(attr) = ps
1,804✔
977
            .container
1,804✔
978
            .attrs
1,804✔
979
            .facet
1,804✔
980
            .iter()
1,804✔
981
            .find(|a| a.is_builtin() && a.key_str() == "proxy")
1,804✔
982
        {
983
            let proxy_type = &attr.args;
5✔
984
            let struct_type = &struct_name_ident;
5✔
985
            let bgp_display = ps.container.bgp.display_without_bounds();
5✔
986
            // Compute bgp locally for the inherent impl
987
            let helper_bgp = ps
5✔
988
                .container
5✔
989
                .bgp
5✔
990
                .with_lifetime(LifetimeName(format_ident!("ʄ")));
5✔
991
            let bgp_def_for_helper = helper_bgp.display_with_bounds();
5✔
992

993
            // Define an inherent impl with the proxy helper methods
994
            // These are NOT in a const block, so generic params ARE available
995
            // We need where clauses for:
996
            // 1. The proxy type must implement Facet (for __facet_proxy_shape)
997
            // 2. The TryFrom conversions (checked when methods are called)
998
            // Compute the where_clauses for the helper impl by adding the proxy Facet bound
999
            // Build the combined where clause - we need to add proxy: Facet to existing clauses
1000
            let proxy_where = {
5✔
1001
                // Build additional clause tokens (comma-separated)
1002
                let additional_clauses = quote! { #proxy_type: #facet_crate::Facet<'ʄ> };
5✔
1003

1004
                // where_clauses is either empty or "where X: Y, ..."
1005
                // We need to append our clause
1006
                if where_clauses.is_empty() {
5✔
1007
                    quote! { where #additional_clauses }
4✔
1008
                } else {
1009
                    quote! { #where_clauses, #additional_clauses }
1✔
1010
                }
1011
            };
1012

1013
            let proxy_impl = quote! {
5✔
1014
                #[doc(hidden)]
1015
                impl #bgp_def_for_helper #struct_type #bgp_display
1016
                #proxy_where
1017
                {
1018
                    #[doc(hidden)]
1019
                    unsafe fn __facet_proxy_convert_in<'mem>(
1020
                        proxy_ptr: #facet_crate::PtrConst<'mem>,
1021
                        field_ptr: #facet_crate::PtrUninit<'mem>,
1022
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, #facet_crate::𝟋::𝟋Str> {
1023
                        extern crate alloc as __alloc;
1024
                        let proxy: #proxy_type = proxy_ptr.read();
1025
                        match <#struct_type #bgp_display as ::core::convert::TryFrom<#proxy_type>>::try_from(proxy) {
1026
                            ::core::result::Result::Ok(value) => ::core::result::Result::Ok(field_ptr.put(value)),
1027
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1028
                        }
1029
                    }
1030

1031
                    #[doc(hidden)]
1032
                    unsafe fn __facet_proxy_convert_out<'mem>(
1033
                        field_ptr: #facet_crate::PtrConst<'mem>,
1034
                        proxy_ptr: #facet_crate::PtrUninit<'mem>,
1035
                    ) -> ::core::result::Result<#facet_crate::PtrMut<'mem>, #facet_crate::𝟋::𝟋Str> {
1036
                        extern crate alloc as __alloc;
1037
                        let field_ref: &#struct_type #bgp_display = field_ptr.get();
1038
                        match <#proxy_type as ::core::convert::TryFrom<&#struct_type #bgp_display>>::try_from(field_ref) {
1039
                            ::core::result::Result::Ok(proxy) => ::core::result::Result::Ok(proxy_ptr.put(proxy)),
1040
                            ::core::result::Result::Err(e) => ::core::result::Result::Err(__alloc::string::ToString::to_string(&e)),
1041
                        }
1042
                    }
1043

1044
                    #[doc(hidden)]
1045
                    const fn __facet_proxy_shape() -> &'static #facet_crate::Shape {
1046
                        <#proxy_type as #facet_crate::Facet>::SHAPE
1047
                    }
1048
                }
1049
            };
1050

1051
            // Reference the inherent methods from within the SHAPE const block
1052
            // Self::method works because Self in the Facet impl refers to the struct type
1053
            let proxy_ref = quote! {
5✔
1054
                .proxy(&const {
1055
                    #facet_crate::ProxyDef {
1056
                        shape: Self::__facet_proxy_shape(),
1057
                        convert_in: Self::__facet_proxy_convert_in,
1058
                        convert_out: Self::__facet_proxy_convert_out,
1059
                    }
1060
                })
1061
            };
1062

1063
            (proxy_impl, proxy_ref)
5✔
1064
        } else {
1065
            (quote! {}, quote! {})
1,799✔
1066
        }
1067
    };
1068

1069
    // Invariants from PStruct - extract invariant function expressions
1070
    let invariant_maybe = {
1,804✔
1071
        let invariant_exprs: Vec<&TokenStream> = ps
1,804✔
1072
            .container
1,804✔
1073
            .attrs
1,804✔
1074
            .facet
1,804✔
1075
            .iter()
1,804✔
1076
            .filter(|attr| attr.is_builtin() && attr.key_str() == "invariants")
1,804✔
1077
            .map(|attr| &attr.args)
1,804✔
1078
            .collect();
1,804✔
1079

1080
        if !invariant_exprs.is_empty() {
1,804✔
1081
            let tests = invariant_exprs.iter().map(|expr| {
3✔
1082
                quote! {
3✔
1083
                    if !#expr(value) {
1084
                        return false;
1085
                    }
1086
                }
1087
            });
3✔
1088

1089
            let bgp_display = ps.container.bgp.display_without_bounds();
3✔
1090
            quote! {
3✔
1091
                unsafe fn invariants<'mem>(value: #facet_crate::PtrConst<'mem>) -> bool {
1092
                    let value = value.get::<#struct_name_ident #bgp_display>();
1093
                    #(#tests)*
1094
                    true
1095
                }
1096

1097
                {
1098
                    vtable.invariants = Some(invariants);
1099
                }
1100
            }
1101
        } else {
1102
            quote! {}
1,801✔
1103
        }
1104
    };
1105

1106
    // Determine if this struct should use transparent semantics
1107
    // Transparent is enabled if:
1108
    // 1. #[facet(transparent)] is explicitly set, OR
1109
    // 2. #[repr(transparent)] is set AND the struct is a tuple struct with exactly 0 or 1 field
1110
    //
1111
    // Note: Rust's repr(transparent) allows multiple fields if all but one are ZST,
1112
    // but facet(transparent) only supports 0 or 1 field for serialization purposes.
1113
    // When repr(transparent) is used with multiple fields, the user must explicitly
1114
    // add #[facet(transparent)] if they want facet transparent semantics.
1115
    let has_explicit_facet_transparent = ps.container.attrs.has_builtin("transparent");
1,804✔
1116
    let has_repr_transparent = ps.container.attrs.is_repr_transparent();
1,804✔
1117

1118
    // Check if repr(transparent) should imply facet transparent
1119
    let repr_implies_facet_transparent = if has_repr_transparent && !has_explicit_facet_transparent
1,804✔
1120
    {
1121
        match &ps.kind {
7✔
1122
            PStructKind::TupleStruct { fields } => fields.len() <= 1,
7✔
NEW
1123
            _ => false,
×
1124
        }
1125
    } else {
1126
        false
1,797✔
1127
    };
1128

1129
    let use_transparent_semantics =
1,804✔
1130
        has_explicit_facet_transparent || repr_implies_facet_transparent;
1,804✔
1131

1132
    // Transparent logic using PStruct
1133
    let inner_field = if use_transparent_semantics {
1,804✔
1134
        match &ps.kind {
44✔
1135
            PStructKind::TupleStruct { fields } => {
44✔
1136
                if fields.len() > 1 {
44✔
1137
                    return quote! {
×
1138
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
1139
                    };
1140
                }
44✔
1141
                fields.first().cloned() // Use first field if it exists, None otherwise (ZST case)
44✔
1142
            }
1143
            _ => {
1144
                return quote! {
×
1145
                    compile_error!("Transparent structs must be tuple structs");
1146
                };
1147
            }
1148
        }
1149
    } else {
1150
        None
1,760✔
1151
    };
1152

1153
    // Add try_from_inner implementation for transparent types
1154
    let try_from_inner_code = if use_transparent_semantics {
1,804✔
1155
        if let Some(inner_field) = &inner_field {
44✔
1156
            if !inner_field.attrs.has_builtin("opaque") {
44✔
1157
                // Transparent struct with one field
1158
                let inner_field_type = &inner_field.ty;
42✔
1159
                let bgp_without_bounds = ps.container.bgp.display_without_bounds();
42✔
1160

1161
                quote! {
42✔
1162
                    // Define the try_from function for the value vtable
1163
                    unsafe fn try_from<'src, 'dst>(
1164
                        src_ptr: #facet_crate::PtrConst<'src>,
1165
                        src_shape: &'static #facet_crate::Shape,
1166
                        dst: #facet_crate::PtrUninit<'dst>
1167
                    ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryFromError> {
1168
                        // Try the inner type's try_from function if it exists
1169
                        let inner_result = match <#inner_field_type as #facet_crate::Facet>::SHAPE.vtable.try_from {
1170
                            Some(inner_try) => unsafe { (inner_try)(src_ptr, src_shape, dst) },
1171
                            None => Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1172
                                src_shape,
1173
                                expected: const { &[ &<#inner_field_type as #facet_crate::Facet>::SHAPE ] },
1174
                            })
1175
                        };
1176

1177
                        match inner_result {
1178
                            Ok(result) => Ok(result),
1179
                            Err(_) => {
1180
                                // If inner_try failed, check if source shape is exactly the inner shape
1181
                                if src_shape != <#inner_field_type as #facet_crate::Facet>::SHAPE {
1182
                                    return Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1183
                                        src_shape,
1184
                                        expected: const { &[ &<#inner_field_type as #facet_crate::Facet>::SHAPE ] },
1185
                                    });
1186
                                }
1187
                                // Read the inner value and construct the wrapper.
1188
                                let inner: #inner_field_type = unsafe { src_ptr.read() };
1189
                                Ok(unsafe { dst.put(inner) }) // Construct wrapper
1190
                            }
1191
                        }
1192
                    }
1193

1194
                    // Define the try_into_inner function for the value vtable
1195
                    unsafe fn try_into_inner<'src, 'dst>(
1196
                        src_ptr: #facet_crate::PtrMut<'src>,
1197
                        dst: #facet_crate::PtrUninit<'dst>
1198
                    ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryIntoInnerError> {
1199
                        let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
1200
                        Ok(unsafe { dst.put(wrapper.0.clone()) }) // Assume tuple struct field 0
1201
                    }
1202

1203
                    // Define the try_borrow_inner function for the value vtable
1204
                    unsafe fn try_borrow_inner<'src>(
1205
                        src_ptr: #facet_crate::PtrConst<'src>
1206
                    ) -> Result<#facet_crate::PtrConst<'src>, #facet_crate::TryBorrowInnerError> {
1207
                        let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
1208
                        // Return a pointer to the inner field (field 0 for tuple struct)
1209
                        Ok(#facet_crate::PtrConst::new(::core::ptr::NonNull::from(&wrapper.0)))
1210
                    }
1211

1212
                    {
1213
                        vtable.try_from = Some(try_from);
1214
                        vtable.try_into_inner = Some(try_into_inner);
1215
                        vtable.try_borrow_inner = Some(try_borrow_inner);
1216
                    }
1217
                }
1218
            } else {
1219
                quote! {} // No try_from can be done for opaque
2✔
1220
            }
1221
        } else {
1222
            // Transparent ZST struct (like struct Unit;)
1223
            quote! {
×
1224
                // Define the try_from function for the value vtable (ZST case)
1225
                unsafe fn try_from<'src, 'dst>(
1226
                    src_ptr: #facet_crate::PtrConst<'src>,
1227
                    src_shape: &'static #facet_crate::Shape,
1228
                    dst: #facet_crate::PtrUninit<'dst>
1229
                ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryFromError> {
1230
                    if src_shape.layout.size() == 0 {
1231
                         Ok(unsafe { dst.put(#struct_name_ident) }) // Construct ZST
1232
                    } else {
1233
                        Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1234
                            src_shape,
1235
                            expected: const { &[ <() as #facet_crate::Facet>::SHAPE ] }, // Expect unit-like shape
1236
                        })
1237
                    }
1238
                }
1239

1240
                {
1241
                    vtable.try_from = Some(try_from);
1242
                }
1243

1244
                // ZSTs cannot be meaningfully borrowed or converted *into* an inner value
1245
                // try_into_inner and try_borrow_inner remain None
1246
            }
1247
        }
1248
    } else {
1249
        quote! {} // Not transparent
1,760✔
1250
    };
1251

1252
    // Generate the inner shape field value for transparent types
1253
    // inner call - only emit for transparent types
1254
    let inner_call = if use_transparent_semantics {
1,804✔
1255
        let inner_shape_val = if let Some(inner_field) = &inner_field {
44✔
1256
            let ty = &inner_field.ty;
44✔
1257
            if inner_field.attrs.has_builtin("opaque") {
44✔
1258
                quote! { <#facet_crate::Opaque<#ty> as #facet_crate::Facet>::SHAPE }
2✔
1259
            } else {
1260
                quote! { <#ty as #facet_crate::Facet>::SHAPE }
42✔
1261
            }
1262
        } else {
1263
            // Transparent ZST case
1264
            quote! { <() as #facet_crate::Facet>::SHAPE }
×
1265
        };
1266
        quote! { .inner(#inner_shape_val) }
44✔
1267
    } else {
1268
        quote! {}
1,760✔
1269
    };
1270

1271
    // Generics from PStruct
1272
    let facet_bgp = ps
1,804✔
1273
        .container
1,804✔
1274
        .bgp
1,804✔
1275
        .with_lifetime(LifetimeName(format_ident!("ʄ")));
1,804✔
1276
    let bgp_def = facet_bgp.display_with_bounds();
1,804✔
1277
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
1,804✔
1278

1279
    let (ty_field, fields) = if opaque {
1,804✔
1280
        (
2✔
1281
            quote! {
2✔
1282
                #facet_crate::Type::User(#facet_crate::UserType::Opaque)
2✔
1283
            },
2✔
1284
            quote! {},
2✔
1285
        )
2✔
1286
    } else {
1287
        // Optimize: use &[] for empty fields to avoid const block overhead
1288
        if fields_vec.is_empty() {
1,802✔
1289
            (
19✔
1290
                quote! {
19✔
1291
                    𝟋Ty::User(𝟋UTy::Struct(
19✔
1292
                        𝟋STyB::new(#kind, &[]).repr(#repr).build()
19✔
1293
                    ))
19✔
1294
                },
19✔
1295
                quote! {},
19✔
1296
            )
19✔
1297
        } else {
1298
            // Inline the const block directly into the builder call
1299
            (
1300
                quote! {
1,783✔
1301
                    𝟋Ty::User(𝟋UTy::Struct(
1302
                        𝟋STyB::new(#kind, &const {[#(#fields_vec),*]}).repr(#repr).build()
1303
                    ))
1304
                },
1305
                quote! {},
1,783✔
1306
            )
1307
        }
1308
    };
1309

1310
    // Generate code to suppress dead_code warnings on structs constructed via reflection.
1311
    // When structs are constructed via reflection (e.g., facet_args::from_std_args()),
1312
    // the compiler doesn't see them being used and warns about dead code.
1313
    // This function ensures the struct type is "used" from the compiler's perspective.
1314
    // See: https://github.com/facet-rs/facet/issues/996
1315
    let dead_code_suppression = quote! {
1,804✔
1316
        const _: () = {
1317
            #[allow(dead_code, clippy::multiple_bound_locations)]
1318
            fn __facet_use_struct #bgp_def (__v: &#struct_name_ident #bgp_without_bounds) #where_clauses {
1319
                let _ = __v;
1320
            }
1321
        };
1322
    };
1323

1324
    // Generate static assertions for declared traits (catches lies at compile time)
1325
    // We put this in a generic function outside the const block so it can reference generic parameters
1326
    let facet_default = ps.container.attrs.has_builtin("default");
1,804✔
1327
    let trait_assertion_fn = if let Some(bounds) =
1,804✔
1328
        gen_trait_bounds(ps.container.attrs.declared_traits.as_ref(), facet_default)
1,804✔
1329
    {
1330
        // Note: where_clauses already includes "where" keyword if non-empty
1331
        // We need to add the trait bounds as an additional constraint
1332
        quote! {
5✔
1333
            const _: () = {
1334
                #[allow(dead_code, clippy::multiple_bound_locations)]
1335
                fn __facet_assert_traits #bgp_def (_: &#struct_name_ident #bgp_without_bounds)
1336
                where
1337
                    #struct_name_ident #bgp_without_bounds: #bounds
1338
                {}
1339
            };
1340
        }
1341
    } else {
1342
        quote! {}
1,799✔
1343
    };
1344

1345
    // Check if we need vtable mutations (invariants or transparent type functions)
1346
    let has_invariants = ps
1,804✔
1347
        .container
1,804✔
1348
        .attrs
1,804✔
1349
        .facet
1,804✔
1350
        .iter()
1,804✔
1351
        .any(|attr| attr.is_builtin() && attr.key_str() == "invariants");
1,804✔
1352
    let is_transparent = use_transparent_semantics;
1,804✔
1353
    let needs_vtable_mutations = has_invariants || is_transparent;
1,804✔
1354

1355
    // Generate vtable field - use simpler form when no mutations needed
1356
    let vtable_field = if needs_vtable_mutations {
1,804✔
1357
        quote! {
47✔
1358
            {
1359
                let mut vtable = #vtable_init;
1360
                #invariant_maybe
1361
                #try_from_inner_code
1362
                vtable
1363
            }
1364
        }
1365
    } else {
1366
        quote! { #vtable_init }
1,757✔
1367
    };
1368

1369
    // Final quote block using refactored parts
1370
    let result = quote! {
1,804✔
1371
        #static_decl
1372

1373
        #dead_code_suppression
1374

1375
        #trait_assertion_fn
1376

1377
        // Proxy inherent impl (outside the Facet impl so generic params are in scope)
1378
        #proxy_inherent_impl
1379

1380
        #[automatically_derived]
1381
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #struct_name_ident #bgp_without_bounds #where_clauses {
1382
            const SHAPE: &'static #facet_crate::Shape = &const {
1383
                use #facet_crate::𝟋::*;
1384
                #fields
1385

1386
                𝟋ShpB::for_sized::<Self>(#type_name_fn, #struct_name_str)
1387
                    .vtable(#vtable_field)
1388
                    .ty(#ty_field)
1389
                    .def(𝟋Def::Undefined)
1390
                    #type_params_call
1391
                    #doc_call
1392
                    #attributes_call
1393
                    #type_tag_call
1394
                    #proxy_call
1395
                    #inner_call
1396
                    .build()
1397
            };
1398
        }
1399
    };
1400

1401
    result
1,804✔
1402
}
1,804✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc