1extern crate alloc;
24
25use alloc::collections::{BTreeMap, BTreeSet};
26use alloc::string::String;
27use alloc::vec::Vec;
28use core::fmt::Write;
29
30use facet_core::{Def, Facet, Field, Shape, StructKind, Type, UserType};
31
32fn is_python_keyword(name: &str) -> bool {
34 const KEYWORDS: &[&str] = &[
36 "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class",
37 "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global",
38 "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return",
39 "try", "while", "with", "yield",
40 ];
41 KEYWORDS.binary_search(&name).is_ok()
42}
43
44struct TypedDictField<'a> {
46 name: &'a str,
47 type_string: String,
48 required: bool,
49 doc: &'a [&'a str],
50}
51
52impl<'a> TypedDictField<'a> {
53 fn new(name: &'a str, type_string: String, required: bool, doc: &'a [&'a str]) -> Self {
54 Self {
55 name,
56 type_string,
57 required,
58 doc,
59 }
60 }
61
62 fn full_type_string(&self) -> String {
64 if self.required {
65 format!("Required[{}]", self.type_string)
66 } else {
67 self.type_string.clone()
68 }
69 }
70}
71
72fn has_reserved_keyword_field(fields: &[TypedDictField]) -> bool {
74 fields.iter().any(|f| is_python_keyword(f.name))
75}
76
77fn write_typed_dict_functional(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
79 writeln!(output, "{} = TypedDict(", class_name).unwrap();
80 writeln!(output, " \"{}\",", class_name).unwrap();
81 output.push_str(" {");
82
83 let mut first = true;
84 for field in fields {
85 if !first {
86 output.push_str(", ");
87 }
88 first = false;
89
90 write!(output, "\"{}\": {}", field.name, field.full_type_string()).unwrap();
91 }
92
93 output.push_str("},\n");
94 output.push_str(" total=False,\n");
95 output.push(')');
96}
97
98fn write_typed_dict_class(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
100 writeln!(output, "class {}(TypedDict, total=False):", class_name).unwrap();
101
102 if fields.is_empty() {
103 output.push_str(" pass");
104 return;
105 }
106
107 for field in fields {
108 for line in field.doc {
110 output.push_str(" #");
111 output.push_str(line);
112 output.push('\n');
113 }
114
115 writeln!(output, " {}: {}", field.name, field.full_type_string()).unwrap();
116 }
117}
118
119fn write_typed_dict(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
121 if has_reserved_keyword_field(fields) {
122 write_typed_dict_functional(output, class_name, fields);
123 } else {
124 write_typed_dict_class(output, class_name, fields);
125 }
126}
127
128pub fn to_python<T: Facet<'static>>(write_imports: bool) -> String {
130 let mut generator = PythonGenerator::new();
131 generator.add_shape(T::SHAPE);
132 generator.finish(write_imports)
133}
134
135pub struct PythonGenerator {
137 generated: BTreeMap<String, String>,
139 queue: Vec<&'static Shape>,
141 imports: BTreeSet<&'static str>,
143}
144
145impl Default for PythonGenerator {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl PythonGenerator {
152 pub const fn new() -> Self {
154 Self {
155 generated: BTreeMap::new(),
156 queue: Vec::new(),
157 imports: BTreeSet::new(),
158 }
159 }
160
161 pub fn add_type<T: Facet<'static>>(&mut self) {
163 self.add_shape(T::SHAPE);
164 }
165
166 pub fn add_shape(&mut self, shape: &'static Shape) {
168 if !self.generated.contains_key(shape.type_identifier) {
169 self.queue.push(shape);
170 }
171 }
172
173 pub fn finish(mut self, write_imports: bool) -> String {
175 while let Some(shape) = self.queue.pop() {
177 if self.generated.contains_key(shape.type_identifier) {
178 continue;
179 }
180 self.generated
182 .insert(shape.type_identifier.to_string(), String::new());
183 self.generate_shape(shape);
184 }
185
186 let mut output = String::new();
188
189 if write_imports {
191 writeln!(output, "from __future__ import annotations").unwrap();
194
195 if !self.imports.is_empty() {
196 let imports: Vec<&str> = self.imports.iter().copied().collect();
197 writeln!(output, "from typing import {}", imports.join(", ")).unwrap();
198 }
199 output.push('\n');
200 }
201
202 for code in self.generated.values() {
203 output.push_str(code);
204 }
205 output
206 }
207
208 fn generate_shape(&mut self, shape: &'static Shape) {
209 let mut output = String::new();
210
211 if let Some(inner) = shape.inner {
213 self.add_shape(inner);
214 let inner_type = self.type_for_shape(inner);
215 write_doc_comment(&mut output, shape.doc);
216 writeln!(output, "type {} = {}", shape.type_identifier, inner_type).unwrap();
217 output.push('\n');
218 self.generated
219 .insert(shape.type_identifier.to_string(), output);
220 return;
221 }
222
223 match &shape.ty {
224 Type::User(UserType::Struct(st)) => {
225 self.generate_struct(&mut output, shape, st.fields, st.kind);
226 }
227 Type::User(UserType::Enum(en)) => {
228 self.generate_enum(&mut output, shape, en);
229 }
230 _ => {
231 let type_str = self.type_for_shape(shape);
233 write_doc_comment(&mut output, shape.doc);
234 writeln!(output, "type {} = {}", shape.type_identifier, type_str).unwrap();
235 output.push('\n');
236 }
237 }
238
239 self.generated
240 .insert(shape.type_identifier.to_string(), output);
241 }
242
243 fn generate_struct(
244 &mut self,
245 output: &mut String,
246 shape: &'static Shape,
247 fields: &'static [Field],
248 kind: StructKind,
249 ) {
250 match kind {
251 StructKind::Unit => {
252 write_doc_comment(output, shape.doc);
253 writeln!(output, "{} = None", shape.type_identifier).unwrap();
254 }
255 StructKind::TupleStruct | StructKind::Tuple if fields.is_empty() => {
256 write_doc_comment(output, shape.doc);
258 writeln!(output, "{} = None", shape.type_identifier).unwrap();
259 }
260 StructKind::TupleStruct if fields.len() == 1 => {
261 let inner_type = self.type_for_shape(fields[0].shape.get());
262 write_doc_comment(output, shape.doc);
263 writeln!(output, "{} = {}", shape.type_identifier, inner_type).unwrap();
264 }
265 StructKind::TupleStruct | StructKind::Tuple => {
266 let types: Vec<String> = fields
267 .iter()
268 .map(|f| self.type_for_shape(f.shape.get()))
269 .collect();
270 write_doc_comment(output, shape.doc);
271 writeln!(
272 output,
273 "{} = tuple[{}]",
274 shape.type_identifier,
275 types.join(", ")
276 )
277 .unwrap();
278 }
279 StructKind::Struct => {
280 self.generate_typed_dict(output, shape, fields);
281 }
282 }
283 output.push('\n');
284 }
285
286 fn generate_typed_dict(
288 &mut self,
289 output: &mut String,
290 shape: &'static Shape,
291 fields: &'static [Field],
292 ) {
293 self.imports.insert("TypedDict");
294
295 let visible_fields: Vec<_> = fields
296 .iter()
297 .filter(|f| !f.flags.contains(facet_core::FieldFlags::SKIP))
298 .collect();
299
300 let typed_dict_fields: Vec<_> = visible_fields
302 .iter()
303 .map(|f| {
304 let (type_string, required) = self.field_type_info(f);
305 TypedDictField::new(f.effective_name(), type_string, required, f.doc)
306 })
307 .collect();
308
309 if typed_dict_fields.iter().any(|f| f.required) {
311 self.imports.insert("Required");
312 }
313
314 write_doc_comment(output, shape.doc);
315 write_typed_dict(output, shape.type_identifier, &typed_dict_fields);
316 }
317
318 fn field_type_info(&mut self, field: &Field) -> (String, bool) {
320 if let Def::Option(opt) = &field.shape.get().def {
321 (self.type_for_shape(opt.t), false)
322 } else {
323 (self.type_for_shape(field.shape.get()), true)
324 }
325 }
326
327 fn generate_enum(
328 &mut self,
329 output: &mut String,
330 shape: &'static Shape,
331 enum_type: &facet_core::EnumType,
332 ) {
333 let all_unit = enum_type
334 .variants
335 .iter()
336 .all(|v| matches!(v.data.kind, StructKind::Unit));
337
338 write_doc_comment(output, shape.doc);
339
340 if all_unit {
341 self.generate_enum_unit_variants(output, shape, enum_type);
342 } else {
343 self.generate_enum_with_data(output, shape, enum_type);
344 }
345 output.push('\n');
346 }
347
348 fn generate_enum_unit_variants(
350 &mut self,
351 output: &mut String,
352 shape: &'static Shape,
353 enum_type: &facet_core::EnumType,
354 ) {
355 self.imports.insert("Literal");
356
357 let variants: Vec<String> = enum_type
358 .variants
359 .iter()
360 .map(|v| format!("Literal[\"{}\"]", v.effective_name()))
361 .collect();
362
363 writeln!(
364 output,
365 "type {} = {}",
366 shape.type_identifier,
367 variants.join(" | ")
368 )
369 .unwrap();
370 }
371
372 fn generate_enum_with_data(
374 &mut self,
375 output: &mut String,
376 shape: &'static Shape,
377 enum_type: &facet_core::EnumType,
378 ) {
379 let mut variant_class_names = Vec::new();
380
381 for variant in enum_type.variants {
382 let variant_type_name = self.generate_enum_variant(variant);
383 variant_class_names.push(variant_type_name);
384 }
385
386 writeln!(
387 output,
388 "type {} = {}",
389 shape.type_identifier,
390 variant_class_names.join(" | ")
391 )
392 .unwrap();
393 }
394
395 fn generate_enum_variant(&mut self, variant: &facet_core::Variant) -> String {
397 let variant_name = variant.effective_name();
398 let pascal_variant_name = to_pascal_case(variant_name);
399
400 match variant.data.kind {
401 StructKind::Unit => {
402 self.imports.insert("Literal");
403 format!("Literal[\"{}\"]", variant_name)
404 }
405 StructKind::TupleStruct if variant.data.fields.len() == 1 => {
406 self.generate_newtype_variant(variant_name, &pascal_variant_name, variant);
407 pascal_variant_name.to_string()
408 }
409 StructKind::TupleStruct => {
410 self.generate_tuple_variant(variant_name, &pascal_variant_name, variant);
411 pascal_variant_name.to_string()
412 }
413 _ => {
414 self.generate_struct_variant(variant_name, &pascal_variant_name, variant);
415 pascal_variant_name.to_string()
416 }
417 }
418 }
419
420 fn generate_newtype_variant(
422 &mut self,
423 variant_name: &str,
424 pascal_variant_name: &str,
425 variant: &facet_core::Variant,
426 ) {
427 self.imports.insert("TypedDict");
428 self.imports.insert("Required");
429
430 let inner_type = self.type_for_shape(variant.data.fields[0].shape.get());
431
432 let fields = [TypedDictField::new(variant_name, inner_type, true, &[])];
433
434 let mut output = String::new();
435 write_typed_dict(&mut output, pascal_variant_name, &fields);
436 output.push('\n');
437
438 self.generated
439 .insert(pascal_variant_name.to_string(), output);
440 }
441
442 fn generate_tuple_variant(
444 &mut self,
445 variant_name: &str,
446 pascal_variant_name: &str,
447 variant: &facet_core::Variant,
448 ) {
449 self.imports.insert("TypedDict");
450 self.imports.insert("Required");
451
452 let types: Vec<String> = variant
453 .data
454 .fields
455 .iter()
456 .map(|f| self.type_for_shape(f.shape.get()))
457 .collect();
458
459 let inner_type = format!("tuple[{}]", types.join(", "));
463
464 let fields = [TypedDictField::new(variant_name, inner_type, true, &[])];
465
466 let mut output = String::new();
467 write_typed_dict(&mut output, pascal_variant_name, &fields);
468 output.push('\n');
469
470 self.generated
471 .insert(pascal_variant_name.to_string(), output);
472 }
473
474 fn generate_struct_variant(
476 &mut self,
477 variant_name: &str,
478 pascal_variant_name: &str,
479 variant: &facet_core::Variant,
480 ) {
481 self.imports.insert("TypedDict");
482 self.imports.insert("Required");
483
484 let data_class_name = format!("{}Data", pascal_variant_name);
485
486 let data_fields: Vec<_> = variant
488 .data
489 .fields
490 .iter()
491 .map(|field| {
492 let field_type = self.type_for_shape(field.shape.get());
493 TypedDictField::new(field.effective_name(), field_type, true, &[])
494 })
495 .collect();
496
497 let mut data_output = String::new();
498 write_typed_dict(&mut data_output, &data_class_name, &data_fields);
499 data_output.push('\n');
500 self.generated.insert(data_class_name.clone(), data_output);
501
502 let wrapper_fields = [TypedDictField::new(
504 variant_name,
505 data_class_name.clone(),
506 true,
507 &[],
508 )];
509
510 let mut wrapper_output = String::new();
511 write_typed_dict(&mut wrapper_output, pascal_variant_name, &wrapper_fields);
512 wrapper_output.push('\n');
513
514 self.generated
515 .insert(pascal_variant_name.to_string(), wrapper_output);
516 }
517
518 fn type_for_shape(&mut self, shape: &'static Shape) -> String {
519 match &shape.def {
521 Def::Scalar => self.scalar_type(shape),
522 Def::Option(opt) => {
523 format!("{} | None", self.type_for_shape(opt.t))
524 }
525 Def::List(list) => {
526 format!("list[{}]", self.type_for_shape(list.t))
527 }
528 Def::Array(arr) => {
529 format!("list[{}]", self.type_for_shape(arr.t))
530 }
531 Def::Set(set) => {
532 format!("list[{}]", self.type_for_shape(set.t))
533 }
534 Def::Map(map) => {
535 format!(
536 "dict[{}, {}]",
537 self.type_for_shape(map.k),
538 self.type_for_shape(map.v)
539 )
540 }
541 Def::Pointer(ptr) => match ptr.pointee {
542 Some(pointee) => self.type_for_shape(pointee),
543 None => {
544 self.imports.insert("Any");
545 "Any".to_string()
546 }
547 },
548 Def::Undefined => {
549 match &shape.ty {
551 Type::User(UserType::Struct(st)) => {
552 if st.kind == StructKind::Tuple {
555 let types: Vec<String> = st
556 .fields
557 .iter()
558 .map(|f| self.type_for_shape(f.shape.get()))
559 .collect();
560 format!("tuple[{}]", types.join(", "))
561 } else {
562 self.add_shape(shape);
563 shape.type_identifier.to_string()
564 }
565 }
566 Type::User(UserType::Enum(_)) => {
567 self.add_shape(shape);
568 shape.type_identifier.to_string()
569 }
570 _ => self.inner_type_or_any(shape),
571 }
572 }
573 _ => self.inner_type_or_any(shape),
574 }
575 }
576
577 fn inner_type_or_any(&mut self, shape: &'static Shape) -> String {
579 match shape.inner {
580 Some(inner) => self.type_for_shape(inner),
581 None => {
582 self.imports.insert("Any");
583 "Any".to_string()
584 }
585 }
586 }
587
588 fn scalar_type(&mut self, shape: &'static Shape) -> String {
590 match shape.type_identifier {
591 "String" | "str" | "&str" | "Cow" => "str".to_string(),
593
594 "bool" => "bool".to_string(),
596
597 "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64"
599 | "i128" | "isize" => "int".to_string(),
600
601 "f32" | "f64" => "float".to_string(),
603
604 "char" => "str".to_string(),
606
607 _ => {
609 self.imports.insert("Any");
610 "Any".to_string()
611 }
612 }
613 }
614}
615
616fn write_doc_comment(output: &mut String, doc: &[&str]) {
618 for line in doc {
619 output.push('#');
620 output.push_str(line);
621 output.push('\n');
622 }
623}
624
625fn to_pascal_case(s: &str) -> String {
627 let mut result = String::new();
628 let mut capitalize_next = true;
629
630 for c in s.chars() {
631 if c == '_' || c == '-' {
632 capitalize_next = true;
633 } else if capitalize_next {
634 result.push(c.to_ascii_uppercase());
635 capitalize_next = false;
636 } else {
637 result.push(c);
638 }
639 }
640
641 result
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use facet::Facet;
648
649 #[test]
650 fn test_simple_struct() {
651 #[derive(Facet)]
652 struct User {
653 name: String,
654 age: u32,
655 }
656
657 let py = to_python::<User>(false);
658 insta::assert_snapshot!(py);
659 }
660
661 #[test]
662 fn test_optional_field() {
663 #[derive(Facet)]
664 struct Config {
665 required: String,
666 optional: Option<String>,
667 }
668
669 let py = to_python::<Config>(false);
670 insta::assert_snapshot!(py);
671 }
672
673 #[test]
674 fn test_simple_enum() {
675 #[derive(Facet)]
676 #[repr(u8)]
677 enum Status {
678 Active,
679 Inactive,
680 Pending,
681 }
682
683 let py = to_python::<Status>(false);
684 insta::assert_snapshot!(py);
685 }
686
687 #[test]
688 fn test_vec() {
689 #[derive(Facet)]
690 struct Data {
691 items: Vec<String>,
692 }
693
694 let py = to_python::<Data>(false);
695 insta::assert_snapshot!(py);
696 }
697
698 #[test]
699 fn test_nested_types() {
700 #[derive(Facet)]
701 struct Inner {
702 value: i32,
703 }
704
705 #[derive(Facet)]
706 struct Outer {
707 inner: Inner,
708 name: String,
709 }
710
711 let py = to_python::<Outer>(false);
712 insta::assert_snapshot!(py);
713 }
714
715 #[test]
716 fn test_enum_rename_all_snake_case() {
717 #[derive(Facet)]
718 #[facet(rename_all = "snake_case")]
719 #[repr(u8)]
720 enum ValidationErrorCode {
721 CircularDependency,
722 InvalidNaming,
723 UnknownRequirement,
724 }
725
726 let py = to_python::<ValidationErrorCode>(false);
727 insta::assert_snapshot!(py);
728 }
729
730 #[test]
731 fn test_enum_rename_individual() {
732 #[derive(Facet)]
733 #[repr(u8)]
734 enum GitStatus {
735 #[facet(rename = "dirty")]
736 Dirty,
737 #[facet(rename = "staged")]
738 Staged,
739 #[facet(rename = "clean")]
740 Clean,
741 }
742
743 let py = to_python::<GitStatus>(false);
744 insta::assert_snapshot!(py);
745 }
746
747 #[test]
748 fn test_struct_rename_all_camel_case() {
749 #[derive(Facet)]
750 #[facet(rename_all = "camelCase")]
751 struct ApiResponse {
752 user_name: String,
753 created_at: String,
754 is_active: bool,
755 }
756
757 let py = to_python::<ApiResponse>(false);
758 insta::assert_snapshot!(py);
759 }
760
761 #[test]
762 fn test_struct_rename_individual() {
763 #[derive(Facet)]
764 struct UserProfile {
765 #[facet(rename = "userName")]
766 user_name: String,
767 #[facet(rename = "emailAddress")]
768 email: String,
769 }
770
771 let py = to_python::<UserProfile>(false);
772 insta::assert_snapshot!(py);
773 }
774
775 #[test]
776 fn test_enum_with_data_rename_all() {
777 #[derive(Facet)]
778 #[facet(rename_all = "snake_case")]
779 #[repr(C)]
780 #[allow(dead_code)]
781 enum Message {
782 TextMessage { content: String },
783 ImageUpload { url: String, width: u32 },
784 }
785
786 let py = to_python::<Message>(false);
787 insta::assert_snapshot!(py);
788 }
789
790 #[test]
791 fn test_unit_struct() {
792 #[derive(Facet)]
793 struct Empty;
794
795 let py = to_python::<Empty>(false);
796 insta::assert_snapshot!(py);
797 }
798
799 #[test]
800 fn test_tuple_struct() {
801 #[derive(Facet)]
802 struct Point(f32, f64);
803
804 let py = to_python::<Point>(false);
805 insta::assert_snapshot!(py);
806 }
807
808 #[test]
809 fn test_newtype_struct() {
810 #[derive(Facet)]
811 struct UserId(u64);
812
813 let py = to_python::<UserId>(false);
814 insta::assert_snapshot!(py);
815 }
816
817 #[test]
818 fn test_hashmap() {
819 use std::collections::HashMap;
820
821 #[derive(Facet)]
822 struct Registry {
823 entries: HashMap<String, i32>,
824 }
825
826 let py = to_python::<Registry>(false);
827 insta::assert_snapshot!(py);
828 }
829
830 #[test]
831 fn test_mixed_enum_variants() {
832 #[derive(Facet)]
833 #[repr(C)]
834 #[allow(dead_code)]
835 enum Event {
836 Empty,
838 Id(u64),
840 Data { name: String, value: f64 },
842 }
843
844 let py = to_python::<Event>(false);
845 insta::assert_snapshot!(py);
846 }
847
848 #[test]
849 fn test_with_imports() {
850 #[derive(Facet)]
851 struct User {
852 name: String,
853 age: u32,
854 }
855
856 let py = to_python::<User>(true);
857 insta::assert_snapshot!(py);
858 }
859
860 #[test]
861 fn test_enum_with_imports() {
862 #[derive(Facet)]
863 #[repr(u8)]
864 enum Status {
865 Active,
866 Inactive,
867 }
868
869 let py = to_python::<Status>(true);
870 insta::assert_snapshot!(py);
871 }
872
873 #[test]
874 fn test_transparent_wrapper() {
875 #[derive(Facet)]
876 #[facet(transparent)]
877 struct UserId(String);
878
879 let py = to_python::<UserId>(false);
880 insta::assert_snapshot!(py);
882 }
883
884 #[test]
885 fn test_transparent_wrapper_with_inner_type() {
886 #[derive(Facet)]
887 struct Inner {
888 value: i32,
889 }
890
891 #[derive(Facet)]
892 #[facet(transparent)]
893 struct Wrapper(Inner);
894
895 let py = to_python::<Wrapper>(false);
896 insta::assert_snapshot!(py);
898 }
899
900 #[test]
901 fn test_struct_with_tuple_field() {
902 #[derive(Facet)]
903 struct Container {
904 coordinates: (i32, i32),
906 }
907
908 let py = to_python::<Container>(false);
909 insta::assert_snapshot!(py);
911 }
912
913 #[test]
914 fn test_struct_with_reserved_keyword_field() {
915 #[derive(Facet)]
916 struct TradeOrder {
917 from: f64,
918 to: f64,
919 quantity: f64,
920 }
921
922 let py = to_python::<TradeOrder>(false);
923 insta::assert_snapshot!(py);
925 }
926
927 #[test]
928 fn test_struct_with_multiple_reserved_keywords() {
929 #[derive(Facet)]
930 struct ControlFlow {
931 r#if: bool,
932 r#else: String,
933 r#return: i32,
934 }
935
936 let py = to_python::<ControlFlow>(false);
937 insta::assert_snapshot!(py);
939 }
940
941 #[test]
942 fn test_enum_variant_name_is_reserved_keyword() {
943 #[derive(Facet)]
944 #[repr(C)]
945 #[facet(rename_all = "snake_case")]
946 #[allow(dead_code)]
947 enum ImportSource {
948 From(String),
950 Url(String),
952 }
953
954 let py = to_python::<ImportSource>(false);
955 insta::assert_snapshot!(py);
958 }
959
960 #[test]
961 fn test_enum_data_variant_with_reserved_keyword_field() {
962 #[derive(Facet)]
963 #[repr(C)]
964 #[allow(dead_code)]
965 enum Transfer {
966 Move {
968 from: String,
969 to: String,
970 amount: f64,
971 },
972 Cancel,
974 }
975
976 let py = to_python::<Transfer>(false);
977 insta::assert_snapshot!(py);
980 }
981
982 #[test]
983 fn test_hashmap_with_integer_keys() {
984 use std::collections::HashMap;
985
986 #[derive(Facet)]
987 struct IntKeyedMap {
988 counts: HashMap<i32, String>,
990 }
991
992 let py = to_python::<IntKeyedMap>(false);
993 insta::assert_snapshot!(py);
994 }
995
996 #[test]
997 fn test_empty_tuple_struct() {
998 #[derive(Facet)]
999 struct EmptyTuple();
1000
1001 let py = to_python::<EmptyTuple>(false);
1002 insta::assert_snapshot!(py);
1003 }
1004
1005 #[test]
1006 fn test_hashmap_with_enum_keys() {
1007 use std::collections::HashMap;
1008
1009 #[derive(Facet, Hash, PartialEq, Eq)]
1010 #[repr(u8)]
1011 enum Priority {
1012 Low,
1013 Medium,
1014 High,
1015 }
1016
1017 #[derive(Facet)]
1018 struct TaskMap {
1019 tasks: HashMap<Priority, String>,
1020 }
1021
1022 let py = to_python::<TaskMap>(false);
1023 insta::assert_snapshot!(py);
1024 }
1025
1026 #[test]
1027 fn test_enum_tuple_variant() {
1028 #[derive(Facet)]
1029 #[repr(C)]
1030 #[allow(dead_code)]
1031 enum TupleVariant {
1032 Point(i32, i32),
1033 }
1034 let py = to_python::<TupleVariant>(false);
1035 insta::assert_snapshot!(py);
1036 }
1037
1038 #[test]
1039 fn test_enum_struct_variant_forward_reference() {
1040 #[derive(Facet)]
1045 #[repr(C)]
1046 #[allow(dead_code)]
1047 enum Message {
1048 Data { name: String, value: f64 },
1050 }
1051 let py = to_python::<Message>(false);
1052 insta::assert_snapshot!(py);
1053 }
1054}