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

facet-rs / facet / 14434510692

13 Apr 2025 11:46PM UTC coverage: 18.06% (-13.6%) from 31.623%
14434510692

Pull #188

github

web-flow
Merge acfca18ec into 046ca7ecc
Pull Request #188: Rewrite facet-reflect for safety

360 of 901 new or added lines in 44 files covered. (39.96%)

707 existing lines in 19 files now uncovered.

1048 of 5803 relevant lines covered (18.06%)

7.5 hits per line

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

0.0
/facet-derive/src/process_enum.rs
1
use super::*;
2
use unsynn::*;
3

4
// mirrors facet_core::types::EnumRepr
5
#[derive(Clone, Copy)]
6
enum Discriminant {
7
    U8,
8
    U16,
9
    U32,
10
    U64,
11
    USize,
12
    I8,
13
    I16,
14
    I32,
15
    I64,
16
    ISize,
17
}
18

19
impl Discriminant {
20
    fn as_enum_repr(&self) -> &'static str {
×
21
        match self {
×
22
            Discriminant::U8 => "U8",
×
23
            Discriminant::U16 => "U16",
×
24
            Discriminant::U32 => "U32",
×
25
            Discriminant::U64 => "U64",
×
26
            Discriminant::USize => "USize",
×
27
            Discriminant::I8 => "I8",
×
28
            Discriminant::I16 => "I16",
×
29
            Discriminant::I32 => "I32",
×
30
            Discriminant::I64 => "I64",
×
31
            Discriminant::ISize => "ISize",
×
32
        }
33
    }
34

35
    fn as_rust_type(&self) -> &'static str {
×
36
        match self {
×
37
            Discriminant::U8 => "u8",
×
38
            Discriminant::U16 => "u16",
×
39
            Discriminant::U32 => "u32",
×
40
            Discriminant::U64 => "u64",
×
41
            Discriminant::USize => "usize",
×
42
            Discriminant::I8 => "i8",
×
43
            Discriminant::I16 => "i16",
×
44
            Discriminant::I32 => "i32",
×
45
            Discriminant::I64 => "i64",
×
46
            Discriminant::ISize => "isize",
×
47
        }
48
    }
49
}
50

51
struct ProcessedEnumBody {
52
    shadow_struct_defs: Vec<String>,
53
    variant_expressions: Vec<String>,
54
    repr_type: String,
55
}
56

57
type EnumVariant = Delimited<EnumVariantLike, Comma>;
58

