1use darling::FromVariant;
2use proc_macro::{self, TokenStream};
3use proc_macro2::Ident;
4use quote::{format_ident, quote};
5use syn::spanned::Spanned;
6use syn::Fields::Unnamed;
7use syn::{parse_macro_input, DeriveInput, Error};
8
9#[derive(FromVariant)]
10#[darling(attributes(vmnet))]
11struct Opts {
12 ffi: Option<String>,
13}
14
15#[proc_macro_derive(Vmnet, attributes(vmnet))]
16pub fn derive(input: TokenStream) -> TokenStream {
17 let input: DeriveInput = parse_macro_input!(input);
18
19 let input_enum = match input.data {
20 syn::Data::Enum(input_enum) => input_enum,
21 _ => {
22 return Error::new(input.span(), "only enumerations are supported")
23 .to_compile_error()
24 .into()
25 }
26 };
27
28 let input_enum_name = input.ident.clone();
30 let kind_enum_name = format_ident!("{}Kind", input.ident);
32
33 let mut kinds: Vec<Ident> = Vec::new();
34 let mut kind_enum_to_vmnet_ffi_key_arms = Vec::new();
35 let mut kind_enum_to_input_enum_arms = Vec::new();
36 let mut input_enum_to_xpc_data_arms = Vec::new();
37 let mut input_enum_to_kind_enum_arms = Vec::new();
38
39 for input_variant in input_enum.variants {
40 let unnamed = match input_variant.fields {
41 Unnamed(ref unnamed) => &unnamed.unnamed,
42 _ => {
43 return Error::new(input_variant.span(), "only unnamed fields are supported")
44 .to_compile_error()
45 .into()
46 }
47 };
48
49 if unnamed.len() > 1 {
50 return Error::new(
51 input_variant.span(),
52 "there should be exactly one unnamed field",
53 )
54 .to_compile_error()
55 .into();
56 }
57
58 let field = unnamed.first().unwrap();
59
60 let typ = match &field.ty {
61 syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("String") => {
62 format_ident!("String")
63 }
64 syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("u64") => {
65 format_ident!("Uint64")
66 }
67 syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("bool") => {
68 format_ident!("Bool")
69 }
70 syn::Type::Path(syn::TypePath { path, .. }) if path.is_ident("Uuid") => {
71 format_ident!("Uuid")
72 }
73 _ => {
74 return Error::new(
75 input_variant.span(),
76 "unsupported unnamed field type (expected String, u64, bool or Uuid)",
77 )
78 .to_compile_error()
79 .into();
80 }
81 };
82
83 let opts: Opts = Opts::from_variant(&input_variant).unwrap();
84
85 let vmnet_ffi_key = match opts.ffi {
86 Some(ffi) => format_ident!("{}", ffi),
87 None => {
88 return Error::new(
89 input_variant.span(),
90 "missing #[vmnet(ffi = \"...\" attribute",
91 )
92 .to_compile_error()
93 .into()
94 }
95 };
96
97 let variant_name = format_ident!("{}", input_variant.ident);
98 kinds.push(variant_name.clone());
99
100 input_enum_to_kind_enum_arms.push(quote! {
101 #input_enum_name::#variant_name(_) => { #kind_enum_name::#variant_name }
102 });
103
104 input_enum_to_xpc_data_arms.push(quote! {
105 #input_enum_name::#variant_name(val) => { XpcData::from(val) }
106 });
107
108 kind_enum_to_vmnet_ffi_key_arms.push(quote! {
109 #kind_enum_name::#variant_name => #vmnet_ffi_key
110 });
111
112 kind_enum_to_input_enum_arms.push(quote! {
113 (#kind_enum_name::#variant_name, XpcData::#typ(val)) => { Some(#input_enum_name::#variant_name(val)) }
114 });
115 }
116
117 let output = quote! {
118 #[derive(Debug, Hash, Eq, PartialEq, Sequence)]
120 pub enum #kind_enum_name {
121 #(#kinds),*
122 }
123
124 impl From<&#input_enum_name> for #kind_enum_name {
125 fn from(val: &#input_enum_name) -> Self {
126 match val {
127 #(#input_enum_to_kind_enum_arms),*
128 }
129 }
130 }
131
132 impl From<#input_enum_name> for XpcData {
133 fn from(val: #input_enum_name) -> Self {
134 match val {
135 #(#input_enum_to_xpc_data_arms),*
136 }
137 }
138 }
139
140 impl #kind_enum_name {
141 pub fn vmnet_ffi_key(&self) -> *const c_char {
142 unsafe {
143 match self {
144 #(#kind_enum_to_vmnet_ffi_key_arms),*
145 }
146 }
147 }
148
149 pub fn vmnet_key(&self) -> String {
150 unsafe { CStr::from_ptr(self.vmnet_ffi_key()).to_string_lossy().to_string() }
151 }
152
153 fn parse(&self, value: XpcData) -> Option<#input_enum_name> {
154 match (self, value) {
155 #(#kind_enum_to_input_enum_arms)*
156 _ => { None }
157 }
158 }
159 }
160 };
161
162 output.into()
163}