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

facet-rs / facet / 16502215615

24 Jul 2025 04:15PM UTC coverage: 61.442% (+0.2%) from 61.273%
16502215615

Pull #857

github

web-flow
Merge bbbdd0e0e into 64edcf286
Pull Request #857: Bump to facet-0.28

11988 of 19511 relevant lines covered (61.44%)

125.99 hits per line

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

90.91
/facet-macros-emit/src/process_struct.rs
1
use quote::{format_ident, quote};
2

3
use super::*;
4

5
/// Generates the `::facet::Field` definition `TokenStream` from a `PStructField`.
6
pub(crate) fn gen_field_from_pfield(
1,329✔
7
    field: &PStructField,
1,329✔
8
    struct_name: &Ident,
1,329✔
9
    bgp: &BoundedGenericParams,
1,329✔
10
    base_offset: Option<TokenStream>,
1,329✔
11
) -> TokenStream {
1,329✔
12
    let field_name_effective = &field.name.effective;
1,329✔
13
    let field_name_raw = &field.name.raw;
1,329✔
14
    let field_type = &field.ty; // TokenStream of the type
1,329✔
15

16
    let tts: facet_macros_parse::TokenStream = field_type.clone();
1,329✔
17
    let field_type_static = tts
1,329✔
18
        .to_token_iter()
1,329✔
19
        .parse::<Vec<LifetimeOrTt>>()
1,329✔
20
        .unwrap()
1,329✔
21
        .into_iter()
1,329✔
22
        .map(|lott| match lott {
2,482✔
23
            LifetimeOrTt::TokenTree(tt) => quote! { #tt },
2,428✔
24
            LifetimeOrTt::Lifetime(_) => quote! { 'static },
54✔
25
        })
2,482✔
26
        .collect::<TokenStream>();
1,329✔
27

28
    let bgp_without_bounds = bgp.display_without_bounds();
1,329✔
29

30
    // Determine field flags and other attributes from field.attrs
31
    let mut flags = quote! {};
1,329✔
32
    let mut flags_empty = true;
1,329✔
33

34
    let mut vtable_items: Vec<TokenStream> = vec![];
1,329✔
35
    let mut attribute_list: Vec<TokenStream> = vec![];
1,329✔
36
    let doc_lines: Vec<String> = field
1,329✔
37
        .attrs
1,329✔
38
        .doc
1,329✔
39
        .iter()
1,329✔
40
        .map(|doc| doc.as_str().replace("\\\"", "\""))
1,329✔
41
        .collect();
1,329✔
42
    let mut shape_of = quote! { shape_of };
1,329✔
43
    let mut asserts: Vec<TokenStream> = vec![];
1,329✔
44

45
    // Process attributes other than rename rules, which are handled by PName
46
    for attr in &field.attrs.facet {
1,526✔
47
        match attr {
197✔
48
            PFacetAttr::Sensitive => {
49
                if flags_empty {
8✔
50
                    flags_empty = false;
7✔
51
                    flags = quote! { ::facet::FieldFlags::SENSITIVE };
7✔
52
                } else {
7✔
53
                    flags = quote! { #flags.union(::facet::FieldFlags::SENSITIVE) };
1✔
54
                }
1✔
55
            }
56
            PFacetAttr::Default => {
57
                if flags_empty {
22✔
58
                    flags_empty = false;
22✔
59
                    flags = quote! { ::facet::FieldFlags::DEFAULT };
22✔
60
                } else {
22✔
61
                    flags = quote! { #flags.union(::facet::FieldFlags::DEFAULT) };
×
62
                }
×
63
                asserts.push(quote! {
22✔
64
                    ::facet::static_assertions::assert_impl_all!(#field_type_static: ::core::default::Default);
65
                })
66
            }
67
            PFacetAttr::DefaultEquals { expr } => {
17✔
68
                if flags_empty {
17✔
69
                    flags_empty = false;
17✔
70
                    flags = quote! { ::facet::FieldFlags::DEFAULT };
17✔
71
                } else {
17✔
72
                    flags = quote! { #flags.union(::facet::FieldFlags::DEFAULT) };
×
73
                }
×
74

75
                vtable_items.push(quote! {
17✔
76
                    .default_fn(|ptr| {
77
                        unsafe { ptr.put::<#field_type>(#expr) }
78
                    })
79
                });
80
            }
81
            PFacetAttr::Child => {
82
                if flags_empty {
×
83
                    flags_empty = false;
×
84
                    flags = quote! { ::facet::FieldFlags::CHILD };
×
85
                } else {
×
86
                    flags = quote! { #flags.union(::facet::FieldFlags::CHILD) };
×
87
                }
×
88
            }
89
            PFacetAttr::Flatten => {
90
                if flags_empty {
3✔
91
                    flags_empty = false;
3✔
92
                    flags = quote! { ::facet::FieldFlags::FLATTEN };
3✔
93
                } else {
3✔
94
                    flags = quote! { #flags.union(::facet::FieldFlags::FLATTEN) };
×
95
                }
×
96
            }
97
            PFacetAttr::Opaque => {
4✔
98
                shape_of = quote! { shape_of_opaque };
4✔
99
            }
4✔
100
            PFacetAttr::Arbitrary { content } => {
138✔
101
                attribute_list.push(quote! { ::facet::FieldAttribute::Arbitrary(#content) });
138✔
102
            }
138✔
103
            PFacetAttr::SkipSerializing => {
104
                if flags_empty {
3✔
105
                    flags_empty = false;
3✔
106
                    flags = quote! { ::facet::FieldFlags::SKIP_SERIALIZING };
3✔
107
                } else {
3✔
108
                    flags = quote! { #flags.union(::facet::FieldFlags::SKIP_SERIALIZING) };
×
109
                }
×
110
            }
111
            PFacetAttr::SkipSerializingIf { expr } => {
2✔
112
                let predicate = expr;
2✔
113
                let field_ty = field_type;
2✔
114
                vtable_items.push(quote! {
2✔
115
                    .skip_serializing_if(unsafe { ::core::mem::transmute((#predicate) as fn(&#field_ty) -> bool) })
2✔
116
                });
2✔
117
            }
2✔
118
            // These are handled by PName or are container-level, so ignore them for field attributes.
119
            PFacetAttr::RenameAll { .. } => {} // Explicitly ignore rename attributes here
×
120
            PFacetAttr::Transparent
121
            | PFacetAttr::Invariants { .. }
122
            | PFacetAttr::DenyUnknownFields
123
            | PFacetAttr::TypeTag { .. } => {}
×
124
        }
125
    }
126

127
    let maybe_attributes = if attribute_list.is_empty() {
1,329✔
128
        quote! {}
1,237✔
129
    } else {
130
        quote! { .attributes(&const { [#(#attribute_list),*] }) }
92✔
131
    };
132

133
    let maybe_field_doc = if doc_lines.is_empty() {
1,329✔
134
        quote! {}
1,298✔
135
    } else {
136
        quote! { .doc(&[#(#doc_lines),*]) }
31✔
137
    };
138

139
    let maybe_vtable = if vtable_items.is_empty() {
1,329✔
140
        quote! {}
1,310✔
141
    } else {
142
        quote! {
19✔
143
            .vtable(&const {
144
                ::facet::FieldVTable::builder()
145
                    #(#vtable_items)*
146
                    .build()
147
            })
148
        }
149
    };
150

151
    let maybe_flags = if flags_empty {
1,329✔
152
        quote! {}
1,277✔
153
    } else {
154
        quote! { .flags(#flags) }
52✔
155
    };
156

157
    // Calculate the final offset, incorporating the base_offset if present
158
    let final_offset = match base_offset {
1,329✔
159
        Some(base) => {
91✔
160
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
91✔
161
        }
162
        None => {
163
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
1,238✔
164
        }
165
    };
166

167
    quote! {
1,329✔
168
        {
169
            #(#asserts)*;
170
            ::facet::Field::builder()
171
                // Use the effective name (after rename rules) for metadata
172
                .name(#field_name_effective)
173
                // Use the raw field name/index TokenStream for shape_of and offset_of
174
                .shape(::facet::#shape_of(&|s: &#struct_name #bgp_without_bounds| &s.#field_name_raw))
175
                .offset(#final_offset)
176
                #maybe_flags
177
                #maybe_attributes
178
                #maybe_field_doc
179
                #maybe_vtable
180
                .build()
181
        }
182
    }
183
}
1,329✔
184

185
/// Processes a regular struct to implement Facet
186
///
187
/// Example input:
188
/// ```rust
189
/// struct Blah {
190
///     foo: u32,
191
///     bar: String,
192
/// }
193
/// ```
194
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
648✔
195
    let ps = PStruct::parse(&parsed); // Use the parsed representation
648✔
196

197
    let struct_name_ident = format_ident!("{}", ps.container.name);
648✔
198
    let struct_name = &ps.container.name;
648✔
199
    let struct_name_str = struct_name.to_string();
648✔
200

201
    let type_name_fn = generate_type_name_fn(struct_name, parsed.generics.as_ref());
648✔
202

203
    // TODO: I assume the `PrimitiveRepr` is only relevant for enums, and does not need to be preserved?
204
    let repr = match &ps.container.attrs.repr {
648✔
205
        PRepr::Transparent => quote! { ::facet::Repr::transparent() },
8✔
206
        PRepr::Rust(_) => quote! { ::facet::Repr::default() },
638✔
207
        PRepr::C(_) => quote! { ::facet::Repr::c() },
2✔
208
    };
209

210
    // Use PStruct for kind and fields
211
    let (kind, fields_vec) = match &ps.kind {
648✔
212
        PStructKind::Struct { fields } => {
587✔
213
            let kind = quote!(::facet::StructKind::Struct);
587✔
214
            let fields_vec = fields
587✔
215
                .iter()
587✔
216
                .map(|field| gen_field_from_pfield(field, struct_name, &ps.container.bgp, None))
988✔
217
                .collect::<Vec<_>>();
587✔
218
            (kind, fields_vec)
587✔
219
        }
220
        PStructKind::TupleStruct { fields } => {
53✔
221
            let kind = quote!(::facet::StructKind::TupleStruct);
53✔
222
            let fields_vec = fields
53✔
223
                .iter()
53✔
224
                .map(|field| gen_field_from_pfield(field, struct_name, &ps.container.bgp, None))
71✔
225
                .collect::<Vec<_>>();
53✔
226
            (kind, fields_vec)
53✔
227
        }
228
        PStructKind::UnitStruct => {
229
            let kind = quote!(::facet::StructKind::Unit);
8✔
230
            (kind, vec![])
8✔
231
        }
232
    };
233

234
    // Still need original AST for where clauses and type params for build_ helpers
235
    let where_clauses_ast = match &parsed.kind {
648✔
236
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
587✔
237
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
53✔
238
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
8✔
239
    };
240
    let where_clauses = build_where_clauses(where_clauses_ast, parsed.generics.as_ref());
648✔
241
    let type_params = build_type_params(parsed.generics.as_ref());
648✔
242

243
    // Static decl using PStruct BGP
244
    let static_decl = if ps.container.bgp.params.is_empty() {
648✔
245
        generate_static_decl(struct_name)
608✔
246
    } else {
247
        TokenStream::new()
40✔
248
    };
249

250
    // Doc comments from PStruct
251
    let maybe_container_doc = if ps.container.attrs.doc.is_empty() {
648✔
252
        quote! {}
627✔
253
    } else {
254
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
21✔
255
        quote! { .doc(&[#(#doc_lines),*]) }
21✔
256
    };
257

258
    // Container attributes from PStruct
259
    let container_attributes_tokens = {
648✔
260
        let mut items = Vec::new();
648✔
261
        for attr in &ps.container.attrs.facet {
699✔
262
            match attr {
51✔
263
                PFacetAttr::DenyUnknownFields => {
6✔
264
                    items.push(quote! { ::facet::ShapeAttribute::DenyUnknownFields });
6✔
265
                }
6✔
266
                PFacetAttr::Default | PFacetAttr::DefaultEquals { .. } => {
3✔
267
                    // Corresponds to `#[facet(default)]` on container
3✔
268
                    items.push(quote! { ::facet::ShapeAttribute::Default });
3✔
269
                }
3✔
270
                PFacetAttr::Transparent => {
12✔
271
                    items.push(quote! { ::facet::ShapeAttribute::Transparent });
12✔
272
                }
12✔
273
                PFacetAttr::RenameAll { .. } => {}
14✔
274
                PFacetAttr::Arbitrary { content } => {
4✔
275
                    items.push(quote! { ::facet::ShapeAttribute::Arbitrary(#content) });
4✔
276
                }
4✔
277
                // Others not applicable at container level or handled elsewhere
278
                PFacetAttr::Sensitive
279
                | PFacetAttr::Opaque
280
                | PFacetAttr::Invariants { .. }
281
                | PFacetAttr::SkipSerializing
282
                | PFacetAttr::SkipSerializingIf { .. }
283
                | PFacetAttr::Flatten
284
                | PFacetAttr::Child
285
                | PFacetAttr::TypeTag { .. } => {}
12✔
286
            }
287
        }
288
        if items.is_empty() {
648✔
289
            quote! {}
625✔
290
        } else {
291
            quote! { .attributes(&[#(#items),*]) }
23✔
292
        }
293
    };
294

295
    // Type tag from PStruct
296
    let type_tag_maybe = {
648✔
297
        if let Some(type_tag) = ps.container.attrs.type_tag() {
648✔
298
            quote! { .type_tag(#type_tag) }
9✔
299
        } else {
300
            quote! {}
639✔
301
        }
302
    };
303

304
    // Invariants from PStruct
305
    let invariant_maybe = {
648✔
306
        let mut invariant_fns = Vec::new();
648✔
307
        for attr in &ps.container.attrs.facet {
699✔
308
            if let PFacetAttr::Invariants { expr } = attr {
51✔
309
                invariant_fns.push(expr);
3✔
310
            }
48✔
311
        }
312

313
        if !invariant_fns.is_empty() {
648✔
314
            let tests = invariant_fns.iter().map(|expr| {
3✔
315
                quote! {
3✔
316
                    if !#expr(value) {
317
                        return false;
318
                    }
319
                }
320
            });
3✔
321

322
            let bgp_display = ps.container.bgp.display_without_bounds(); // Use the BGP from PStruct
3✔
323
            quote! {
3✔
324
                unsafe fn invariants<'mem>(value: ::facet::PtrConst<'mem>) -> bool {
325
                    let value = value.get::<#struct_name_ident #bgp_display>();
326
                    #(#tests)*
327
                    true
328
                }
329

330
                {
331
                    let vtable_sized = vtable.sized_mut().unwrap();
332
                    vtable_sized.invariants = || Some(invariants);
333
                }
334
            }
335
        } else {
336
            quote! {}
645✔
337
        }
338
    };
339

340
    // Transparent logic using PStruct
341
    let inner_field = if ps.container.attrs.is_transparent() {
648✔
342
        match &ps.kind {
12✔
343
            PStructKind::TupleStruct { fields } => {
12✔
344
                if fields.len() > 1 {
12✔
345
                    return quote! {
×
346
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
347
                    };
348
                }
12✔
349
                fields.first().cloned() // Use first field if it exists, None otherwise (ZST case)
12✔
350
            }
351
            _ => {
352
                return quote! {
×
353
                    compile_error!("Transparent structs must be tuple structs");
354
                };
355
            }
356
        }
357
    } else {
358
        None
636✔
359
    };
360

361
    // Add try_from_inner implementation for transparent types
362
    let try_from_inner_code = if ps.container.attrs.is_transparent() {
648✔
363
        if let Some(inner_field) = &inner_field {
12✔
364
            // Transparent struct with one field
365
            let inner_field_type = &inner_field.ty;
12✔
366
            let bgp_without_bounds = ps.container.bgp.display_without_bounds();
12✔
367

368
            quote! {
12✔
369
                // Define the try_from function for the value vtable
370
                unsafe fn try_from<'src, 'dst>(
371
                    src_ptr: ::facet::PtrConst<'src>,
372
                    src_shape: &'static ::facet::Shape,
373
                    dst: ::facet::PtrUninit<'dst>
374
                ) -> Result<::facet::PtrMut<'dst>, ::facet::TryFromError> {
375
                    // Try the inner type's try_from function if it exists
376
                    let inner_result = match (<#inner_field_type as ::facet::Facet>::SHAPE.vtable.sized().and_then(|v| (v.try_from)())) {
377
                        Some(inner_try) => unsafe { (inner_try)(src_ptr, src_shape, dst) },
378
                        None => Err(::facet::TryFromError::UnsupportedSourceShape {
379
                            src_shape,
380
                            expected: const { &[ &<#inner_field_type as ::facet::Facet>::SHAPE ] },
381
                        })
382
                    };
383

384
                    match inner_result {
385
                        Ok(result) => Ok(result),
386
                        Err(_) => {
387
                            // If inner_try failed, check if source shape is exactly the inner shape
388
                            if src_shape != <#inner_field_type as ::facet::Facet>::SHAPE {
389
                                return Err(::facet::TryFromError::UnsupportedSourceShape {
390
                                    src_shape,
391
                                    expected: const { &[ &<#inner_field_type as ::facet::Facet>::SHAPE ] },
392
                                });
393
                            }
394
                            // Read the inner value and construct the wrapper.
395
                            let inner: #inner_field_type = unsafe { src_ptr.read() };
396
                            Ok(unsafe { dst.put(inner) }) // Construct wrapper
397
                        }
398
                    }
399
                }
400

401
                // Define the try_into_inner function for the value vtable
402
                unsafe fn try_into_inner<'src, 'dst>(
403
                    src_ptr: ::facet::PtrMut<'src>,
404
                    dst: ::facet::PtrUninit<'dst>
405
                ) -> Result<::facet::PtrMut<'dst>, ::facet::TryIntoInnerError> {
406
                    let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
407
                    Ok(unsafe { dst.put(wrapper.0.clone()) }) // Assume tuple struct field 0
408
                }
409

410
                // Define the try_borrow_inner function for the value vtable
411
                unsafe fn try_borrow_inner<'src>(
412
                    src_ptr: ::facet::PtrConst<'src>
413
                ) -> Result<::facet::PtrConst<'src>, ::facet::TryBorrowInnerError> {
414
                    let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
415
                    // Return a pointer to the inner field (field 0 for tuple struct)
416
                    Ok(::facet::PtrConst::new(&wrapper.0 as *const _ as *const u8))
417
                }
418

419
                {
420
                    let vtable_sized = vtable.sized_mut().unwrap();
421
                    vtable_sized.try_from = || Some(try_from);
422
                    vtable_sized.try_into_inner = || Some(try_into_inner);
423
                    vtable_sized.try_borrow_inner = || Some(try_borrow_inner);
424
                }
425
            }
426
        } else {
427
            // Transparent ZST struct (like struct Unit;)
428
            quote! {
×
429
                // Define the try_from function for the value vtable (ZST case)
430
                unsafe fn try_from<'src, 'dst>(
431
                    src_ptr: ::facet::PtrConst<'src>,
432
                    src_shape: &'static ::facet::Shape,
433
                    dst: ::facet::PtrUninit<'dst>
434
                ) -> Result<::facet::PtrMut<'dst>, ::facet::TryFromError> {
435
                    if src_shape.layout.size() == 0 {
436
                         Ok(unsafe { dst.put(#struct_name_ident) }) // Construct ZST
437
                    } else {
438
                        Err(::facet::TryFromError::UnsupportedSourceShape {
439
                            src_shape,
440
                            expected: const { &[ <() as ::facet::Facet>::SHAPE ] }, // Expect unit-like shape
441
                        })
442
                    }
443
                }
444

445
                {
446
                    let vtable_sized = vtable.sized_mut().unwrap();
447
                    vtable_sized.try_from = || Some(try_from);
448
                }
449

450
                // ZSTs cannot be meaningfully borrowed or converted *into* an inner value
451
                // try_into_inner and try_borrow_inner remain None
452
            }
453
        }
454
    } else {
455
        quote! {} // Not transparent
636✔
456
    };
457

458
    // Generate the inner shape function for transparent types
459
    let inner_shape_fn = if ps.container.attrs.is_transparent() {
648✔
460
        if let Some(inner_field) = &inner_field {
12✔
461
            let ty = &inner_field.ty;
12✔
462
            quote! {
12✔
463
                // Function to return inner type's shape
464
                fn inner_shape() -> &'static ::facet::Shape {
465
                    <#ty as ::facet::Facet>::SHAPE
466
                }
467
            }
468
        } else {
469
            // Transparent ZST case
470
            quote! {
×
471
                fn inner_shape() -> &'static ::facet::Shape {
472
                    <() as ::facet::Facet>::SHAPE // Inner shape is unit
473
                }
474
            }
475
        }
476
    } else {
477
        quote! {}
636✔
478
    };
479

480
    let inner_setter = if ps.container.attrs.is_transparent() {
648✔
481
        quote! { .inner(inner_shape) }
12✔
482
    } else {
483
        quote! {}
636✔
484
    };
485

486
    // Generics from PStruct
487
    let facet_bgp = ps
648✔
488
        .container
648✔
489
        .bgp
648✔
490
        .with_lifetime(LifetimeName(format_ident!("__facet")));
648✔
491
    let bgp_def = facet_bgp.display_with_bounds();
648✔
492
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
648✔
493

494
    // Final quote block using refactored parts
495
    let result = quote! {
648✔
496
        #static_decl
497

498
        #[automatically_derived]
499
        unsafe impl #bgp_def ::facet::Facet<'__facet> for #struct_name_ident #bgp_without_bounds #where_clauses {
500
            const VTABLE: &'static ::facet::ValueVTable = &const {
501
                let mut vtable = ::facet::value_vtable!(Self, #type_name_fn);
502
                #invariant_maybe
503
                #try_from_inner_code // Use the generated code for transparent types
504
                vtable
505
            };
506

507
            const SHAPE: &'static ::facet::Shape = &const {
508
                let fields: &'static [::facet::Field] = &const {[#(#fields_vec),*]};
509

510
                #inner_shape_fn // Include inner_shape function if needed
511

512
                ::facet::Shape::builder_for_sized::<Self>()
513
                    .type_identifier(#struct_name_str)
514
                    #type_params // Still from parsed.generics
515
                    .ty(::facet::Type::User(::facet::UserType::Struct(::facet::StructType::builder()
516
                        .repr(#repr)
517
                        .kind(#kind)
518
                        .fields(fields)
519
                        .build()
520
                    )))
521
                    #inner_setter // Use transparency flag from PStruct
522
                    #maybe_container_doc // From ps.container.attrs.doc
523
                    #container_attributes_tokens // From ps.container.attrs.facet
524
                    #type_tag_maybe
525
                    .build()
526
            };
527
        }
528
    };
529

530
    result
648✔
531
}
648✔
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