neli_proc_macros/lib.rs
1//! Procedural macros to be used with the library
2//! [`neli`](https://github.com/jbaublitz/neli).
3//!
4//! All derive macros other than `Header` generate implicit type
5//! parameter bounds on every type parameter which can be overriden
6//! with struct attributes.
7
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::{parse, Item, Meta};
11
12#[macro_use]
13mod shared;
14
15mod derive_frombytes;
16mod derive_header;
17mod derive_size;
18mod derive_tobytes;
19mod neli_enum;
20
21use derive_frombytes::*;
22use derive_header::*;
23use derive_size::*;
24use derive_tobytes::*;
25use neli_enum::*;
26
27/// Converts an enum from the form:
28///
29/// ```no_compile
30/// use neli_proc_macros::neli_enum;
31///
32/// #[neli_enum(serialized_type = "u16")]
33/// pub enum MyConstants {
34/// ConstOne = 1,
35/// ConstTwo = 2,
36/// ConstThree = 3,
37/// }
38/// ```
39///
40/// to:
41///
42/// ```
43/// pub enum MyConstants {
44/// ConstOne,
45/// ConstTwo,
46/// ConstThree,
47/// }
48/// ```
49///
50/// with [`From`] implemented reflexively for `MyConstants` and
51/// [`u16`].
52#[proc_macro_attribute]
53pub fn neli_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
54 let attr_string = attr.to_string();
55 let meta =
56 parse::<Meta>(attr).unwrap_or_else(|_| panic!("{} is not a valid attribute", attr_string));
57 let enum_item = parse::<Item>(item).unwrap();
58
59 let enm = if let Item::Enum(e) = enum_item {
60 e
61 } else {
62 panic!("This macro only operates on enums");
63 };
64
65 TokenStream::from(generate_neli_enum(enm, meta))
66}
67
68/// Derives the neli `Size` trait for a struct or enum.
69///
70/// Acceptable struct attribute is:
71/// * `#[neli(size_bound = "T: MyTrait")]` which will generate a
72/// trait bound in the impl for the specified type parameter.
73///
74/// Implicit type parameter bound: `Size`.
75#[proc_macro_derive(Size, attributes(neli))]
76pub fn proc_macro_size(ts: TokenStream) -> TokenStream {
77 let item = parse::<Item>(ts).unwrap();
78 TokenStream::from(match item {
79 Item::Struct(strct) => impl_size_struct(strct),
80 Item::Enum(enm) => impl_size_enum(enm),
81 _ => panic!("Size can only be derived for structs and enums"),
82 })
83}
84
85/// Derives the neli `Header` trait for a struct or enum. Unlike
86/// other derive macros in this crate, the `Header` derive macro
87/// does not impose type parameter bounds on type parameters.
88/// See the accepted attribute for more information. The reason for
89/// this is that the last field is considered to be the payload.
90/// Because the payload may be represented by a type parameter,
91/// we cannot blindly restrict type parameters or else we impose
92/// an artificial restriction of `TypeSize` on the payload type
93/// parameter. This is a problem for the `Header` trait as the
94/// payload may be unsized even if the rest of the header is
95/// composed exclusively of statically sized types and are therefore
96/// compatible with the `TypeSize` trait.
97///
98/// Acceptable struct attribute is:
99/// * `#[neli(header_bound = "T: MyTrait")]` which will generate a
100/// trait bound in the impl for the specified type parameter.
101///
102/// While there is no implicit type parameter bound, every type
103/// parameter that does not correspond to a payload should have
104/// a specified type parameter bound of `TypeSize`.
105#[proc_macro_derive(Header, attributes(neli))]
106pub fn proc_macro_header(ts: TokenStream) -> TokenStream {
107 let item = parse::<Item>(ts).unwrap();
108 TokenStream::from(match item {
109 Item::Struct(strct) => impl_header_struct(strct),
110 _ => panic!("Header can only be derived for structs"),
111 })
112}
113
114/// Derives the neli `FromBytes` trait for a struct.
115///
116/// Acceptable struct attribute is:
117/// * `#[neli(from_bytes_bound = "T: MyTrait")]` which will generate
118/// a trait bound in the impl for the specified type parameter.
119/// * `#[neli(padding)]` which will add special handling for padding
120/// for this struct.
121///
122/// Acceptable field attribute forms are:
123/// * `#[neli(input = "input_expression")]` which may only be used
124/// once for a struct. The behavior of this attribute is that a
125/// bound requirement will change from the implicit `FromBytes` to
126/// an implicit `FromBytesWithInput` bound. The method in this trait
127/// will be called with `input_expression` as the input provided.
128/// * `#[neli(input)]` which will transparently pass the input
129/// provided in the `FromBytesWithInput` method through to the
130/// `FromBytesWithInput` method for this field unchanged according
131/// to the rules described above.
132/// * `#[neli(size = "size_var_name")]` which allows specifying a size of the data type
133/// that is different from the input specified by `#[neli(input)]`. Not specifying
134/// this attribute defaults to using `input` as the size as well.
135///
136/// Implicit type parameter bound: `FromBytes`.
137#[proc_macro_derive(FromBytes, attributes(neli))]
138pub fn proc_macro_frombytes(ts: TokenStream) -> TokenStream {
139 let item = parse::<Item>(ts).unwrap();
140 TokenStream::from(match item {
141 Item::Struct(strct) => impl_frombytes_struct(strct, "FromBytes", "from_bytes", None, None),
142 _ => panic!("FromBytes can only be derived for structs"),
143 })
144}
145
146/// Derives the neli `FromBytesWithInput` trait for a struct.
147///
148/// Acceptable struct attribute is:
149/// * `#[neli(from_bytes_bound = "T: MyTrait")]` which will generate
150/// a trait bound in the impl for the specified type parameter.
151/// * `#[neli(padding)]` which will add special handling for padding
152/// for this struct.
153///
154/// Acceptable field attribute forms are:
155/// * `#[neli(input = "input_expression")]` which may only be used
156/// once for a struct. The behavior of this attribute is that a
157/// bound requirement will change from the implicit `FromBytes` to
158/// an implicit `FromBytesWithInput` bound. The method in this trait
159/// will be called with `input_expression` as the input provided.
160/// * `#[neli(input)]` which will transparently pass the input
161/// provided in the `FromBytesWithInput` method through to the
162/// `FromBytesWithInput` method for this field unchanged according
163/// to the rules described above.
164/// * `#[neli(size = "size_var_name")]` which allows specifying a size of the data type
165/// that is different from the input specified by `#[neli(input)]`. Not specifying
166/// this attribute defaults to using `input` as the size as well.
167///
168/// Implicit type parameter bound: `FromBytes`.
169#[proc_macro_derive(FromBytesWithInput, attributes(neli))]
170pub fn proc_macro_frombyteswithinput(ts: TokenStream) -> TokenStream {
171 let item = parse::<Item>(ts).unwrap();
172 TokenStream::from(match item {
173 Item::Struct(strct) => impl_frombytes_struct(
174 strct,
175 "FromBytesWithInput",
176 "from_bytes_with_input",
177 Some(quote! {
178 type Input = usize;
179 }),
180 Some(quote! {
181 , input: Self::Input
182 }),
183 ),
184 _ => panic!("FromBytesWithInput can only be derived for structs"),
185 })
186}
187
188/// Derives the neli `ToBytes` trait for a struct or enum.
189///
190/// Acceptable struct attribute is:
191/// * `#[neli(to_bytes_bound = "T: MyTrait")]` which will generate a
192/// trait bound in the impl for the specified type parameter.
193/// * `#[neli(padding)]` which will add special handling for padding
194/// for this struct.
195///
196/// Implicit type parameter bound: `ToBytes`.
197#[proc_macro_derive(ToBytes, attributes(neli))]
198pub fn proc_macro_tobytes(ts: TokenStream) -> TokenStream {
199 let item = parse::<Item>(ts).unwrap();
200 TokenStream::from(match item {
201 Item::Struct(strct) => impl_tobytes_struct(strct),
202 Item::Enum(enm) => impl_tobytes_enum(enm),
203 _ => panic!("ToBytes can only be derived for structs and enums"),
204 })
205}