59
/// Processes an enum to implement Facet
60
///
61
/// Example input:
62
/// ```rust
63
/// #[repr(u8)]
64
/// enum Color {
65
///     Red,
66
///     Green,
67
///     Blue(u8, u8),
68
///     Custom { r: u8, g: u8, b: u8 }
69
/// }
70
/// ```
71
pub(crate) fn process_enum(parsed: Enum) -> proc_macro::TokenStream {
×
72
    let enum_name = parsed.name.to_string();
×
73
    let (generics_def, generics_use) = generics_split_for_impl(parsed.generics.as_ref());
×
74
    let where_clauses = parsed
×
75
        .clauses
×
76
        .as_ref()
77
        .map_or(String::new(), ToString::to_string);
×
78

79
    // collect all `#repr(..)` attrs
80
    // either multiple attrs, or a single attr with multiple values
81
    let attr_iter = parsed
×
82
        .attributes
×
83
        .iter()
84
        .filter_map(|attr| {
×
85
            if let AttributeInner::Repr(repr_attr) = &attr.body.content {
×
86
                if repr_attr.attr.content.0.is_empty() {
×
87
                    // treat empty repr as non-existent
88
                    // (this shouldn't be possible, but just in case)
89
                    None
×
90
                } else {
91
                    Some(repr_attr)
×
92
                }
93
            } else {
94
                None
×
95
            }
96
        })
97
        .flat_map(|repr_attr| repr_attr.attr.content.0.iter());
×
98

99
    let mut repr_c = false;
×
100
    let mut discriminant_type = None;
×
101

102
    for attr in attr_iter {
×
103
        let attr = attr.value.to_string();
×
104
        match attr.as_str() {
×
105
            // this is #[repr(C)]
106
            "C" => repr_c = true,
×
107

108
            // set the repr type
109
            // NOTE: we're not worried about multiple
110
            // clashing types here -- that's rustc's problem
111
            "u8" => discriminant_type = Some(Discriminant::U8),
×
112
            "u16" => discriminant_type = Some(Discriminant::U16),
×
113
            "u32" => discriminant_type = Some(Discriminant::U32),
×
114
            "u64" => discriminant_type = Some(Discriminant::U64),
×
115
            "usize" => discriminant_type = Some(Discriminant::USize),
×
116
            "i8" => discriminant_type = Some(Discriminant::I8),
×
117
            "i16" => discriminant_type = Some(Discriminant::I16),
×
118
            "i32" => discriminant_type = Some(Discriminant::I32),
×
119
            "i64" => discriminant_type = Some(Discriminant::I64),
×
120
            "isize" => discriminant_type = Some(Discriminant::ISize),
×
121
            _ => {
122
                return r#"compile_error!("Facet only supports enums with a primitive representation (e.g. #[repr(u8)]) or C-style (e.g. #[repr(C)]")"#
×
123
            .into_token_stream()
×
124
            .into();
×
125
            }
126
        }
127
    }
128

129
    let processed_body = match (repr_c, discriminant_type) {
×
130
        (true, _) => {
131
            // C-style enum, no discriminant type
132
            process_c_style_enum(
133
                &enum_name,
×
134
                &parsed.body.content.0,
×
135
                discriminant_type,
×
136
                &generics_def,
×
137
                &generics_use,
×
138
                &where_clauses,
×
139
            )
140
        }
141
        (false, Some(discriminant_type)) => process_primitive_enum(
142
            &enum_name,
×
143
            &parsed.body.content.0,
×
144
            discriminant_type,
×
145
            &generics_def,
×
146
            &generics_use,
×
147
            &where_clauses,
×
148
        ),
149
        _ => {
150
            return r#"compile_error!("Enums must have an explicit representation (e.g. #[repr(u8)] or #[repr(C)]) to be used with Facet")"#
×
151
            .into_token_stream()
×
152
            .into();
×
153
        }
154
    };
155

156
    let ProcessedEnumBody {
157
        shadow_struct_defs,
×
158
        variant_expressions,
×
159
        repr_type,
×
160
    } = processed_body;
×
161

162
    // Join the shadow struct definitions and variant expressions
163
    let shadow_structs = shadow_struct_defs.join("\n\n");
×
164
    let variants = variant_expressions.join(", ");
×
165

166
    let static_decl = if parsed.generics.is_none() {
×
167
        generate_static_decl(&enum_name)
×
168
    } else {
169
        String::new()
×
170
    };
171
    let maybe_container_doc = build_maybe_doc(&parsed.attributes);
×
172

173
    // Generate the impl
174
    let output = format!(
×
175
        r#"
176
{static_decl}
177

178
#[automatically_derived]
179
unsafe impl<{generics_def}> ::facet::Facet for {enum_name}<{generics_use}> {where_clauses} {{
180
    const SHAPE: &'static ::facet::Shape = &const {{
181
        // Define all shadow structs at the beginning of the const block
182
        // to ensure they're in scope for offset_of! macros
183
        {shadow_structs}
184

185
        ::facet::Shape::builder()
186
            .id(::facet::ConstTypeId::of::<Self>())
187
            .layout(::core::alloc::Layout::new::<Self>())
188
            .vtable(::facet::value_vtable!(
189
                Self,
190
                |f, _opts| ::core::fmt::Write::write_str(f, "{enum_name}")
191
            ))
192
            .def(::facet::Def::Enum(::facet::EnumDef::builder()
193
                // Use variant expressions that just reference the shadow structs
194
                // which are now defined above
195
                .variants(&const {{[ {variants} ]}})
196
                .repr(::facet::EnumRepr::{repr_type})
197
                .build()))
198
            {maybe_container_doc}
199
            .build()
200
    }};
201
}}
202
        "#,
203
    );
204

205
    // Output generated code
206
    // Don't use panic for debugging as it makes code unreachable
207

