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

facet-rs / facet / 19995843284

06 Dec 2025 11:34PM UTC coverage: 58.532% (-0.05%) from 58.581%
19995843284

push

github

fasterthanlime
feat: optimize field attribute lookup from O(n) to O(1)

Add dedicated storage for frequently-accessed field attributes to avoid
linear scanning through the attributes slice.

Changes:
- Add custom `bitflags!` macro in facet-core (no external dependency)
- Add `FieldFlags` with bits for: SENSITIVE, FLATTEN, SKIP, SKIP_SERIALIZING,
  SKIP_DESERIALIZING, CHILD, RECURSIVE_TYPE, HAS_DEFAULT
- Expand `Field` struct with `flags`, `rename`, and `alias` fields
- Update `FieldBuilder` with `.flags()`, `.rename()`, `.alias()` methods
- Update accessor methods to use O(1) flag checks
- Add `#[storage(flag)]` and `#[storage(field)]` annotations to grammar DSL
- Update derive macro to route flag/field attrs to dedicated storage

Closes #1130

111 of 198 new or added lines in 4 files covered. (56.06%)

6 existing lines in 2 files now uncovered.

24550 of 41943 relevant lines covered (58.53%)

542.56 hits per line

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

90.0
/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
use crate::parsed::{DeclaredTraits, KnownDerives, PAttrs};
69

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567
    let bgp_without_bounds = bgp.display_without_bounds();
3,680✔
568

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

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

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

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

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

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

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

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

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

