1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use proc_macro_crate::{crate_name, FoundCrate};
4use quote::{quote, ToTokens};
5use syn::{
6 parse::Parse, punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprLit, Generics, Ident,
7 Lit, Meta, MetaNameValue,
8};
9
10#[proc_macro_attribute]
52pub fn entity_allocatable(attr: TokenStream, item: TokenStream) -> TokenStream {
53 let mut attr = syn::parse_macro_input!(attr as EntityAllocatableAttr);
54 let item = syn::parse_macro_input!(item as syn::DeriveInput);
55 match &item.data {
57 syn::Data::Struct(_) | syn::Data::Enum(_) => {}
58 _ => {
59 return syn::Error::new(
60 item.ident.span(),
61 "#[entity_allocatable] 只能用于 struct 或 enum",
62 )
63 .to_compile_error()
64 .into();
65 }
66 }
67
68 attr.ident = item.ident.clone();
70 attr.generics = item.generics.clone();
71
72 let output = quote! {
73 #item
74 #attr
75 };
76 TokenStream::from(output)
77}
78
79struct EntityAllocatableAttr {
80 crate_path: proc_macro2::TokenStream,
81 ident: Ident,
82 generics: syn::Generics,
83 policy: AllocPolicy,
84 ptrid_wrapper: PtrIDWrapper,
85}
86
87impl Parse for EntityAllocatableAttr {
88 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
89 let crate_path: proc_macro2::TokenStream = match crate_name("mtb-entity-slab") {
91 Ok(FoundCrate::Itself) => quote! { crate },
92 Ok(FoundCrate::Name(name)) => {
93 let ident = Ident::new(&name, proc_macro2::Span::call_site());
94 quote! { #ident }
95 }
96 Err(_) => quote! { mtb_entity_slab },
97 };
98
99 let mut ret = Self {
100 crate_path,
101 ident: syn::parse_quote! { __entity_placeholder }, generics: syn::Generics::default(),
103 policy: AllocPolicy::Policy256,
104 ptrid_wrapper: PtrIDWrapper::Default,
105 };
106 let metas = Punctuated::<Meta, syn::token::Comma>::parse_terminated(input)?;
108 for meta in metas {
109 ret.parse_meta(meta)?;
110 }
111 Ok(ret)
112 }
113}
114
115impl EntityAllocatableAttr {
116 fn parse_meta(&mut self, meta: Meta) -> syn::Result<()> {
117 const UNSUPPORTED_ARG: &str =
118 "Unsupported argument; available arguments: policy=..., ptrid=..., wrapper=..., opaque_wrapper=...";
119
120 let Meta::NameValue(named) = meta else {
121 return Err(Error::new(meta.span(), UNSUPPORTED_ARG));
122 };
123 let Some(ident) = named.path.get_ident() else {
124 return Err(Error::new(named.path.span(), UNSUPPORTED_ARG));
125 };
126 match ident.to_string().as_str() {
127 "policy" => self.parse_policy(&named),
128 "ptrid" => self.parse_ptrid(&named),
129 "wrapper" => self.parse_wrapper(&named),
130 "opaque_wrapper" => self.parse_opaque_wrapper(&named),
131 _ => return Err(Error::new(named.span(), UNSUPPORTED_ARG)),
132 }
133 }
134
135 fn parse_policy(&mut self, named: &MetaNameValue) -> syn::Result<()> {
136 const POLICY_TRUE_KIND: &str =
137 "policy should be an identifier or integer, e.g., Policy256 or 256";
138 self.policy = match &named.value {
139 Expr::Path(ep) => {
140 let last = ep.path.segments.last();
141 let ident = match last {
142 Some(last) => last.ident.to_string(),
143 None => String::new(),
144 };
145 AllocPolicy::from_str(&ident, ep.span())
146 }
147 Expr::Lit(ExprLit {
148 lit: Lit::Int(i), ..
149 }) => {
150 let s = i.base10_digits();
151 AllocPolicy::from_str(s, i.span())
152 }
153 other => Err(Error::new(other.span(), POLICY_TRUE_KIND)),
154 }?;
155 Ok(())
156 }
157 fn parse_ptrid(&mut self, named: &MetaNameValue) -> syn::Result<()> {
158 const PTRID_CONFLICTS: &str = "`ptrid` conflicts with `wrapper`/`opaque_wrapper`";
159 const REPEATED_PTRID: &str = "`ptrid` specified multiple times";
160 const SHOULD_BE_PATH: &str = "external `ptrid` must be a type path, e.g., MyPtrID";
161 match self.ptrid_wrapper {
162 PtrIDWrapper::Transparent(_) | PtrIDWrapper::Opaque(_) => {
163 return Err(Error::new(named.span(), PTRID_CONFLICTS));
164 }
165 PtrIDWrapper::External(_) => {
166 return Err(Error::new(named.span(), REPEATED_PTRID));
167 }
168 _ => {}
169 }
170 let Expr::Path(ep) = &named.value else {
171 return Err(Error::new(named.value.span(), SHOULD_BE_PATH));
172 };
173 let ty: syn::Type = syn::parse_quote! { #ep };
174 self.ptrid_wrapper = PtrIDWrapper::External(ty);
175 Ok(())
176 }
177 fn parse_wrapper(&mut self, named: &MetaNameValue) -> Result<(), Error> {
178 const WRAPPER_CONFLICTS: &str = "`wrapper` conflicts with `ptrid`/`opaque_wrapper`";
179 const REPEATED_WRAPPER: &str = "`wrapper` specified multiple times";
180 const SHOULD_BE_INDENT: &str = "wrapper must be an identifier, e.g., wrapper = MyPtrID";
181 match self.ptrid_wrapper {
182 PtrIDWrapper::External(_) | PtrIDWrapper::Opaque(_) => {
183 return Err(Error::new(named.span(), WRAPPER_CONFLICTS));
184 }
185 PtrIDWrapper::Transparent(_) => {
186 return Err(Error::new(named.span(), REPEATED_WRAPPER));
187 }
188 _ => {}
189 }
190 let Expr::Path(ep) = &named.value else {
191 return Err(Error::new(named.value.span(), SHOULD_BE_INDENT));
192 };
193 if let Some(seg) = ep.path.segments.last() {
194 self.ptrid_wrapper = PtrIDWrapper::Transparent(seg.ident.clone());
195 Ok(())
196 } else {
197 Err(Error::new(ep.span(), SHOULD_BE_INDENT))
198 }
199 }
200 fn parse_opaque_wrapper(&mut self, named: &MetaNameValue) -> syn::Result<()> {
201 const WRAPPER_CONFLICTS: &str = "`opaque_wrapper` conflicts with `ptrid`/`wrapper`";
202 const REPEATED_WRAPPER: &str = "`opaque_wrapper` specified multiple times";
203 const SHOULD_BE_INDENT: &str =
204 "opaque_wrapper must be an identifier, e.g., opaque_wrapper = MyPtrID";
205 match self.ptrid_wrapper {
206 PtrIDWrapper::External(_) | PtrIDWrapper::Transparent(_) => {
207 return Err(Error::new(named.span(), WRAPPER_CONFLICTS));
208 }
209 PtrIDWrapper::Opaque(_) => {
210 return Err(Error::new(named.span(), REPEATED_WRAPPER));
211 }
212 _ => {}
213 }
214 let Expr::Path(ep) = &named.value else {
215 return Err(Error::new(named.value.span(), SHOULD_BE_INDENT));
216 };
217 if let Some(seg) = ep.path.segments.last() {
218 self.ptrid_wrapper = PtrIDWrapper::Opaque(seg.ident.clone());
219 Ok(())
220 } else {
221 Err(Error::new(ep.span(), SHOULD_BE_INDENT))
222 }
223 }
224}
225
226impl ToTokens for EntityAllocatableAttr {
227 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
228 let crate_path = &self.crate_path;
229 let ident = &self.ident;
230 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
231 let entity_ty = quote! { #ident #ty_generics };
232 let ptrid_ty_tokens = self
233 .ptrid_wrapper
234 .ptrid_ty_tokens(crate_path, ident, &ty_generics);
235 let policy_ty_tokens = self.policy.to_tokens(crate_path);
236
237 tokens.extend(quote! {
238 impl #impl_generics #crate_path::IEntityAllocatable for #ident #ty_generics #where_clause {
239 type AllocatePolicyT = #policy_ty_tokens<#entity_ty>;
240 type PtrID = #ptrid_ty_tokens;
241 }
242 });
243
244 if let Some(wrapper) = self
245 .ptrid_wrapper
246 .define_wrapper(crate_path, ident, &self.generics)
247 {
248 tokens.extend(wrapper);
249 }
250 }
251}
252
253enum PtrIDWrapper {
254 External(syn::Type),
255 Transparent(Ident),
256 Opaque(Ident),
257 Default,
258}
259impl PtrIDWrapper {
260 fn define_wrapper(
261 &self,
262 crate_path: &proc_macro2::TokenStream,
263 ident: &Ident,
264 generics: &Generics,
265 ) -> Option<proc_macro2::TokenStream> {
266 use PtrIDWrapper::*;
267 let wrapper_ident = match self {
268 Transparent(id) | Opaque(id) => id,
269 _ => return None,
270 };
271 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
272 let field_vis = if matches!(self, Transparent(_)) {
273 quote!(pub)
274 } else {
275 quote!()
276 };
277
278 let debug_body = if matches!(self, Opaque(_)) {
280 quote! { write!(f, concat!(stringify!(#wrapper_ident), "(<opaque>)")) }
281 } else {
282 quote! { write!(f, concat!(stringify!(#wrapper_ident), "({:p})"), self.0) }
283 };
284
285 Some(quote! {
286 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
287 pub struct #wrapper_ident #impl_generics (#field_vis #crate_path::PtrID<#ident #ty_generics>) #where_clause;
288
289 impl #impl_generics std::fmt::Debug for #wrapper_ident #ty_generics #where_clause {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 #debug_body
292 }
293 }
294 impl #impl_generics From<#crate_path::PtrID<#ident #ty_generics>> for #wrapper_ident #ty_generics #where_clause {
295 fn from(p: #crate_path::PtrID<#ident #ty_generics>) -> Self { Self(p) }
296 }
297 impl #impl_generics From<#wrapper_ident #ty_generics> for #crate_path::PtrID<#ident #ty_generics> #where_clause {
298 fn from(x: #wrapper_ident #ty_generics) -> Self { x.0 }
299 }
300 })
301 }
302 fn ptrid_ty_tokens(
303 &self,
304 crate_path: &proc_macro2::TokenStream,
305 ident: &Ident,
306 ty_generics: &syn::TypeGenerics,
307 ) -> proc_macro2::TokenStream {
308 use PtrIDWrapper::*;
309 match self {
310 External(ty) => quote! { #ty },
311 Transparent(wrapper_ident) | Opaque(wrapper_ident) => {
312 quote! { #wrapper_ident #ty_generics }
313 }
314 Default => quote! { #crate_path::PtrID<#ident #ty_generics> },
315 }
316 }
317}
318
319enum AllocPolicy {
320 Policy128,
321 Policy256,
322 Policy512,
323 Policy1024,
324 Policy2048,
325 Policy4096,
326}
327
328impl AllocPolicy {
329 const ERRMSG: &str =
330 "Unknown allocation policy. Expected one of: Policy{128|256|512|1024|2048|4096}";
331
332 fn to_tokens(&self, crate_path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
333 match self {
334 AllocPolicy::Policy128 => quote! { #crate_path::EntityAllocPolicy128 },
335 AllocPolicy::Policy256 => quote! { #crate_path::EntityAllocPolicy256 },
336 AllocPolicy::Policy512 => quote! { #crate_path::EntityAllocPolicy512 },
337 AllocPolicy::Policy1024 => quote! { #crate_path::EntityAllocPolicy1024 },
338 AllocPolicy::Policy2048 => quote! { #crate_path::EntityAllocPolicy2048 },
339 AllocPolicy::Policy4096 => quote! { #crate_path::EntityAllocPolicy4096 },
340 }
341 }
342
343 fn from_str(s: &str, errpos: Span) -> syn::Result<Self> {
344 match s {
345 "128" | "Policy128" | "EntityAllocPolicy128" => Ok(AllocPolicy::Policy128),
346 "256" | "Policy256" | "EntityAllocPolicy256" => Ok(AllocPolicy::Policy256),
347 "512" | "Policy512" | "EntityAllocPolicy512" => Ok(AllocPolicy::Policy512),
348 "1024" | "Policy1024" | "EntityAllocPolicy1024" => Ok(AllocPolicy::Policy1024),
349 "2048" | "Policy2048" | "EntityAllocPolicy2048" => Ok(AllocPolicy::Policy2048),
350 "4096" | "Policy4096" | "EntityAllocPolicy4096" => Ok(AllocPolicy::Policy4096),
351 _ => Err(syn::Error::new(errpos, Self::ERRMSG)),
352 }
353 }
354}