Skip to main content

basalt_derive/
lib.rs

1//! Proc-macro crate for Minecraft protocol serialization.
2//!
3//! Provides two ways to generate `Encode`, `Decode`, and `EncodedSize` impls:
4//!
5//! ## `#[packet(id = N)]` — for protocol packets
6//!
7//! Attribute macro that generates all three trait impls plus a `PACKET_ID`
8//! constant. Use this on every packet struct:
9//!
10//! ```ignore
11//! #[derive(Debug, Clone, PartialEq)]
12//! #[packet(id = 0x00)]
13//! pub struct StatusRequest;
14//! ```
15//!
16//! ## `#[derive(Encode, Decode, EncodedSize)]` — for non-packet types
17//!
18//! Standard derive macros for inner data structures, enums, and other types
19//! that need serialization but aren't protocol packets:
20//!
21//! ```ignore
22//! #[derive(Debug, Encode, Decode, EncodedSize)]
23//! pub struct SomeInnerData {
24//!     pub value: i32,
25//! }
26//! ```
27//!
28//! ## Field attributes
29//!
30//! - `#[field(varint)]` — encode i32/i64 as VarInt/VarLong
31//! - `#[field(length = "varint")]` — VarInt length prefix for Vec
32//! - `#[field(optional)]` — boolean-prefixed Option
33//! - `#[field(rest)]` — consume remaining bytes (last field only, must be Vec<u8>)
34//!
35//! ## Enum attributes
36//!
37//! - `#[variant(id = N)]` — explicit discriminant (default: sequential from 0)
38
39mod attrs;
40mod codegen;
41mod decode;
42mod encode;
43mod packet;
44mod size;
45
46use proc_macro::TokenStream;
47use syn::{DeriveInput, parse_macro_input};
48
49/// Parses the `#[packet(id = N)]` attribute arguments.
50///
51/// Handles both positive (`id = 0x00`) and negative (`id = -1`) integer
52/// literals. The attribute arguments are passed separately from the item
53/// by the proc macro framework.
54struct PacketAttrArgs {
55    id: i32,
56}
57
58impl syn::parse::Parse for PacketAttrArgs {
59    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
60        let ident: syn::Ident = input.parse()?;
61        if ident != "id" {
62            return Err(syn::Error::new(ident.span(), "expected `id`"));
63        }
64        let _eq: syn::Token![=] = input.parse()?;
65
66        // Handle negative literals (unary minus + int literal)
67        let negative = input.peek(syn::Token![-]);
68        if negative {
69            let _neg: syn::Token![-] = input.parse()?;
70            let lit: syn::LitInt = input.parse()?;
71            Ok(Self {
72                id: -(lit.base10_parse::<i32>()?),
73            })
74        } else {
75            let lit: syn::LitInt = input.parse()?;
76            Ok(Self {
77                id: lit.base10_parse::<i32>()?,
78            })
79        }
80    }
81}
82
83/// Attribute macro for protocol packet structs.
84///
85/// Generates `Encode`, `Decode`, `EncodedSize` implementations and a
86/// `pub const PACKET_ID: i32` associated constant. The packet ID is NOT
87/// included in the wire format — it is only a declarative constant used
88/// by the packet registry for dispatch.
89///
90/// This replaces the need to manually derive all three traits on packet
91/// structs. Field attributes (`#[field(varint)]`, etc.) work the same
92/// as with the individual derives.
93///
94/// # Example
95///
96/// ```ignore
97/// #[derive(Debug, Clone, PartialEq)]
98/// #[packet(id = 0x00)]
99/// pub struct HandshakePacket {
100///     #[field(varint)]
101///     pub protocol_version: i32,
102///     pub server_address: String,
103///     pub server_port: u16,
104///     #[field(varint)]
105///     pub next_state: i32,
106/// }
107///
108/// assert_eq!(HandshakePacket::PACKET_ID, 0x00);
109/// ```
110#[proc_macro_attribute]
111pub fn packet(attr: TokenStream, item: TokenStream) -> TokenStream {
112    // Parse "id = N" from the attribute arguments
113    let packet_id = match syn::parse::<PacketAttrArgs>(attr) {
114        Ok(args) => args.id,
115        Err(err) => return err.to_compile_error().into(),
116    };
117    let input = parse_macro_input!(item as DeriveInput);
118    match packet::expand_packet(&input, packet_id) {
119        Ok(tokens) => {
120            // Re-emit the original struct definition + generated impls
121            let name = &input.ident;
122            let vis = &input.vis;
123            let attrs: Vec<_> = input
124                .attrs
125                .iter()
126                .filter(|a| !a.path().is_ident("packet"))
127                .collect();
128            let generics = &input.generics;
129
130            let struct_def = match &input.data {
131                syn::Data::Struct(data) => match &data.fields {
132                    syn::Fields::Named(fields) => {
133                        // Re-emit fields without #[field(...)] attributes
134                        let clean_fields: Vec<_> = fields
135                            .named
136                            .iter()
137                            .map(|f| {
138                                let field_attrs: Vec<_> = f
139                                    .attrs
140                                    .iter()
141                                    .filter(|a| !a.path().is_ident("field"))
142                                    .collect();
143                                let vis = &f.vis;
144                                let name = &f.ident;
145                                let ty = &f.ty;
146                                quote::quote! {
147                                    #(#field_attrs)*
148                                    #vis #name: #ty
149                                }
150                            })
151                            .collect();
152                        quote::quote! {
153                            #(#attrs)*
154                            #vis struct #name #generics {
155                                #(#clean_fields),*
156                            }
157                        }
158                    }
159                    syn::Fields::Unit => {
160                        quote::quote! {
161                            #(#attrs)*
162                            #vis struct #name #generics;
163                        }
164                    }
165                    syn::Fields::Unnamed(fields) => {
166                        let fields = &fields.unnamed;
167                        quote::quote! {
168                            #(#attrs)*
169                            #vis struct #name #generics(#fields);
170                        }
171                    }
172                },
173                _ => {
174                    return syn::Error::new_spanned(input, "#[packet] can only be used on structs")
175                        .to_compile_error()
176                        .into();
177                }
178            };
179
180            let combined = quote::quote! {
181                #struct_def
182                #tokens
183            };
184            combined.into()
185        }
186        Err(err) => err.to_compile_error().into(),
187    }
188}
189
190/// Derives the `Encode` trait for a struct or enum.
191///
192/// For structs, encodes each field in declaration order. Field attributes
193/// control encoding behavior (varint, optional, length-prefixed, rest).
194///
195/// For enums, writes a VarInt discriminant followed by the variant's fields.
196/// Discriminants are sequential from 0 unless overridden with `#[variant(id)]`.
197///
198/// **For packet structs, use `#[packet(id = N)]` instead** — it generates
199/// Encode, Decode, EncodedSize, and PACKET_ID all at once.
200#[proc_macro_derive(Encode, attributes(field, variant))]
201pub fn derive_encode(input: TokenStream) -> TokenStream {
202    let input = parse_macro_input!(input as DeriveInput);
203    match encode::derive_encode(&input) {
204        Ok(tokens) => tokens.into(),
205        Err(err) => err.to_compile_error().into(),
206    }
207}
208
209/// Derives the `Decode` trait for a struct or enum.
210///
211/// For structs, decodes each field in declaration order.
212///
213/// For enums, reads a VarInt discriminant and decodes the matching variant.
214/// Returns `Error::InvalidData` for unknown discriminants.
215///
216/// **For packet structs, use `#[packet(id = N)]` instead.**
217#[proc_macro_derive(Decode, attributes(field, variant))]
218pub fn derive_decode(input: TokenStream) -> TokenStream {
219    let input = parse_macro_input!(input as DeriveInput);
220    match decode::derive_decode(&input) {
221        Ok(tokens) => tokens.into(),
222        Err(err) => err.to_compile_error().into(),
223    }
224}
225
226/// Derives the `EncodedSize` trait for a struct or enum.
227///
228/// For structs, sums the encoded size of each field.
229///
230/// For enums, returns the VarInt discriminant size plus the matched
231/// variant's fields size.
232///
233/// **For packet structs, use `#[packet(id = N)]` instead.**
234#[proc_macro_derive(EncodedSize, attributes(field, variant))]
235pub fn derive_encoded_size(input: TokenStream) -> TokenStream {
236    let input = parse_macro_input!(input as DeriveInput);
237    match size::derive_encoded_size(&input) {
238        Ok(tokens) => tokens.into(),
239        Err(err) => err.to_compile_error().into(),
240    }
241}