208
    // Return the generated code
209
    output.into_token_stream().into()
×
210
}
211

212
/// C-style enums (i.e. #[repr(C)], #[repr(C, u*)] and #[repr(C, i*)]) are laid out
213
/// as a #[repr(C)] struct with two fiels: the discriminant and the union of all the variants.
214
///
215
/// See: <https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.primitive.adt>
216
///
217
/// To calculate the offsets of each variant, we create a shadow struct that mimics this
218
/// structure and use the `offset_of!` macro to calculate the offsets of each field.
219
fn process_c_style_enum(
×
220
    enum_name: &str,
221
    variants: &[EnumVariant],
222
    discriminant_type: Option<Discriminant>,
223
    generics_def: &str,
224
    generics_use: &str,
225
    where_clauses: &str,
226
) -> ProcessedEnumBody {
227
    // Collect shadow struct definitions separately from variant expressions
228
    let mut shadow_struct_defs = Vec::new();
×
229
    let mut variant_expressions = Vec::new();
×
230

231
    // first, create an enum to represent the discriminant type
232
    let shadow_discriminant_name = format!("__ShadowDiscriminant{enum_name}");
×
233
    let all_variant_names = variants
×
234
        .iter()
235
        .map(|var_like| match &var_like.value {
×
236
            EnumVariantLike::Unit(unit) => unit.name.to_string(),
×
237
            EnumVariantLike::Tuple(tuple) => tuple.name.to_string(),
×
238
            EnumVariantLike::Struct(struct_var) => struct_var.name.to_string(),
×
239
        })
240
        .collect::<Vec<_>>()
241
        .join(", ");
242
    shadow_struct_defs.push(format!(
×
243
        "#[repr({repr})] enum {shadow_discriminant_name} {{ {all_variant_names} }}",
×
244
        // repr is either C or the explicit discriminant type
245
        repr = discriminant_type.map(|d| d.as_rust_type()).unwrap_or("C")
×
246
    ));
247

248
    // we'll also generate a shadow union for the fields
249
    let shadow_union_name = format!("__ShadowFields{enum_name}");
×
250
    let all_union_fields = variants
×
251
        .iter()
252
        .map(|var_like| match &var_like.value {
×
253
            EnumVariantLike::Unit(unit) => unit.name.to_string(),
×
254
            EnumVariantLike::Tuple(tuple) => tuple.name.to_string(),
×
255
            EnumVariantLike::Struct(struct_var) => struct_var.name.to_string(),
×
256
        })
257
        .map(|variant_name| {
×
258
            format!(
×
259
                "{variant_name}: std::mem::ManuallyDrop<__ShadowField{enum_name}_{variant_name}>"
×
260
            )
261
        })
262
        .collect::<Vec<_>>()
263
        .join(", ");
264

265
    shadow_struct_defs.push(format!(
×
266
        "#[repr(C)] union {shadow_union_name} {{ {all_union_fields} }}",
×
267
    ));
268

269
    // Create a shadow struct to represent the enum layout
270
    let shadow_repr_name = format!("__ShadowRepr{enum_name}");
×
271

272
    shadow_struct_defs.push(format!(
×
273
        "#[repr(C)] struct {shadow_repr_name} {{
×
274
            _discriminant: {shadow_discriminant_name},
×
275
            _fields: {shadow_union_name},
×
276
        }}",
×
277
    ));
278

279
    // Process each variant using enumerate to get discriminant values
