1use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6 Data, DeriveInput, Error, Fields, Ident, Index, Member, Result, Type, parse_macro_input,
7 parse_quote,
8};
9
10#[proc_macro_derive(TypeStructCodec, attributes(type_struct_codec))]
24pub fn derive_type_struct_codec(input: TokenStream) -> TokenStream {
25 let input = parse_macro_input!(input as DeriveInput);
26 match expand_type_struct_codec(&input) {
27 Ok(tokens) => tokens.into(),
28 Err(error) => error.into_compile_error().into(),
29 }
30}
31
32fn expand_type_struct_codec(input: &DeriveInput) -> Result<proc_macro2::TokenStream> {
33 let Data::Struct(data) = &input.data else {
34 return Err(Error::new_spanned(
35 input,
36 "TypeStructCodec can only be derived for structs",
37 ));
38 };
39 let kind = type_struct_codec_kind(input)?;
40 let name = &input.ident;
41
42 let fields: Vec<(Member, &Type)> = match &data.fields {
43 Fields::Named(fields) => fields
44 .named
45 .iter()
46 .map(|field| {
47 (
48 Member::Named(field.ident.clone().expect("named field")),
49 &field.ty,
50 )
51 })
52 .collect(),
53 Fields::Unnamed(fields) => fields
54 .unnamed
55 .iter()
56 .enumerate()
57 .map(|(index, field)| (Member::Unnamed(Index::from(index)), &field.ty))
58 .collect(),
59 Fields::Unit => Vec::new(),
60 };
61
62 let mut bounded_generics = input.generics.clone();
63 for (_, field_type) in &fields {
64 bounded_generics
65 .make_where_clause()
66 .predicates
67 .push(parse_quote!(#field_type: ::mcproto_types::TypeCodec));
68 }
69 let (impl_generics, _, where_clause) = bounded_generics.split_for_impl();
70 let (_, type_generics, _) = input.generics.split_for_impl();
71
72 let encode_fields = fields.iter().map(|(member, _)| {
73 quote! {
74 ::mcproto_types::TypeCodec::encode(&self.#member, writer)
75 .map_err(|error| error.with_context(
76 ::mcproto_types::__private::CodecKind::#kind,
77 ))?;
78 }
79 });
80 let decode_fields: Vec<_> = fields
81 .iter()
82 .map(|(_, field_type)| {
83 quote! {
84 <#field_type as ::mcproto_types::TypeCodec>::decode(reader)
85 .map_err(|error| error.with_context(
86 ::mcproto_types::__private::CodecKind::#kind,
87 ))?
88 }
89 })
90 .collect();
91 let construct = match &data.fields {
92 Fields::Named(fields) => {
93 let names = fields
94 .named
95 .iter()
96 .map(|field| field.ident.as_ref().unwrap());
97 quote! { Self { #(#names: #decode_fields,)* } }
98 }
99 Fields::Unnamed(_) => quote! { Self(#(#decode_fields,)*) },
100 Fields::Unit => quote! { Self },
101 };
102
103 Ok(quote! {
104 impl #impl_generics ::mcproto_types::TypeCodec for #name #type_generics #where_clause {
105 fn encode(
106 &self,
107 writer: &mut impl ::std::io::Write,
108 ) -> ::std::result::Result<(), ::mcproto_types::__private::CodecError> {
109 #(#encode_fields)*
110 ::std::result::Result::Ok(())
111 }
112
113 fn decode(
114 reader: &mut impl ::std::io::Read,
115 ) -> ::std::result::Result<Self, ::mcproto_types::__private::CodecError> {
116 ::std::result::Result::Ok(#construct)
117 }
118 }
119 })
120}
121
122fn type_struct_codec_kind(input: &DeriveInput) -> Result<Ident> {
123 let mut kind = None;
124 for attribute in &input.attrs {
125 if !attribute.path().is_ident("type_struct_codec") {
126 continue;
127 }
128 attribute.parse_nested_meta(|meta| {
129 if !meta.path.is_ident("kind") {
130 return Err(meta.error("expected `kind = CodecKindVariant`"));
131 }
132 if kind.is_some() {
133 return Err(meta.error("duplicate `kind` argument"));
134 }
135 kind = Some(meta.value()?.parse()?);
136 Ok(())
137 })?;
138 }
139 kind.ok_or_else(|| {
140 Error::new_spanned(
141 input,
142 "TypeStructCodec requires `#[type_struct_codec(kind = CodecKindVariant)]`",
143 )
144 })
145}
146
147#[proc_macro_derive(ProtocolEnum, attributes(protocol_enum))]
165pub fn derive_protocol_enum(input: TokenStream) -> TokenStream {
166 let input = parse_macro_input!(input as DeriveInput);
167 match expand_protocol_enum(&input) {
168 Ok(tokens) => tokens.into(),
169 Err(error) => error.into_compile_error().into(),
170 }
171}
172
173fn expand_protocol_enum(input: &DeriveInput) -> Result<proc_macro2::TokenStream> {
174 let repr = enum_repr(input)?;
175 let Data::Enum(data) = &input.data else {
176 return Err(Error::new_spanned(
177 input,
178 "ProtocolEnum can only be derived for enums",
179 ));
180 };
181 if data.variants.is_empty() {
182 return Err(Error::new_spanned(
183 input,
184 "ProtocolEnum requires at least one enum variant",
185 ));
186 }
187
188 let mut variants = Vec::with_capacity(data.variants.len());
189 for variant in &data.variants {
190 if !matches!(variant.fields, Fields::Unit) {
191 return Err(Error::new_spanned(
192 variant,
193 "ProtocolEnum only supports fieldless enum variants",
194 ));
195 }
196 variants.push(&variant.ident);
197 }
198
199 let name = &input.ident;
200 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
201
202 Ok(quote! {
203 impl #impl_generics ::mcproto_types::ProtocolEnum for #name #type_generics #where_clause {
204 type Repr = #repr;
205
206 fn discriminant(&self) -> i128 {
207 match self {
208 #(Self::#variants => Self::#variants as i128,)*
209 }
210 }
211
212 fn to_repr(&self) -> ::std::option::Option<Self::Repr> {
213 <Self::Repr as ::mcproto_types::EnumRepr>::from_discriminant(
214 <Self as ::mcproto_types::ProtocolEnum>::discriminant(self),
215 )
216 }
217
218 fn from_repr(repr: Self::Repr) -> ::std::option::Option<Self> {
219 let value = <Self::Repr as ::mcproto_types::EnumRepr>::discriminant(&repr);
220 match value {
221 #(value if value == Self::#variants as i128 => ::std::option::Option::Some(Self::#variants),)*
222 _ => ::std::option::Option::None,
223 }
224 }
225 }
226
227 impl #impl_generics ::mcproto_types::TypeCodec for #name #type_generics #where_clause {
228 fn encode(
229 &self,
230 writer: &mut impl ::std::io::Write,
231 ) -> ::std::result::Result<(), ::mcproto_types::__private::CodecError> {
232 ::mcproto_types::__private::encode_protocol_enum(self, writer)
233 }
234
235 fn decode(
236 reader: &mut impl ::std::io::Read,
237 ) -> ::std::result::Result<Self, ::mcproto_types::__private::CodecError> {
238 ::mcproto_types::__private::decode_protocol_enum(reader)
239 }
240 }
241 })
242}
243
244fn enum_repr(input: &DeriveInput) -> Result<Type> {
245 let mut repr = None;
246
247 for attribute in &input.attrs {
248 if !attribute.path().is_ident("protocol_enum") {
249 continue;
250 }
251
252 attribute.parse_nested_meta(|meta| {
253 if !meta.path.is_ident("repr") {
254 return Err(meta.error("expected `repr = Type`"));
255 }
256 if repr.is_some() {
257 return Err(meta.error("duplicate `repr` argument"));
258 }
259
260 repr = Some(meta.value()?.parse()?);
261 Ok(())
262 })?;
263 }
264
265 repr.ok_or_else(|| {
266 Error::new_spanned(
267 input,
268 "ProtocolEnum requires `#[protocol_enum(repr = Type)]`",
269 )
270 })
271}