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

facet-rs / facet / 14794221806

02 May 2025 11:20AM UTC coverage: 57.142% (-0.2%) from 57.359%
14794221806

Pull #491

github

web-flow
Merge b826912c2 into f99eb208a
Pull Request #491: JSON facet-serialize?

360 of 437 new or added lines in 13 files covered. (82.38%)

10 existing lines in 3 files now uncovered.

7669 of 13421 relevant lines covered (57.14%)

89.59 hits per line

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

92.56
/facet-derive-emit/src/process_struct.rs
1
use super::*;
2
use quote::{format_ident, quote};
3

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

685✔
15
    let bgp_without_bounds = bgp.display_without_bounds();
685✔
16

685✔
17
    // Determine field flags and other attributes from field.attrs
685✔
18
    let mut flags = quote! {};
685✔
19
    let mut flags_empty = true;
685✔
20

685✔
21
    let mut vtable_items: Vec<TokenStream> = vec![];
685✔
22
    let mut attribute_list: Vec<TokenStream> = vec![];
685✔
23
    let doc_lines: Vec<TokenStream> = field.attrs.doc.iter().map(|doc| quote!(#doc)).collect();
685✔
24
    let mut shape_of = quote! { shape_of };
685✔
25

26
    // Process attributes other than rename rules, which are handled by PName
27
    for attr in &field.attrs.facet {
742✔
28
        match attr {
57✔
29
            PFacetAttr::Sensitive => {
30
                if flags_empty {
8✔
31
                    flags_empty = false;
7✔
32
                    flags = quote! { ::facet::FieldFlags::SENSITIVE };
7✔
33
                } else {
7✔
34
                    flags = quote! { #flags.union(::facet::FieldFlags::SENSITIVE) };
1✔
35
                }
1✔
36
            }
37
            PFacetAttr::Default => {
38
                if flags_empty {
6✔
39
                    flags_empty = false;
6✔
40
                    flags = quote! { ::facet::FieldFlags::DEFAULT };
6✔
41
                } else {
6✔
NEW
42
                    flags = quote! { #flags.union(::facet::FieldFlags::DEFAULT) };
×
NEW
43
                }
×
44
            }
45
            PFacetAttr::DefaultEquals { expr } => {
4✔
46
                if flags_empty {
4✔
47
                    flags_empty = false;
4✔
48
                    flags = quote! { ::facet::FieldFlags::DEFAULT };
4✔
49
                } else {
4✔
NEW
50
                    flags = quote! { #flags.union(::facet::FieldFlags::DEFAULT) };
×
NEW
51
                }
×
52

53
                vtable_items.push(quote! {
4✔
54
                    .default_fn(|ptr| {
55
                        unsafe { ptr.put::<#field_type>(#expr) }
56
                    })
57
                });
58
            }
59
            PFacetAttr::Child => {
60
                if flags_empty {
1✔
61
                    flags_empty = false;
1✔
62
                    flags = quote! { ::facet::FieldFlags::CHILD };
1✔
63
                } else {
1✔
NEW
64
                    flags = quote! { #flags.union(::facet::FieldFlags::CHILD) };
×
NEW
65
                }
×
66
            }
67
            PFacetAttr::Flatten => {
68
                if flags_empty {
3✔
69
                    flags_empty = false;
3✔
70
                    flags = quote! { ::facet::FieldFlags::FLATTEN };
3✔
71
                } else {
3✔
NEW
72
                    flags = quote! { #flags.union(::facet::FieldFlags::FLATTEN) };
×
NEW
73
                }
×
74
            }
75
            PFacetAttr::Opaque => {
3✔
76
                shape_of = quote! { shape_of_opaque };
3✔
77
            }
3✔
78
            PFacetAttr::Arbitrary { content } => {
27✔
79
                attribute_list.push(quote! { ::facet::FieldAttribute::Arbitrary(#content) });
27✔
80
            }
27✔
81
            PFacetAttr::SkipSerializing => {
82
                if flags_empty {
3✔
83
                    flags_empty = false;
3✔
84
                    flags = quote! { ::facet::FieldFlags::SKIP_SERIALIZING };
3✔
85
                } else {
3✔
86
                    flags = quote! { #flags.union(::facet::FieldFlags::SKIP_SERIALIZING) };
×
87
                }
×
88
            }
89
            PFacetAttr::SkipSerializingIf { expr } => {
2✔
90
                let predicate = expr;
2✔
91
                let field_ty = field_type;
2✔
92
                vtable_items.push(quote! {
2✔
93
                    .skip_serializing_if(unsafe { ::std::mem::transmute((#predicate) as fn(&#field_ty) -> bool) })
2✔
94
                });
2✔
95
            }
2✔
96
            // These are handled by PName or are container-level, so ignore them for field attributes.
97
            PFacetAttr::RenameAll { .. } => {} // Explicitly ignore rename attributes here
×
98
            PFacetAttr::Transparent
99
            | PFacetAttr::Invariants { .. }
100
            | PFacetAttr::DenyUnknownFields => {}
×
101
        }
102
    }
103

104
    let maybe_attributes = if attribute_list.is_empty() {
685✔
105
        quote! {}
668✔
106
    } else {
107
        quote! { .attributes(&const { [#(#attribute_list),*] }) }
17✔
108
    };
109

110
    let maybe_field_doc = if doc_lines.is_empty() {
685✔
111
        quote! {}
655✔
112
    } else {
113
        quote! { .doc(&[#(#doc_lines),*]) }
30✔
114
    };
115

116
    let maybe_vtable = if vtable_items.is_empty() {
685✔
117
        quote! {}
679✔
118
    } else {
119
        quote! {
6✔
120
            .vtable(&const {
121
                ::facet::FieldVTable::builder()
122
                    #(#vtable_items)*
123
                    .build()
124
            })
125
        }
126
    };
127

128
    let maybe_flags = if flags_empty {
685✔
129
        quote! {}
661✔
130
    } else {
131
        quote! { .flags(#flags) }
24✔
132
    };
133

134
    // Calculate the final offset, incorporating the base_offset if present
135
    let final_offset = match base_offset {
685✔
136
        Some(base) => {
59✔
137
            quote! { #base + ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
59✔
138
        }
139
        None => {
140
            quote! { ::core::mem::offset_of!(#struct_name #bgp_without_bounds, #field_name_raw) }
626✔
141
        }
142
    };
143

144
    quote! {
685✔
145
        ::facet::Field::builder()
685✔
146
            // Use the effective name (after rename rules) for metadata
685✔
147
            .name(#field_name_effective)
685✔
148
            // Use the raw field name/index TokenStream for shape_of and offset_of
685✔
149
            .shape(|| ::facet::#shape_of(&|s: &#struct_name #bgp_without_bounds| &s.#field_name_raw))
685✔
150
            .offset(#final_offset)
685✔
151
            #maybe_flags
685✔
152
            #maybe_attributes
685✔
153
            #maybe_field_doc
685✔
154
            #maybe_vtable
685✔
155
            .build()
685✔
156
    }
685✔
157
}
685✔
158

159
/// Processes a regular struct to implement Facet
160
///
161
/// Example input:
162
/// ```rust
163
/// struct Blah {
164
///     foo: u32,
165
///     bar: String,
166
/// }
167
/// ```
168
pub(crate) fn process_struct(parsed: Struct) -> TokenStream {
284✔
169
    let ps = PStruct::parse(&parsed); // Use the parsed representation
284✔
170

284✔
171
    let struct_name_ident = format_ident!("{}", ps.container.name);
284✔
172
    let struct_name = &ps.container.name;
284✔
173
    let struct_name_str = struct_name.to_string();
284✔
174

175
    // Use PStruct for kind and fields
176
    let (kind, fields_vec) = match &ps.kind {
284✔
177
        PStructKind::Struct { fields } => {
242✔
178
            let kind = quote!(::facet::StructKind::Struct);
242✔
179
            let fields_vec = fields
242✔
180
                .iter()
242✔
181
                .map(|field| gen_field_from_pfield(field, struct_name, &ps.container.bgp, None))
444✔
182
                .collect::<Vec<_>>();
242✔
183
            (kind, fields_vec)
242✔
184
        }
185
        PStructKind::TupleStruct { fields } => {
37✔
186
            let kind = quote!(::facet::StructKind::TupleStruct);
37✔
187
            let fields_vec = fields
37✔
188
                .iter()
37✔
189
                .map(|field| gen_field_from_pfield(field, struct_name, &ps.container.bgp, None))
56✔
190
                .collect::<Vec<_>>();
37✔
191
            (kind, fields_vec)
37✔
192
        }
193
        PStructKind::UnitStruct => {
194
            let kind = quote!(::facet::StructKind::Unit);
5✔
195
            (kind, vec![])
5✔
196
        }
197
    };
198

199
    // Still need original AST for where clauses and type params for build_ helpers
200
    let where_clauses_ast = match &parsed.kind {
284✔
201
        StructKind::Struct { clauses, .. } => clauses.as_ref(),
242✔
202
        StructKind::TupleStruct { clauses, .. } => clauses.as_ref(),
37✔
203
        StructKind::UnitStruct { clauses, .. } => clauses.as_ref(),
5✔
204
    };
205
    let where_clauses = build_where_clauses(where_clauses_ast, parsed.generics.as_ref());
284✔
206
    let type_params = build_type_params(parsed.generics.as_ref());
284✔
207

208
    // Static decl using PStruct BGP
209
    let static_decl = if ps.container.bgp.params.is_empty() {
284✔
210
        generate_static_decl(struct_name)
260✔
211
    } else {
212
        TokenStream::new()
24✔
213
    };
214

215
    // Doc comments from PStruct
216
    let maybe_container_doc = if ps.container.attrs.doc.is_empty() {
284✔
217
        quote! {}
268✔
218
    } else {
219
        let doc_lines = ps.container.attrs.doc.iter().map(|s| quote!(#s));
20✔
220
        quote! { .doc(&[#(#doc_lines),*]) }
16✔
221
    };
222

223
    // Container attributes from PStruct
224
    let container_attributes_tokens = {
284✔
225
        let mut items = Vec::new();
284✔
226
        for attr in &ps.container.attrs.facet {
307✔
227
            match attr {
23✔
228
                PFacetAttr::DenyUnknownFields => {
3✔
229
                    items.push(quote! { ::facet::ShapeAttribute::DenyUnknownFields });
3✔
230
                }
3✔
231
                PFacetAttr::Default | PFacetAttr::DefaultEquals { .. } => {
2✔
232
                    // Corresponds to `#[facet(default)]` on container
2✔
233
                    items.push(quote! { ::facet::ShapeAttribute::Default });
2✔
234
                }
2✔
235
                PFacetAttr::Transparent => {
3✔
236
                    items.push(quote! { ::facet::ShapeAttribute::Transparent });
3✔
237
                }
3✔
238
                PFacetAttr::RenameAll { .. } => {}
11✔
239
                PFacetAttr::Arbitrary { content } => {
3✔
240
                    items.push(quote! { ::facet::ShapeAttribute::Arbitrary(#content) });
3✔
241
                }
3✔
242
                // Others not applicable at container level or handled elsewhere
243
                PFacetAttr::Sensitive
244
                | PFacetAttr::Opaque
245
                | PFacetAttr::Invariants { .. }
246
                | PFacetAttr::SkipSerializing
247
                | PFacetAttr::SkipSerializingIf { .. }
248
                | PFacetAttr::Flatten
249
                | PFacetAttr::Child => {}
1✔
250
            }
251
        }
252
        if items.is_empty() {
284✔
253
            quote! {}
275✔
254
        } else {
255
            quote! { .attributes(&[#(#items),*]) }
9✔
256
        }
257
    };
258

259
    // Invariants from PStruct
260
    let invariant_maybe = {
284✔
261
        let mut invariant_fns = Vec::new();
284✔
262
        for attr in &ps.container.attrs.facet {
307✔
263
            if let PFacetAttr::Invariants { expr } = attr {
23✔
264
                invariant_fns.push(expr);
1✔
265
            }
22✔
266
        }
267

268
        if !invariant_fns.is_empty() {
284✔
269
            let tests = invariant_fns.iter().map(|expr| {
1✔
270
                quote! {
1✔
271
                    if !#expr(value) {
1✔
272
                        return false;
1✔
273
                    }
1✔
274
                }
1✔
275
            });
1✔
276

277
            let bgp_display = ps.container.bgp.display_without_bounds(); // Use the BGP from PStruct
1✔
278
            quote! {
1✔
279
                unsafe fn invariants<'mem>(value: ::facet::PtrConst<'mem>) -> bool {
280
                    let value = value.get::<#struct_name_ident #bgp_display>();
281
                    #(#tests)*
282
                    true
283
                }
284

285
                vtable.invariants = Some(invariants);
286
            }
287
        } else {
288
            quote! {}
283✔
289
        }
290
    };
291

292
    // Transparent logic using PStruct
293
    let inner_field = if ps.container.attrs.is_transparent() {
284✔
294
        match &ps.kind {
3✔
295
            PStructKind::TupleStruct { fields } => {
3✔
296
                if fields.len() > 1 {
3✔
297
                    return quote! {
×
298
                        compile_error!("Transparent structs must be tuple structs with zero or one field");
299
                    };
300
                }
3✔
301
                fields.first().cloned() // Use first field if it exists, None otherwise (ZST case)
3✔
302
            }
303
            _ => {
304
                return quote! {
×
305
                    compile_error!("Transparent structs must be tuple structs");
306
                };
307
            }
308
        }
309
    } else {
310
        None
281✔
311
    };
312

313
    // Add try_from_inner implementation for transparent types
314
    let try_from_inner_code = if ps.container.attrs.is_transparent() {
284✔
315
        if let Some(inner_field) = &inner_field {
3✔
316
            // Transparent struct with one field
317
            let inner_field_type = &inner_field.ty;
3✔
318
            let bgp_without_bounds = ps.container.bgp.display_without_bounds();
3✔
319

3✔
320
            quote! {
3✔
321
                // Define the try_from function for the value vtable
322
                unsafe fn try_from<'src, 'dst>(
323
                    src_ptr: ::facet::PtrConst<'src>,
324
                    src_shape: &'static ::facet::Shape,
325
                    dst: ::facet::PtrUninit<'dst>
326
                ) -> Result<::facet::PtrMut<'dst>, ::facet::TryFromError> {
327
                    match <#inner_field_type as ::facet::Facet>::SHAPE.vtable.try_from {
328
                        Some(inner_try) => unsafe { (inner_try)(src_ptr, src_shape, dst) },
329
                        None => {
330
                            // Otherwise, check if source shape is exactly the inner shape
331
                            if src_shape != <#inner_field_type as ::facet::Facet>::SHAPE {
332
                                return Err(::facet::TryFromError::UnsupportedSourceShape {
333
                                    src_shape,
334
                                    expected: const { &[ &<#inner_field_type as ::facet::Facet>::SHAPE ] },
335
                                });
336
                            }
337
                            // Read the inner value and construct the wrapper.
338
                            let inner: #inner_field_type = unsafe { src_ptr.read() };
339
                            Ok(unsafe { dst.put(inner) }) // Construct wrapper
340
                        }
341
                    }
342
                }
343
                vtable.try_from = Some(try_from);
344

345
                // Define the try_into_inner function for the value vtable
346
                unsafe fn try_into_inner<'src, 'dst>(
347
                    src_ptr: ::facet::PtrConst<'src>,
348
                    dst: ::facet::PtrUninit<'dst>
349
                ) -> Result<::facet::PtrMut<'dst>, ::facet::TryIntoInnerError> {
350
                    let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
351
                    Ok(unsafe { dst.put(wrapper.0.clone()) }) // Assume tuple struct field 0
352
                }
353
                vtable.try_into_inner = Some(try_into_inner);
354

355
                // Define the try_borrow_inner function for the value vtable
356
                unsafe fn try_borrow_inner<'src>(
357
                    src_ptr: ::facet::PtrConst<'src>
358
                ) -> Result<::facet::PtrConst<'src>, ::facet::TryBorrowInnerError> {
359
                    // Get the wrapper value
360
                    let wrapper = unsafe { src_ptr.get::<#struct_name_ident #bgp_without_bounds>() };
361
                    // Return a pointer to the inner value (field 0)
362
                    Ok(::facet::PtrConst::new(&wrapper.0 as *const _ as *const u8))
363
                }
364
                vtable.try_borrow_inner = Some(try_borrow_inner);
365
            }
366
        } else {
367
            // Transparent ZST struct (like struct Unit;)
368
            quote! {
×
369
                // Define the try_from function for the value vtable (ZST case)
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
                     if src_shape.layout.size() == 0 {
376
                         Ok(unsafe { dst.put(#struct_name_ident) }) // Construct ZST
377
                     } else {
378
                         Err(::facet::TryFromError::UnsupportedSourceShape {
379
                             src_shape,
380
                             expected: const { &[ <() as ::facet::Facet>::SHAPE ] }, // Expect unit-like shape
381
                         })
382
                     }
383
                }
384
                 vtable.try_from = Some(try_from);
385

386
                // ZSTs cannot be meaningfully borrowed or converted *into* an inner value
387
                // try_into_inner and try_borrow_inner remain None
388
            }
389
        }
390
    } else {
391
        quote! {} // Not transparent
281✔
392
    };
393

394
    // Generate the inner shape function for transparent types
395
    let inner_shape_fn = if ps.container.attrs.is_transparent() {
284✔
396
        if let Some(inner_field) = &inner_field {
3✔
397
            let ty = &inner_field.ty;
3✔
398
            quote! {
3✔
399
                // Function to return inner type's shape
400
                fn inner_shape() -> &'static ::facet::Shape {
401
                    <#ty as ::facet::Facet>::SHAPE
402
                }
403
            }
404
        } else {
405
            // Transparent ZST case
406
            quote! {
×
407
                fn inner_shape() -> &'static ::facet::Shape {
408
                    <() as ::facet::Facet>::SHAPE // Inner shape is unit
409
                }
410
            }
411
        }
412
    } else {
413
        quote! {}
281✔
414
    };
415

416
    let inner_setter = if ps.container.attrs.is_transparent() {
284✔
417
        quote! { .inner(inner_shape) }
3✔
418
    } else {
419
        quote! {}
281✔
420
    };
421

422
    // Generics from PStruct
423
    let facet_bgp = ps
284✔
424
        .container
284✔
425
        .bgp
284✔
426
        .with_lifetime(LifetimeName(format_ident!("__facet")));
284✔
427
    let bgp_def = facet_bgp.display_with_bounds();
284✔
428
    let bgp_without_bounds = ps.container.bgp.display_without_bounds();
284✔
429

430
    // Final quote block using refactored parts
431
    let result = quote! {
284✔
432
        #static_decl
433

434
        #[automatically_derived]
435
        unsafe impl #bgp_def ::facet::Facet<'__facet> for #struct_name_ident #bgp_without_bounds #where_clauses {
436
            const SHAPE: &'static ::facet::Shape = &const {
437
                let fields: &'static [::facet::Field] = &const {[#(#fields_vec),*]};
438

439
                let vtable = &const {
440
                    let mut vtable = ::facet::value_vtable!(
441
                        Self,
442
                        |f, _opts| ::core::fmt::Write::write_str(f, #struct_name_str)
443
                    );
444
                    #invariant_maybe
445
                    #try_from_inner_code // Use the generated code for transparent types
446
                    vtable
447
                };
448

449
                #inner_shape_fn // Include inner_shape function if needed
450

451
                ::facet::Shape::builder()
452
                    .id(::facet::ConstTypeId::of::<Self>())
453
                    .layout(::core::alloc::Layout::new::<Self>())
454
                    #type_params // Still from parsed.generics
455
                    .vtable(vtable)
456
                    .def(::facet::Def::Struct(::facet::StructDef::builder()
457
                        .kind(#kind) // From ps.kind match
458
                        .fields(fields)
459
                        .build()))
460
                    #inner_setter // Use transparency flag from PStruct
461
                    #maybe_container_doc // From ps.container.attrs.doc
462
                    #container_attributes_tokens // From ps.container.attrs.facet
463
                    .build()
464
            };
465
        }
466
    };
467

468
    result
284✔
469
}
284✔
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