280
    for (discriminant_value, var_like) in variants.iter().enumerate() {
×
281
        match &var_like.value {
×
282
            EnumVariantLike::Unit(unit) => {
×
283
                let variant_name = unit.name.to_string();
×
284
                let maybe_doc = build_maybe_doc(&unit.attributes);
×
285

286
                // Generate shadow struct for this tuple variant to calculate offsets
287
                let shadow_struct_name = format!("__ShadowField{enum_name}_{variant_name}");
×
288

289
                // Add shadow struct definition
290
                shadow_struct_defs.push(format!("#[repr(C)] struct {shadow_struct_name};",));
×
291

292
                // variant offset is offset of the `_fields` union
293
                variant_expressions.push(format!(
×
294
                    "::facet::Variant::builder()
×
295
                    .name({variant_name:?})
×
NEW
296
                    .discriminant({discriminant_value})
×
297
                    .offset(::core::mem::offset_of!({shadow_repr_name}, _fields))
×
NEW
298
                    .fields(::facet::Struct::builder().unit().build(),
×
299
                    {maybe_doc}
×
300
                    .build()",
×
301
                ));
302
            }
303
            EnumVariantLike::Tuple(tuple) => {
×
304
                let variant_name = tuple.name.to_string();
×
305
                let maybe_doc = build_maybe_doc(&tuple.attributes);
×
306

307
                // Generate shadow struct for this tuple variant to calculate offsets
308
                let shadow_struct_name = format!("__ShadowField{enum_name}_{variant_name}");
×
309

310
                // Build the list of fields and types for the shadow struct
311
                let fields_with_types = tuple
×
312
                    .fields
×
313
                    .content
×
314
                    .0
×
315
                    .iter()
316
                    .enumerate()
317
                    .map(|(idx, field)| {
×
318
                        let typ = VerbatimDisplay(&field.value.typ).to_string();
×
319
                        format!("_{}: {}", idx, typ)
×
320
                    })
321
                    .collect::<Vec<String>>()
322
                    .join(", ");
323

324
                // Add shadow struct definition
325
                shadow_struct_defs.push(format!(
×
326
                    "#[repr(C)] struct {shadow_struct_name}<{generics_def}> {where_clauses} {{  {fields_with_types} }}",
×
327
                ));
328

329
                // Build the list of field types with calculated offsets
330
                let fields = tuple
×
331
                    .fields
×
332
                    .content
×
333
                    .0
×
334
                    .iter()
335
                    .enumerate()
336
                    .map(|(idx, field)| {
×
337
                        let field_name = format!("_{idx}");
×
338
                        gen_struct_field(
×
339
                            &field_name,
×
340
                            &shadow_struct_name,
×
341
                            generics_use,
×
342
                            &field.value.attributes,
×
343
                        )
344
                    })
345
                    .collect::<Vec<String>>()
346
                    .join(", ");
347

348
                // Add variant expression - now with discriminant
349
                variant_expressions.push(format!(
×
350
                    "{{
×
351
                        let fields: &'static [::facet::Field] = &const {{[
×
352
                            {fields}
×
353
                        ]}};
354

355
                        ::facet::Variant::builder()
×
356
                            .name({variant_name:?})
×
NEW
357
                            .discriminant({discriminant_value})
×
358
                            .offset(::core::mem::offset_of!({shadow_repr_name}, _fields))
×
NEW
359
                            .fields(::facet::Struct::builder().tuple().fields(fields).build(),
×
360
                            {maybe_doc}
×
361
                            .build()
×
362
                    }}",
×
363
                ));
364
            }
365
            EnumVariantLike::Struct(struct_var) => {
×
366
                let variant_name = struct_var.name.to_string();
×
367
                let maybe_doc = build_maybe_doc(&struct_var.attributes);
×
368

369
                // Generate shadow struct for this struct variant to calculate offsets
370
                let shadow_struct_name = format!("__ShadowField{}_{}", enum_name, variant_name);
×
371

372
                // Build the list of fields and types
373
                let fields_with_types = struct_var
×
374
                    .fields
×
375
                    .content
×
376
                    .0
×
377
                    .iter()
378
                    .map(|field| {
×
379
                        let name = field.value.name.to_string();
×
380
                        let typ = VerbatimDisplay(&field.value.typ).to_string();
×
381
                        format!("{}: {}", name, typ)
×
382
                    })
383
                    .collect::<Vec<String>>()
384
                    .join(", ");
385

386
                // Add shadow struct definition
387
                shadow_struct_defs.push(format!(
×
388
                    "#[repr(C)] struct {shadow_struct_name}<{generics_def}> {where_clauses} {{  {fields_with_types} }}"
×
389
                ));
390

391
                // Build the list of field types with calculated offsets
392
                let fields = struct_var
×
393
                    .fields
×
394
                    .content
×
395
                    .0
×
396
                    .iter()
397
                    .map(|field| {
×
398
                        let field_name = field.value.name.to_string();
×
399
                        gen_struct_field(
×
400
                            &field_name,
×
401
                            &shadow_struct_name,
×
402
                            generics_use,
×
403
                            &field.value.attributes,
×
404
                        )
405
                    })
406
                    .collect::<Vec<String>>()
407
                    .join(", ");
408

409
                // Add variant expression - now with discriminant
410
                variant_expressions.push(format!(
×
411
                    "{{
×
412
                        let fields: &'static [::facet::Field] = &const {{[
×
413
                            {fields}
×
414
                        ]}};
