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}