720
    let maybe_attributes = if attribute_list.is_empty() {
3,680✔
721
        quote! { &[] }
2,682✔
722
    } else {
723
        quote! { &const {[#(#attribute_list),*]} }
998✔
724
    };
725

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

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

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

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

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

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

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

793
    // Add attributes if any
794
    if !attribute_list.is_empty() {
3,680✔
795
        builder_chain = quote! { #builder_chain.attributes(#maybe_attributes) };
998✔
796
    }
2,682✔
797

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

967
    // Invariants from PStruct - extract invariant function expressions
968
    let invariant_maybe = {
1,781✔
969
        let invariant_exprs: Vec<&TokenStream> = ps
1,781✔
970
            .container
1,781✔
971
            .attrs
1,781✔
972
            .facet
1,781✔
973
            .iter()
1,781✔
974
            .filter(|attr| attr.is_builtin() && attr.key_str() == "invariants")
1,781✔
975
            .map(|attr| &attr.args)
1,781✔
976
            .collect();
1,781✔
977

978
        if !invariant_exprs.is_empty() {
1,781✔
979
            let tests = invariant_exprs.iter().map(|expr| {
3✔
980
                quote! {
3✔
981
                    if !#expr(value) {
982
                        return false;
983
                    }
984
                }
985
            });
3✔
986

987
            let bgp_display = ps.container.bgp.display_without_bounds();
3✔
988
            quote! {
3✔
989
                unsafe fn invariants<'mem>(value: #facet_crate::PtrConst<'mem>) -> bool {
990
                    let value = value.get::<#struct_name_ident #bgp_display>();
991
                    #(#tests)*
992
                    true
993
                }
994

995
                {
996
                    vtable.invariants = Some(invariants);
997
                }
998
            }
999
        } else {
1000
            quote! {}
1,778✔
1001
        }
1002
    };
1003

1004
    // Transparent logic using PStruct
1005
    let inner_field = if ps.container.attrs.has_builtin("transparent") {
1,781✔
1006
        match &ps.kind {
36✔
1007
            PStructKind::TupleStruct { fields } => {
36✔
1008
                if fields.len() > 1 {
36✔
1009
                    return quote! {
×
1010
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
1011
                    };
1012
                }
36✔
1013
                fields.first().cloned() // Use first field if it exists, None otherwise (ZST case)
36✔
1014
            }
1015
            _ => {
1016
                return quote! {
×
1017
                    compile_error!("Transparent structs must be tuple structs");
1018
                };
1019
            }
1020
        }
1021
    } else {
1022
        None
1,745✔
1023
    };
1024

1025
    // Add try_from_inner implementation for transparent types
1026
    let try_from_inner_code = if ps.container.attrs.has_builtin("transparent") {
1,781✔
1027
        if let Some(inner_field) = &inner_field {
36✔
1028
            if !inner_field.attrs.has_builtin("opaque") {
36✔
1029
                // Transparent struct with one field
1030
                let inner_field_type = &inner_field.ty;
34✔
1031
                let bgp_without_bounds = ps.container.bgp.display_without_bounds();
34✔
1032

1033
                quote! {
34✔
1034
                    // Define the try_from function for the value vtable
1035
                    unsafe fn try_from<'src, 'dst>(
1036
                        src_ptr: #facet_crate::PtrConst<'src>,
1037
                        src_shape: &'static #facet_crate::Shape,
1038
                        dst: #facet_crate::PtrUninit<'dst>
1039
                    ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryFromError> {
1040
                        // Try the inner type's try_from function if it exists
1041
                        let inner_result = match <#inner_field_type as #facet_crate::Facet>::SHAPE.vtable.try_from {
1042
                            Some(inner_try) => unsafe { (inner_try)(src_ptr, src_shape, dst) },
1043
                            None => Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1044
                                src_shape,
1045
                                expected: const { &[ &<#inner_field_type as #facet_crate::Facet>::SHAPE ] },
1046
                            })
1047
                        };
1048

1049
                        match inner_result {
1050
                            Ok(result) => Ok(result),
1051
                            Err(_) => {
1052
                                // If inner_try failed, check if source shape is exactly the inner shape
1053
                                if src_shape != <#inner_field_type as #facet_crate::Facet>::SHAPE {
1054
                                    return Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1055
                                        src_shape,
1056
                                        expected: const { &[ &<#inner_field_type as #facet_crate::Facet>::SHAPE ] },
1057
                                    });
1058
                                }
1059
                                // Read the inner value and construct the wrapper.
1060
                                let inner: #inner_field_type = unsafe { src_ptr.read() };
1061
                                Ok(unsafe { dst.put(inner) }) // Construct wrapper
1062
                            }
1063
                        }
1064
                    }
1065

1066
                    // Define the try_into_inner function for the value vtable
1067
                    unsafe fn try_into_inner<'src, 'dst>(
1068
                        src_ptr: #facet_crate::PtrMut<'src>,
1069
                        dst: #facet_crate::PtrUninit<'dst>
1070
                    ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryIntoInnerError> {
1071
                        let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
1072
                        Ok(unsafe { dst.put(wrapper.0.clone()) }) // Assume tuple struct field 0
1073
                    }
1074

1075
                    // Define the try_borrow_inner function for the value vtable
1076
                    unsafe fn try_borrow_inner<'src>(
1077
                        src_ptr: #facet_crate::PtrConst<'src>
1078
                    ) -> Result<#facet_crate::PtrConst<'src>, #facet_crate::TryBorrowInnerError> {
1079
                        let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
1080
                        // Return a pointer to the inner field (field 0 for tuple struct)
1081
                        Ok(#facet_crate::PtrConst::new(::core::ptr::NonNull::from(&wrapper.0)))
1082
                    }
1083

1084
                    {
1085
                        vtable.try_from = Some(try_from);
1086
                        vtable.try_into_inner = Some(try_into_inner);
1087
                        vtable.try_borrow_inner = Some(try_borrow_inner);
1088
                    }
1089
                }
1090
            } else {
1091
                quote! {} // No try_from can be done for opaque
2✔
1092
            }
1093
        } else {
1094
            // Transparent ZST struct (like struct Unit;)
1095
            quote! {
×
1096
                // Define the try_from function for the value vtable (ZST case)
1097
                unsafe fn try_from<'src, 'dst>(
1098
                    src_ptr: #facet_crate::PtrConst<'src>,
1099
                    src_shape: &'static #facet_crate::Shape,
1100
                    dst: #facet_crate::PtrUninit<'dst>
1101
                ) -> Result<#facet_crate::PtrMut<'dst>, #facet_crate::TryFromError> {
1102
                    if src_shape.layout.size() == 0 {
1103
                         Ok(unsafe { dst.put(#struct_name_ident) }) // Construct ZST
1104
                    } else {
1105
                        Err(#facet_crate::TryFromError::UnsupportedSourceShape {
1106
                            src_shape,
1107
                            expected: const { &[ <() as #facet_crate::Facet>::SHAPE ] }, // Expect unit-like shape
1108
                        })
1109
                    }
1110
                }
1111

1112
                {
1113
                    vtable.try_from = Some(try_from);
1114
                }
1115

1116
                // ZSTs cannot be meaningfully borrowed or converted *into* an inner value
1117
                // try_into_inner and try_borrow_inner remain None
1118
            }
1119
        }
1120
    } else {
1121
        quote! {} // Not transparent
1,745✔
1122
    };
1123

1124
    // Generate the inner shape field value for transparent types
1125
    // inner call - only emit for transparent types
1126
    let inner_call = if ps.container.attrs.has_builtin("transparent") {
1,781✔
1127
        let inner_shape_val = if let Some(inner_field) = &inner_field {
36✔
1128
            let ty = &inner_field.ty;
36✔
1129
            if inner_field.attrs.has_builtin("opaque") {
36✔
1130
                quote! { <#facet_crate::Opaque<#ty> as #facet_crate::Facet>::SHAPE }
2✔
1131
            } else {
1132
                quote! { <#ty as #facet_crate::Facet>::SHAPE }
34✔
1133
            }
1134
        } else {
1135
            // Transparent ZST case
1136
            quote! { <() as #facet_crate::Facet>::SHAPE }
×
1137
        };
1138
        quote! { .inner(#inner_shape_val) }
36✔
1139
    } else {
1140
        quote! {}
1,745✔
1141
    };
1142

1143
    // Generics from PStruct
1144
    let facet_bgp = ps
1,781✔
1145
        .container
1,781✔
1146
        .bgp
1,781✔
1147
        .with_lifetime(LifetimeName(format_ident!("ʄ")));
1,781✔
1148
    let bgp_def = facet_bgp.display_with_bounds();
1,781✔
1149
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
1,781✔
1150

1151
    let (ty_field, fields) = if opaque {
1,781✔
1152
        (
2✔
1153
            quote! {
2✔
1154
                #facet_crate::Type::User(#facet_crate::UserType::Opaque)
2✔
1155
            },
2✔
1156
            quote! {},
2✔
1157
        )
2✔
1158
    } else {
1159
        // Optimize: use &[] for empty fields to avoid const block overhead
1160
        if fields_vec.is_empty() {
1,779✔
1161
            (
19✔
1162
                quote! {
19✔
1163
                    𝟋Ty::User(𝟋UTy::Struct(
19✔
1164
                        𝟋STyB::new(#kind, &[]).repr(#repr).build()
19✔
1165
                    ))
19✔
1166
                },
19✔
1167
                quote! {},
19✔
1168
            )
19✔
1169
        } else {
1170
            // Inline the const block directly into the builder call
1171
            (
1172
                quote! {
1,760✔
1173
                    𝟋Ty::User(𝟋UTy::Struct(
1174
                        𝟋STyB::new(#kind, &const {[#(#fields_vec),*]}).repr(#repr).build()
1175
                    ))
1176
                },
1177
                quote! {},
1,760✔
1178
            )
1179
        }
1180
    };
1181

1182
    // Generate code to suppress dead_code warnings on structs constructed via reflection.
1183
    // When structs are constructed via reflection (e.g., facet_args::from_std_args()),
1184
    // the compiler doesn't see them being used and warns about dead code.
1185
    // This function ensures the struct type is "used" from the compiler's perspective.
1186
    // See: https://github.com/facet-rs/facet/issues/996
1187
    let dead_code_suppression = quote! {
1,781✔
1188
        const _: () = {
1189
            #[allow(dead_code, clippy::multiple_bound_locations)]
1190
            fn __facet_use_struct #bgp_def (__v: &#struct_name_ident #bgp_without_bounds) #where_clauses {
1191
                let _ = __v;
1192
            }
1193
        };
1194
    };
1195

1196
    // Generate static assertions for declared traits (catches lies at compile time)
1197
    // We put this in a generic function outside the const block so it can reference generic parameters
1198
    let facet_default = ps.container.attrs.has_builtin("default");
1,781✔
1199
    let trait_assertion_fn = if let Some(bounds) =
1,781✔
1200
        gen_trait_bounds(ps.container.attrs.declared_traits.as_ref(), facet_default)
1,781✔
1201
    {
1202
        // Note: where_clauses already includes "where" keyword if non-empty
1203
        // We need to add the trait bounds as an additional constraint
1204
        quote! {
5✔
1205
            const _: () = {
1206
                #[allow(dead_code, clippy::multiple_bound_locations)]
1207
                fn __facet_assert_traits #bgp_def (_: &#struct_name_ident #bgp_without_bounds)
1208
                where
1209
                    #struct_name_ident #bgp_without_bounds: #bounds
1210
                {}
1211
            };
1212
        }
1213
    } else {
1214
        quote! {}
1,776✔
1215
    };
1216

1217
    // Check if we need vtable mutations (invariants or transparent type functions)
1218
    let has_invariants = ps
1,781✔
1219
        .container
1,781✔
1220
        .attrs
1,781✔
1221
        .facet
1,781✔
1222
        .iter()
1,781✔
1223
        .any(|attr| attr.is_builtin() && attr.key_str() == "invariants");
1,781✔
1224
    let is_transparent = ps.container.attrs.has_builtin("transparent");
1,781✔
1225
    let needs_vtable_mutations = has_invariants || is_transparent;
1,781✔
1226

1227
    // Generate vtable field - use simpler form when no mutations needed
1228
    let vtable_field = if needs_vtable_mutations {
1,781✔
1229
        quote! {
39✔
1230
            {
1231
                let mut vtable = #vtable_init;
1232
                #invariant_maybe
1233
                #try_from_inner_code
1234
                vtable
1235
            }
1236
        }
1237
    } else {
1238
        quote! { #vtable_init }
1,742✔
1239
    };
1240

1241
    // Final quote block using refactored parts
1242
    let result = quote! {
1,781✔
1243
        #static_decl
1244

1245
        #dead_code_suppression
1246

1247
        #trait_assertion_fn
1248

1249
        #[automatically_derived]
1250
        unsafe impl #bgp_def #facet_crate::Facet<'ʄ> for #struct_name_ident #bgp_without_bounds #where_clauses {
1251
            const SHAPE: &'static #facet_crate::Shape = &const {
1252
                use #facet_crate::𝟋::*;
1253
                #fields
1254

1255
                𝟋ShpB::for_sized::<Self>(#type_name_fn, #struct_name_str)
1256
                    .vtable(#vtable_field)
1257
                    .ty(#ty_field)
1258
                    .def(𝟋Def::Undefined)
1259
                    #type_params_call
1260
                    #doc_call
1261
                    #attributes_call
1262
                    #type_tag_call
1263
                    #inner_call
1264
                    .build()
1265
            };
1266
        }
1267
    };
1268

1269
    result
1,781✔
1270
}
1,781✔
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