415

416
                        ::facet::Variant::builder()
×
417
                            .name({variant_name:?})
×
NEW
418
                            .discriminant(discriminant_value)
×
419
                            .offset(::core::mem::offset_of!({shadow_repr_name}, _fields))
×
NEW
420
                            .fields(::facet::Struct::builder().struct_().fields(fields).build(),
×
421
                            {maybe_doc}
×
422
                            .build()
×
423
                    }}",
×
424
                ));
425
            }
426
        }
427
    }
428

429
    ProcessedEnumBody {
430
        shadow_struct_defs,
431
        variant_expressions,
432
        repr_type: discriminant_type.map_or_else(
×
433
            || format!("from_discriminant_size::<{shadow_discriminant_name}>()"),
434
            |d| d.as_enum_repr().to_string(),
435
        ),
436
    }
437
}
438

439
/// Primitive enums (i.e. #[repr(u*)] and #[repr(i*)]) are laid out
440
/// as a union of all the variants, with the discriminant as an "inner" tag in the struct.
441
///
442
/// See: <https://doc.rust-lang.org/reference/type-layout.html#r-layout.repr.primitive.adt>
443
///
444
/// To calculate the offsets of each variant, we create a shadow struct that mimics this
445
/// structure and use the `offset_of!` macro to calculate the offsets of each field.
446
fn process_primitive_enum(
×
447
    enum_name: &str,
448
    variants: &[EnumVariant],
449
    discriminant_type: Discriminant,
450
    generics_def: &str,
451
    generics_use: &str,
452
    where_clauses: &str,
453
) -> ProcessedEnumBody {
454
    // Collect shadow struct definitions separately from variant expressions
455
    let mut shadow_struct_defs = Vec::new();
×
456
    let mut variant_expressions = Vec::new();
×
457

458
    // Process each variant using enumerate to get discriminant values
459
    for (discriminant_value, var_like) in variants.iter().enumerate() {
×
460
        match &var_like.value {
×
461
            EnumVariantLike::Unit(unit) => {
×
462
                let variant_name = unit.name.to_string();
×
463
                let maybe_doc = build_maybe_doc(&unit.attributes);
×
464

465
                variant_expressions.push(format!(
×
466
                    "::facet::Variant::builder()
×
467
                    .name({variant_name:?})
×
NEW
468
                    .discriminant({discriminant_value})
×
469
                    .offset(0)
×
NEW
470
                    .fields(::facet::Struct::builder().unit().build())
×
471
                    {maybe_doc}
×
472
                    .build()",
×
473
                ));
474
            }
475
            EnumVariantLike::Tuple(tuple) => {
×
476
                let variant_name = tuple.name.to_string();
×
477
                let maybe_doc = build_maybe_doc(&tuple.attributes);
×
478

479
                // Generate shadow struct for this tuple variant to calculate offsets
480
                let shadow_struct_name = format!("__Shadow{}_{}", enum_name, variant_name);
×
481

482
                // Build the list of fields and types for the shadow struct
483
                let fields_with_types = tuple
×
484
                    .fields
×
485
                    .content
×
486
                    .0
×
487
                    .iter()
488
                    .enumerate()
489
                    .map(|(idx, field)| {
×
490
                        let typ = VerbatimDisplay(&field.value.typ).to_string();
×
491
                        format!("_{}: {}", idx, typ)
×
492
                    })
493
                    .collect::<Vec<String>>()
494
                    .join(", ");
495

496
                // Add shadow struct definition
497
                shadow_struct_defs.push(format!(
×
498
                    "#[repr(C)] struct {shadow_struct_name}<{generics_def}> {where_clauses}  {{ _discriminant: {}, {fields_with_types} }}",
×
499
                    discriminant_type.as_rust_type(),
×
500
                ));
501

502
                // Build the list of field types with calculated offsets
503
                let fields = tuple
×
504
                    .fields
×
505
                    .content
×
506
                    .0
×
507
                    .iter()
508
                    .enumerate()
509
                    .map(|(idx, field)| {
×
510
                        let field_name = format!("_{idx}");
×
511
                        gen_struct_field(
×
512
                            &field_name,
×
513
                            &shadow_struct_name,
×
514
                            generics_use,
×
515
                            &field.value.attributes,
×
516
                        )
517
                    })
518
                    .collect::<Vec<String>>()
519
                    .join(", ");
520

521
                // Add variant expression - now with discriminant
522
                variant_expressions.push(format!(
×
523
                    "{{
×
524
                        let fields: &'static [::facet::Field] = &const {{[
×
525
                            {fields}
×
526
                        ]}};
527

528
                        ::facet::Variant::builder()
×
529
                            .name({variant_name:?})
×
NEW
530
                            .discriminant({discriminant_value})
×
531
                            .offset(0)
×
NEW
532
                            .fields(::facet::Struct::builder().tuple().fields(fields).build())
×
533
                            {maybe_doc}
×
534
                            .build()
×
535
                    }}",
×
536
                ));
