valence_protocol_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream as StdTokenStream;
4use proc_macro2::TokenStream;
5use quote::ToTokens;
6use syn::{
7    parse_quote, Attribute, GenericParam, Generics, Lifetime, LifetimeParam, LitInt, Result,
8    Variant,
9};
10
11mod decode;
12mod encode;
13mod packet;
14
15#[proc_macro_derive(Encode, attributes(packet))]
16pub fn derive_encode(item: StdTokenStream) -> StdTokenStream {
17    match encode::derive_encode(item.into()) {
18        Ok(tokens) => tokens.into(),
19        Err(e) => e.into_compile_error().into(),
20    }
21}
22
23#[proc_macro_derive(Decode, attributes(packet))]
24pub fn derive_decode(item: StdTokenStream) -> StdTokenStream {
25    match decode::derive_decode(item.into()) {
26        Ok(tokens) => tokens.into(),
27        Err(e) => e.into_compile_error().into(),
28    }
29}
30
31#[proc_macro_derive(Packet, attributes(packet))]
32pub fn derive_packet(item: StdTokenStream) -> StdTokenStream {
33    match packet::derive_packet(item.into()) {
34        Ok(tokens) => tokens.into(),
35        Err(e) => e.into_compile_error().into(),
36    }
37}
38
39fn pair_variants_with_discriminants(
40    variants: impl IntoIterator<Item = Variant>,
41) -> Result<Vec<(i32, Variant)>> {
42    let mut discriminant = 0;
43    variants
44        .into_iter()
45        .map(|v| {
46            if let Some(i) = parse_tag_attr(&v.attrs)? {
47                discriminant = i;
48            }
49
50            let pair = (discriminant, v);
51            discriminant += 1;
52            Ok(pair)
53        })
54        .collect::<Result<_>>()
55}
56
57fn parse_tag_attr(attrs: &[Attribute]) -> Result<Option<i32>> {
58    for attr in attrs {
59        if attr.path().is_ident("packet") {
60            let mut res = 0;
61
62            attr.parse_nested_meta(|meta| {
63                if meta.path.is_ident("tag") {
64                    res = meta.value()?.parse::<LitInt>()?.base10_parse::<i32>()?;
65                    Ok(())
66                } else {
67                    Err(meta.error("unrecognized argument"))
68                }
69            })?;
70
71            return Ok(Some(res));
72        }
73    }
74
75    Ok(None)
76}
77
78/// Adding our lifetime to the generics before calling `.split_for_impl()` would
79/// also add it to the resulting `ty_generics`, which we don't want. So I'm
80/// doing this hack.
81fn decode_split_for_impl(
82    mut generics: Generics,
83    lifetime: Lifetime,
84) -> (TokenStream, TokenStream, TokenStream) {
85    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
86
87    let mut impl_generics = impl_generics.to_token_stream();
88    let ty_generics = ty_generics.to_token_stream();
89    let where_clause = where_clause.to_token_stream();
90
91    if generics.lifetimes().next().is_none() {
92        generics
93            .params
94            .push(GenericParam::Lifetime(LifetimeParam::new(lifetime)));
95
96        impl_generics = generics.split_for_impl().0.to_token_stream();
97    }
98
99    (impl_generics, ty_generics, where_clause)
100}
101
102fn add_trait_bounds(generics: &mut Generics, trait_: TokenStream) {
103    for param in &mut generics.params {
104        if let GenericParam::Type(type_param) = param {
105            type_param.bounds.push(parse_quote!(#trait_))
106        }
107    }
108}