Skip to main content

wazabin_jstd_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
4
5/// Which primitive integer backs an `Identifier` tuple struct.
6enum Backing {
7    Usize,
8    U32,
9}
10
11#[proc_macro_derive(Identifier)]
12pub fn derive_identifier(input: TokenStream) -> TokenStream {
13    let input = parse_macro_input!(input as DeriveInput);
14    let name = input.ident;
15
16    let backing = match input.data {
17        Data::Struct(data) => match data.fields {
18            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
19                match fields.unnamed.first().map(|f| &f.ty) {
20                    Some(Type::Path(type_path)) if type_path.path.is_ident("usize") => {
21                        Some(Backing::Usize)
22                    }
23                    Some(Type::Path(type_path)) if type_path.path.is_ident("u32") => {
24                        Some(Backing::U32)
25                    }
26                    _ => None,
27                }
28            }
29            _ => None,
30        },
31        _ => None,
32    };
33
34    let Some(backing) = backing else {
35        return syn::Error::new_spanned(
36            name,
37            "Identifier can only be derived for tuple structs with a single `usize` or `u32` field, e.g. struct MyId(usize);",
38        )
39        .to_compile_error()
40        .into();
41    };
42
43    // `usize`-backed IDs keep their original, cast-free codegen so downstream
44    // clippy (`-D warnings`) never sees a no-op `as usize`. `u32`-backed IDs
45    // (the function-local IR IDs) cast to/from the `usize` lingua franca of
46    // `Registry`.
47    let (new_body, from_usize_body, into_usize_body, ser_body, de_body) = match backing {
48        Backing::Usize => (
49            quote! { Self(id) },
50            quote! { Self(value) },
51            quote! { id.0 },
52            quote! { self.0 as u64 },
53            quote! { Self(id as usize) },
54        ),
55        Backing::U32 => (
56            quote! { Self(id as u32) },
57            quote! { Self(value as u32) },
58            quote! { id.0 as usize },
59            quote! { self.0 as u64 },
60            quote! { Self(id as u32) },
61        ),
62    };
63
64    TokenStream::from(quote! {
65        impl #name {
66            /// Creates an identifier from its index.
67            pub const fn new(id: usize) -> Self {
68                #new_body
69            }
70        }
71
72        impl ::core::marker::Copy for #name {}
73
74        impl ::core::clone::Clone for #name {
75            fn clone(&self) -> Self {
76                *self
77            }
78        }
79
80        impl ::core::default::Default for #name {
81            fn default() -> Self {
82                Self(0)
83            }
84        }
85
86        impl ::core::cmp::PartialEq for #name {
87            fn eq(&self, other: &Self) -> bool {
88                self.0 == other.0
89            }
90        }
91
92        impl ::core::cmp::Eq for #name {}
93
94        impl ::core::cmp::PartialOrd for #name {
95            fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> {
96                ::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
97            }
98        }
99
100        impl ::core::cmp::Ord for #name {
101            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
102                ::core::cmp::Ord::cmp(&self.0, &other.0)
103            }
104        }
105
106        impl ::core::hash::Hash for #name {
107            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
108                ::core::hash::Hash::hash(&self.0, state);
109            }
110        }
111
112        impl ::core::fmt::Debug for #name {
113            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
114                f.debug_tuple(stringify!(#name)).field(&self.0).finish()
115            }
116        }
117
118        impl ::core::fmt::Display for #name {
119            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
120                write!(f, "{}", self.0)
121            }
122        }
123
124        impl From<usize> for #name {
125            fn from(value: usize) -> Self {
126                #from_usize_body
127            }
128        }
129
130        impl From<#name> for usize {
131            fn from(id: #name) -> usize {
132                #into_usize_body
133            }
134        }
135
136
137        impl ::jstd::registry::Identifier for #name {}
138
139        impl ::serde::Serialize for #name {
140            fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
141            where
142                S: ::serde::Serializer,
143            {
144                ::serde::Serialize::serialize(&(#ser_body), serializer)
145            }
146        }
147
148        impl<'de> ::serde::Deserialize<'de> for #name {
149            fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
150            where
151                D: ::serde::Deserializer<'de>,
152            {
153                let id = <u64 as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
154                Ok(#de_body)
155            }
156        }
157    })
158}