537
            }
538
            EnumVariantLike::Struct(struct_var) => {
×
539
                let variant_name = struct_var.name.to_string();
×
540
                let maybe_doc = build_maybe_doc(&struct_var.attributes);
×
541

542
                // Generate shadow struct for this struct variant to calculate offsets
543
                let shadow_struct_name = format!("__Shadow{}_{}", enum_name, variant_name);
×
544

545
                // Build the list of fields and types
546
                let fields_with_types = struct_var
×
547
                    .fields
×
548
                    .content
×
549
                    .0
×
550
                    .iter()
551
                    .map(|field| {
×
552
                        let name = field.value.name.to_string();
×
553
                        let typ = VerbatimDisplay(&field.value.typ).to_string();
×
554
                        format!("{}: {}", name, typ)
×
555
                    })
556
                    .collect::<Vec<String>>()
557
                    .join(", ");
558

559
                // Add shadow struct definition
560
                shadow_struct_defs.push(format!(
×
561
                    "#[repr(C)] struct {shadow_struct_name}<{generics_def}> {where_clauses} {{ _discriminant: {}, {fields_with_types} }}",
×
562
                    discriminant_type.as_rust_type(),
×
563
                ));
564

565
                // Build the list of field types with calculated offsets
566
                let fields = struct_var
×
567
                    .fields
×
568
                    .content
×
569
                    .0
×
570
                    .iter()
571
                    .map(|field| {
×
572
                        let field_name = field.value.name.to_string();
×
573
                        gen_struct_field(
×
574
                            &field_name,
×
575
                            &shadow_struct_name,
×
576
                            generics_use,
×
577
                            &field.value.attributes,
×
578
                        )
579
                    })
580
                    .collect::<Vec<String>>()
581
                    .join(", ");
582

583
                // Add variant expression - now with discriminant
584
                // variant offset is zero since all fields are
585
                // already computed relative to the discriminant
586
                variant_expressions.push(format!(
×
587
                    "{{
×
588
                        let fields: &'static [::facet::Field] = &const {{[
×
589
                            {fields}
×
590
                        ]}};
591

592
                        ::facet::Variant::builder()
×
593
                            .name({variant_name:?})
×
NEW
594
                            .discriminant({discriminant_value})
×
595
                            .offset(0)
×
NEW
596
                            .fields(::facet::Struct::builder().struct_().fields(fields).build())
×
597
                            {maybe_doc}
×
598
                            .build()
×
599
                    }}",
×
600
                ));
601
            }
602
        }
603
    }
604

605
    ProcessedEnumBody {
606
        shadow_struct_defs,
607
        variant_expressions,
608
        repr_type: discriminant_type.as_enum_repr().to_string(),
×
609
    }
610
}
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