1use {
2 proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree},
3 quote::{quote, quote_spanned},
4 syn::{
5 parse::{Parse, ParseStream},
6 parse_macro_input, parse_quote,
7 spanned::Spanned,
8 Attribute, Error, Generics, Item, ItemEnum, ItemStruct, LitInt, Path, Token, Type,
9 },
10};
11
12#[proc_macro_derive(Linearize, attributes(linearize))]
80pub fn derive_linearize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
81 let mut input: Input = parse_macro_input!(input as Input);
82 let crate_name = &input.attributes.crate_name;
83 let FullyLinearized {
84 linearize,
85 delinearize,
86 const_linearize,
87 const_delinearize,
88 const_names,
89 consts,
90 max_len,
91 } = input.build_linearize();
92 let where_clause = input.generics.make_where_clause();
93 for ty in &input.critical_types {
94 where_clause
95 .predicates
96 .push(parse_quote!(#ty: #crate_name::Linearize));
97 }
98 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
99 let ident = input.ident;
100 let mut const_impl = quote! {};
101 if input.attributes.enable_const {
102 const_impl = quote! {
103 #[doc(hidden)]
104 impl #impl_generics #ident #type_generics #where_clause {
105 #[inline]
106 pub const fn __linearize_d66aa8fa_6974_4651_b2b7_75291a9e7105(&self) -> usize {
107 #const_linearize
108 }
109
110 #[inline]
111 pub const unsafe fn __from_linear_unchecked_fb2f0b31_5b5a_48b4_9264_39d0bdf94f1d(linear: usize) -> Self {
112 #const_delinearize
113 }
114 }
115 };
116 }
117 let res = quote_spanned! { input.span =>
118 #[allow(clippy::modulo_one, clippy::manual_range_contains)]
119 const _: () = {
120 trait __C {
121 #(const #const_names: usize;)*
122 }
123
124 impl #impl_generics __C for #ident #type_generics #where_clause {
125 #(#consts)*
126 }
127
128 #[automatically_derived]
147 unsafe impl #impl_generics
148 #crate_name::Linearize for #ident #type_generics
149 #where_clause
150 {
151 type Storage<__T> = [__T; <Self as #crate_name::Linearize>::LENGTH];
152
153 type CopyStorage<__T> = [__T; <Self as #crate_name::Linearize>::LENGTH] where __T: Copy;
154
155 const LENGTH: usize = <Self as __C>::#max_len;
156
157 #[inline]
158 fn linearize(&self) -> usize {
159 #linearize
160 }
161
162 #[inline]
163 unsafe fn from_linear_unchecked(linear: usize) -> Self {
164 #delinearize
165 }
166 }
167
168 #const_impl
169 };
170 };
171 res.into()
172}
173
174struct Input {
175 span: Span,
176 ident: Ident,
177 generics: Generics,
178 critical_types: Vec<Type>,
179 kind: Kind,
180 attributes: InputAttributes,
181}
182
183struct InputAttributes {
184 crate_name: Path,
185 enable_const: bool,
186}
187
188#[derive(Default)]
189struct InputAttributesOpt {
190 crate_name: Option<Path>,
191 enable_const: bool,
192}
193
194enum Kind {
195 Struct(StructInput),
196 Enum(EnumInput),
197}
198
199struct StructInput {
200 fields: Vec<StructField>,
201}
202
203struct EnumInput {
204 variants: Vec<EnumVariant>,
205}
206
207struct EnumVariant {
208 ident: Ident,
209 fields: Vec<StructField>,
210}
211
212struct PartialLinearized {
213 linearize: TokenStream,
214 delinearize: TokenStream,
215 const_linearize: TokenStream,
216 const_delinearize: TokenStream,
217 max_len: Option<TokenStream>,
218}
219
220struct FullyLinearized {
221 linearize: TokenStream,
222 delinearize: TokenStream,
223 const_linearize: TokenStream,
224 const_delinearize: TokenStream,
225 const_names: Vec<Ident>,
226 consts: Vec<TokenStream>,
227 max_len: Ident,
228}
229
230struct StructField {
231 original_name: Option<Ident>,
232 generated_name: Option<Ident>,
233 ty: Type,
234}
235
236fn build_linearize_struct(
237 input: &Input,
238 fields: &[StructField],
239 base: &Ident,
240) -> PartialLinearized {
241 let crate_name = &input.attributes.crate_name;
242 let mut linearize_parts = vec![];
243 let mut delinearize_parts = vec![];
244 let mut const_linearize_parts = vec![];
245 let mut const_delinearize_parts = vec![];
246 let mut max_len = quote!(1usize);
247 for (idx, field) in fields.iter().enumerate().rev() {
248 let idx = LitInt::new(&idx.to_string(), Span::call_site());
249 let ref_name = match &field.generated_name {
250 Some(i) => quote! {#i},
251 None => match &field.original_name {
252 Some(i) => quote! { &self.#i },
253 None => quote! { &self.#idx },
254 },
255 };
256 let mut_name = match &field.original_name {
257 Some(i) => quote! { #i },
258 None => quote! { #idx },
259 };
260 let ty = &field.ty;
261 linearize_parts.push(quote! {
262 res = res.wrapping_add(<#ty as #crate_name::Linearize>::linearize(#ref_name).wrapping_mul(const { #max_len }));
263 });
264 delinearize_parts.push(quote! {
265 #mut_name: {
266 let idx = (linear / const { #max_len }) % <#ty as #crate_name::Linearize>::LENGTH;
267 <#ty as #crate_name::Linearize>::from_linear_unchecked(idx)
268 },
269 });
270 if input.attributes.enable_const {
271 const_linearize_parts.push(quote! {
272 res = res.wrapping_add(<#ty>::__linearize_d66aa8fa_6974_4651_b2b7_75291a9e7105(#ref_name).wrapping_mul(const { #max_len }));
273 });
274 const_delinearize_parts.push(quote! {
275 #mut_name: {
276 let idx = (linear / const { #max_len }) % <#ty as #crate_name::Linearize>::LENGTH;
277 <#ty>::__from_linear_unchecked_fb2f0b31_5b5a_48b4_9264_39d0bdf94f1d(idx)
278 },
279 });
280 }
281 max_len = quote! {
282 #max_len * <#ty as #crate_name::Linearize>::LENGTH
283 };
284 }
285 delinearize_parts.reverse();
286 const_delinearize_parts.reverse();
287 let make_linearize = |parts: &[TokenStream]| {
288 if fields.is_empty() {
289 quote! { <Self as __C>::#base }
290 } else {
291 quote! {
292 let mut res = <Self as __C>::#base;
293 #(#parts)*
294 res
295 }
296 }
297 };
298 let make_delinearize = |parts: &[TokenStream]| {
299 quote! {
300 { #(#parts)* }
301 }
302 };
303 let mut max_len = Some(max_len);
304 if fields.is_empty() {
305 max_len = None;
306 }
307 PartialLinearized {
308 linearize: make_linearize(&linearize_parts),
309 delinearize: make_delinearize(&delinearize_parts),
310 const_linearize: make_linearize(&const_linearize_parts),
311 const_delinearize: make_delinearize(&const_delinearize_parts),
312 max_len,
313 }
314}
315
316impl StructInput {
317 fn build_linearize(&self, input: &Input) -> FullyLinearized {
318 let b0 = Ident::new("B0", Span::mixed_site());
319 let b1 = Ident::new("B1", Span::mixed_site());
320 let PartialLinearized {
321 linearize,
322 delinearize,
323 const_linearize,
324 const_delinearize,
325 max_len,
326 } = build_linearize_struct(input, &self.fields, &b0);
327 let max_len = max_len.unwrap_or_else(|| quote!(1usize));
328 let mut consts = vec![];
329 consts.push(quote! { const B0: usize = 0; });
330 consts.push(quote! { const B1: usize = #max_len; });
331 FullyLinearized {
332 linearize,
333 delinearize: quote! { Self #delinearize },
334 const_linearize,
335 const_delinearize: quote! { Self #const_delinearize },
336 max_len: b1.clone(),
337 consts,
338 const_names: vec![b0, b1],
339 }
340 }
341}
342
343impl EnumInput {
344 fn build_linearize(&self, input: &Input) -> FullyLinearized {
345 let mut linearize_cases = vec![];
346 let mut delinearize_cases = vec![];
347 let mut const_linearize_cases = vec![];
348 let mut const_delinearize_cases = vec![];
349 let mut consts = vec![];
350 consts.push(quote! { const B0: usize = 0; });
351 let mut prev_const_name = Ident::new("B0", Span::mixed_site());
352 let mut const_base = prev_const_name.clone();
353 let mut const_base_offset = 0;
354 let mut const_names = vec![prev_const_name.clone()];
355 for (variant_idx, variant) in self.variants.iter().enumerate() {
356 let mut exposition = vec![];
357 for (idx, field) in variant.fields.iter().enumerate() {
358 let idx = LitInt::new(&idx.to_string(), Span::call_site());
359 let generated_name = field.generated_name.as_ref().unwrap();
360 match &field.original_name {
361 None => exposition.push(quote! { #idx: #generated_name }),
362 Some(i) => exposition.push(quote! { #i: #generated_name }),
363 }
364 }
365 let exposition = quote! {
366 { #(#exposition),* }
367 };
368 let PartialLinearized {
369 linearize,
370 delinearize,
371 const_linearize,
372 const_delinearize,
373 max_len,
374 } = build_linearize_struct(input, &variant.fields, &prev_const_name);
375 let next_base = match &max_len {
376 Some(len) => quote! { <Self as __C>::#prev_const_name + #len },
377 None => {
378 const_base_offset += 1;
379 let offset = Literal::usize_unsuffixed(const_base_offset);
380 quote! { <Self as __C>::#const_base + #offset }
381 }
382 };
383 let ident = &variant.ident;
384 linearize_cases.push(quote! {
385 Self::#ident #exposition => {
386 #linearize
387 }
388 });
389 if input.attributes.enable_const {
390 const_linearize_cases.push(quote! {
391 Self::#ident #exposition => {
392 #const_linearize
393 }
394 });
395 }
396 let const_name = Ident::new(&format!("B{}", variant_idx + 1), Span::mixed_site());
397 consts.push(quote! { const #const_name: usize = #next_base; });
398 if variant.fields.is_empty() {
399 let guard = if input.generics.params.is_empty() {
400 quote! {
401 <Self as __C>::#prev_const_name
402 }
403 } else {
404 quote! {
405 n if n == <Self as __C>::#prev_const_name
406 }
407 };
408 delinearize_cases.push(quote! {
409 #guard => Self::#ident { },
410 });
411 if input.attributes.enable_const {
412 const_delinearize_cases.push(quote! {
413 #guard => Self::#ident { },
414 });
415 }
416 } else {
417 let make_case = |delinearize: &TokenStream| {
418 quote! {
419 #[allow(clippy::impossible_comparisons)]
420 n if n >= <Self as __C>::#prev_const_name && n < <Self as __C>::#const_name => {
421 let linear = linear.wrapping_sub(<Self as __C>::#prev_const_name);
422 Self::#ident #delinearize
423 },
424 }
425 };
426 delinearize_cases.push(make_case(&delinearize));
427 if input.attributes.enable_const {
428 const_delinearize_cases.push(make_case(&const_delinearize));
429 }
430 }
431 prev_const_name = const_name;
432 const_names.push(prev_const_name.clone());
433 if max_len.is_some() {
434 const_base = prev_const_name.clone();
435 const_base_offset = 0;
436 }
437 }
438 let make_linearize = |cases: &[TokenStream]| {
439 if self.variants.is_empty() {
440 quote! {
441 #[cold]
442 const fn unreachable() -> ! {
443 unsafe { ::core::hint::unreachable_unchecked() }
444 }
445 unreachable()
446 }
447 } else {
448 quote! {
449 match self {
450 #(#cases)*
451 }
452 }
453 }
454 };
455 let make_delinearize = |cases: &[TokenStream]| {
456 quote! {
457 match linear {
458 #(#cases)*
459 _ => {
460 #[cold]
461 const fn unreachable() -> ! {
462 unsafe { ::core::hint::unreachable_unchecked() }
463 }
464 unreachable()
465 },
466 }
467 }
468 };
469 FullyLinearized {
470 linearize: make_linearize(&linearize_cases),
471 const_linearize: make_linearize(&const_linearize_cases),
472 delinearize: make_delinearize(&delinearize_cases),
473 const_delinearize: make_delinearize(&const_delinearize_cases),
474 max_len: prev_const_name,
475 const_names,
476 consts,
477 }
478 }
479}
480
481impl Input {
482 fn parse_enum(input: ItemEnum) -> syn::Result<Self> {
483 let span = input.span();
484 let mut critical_types = Vec::new();
485 let mut variants = vec![];
486 let mut i = 0;
487 for variant in input.variants {
488 let mut fields = vec![];
489 for field in variant.fields {
490 critical_types.push(field.ty.clone());
491 let name = Ident::new(&format!("f{i}"), Span::mixed_site());
492 i += 1;
493 fields.push(StructField {
494 original_name: field.ident,
495 generated_name: Some(name),
496 ty: field.ty,
497 })
498 }
499 variants.push(EnumVariant {
500 ident: variant.ident,
501 fields,
502 });
503 }
504 Ok(Self {
505 span,
506 ident: input.ident,
507 generics: input.generics,
508 critical_types,
509 kind: Kind::Enum(EnumInput { variants }),
510 attributes: parse_attributes(&input.attrs)?,
511 })
512 }
513
514 fn parse_struct(input: ItemStruct) -> syn::Result<Self> {
515 let span = input.span();
516 let mut critical_types = Vec::new();
517 let mut fields = vec![];
518 for field in input.fields {
519 critical_types.push(field.ty.clone());
520 fields.push(StructField {
521 original_name: field.ident,
522 generated_name: None,
523 ty: field.ty,
524 });
525 }
526 Ok(Self {
527 span,
528 ident: input.ident,
529 generics: input.generics,
530 critical_types,
531 kind: Kind::Struct(StructInput { fields }),
532 attributes: parse_attributes(&input.attrs)?,
533 })
534 }
535
536 fn build_linearize(&self) -> FullyLinearized {
537 match &self.kind {
538 Kind::Struct(s) => s.build_linearize(self),
539 Kind::Enum(e) => e.build_linearize(self),
540 }
541 }
542}
543
544fn parse_attributes(attrs: &[Attribute]) -> syn::Result<InputAttributes> {
545 let mut res = InputAttributesOpt::default();
546 for attr in attrs {
547 if !attr.meta.path().is_ident("linearize") {
548 continue;
549 }
550 let new: InputAttributesOpt = attr.meta.require_list()?.parse_args()?;
551 res.enable_const |= new.enable_const;
552 macro_rules! opt {
553 ($name:ident) => {
554 if new.$name.is_some() {
555 res.$name = new.$name;
556 }
557 };
558 }
559 opt!(crate_name);
560 }
561 Ok(InputAttributes {
562 crate_name: res.crate_name.unwrap_or_else(|| parse_quote!(::linearize)),
563 enable_const: res.enable_const,
564 })
565}
566
567impl Parse for InputAttributesOpt {
568 fn parse(input: ParseStream) -> syn::Result<Self> {
569 let mut res = Self::default();
570 while !input.is_empty() {
571 let key: TokenTree = input.parse()?;
572 match key.to_string().as_str() {
573 "crate" => {
574 let _: Token![=] = input.parse()?;
575 let path: Path = input.parse()?;
576 res.crate_name = Some(path);
577 }
578 "const" => {
579 res.enable_const = true;
580 }
581 _ => {
582 return Err(Error::new(
583 key.span(),
584 format!("Unknown attribute: {}", key),
585 ))
586 }
587 }
588 if !input.is_empty() {
589 let _: Token![,] = input.parse()?;
590 }
591 }
592 Ok(res)
593 }
594}
595
596impl Parse for Input {
597 fn parse(input: ParseStream) -> syn::Result<Self> {
598 let item: Item = input.parse()?;
599 match item {
600 Item::Enum(e) => Self::parse_enum(e),
601 Item::Struct(s) => Self::parse_struct(s),
602 _ => Err(Error::new(item.span(), "expected enum or struct")),
603 }
604 }
605}