1#![recursion_limit = "128"]
2#![allow(warnings)]
3#![allow(clippy::needless_borrowed_reference)]
4#![allow(clippy::match_like_matches_macro)]
5#![allow(clippy::bool_assert_comparison)]
6#![allow(clippy::bool_comparison)]
7#![allow(clippy::match_ref_pats)] #![allow(clippy::needless_late_init)]
9#![allow(clippy::len_zero)]
10#![allow(clippy::let_and_return)]
11#![allow(clippy::collapsible_match)]
12#![allow(clippy::single_match)]
13
14extern crate proc_macro;
18extern crate proc_macro2;
19#[macro_use]
20extern crate quote;
21extern crate syn;
22#[macro_use]
23
24use crate::savefile_abi::is_well_known;
25use common::{
26 check_is_remove, compile_time_check_reprc, compile_time_size, get_extra_where_clauses, parse_attr_tag,
27 path_to_string, FieldInfo,
28};
29use proc_macro2::TokenStream;
30use proc_macro2::{Span, TokenTree};
31use quote::ToTokens;
32use std::collections::{HashMap, HashSet};
33#[allow(unused_imports)]
34use std::iter::IntoIterator;
35use syn::__private::bool;
36use syn::parse::Parse;
37use syn::spanned::Spanned;
38use syn::token::Paren;
39use syn::Type::Tuple;
40use syn::{
41 Attribute, Data, DeriveInput, FnArg, GenericArgument, GenericParam, Generics, Ident, ImplGenerics, Index,
42 ItemTrait, Lifetime, Pat, PathArguments, ReturnType, TraitItem, Type, TypeGenerics, TypeParamBound, TypeTuple,
43 WherePredicate,
44};
45
46pub(crate) fn doc_hidden(x: &Vec<Attribute>) -> TokenStream {
47 for attr in x {
48 if attr.path().is_ident("savefile_doc_hidden") {
49 return quote! {#[doc(hidden)]};
50 }
51 }
52 quote!()
53}
54macro_rules! abort {
55 ($span:expr, $($x:tt)*) => {
56 return Err(syn::Error::new($span, std::format!($($x)*)))
57 }
58}
59macro_rules! abort_call_site {
60 ($($x:tt)*) => {
61 return Err(syn::Error::new($crate::proc_macro2::Span::call_site(), std::format!($($x)*)))
62 }
63}
64fn implement_fields_serialize(
65 field_infos: Vec<FieldInfo>,
66 implicit_self: bool,
67 index: bool,
68) -> syn::Result<(TokenStream, Vec<TokenStream>)> {
69 let mut output = Vec::new();
70
71 let defspan = proc_macro2::Span::call_site();
72 let span = proc_macro2::Span::call_site();
73 let local_serializer = quote_spanned! { defspan => local_serializer};
74
75 let reprc = quote! {
76 _savefile::prelude::Packed
77 };
78
79 let mut deferred_reprc: Option<(usize , Vec<TokenStream>)> = None;
80 fn realize_any_deferred(
81 local_serializer: &TokenStream,
82 deferred_reprc: &mut Option<(usize, Vec<TokenStream>)>,
83 output: &mut Vec<TokenStream>,
84 ) {
85 let local_serializer: TokenStream = local_serializer.clone();
86 if let Some((_align, deferred)) = deferred_reprc.take() {
87 assert_eq!(deferred.is_empty(), false);
88 let mut conditions = vec![];
89 for item in deferred.windows(2) {
90 let a = item[0].clone();
91 let b = item[1].clone();
92 if conditions.is_empty() == false {
93 conditions.push(quote!(&&));
94 }
95 conditions.push(quote!(
96 std::ptr::addr_of!(#a).add(1) as *const u8 == std::ptr::addr_of!(#b) as *const u8
97 ));
98 }
99 if conditions.is_empty() {
100 conditions.push(quote!(true));
101 }
102 let mut fallbacks = vec![];
103 for item in deferred.iter() {
104 fallbacks.push(quote!(
105 <_ as _savefile::prelude::Serialize>::serialize(&#item, #local_serializer)?;
106 ));
107 }
108 if deferred.len() == 1 {
109 return output.push(quote!( #(#fallbacks)* ));
110 }
111 let mut iter = deferred.into_iter();
112 let deferred_from = iter.next().expect("expected deferred_from");
113 let deferred_to = iter.last().unwrap_or(deferred_from.clone());
114
115 output.push(
116 quote!(
117 unsafe {
118 if #(#conditions)* {
119 #local_serializer.raw_write_region(self,&#deferred_from,&#deferred_to, local_serializer.file_version)?;
120 } else {
121 #(#fallbacks)*
122 }
123 }
124 ));
125 }
126 }
127
128 let get_obj_id = |field: &FieldInfo| -> TokenStream {
129 let objid = if index {
130 assert!(implicit_self);
131 let id = syn::Index {
132 index: field.index,
133 span,
134 };
135 quote! { self.#id}
136 } else {
137 let id = field.ident.clone().expect("Expected identifier[3]");
138 if implicit_self {
139 quote! { self.#id}
140 } else {
141 quote! { *#id}
142 }
143 };
144 objid
145 };
146
147 let mut first_non_ignored_fieldinfo = None;
148 let mut last_non_ignored_fieldinfo = None;
149 for field in &field_infos {
150 {
151 let verinfo = parse_attr_tag(field.attrs)?;
152
153 if verinfo.ignore {
154 continue;
155 }
156 if first_non_ignored_fieldinfo.is_none() {
157 first_non_ignored_fieldinfo = Some(field);
158 }
159 last_non_ignored_fieldinfo = Some(field);
160
161 let (field_from_version, field_to_version) = (verinfo.version_from, verinfo.version_to);
162
163 let removed = check_is_remove(field.ty);
164
165 let type_size_align = compile_time_size(field.ty);
166 let compile_time_reprc = compile_time_check_reprc(field.ty) && type_size_align.is_some();
167
168 let obj_id = get_obj_id(field);
169
170 if field_from_version == 0 && field_to_version == std::u32::MAX {
171 if removed.is_removed() {
172 abort!(
173 field.ty.span(),
174 "The Removed type can only be used for removed fields. Use the savefile_versions attribute."
175 );
176 }
177
178 if compile_time_reprc {
179 let (_cursize, curalign) = type_size_align.expect("type_size_align");
180 if let Some((deferred_align, deferred_items)) = &mut deferred_reprc {
181 if *deferred_align == curalign {
182 deferred_items.push(obj_id);
183 continue;
184 }
185 } else {
186 deferred_reprc = Some((curalign, vec![obj_id]));
187 continue;
188 }
189 }
190 realize_any_deferred(&local_serializer, &mut deferred_reprc, &mut output);
191
192 output.push(quote!(
193 <_ as _savefile::prelude::Serialize>::serialize(&#obj_id, #local_serializer)?;
194 ));
195 } else {
196 realize_any_deferred(&local_serializer, &mut deferred_reprc, &mut output);
197
198 output.push(quote!(
199 if #local_serializer.file_version >= #field_from_version && #local_serializer.file_version <= #field_to_version {
200 <_ as _savefile::prelude::Serialize>::serialize(&#obj_id, #local_serializer)?;
201 }));
202 }
203 }
204 }
205 realize_any_deferred(&local_serializer, &mut deferred_reprc, &mut output);
206
207 let total_reprc_opt: TokenStream;
210 if let (Some(first), Some(last)) = (first_non_ignored_fieldinfo, last_non_ignored_fieldinfo) {
211 let first_field = get_obj_id(first);
212 let last_field = get_obj_id(last);
213 total_reprc_opt = quote!( unsafe { #local_serializer.raw_write_region(self,&#first_field, &#last_field, local_serializer.file_version)?; } );
214 } else {
215 total_reprc_opt = quote!();
216 }
217
218 let serialize2 = quote! {
219 let local_serializer = serializer;
220
221 if unsafe { <Self as #reprc>::repr_c_optimization_safe(local_serializer.file_version).is_yes() } {
222 #total_reprc_opt
223 } else {
224 #(#output)*
225 }
226 };
227
228 let fields_names = field_infos
229 .iter()
230 .map(|field| {
231 let fieldname = field.ident.clone();
232 quote! { #fieldname }
233 })
234 .collect();
235 Ok((serialize2, fields_names))
236}
237
238pub(crate) mod common;
239
240mod serialize;
241
242mod deserialize;
243
244mod savefile_abi;
245
246#[proc_macro_attribute]
247pub fn savefile_abi_exportable(
248 attr: proc_macro::TokenStream,
249 input: proc_macro::TokenStream,
250) -> proc_macro::TokenStream {
251 match savefile_abi_exportable_impl(attr, input) {
252 Ok(output) => output,
253 Err(err) => err.to_compile_error().into()
254 }
255}
256
257fn savefile_abi_exportable_impl(
258 attr: proc_macro::TokenStream,
259 input: proc_macro::TokenStream,
260) -> syn::Result<proc_macro::TokenStream> {
261 let parsed: ItemTrait = syn::parse(input.clone()).expect("Expected valid rust-code");
262
263 let mut version: Option<u32> = None;
264 let atstr : String = attr.to_string();
265 for item in atstr.split(',') {
266 let keyvals: Vec<_> = item.split('=').collect();
267 if keyvals.len() != 2 {
268 abort!(
269 item.span(),
270 "savefile_abi_exportable arguments should be of form #[savefile_abi_exportable(version=0)], not '{}'",
271 attr
272 );
273 }
274 let key = keyvals[0].trim();
275 let val = keyvals[1].trim();
276 match key {
277 "version" => {
278 if version.is_some() {
279 abort!(item.span(), "version specified more than once");
280 }
281 version = Some(match val.parse() {
282 Ok(v) => v,
283 Err(_) => abort!(item.span(), "Version must be numeric, but was: {}", val),
284 });
285 }
286 _ => abort!(item.span(), "Unknown savefile_abi_exportable key: '{}'", key),
287 }
288 }
289
290 for attr in &parsed.attrs {
291 let name_segs: Vec<_> = attr.path().segments.iter().map(|x| &x.ident).collect();
292 if name_segs == ["async_trait"] || name_segs == ["async_trait", "async_trait"] {
293 abort!(attr.path().segments.span(), "async_trait-attribute macro detected. The {} macro must go _before_ the #[savefile_abi_exportable(..)] macro!",
294 attr.to_token_stream());
295 }
296 }
297 let version: u32 = version.unwrap_or(0);
298
299 let trait_name_str = parsed.ident.to_string();
300 let trait_name = parsed.ident;
301 let defspan = proc_macro2::Span::mixed_site();
302 let uses = quote_spanned! { defspan =>
303 extern crate savefile;
304 extern crate savefile_abi;
305 extern crate savefile_derive;
306 use savefile::prelude::{Packed, Schema, SchemaPrimitive, WithSchema, WithSchemaContext, get_schema, get_result_schema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,ReceiverType,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition};
307 use savefile_abi::{parse_return_value_impl,abi_result_receiver,abi_boxed_trait_receiver, FlexBuffer, AbiExportable, TraitObject, PackagedTraitObject, Owning, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, AbiProtocol, abi_entry_light, AbiWaker};
308 use std::collections::HashMap;
309 use std::mem::MaybeUninit;
310 use std::io::Cursor;
311 use std::pin::Pin;
312 use std::marker::Unpin;
313 use std::future::Future;
314 use std::task::{Waker, Poll, Context};
315 use std::sync::Arc;
316 use savefile_derive::savefile_abi_exportable;
317 };
318
319 let mut method_metadata: Vec<TokenStream> = vec![];
320 let mut callee_method_trampoline: Vec<TokenStream> = vec![];
321 let mut caller_method_trampoline = vec![];
322 let mut extra_definitions = HashMap::new();
323
324 if parsed.generics.params.is_empty() == false {
325 abort!(
326 parsed.generics.params.span(),
327 "Savefile does not support generic traits."
328 );
329 }
330 let mut send = false;
331 let mut sync = false;
332 for supertrait in parsed.supertraits.iter() {
333 match supertrait {
334 TypeParamBound::Trait(trait_bound) => {
335 if let Some(lif) = &trait_bound.lifetimes {
336 abort!(lif.span(), "Savefile does not support lifetimes");
337 }
338 if let Some(seg) = trait_bound.path.segments.last() {
339 let id = seg.ident.to_string();
340 match id.as_str() {
341 "Copy" => abort!(seg.span(), "Savefile does not support Copy bounds for traits. The reason is savefile-abi needs to generate a wrapper, and this wrapper can't be copy."),
342 "Clone" => abort!(seg.span(), "Savefile does not support Clone bounds for traits. The reason is savefile-abi needs to generate a wrapper, and this wrapper can't be clone."),
343 "Sync" => { sync = true;}
345 "Send" => { send = true;}
346 "Sized" => {}
347 "Debug" => {}
348 _ => abort!(seg.span(), "Savefile does not support bounds for traits. The reason is savefile-abi needs to generate a wrapper, and this wrapper doesn't know how to implement arbitrary bounds."),
349 }
350 }
351 }
352 TypeParamBound::Lifetime(lif) => {
353 if lif.ident != "static" {
354 abort!(lif.span(), "Savefile does not support lifetimes");
355 }
356 }
357 TypeParamBound::PreciseCapture(c) => {
358 abort!(c.span(), "Savefile does not support precise captures");
359 }
360 TypeParamBound::Verbatim(v) => {
361 abort!(v.span(), "Savefile does not support verbatim bounds");
362 }
363 x => {
364 abort!(x.span(), "Savefile does not support this syntax");
365 }
366 }
367 }
368
369 if parsed.generics.where_clause.is_some() {
370 abort!(
371 parsed.generics.where_clause.span(),
372 "Savefile does not support where-clauses for traits"
373 );
374 }
375
376 for (method_number, item) in parsed.items.iter().enumerate() {
377 if method_number > u16::MAX.into() {
378 abort!(item.span(), "Savefile only supports 2^16 methods per interface. Sorry.");
379 }
380 let method_number = method_number as u16;
381
382 match item {
383 TraitItem::Const(c) => {
384 abort!(
385 c.span(),
386 "savefile_abi_exportable does not support associated consts: {}",
387 c.ident
388 );
389 }
390 TraitItem::Fn(method) => {
391 let mut is_ok = true;
392 let mut async_trait_life_time = 0;
393 let mut life0_life_time = 0;
394 if let Some(wher) = &method.sig.generics.where_clause {
395 for w in wher.predicates.iter() {
396 match w {
397 WherePredicate::Type(t) => {
398 match &t.bounded_ty {
399 Type::Path(p) => {
400 if p.path.segments.len() == 1 {
401 if p.path.segments[0].ident != "Self" {
402 is_ok = false;
403 }
404 } else {
405 is_ok = false;
406 }
407 }
408 _ => {
409 is_ok = false;
410 break;
411 }
412 }
413 if let Some(l) = &t.lifetimes {
414 is_ok = false;
415 }
416 for bound in &t.bounds {
417 match bound {
418 TypeParamBound::Trait(t) => {
419 if t.path.segments.len() == 1 {
420 if t.path.segments[0].ident != "Sync" {
421 is_ok = false;
422 }
423 } else {
424 is_ok = false;
425 }
426 }
427 TypeParamBound::Lifetime(l) => {
428 if l.ident != "async_trait" {
429 is_ok = false;
430 }
431 }
432 x => {
433 abort!(x.span(), "Savefile does not support this syntax",);
434 }
435 }
436 }
437 }
438 WherePredicate::Lifetime(l) => {
439 if l.lifetime.ident != "life0" {
440 if !is_life(&l.lifetime) {
441 is_ok = false;
442 }
443 } else {
444 life0_life_time += 1;
445 }
446 for bound in &l.bounds {
447 if bound.ident != "async_trait" {
448 is_ok = false;
449 } else {
450 async_trait_life_time += 1;
451 }
452 }
453 }
454 x => {
455 abort!(x.span(), "Savefile does not support this syntax");
456 }
457 }
458 }
459 if !is_ok {
460 abort!(
461 method.sig.generics.where_clause.span(),
462 "Savefile does not support where-clauses for methods"
463 );
464 }
465 }
466
467 let method_name = method.sig.ident.clone();
468
469 let mut current_name_index = 0u32;
470 let name_baseplate = format!("Temp{}_{}", trait_name_str, method_name);
471 let mut temp_name_generator = move || {
472 current_name_index += 1;
473 format!("{}_{}", name_baseplate, current_name_index)
474 };
475 let mut receiver_is_mut = false;
476 let mut receiver_is_pin = false;
477 let ret_type: Type;
478 let ret_declaration;
479 let no_return;
480
481 match &method.sig.output {
482 ReturnType::Default => {
483 ret_type = Tuple(TypeTuple {
484 paren_token: Paren::default(),
485 elems: Default::default(),
486 });
487 ret_declaration = quote! {};
488 no_return = true;
489 }
490 ReturnType::Type(_, ty) => {
491 ret_type = (**ty).clone();
492 match &**ty {
493 Type::Tuple(tup) if tup.elems.is_empty() => {
494 ret_declaration = quote! {};
495 no_return = true;
496 }
497 _ => {
498 ret_declaration = quote! { -> #ret_type };
499 no_return = false;
500 }
501 }
502 }
503 }
504
505 let Some(self_arg) = method.sig.inputs.iter().next() else {
506 abort!(
507 method.span(),
508 "Method '{}' has no arguments. This is not supported by savefile-abi - it must at least have a self-argument.",
509 method_name
510 );
511 };
512 let unsupported = || {
513 abort!(
514 method.sig.span(),
515 "Method '{}' has an unsupported 'self'-parameter. Try '&self', '&mut self', or 'self: Pin<&mut Self>'. Not supported: {}",
516 method_name, self_arg.to_token_stream()
517 );
518 };
519
520 let mut parse_receiver_ty = |typ: &Type| -> syn::Result<()> {
521 if let Type::Path(path) = typ {
522 if !is_well_known(&path.path.segments, ["std", "pin", "Pin"]) {
523 return unsupported();
524 }
525 let seg = &path.path.segments.last().unwrap();
526 let PathArguments::AngleBracketed(args) = &seg.arguments else {
527 return unsupported();
528 unreachable!();
529 };
530 if args.args.len() != 1 {
531 return unsupported();
532 }
533 let arg = &args.args[0];
534 let GenericArgument::Type(Type::Reference(typref)) = arg else {
535 return unsupported();
536 };
537 if typref.mutability.is_none() {
538 abort!(
539 method.sig.span(),
540 "Method '{}' has an unsupported 'self'-parameter. Non-mutable references in Pin are presently not supported: {}",
541 method_name, self_arg.to_token_stream()
542 );
543 }
544 let Type::Path(typepath) = &*typref.elem else {
545 return unsupported();
546 };
547 if typepath.path.segments.len() != 1 {
548 return unsupported();
549 };
550 if typepath.path.segments[0].ident != "Self" {
551 return unsupported();
552 }
553 receiver_is_mut = true;
554 receiver_is_pin = true;
555 } else {
556 return unsupported();
557 }
558 Ok(())
559 };
560 match self_arg {
561 FnArg::Receiver(recv) => {
562 if recv.colon_token.is_some() {
563 parse_receiver_ty(&*recv.ty)?;
564 } else {
565 if let Some(reference) = &recv.reference {
566 if let Some(reference) = &reference.1 {
567 if reference.ident != "life0" {
568 abort!(
569 reference.span(),
570 "Method '{}' has a lifetime \"'{}\" for 'self' argument. This is not supported by savefile-abi",
571 method_name,
572 reference.ident,
573 );
574 } else {
575 life0_life_time += 1;
576 }
577 }
578 if recv.mutability.is_some() {
579 receiver_is_mut = true;
580 }
581 } else {
582 abort!(
583 self_arg.span(),
584 "Method '{}' takes 'self' by value. This is not supported by savefile-abi. Use &self",
585 method_name
586 );
587 }
588 }
589 }
590 FnArg::Typed(pat) => match &*pat.pat {
591 Pat::Ident(ident) if ident.ident == "self" => {
592 if ident.by_ref.is_some() || ident.mutability.is_some() {
593 unsupported()?;
594 }
595 parse_receiver_ty(&*pat.ty)?;
596 }
597 _ => {
598 abort!(
599 pat.pat.span(),
600 "Method '{}' must have 'self'-parameter (savefile-abi does not support methods without self)",
601 method_name
602 );
603 }
604 },
605 }
606 let mut args = Vec::with_capacity(method.sig.inputs.len());
607 for arg in method.sig.inputs.iter().skip(1) {
608 match arg {
609 FnArg::Typed(typ) => {
610 match &*typ.pat {
611 Pat::Ident(name) => {
612 args.push((name.ident.clone(), &*typ.ty));
613 }
614 _ => abort!(typ.pat.span(), "Method '{}' has a parameter which contains a complex pattern. This is not supported by savefile-abi.", method_name)
615 }
616 },
617 _ => abort!(arg.span(), "Unexpected error: method {} had a self parameter that wasn't the first parameter!", method_name)
618 }
619 }
620 if method.sig.asyncness.is_some() {
621 let out = match &method.sig.output {
622 ReturnType::Default => {
623 quote! {()}
624 }
625 ReturnType::Type(_, t) => t.to_token_stream(),
626 };
627 abort!(
628 method.sig.asyncness.span(),
629 "savefile-abi does not support async methods. You can try returning a boxed future instead: Pin<Box<Future<Output={}>>>",
630 out
631 )
632 }
633 if method.sig.variadic.is_some() {
634 abort!(
635 method.sig.variadic.span(),
636 "savefile-abi does not support variadic methods."
637 )
638 }
639 if method.sig.unsafety.is_some() {
640 abort!(
641 method.sig.unsafety.span(),
642 "savefile-abi does not presently support unsafe methods."
643 )
644 }
645 if method.sig.abi.is_some() {
646 abort!(method.sig.abi.span(), "savefile-abi does not need (or support) 'extern \"C\"' or similar ABI-constructs. Just remove this keyword.")
647 }
648
649 fn is_life(id: &syn::Lifetime) -> bool {
654 let s = id.ident.to_string();
655 if !s.starts_with("life") {
656 return false;
657 }
658 s.strip_prefix("life").unwrap().parse::<usize>().is_ok()
659 }
660
661 if method.sig.generics.params.is_empty() == false {
662 for item in method.sig.generics.params.iter() {
663 match item {
664 GenericParam::Type(typ) => {
665 abort!(typ.span(), "savefile-abi does not support generic methods.")
666 }
667 GenericParam::Const(typ) => {
668 abort!(typ.span(), "savefile-abi does not support const-generic methods.")
669 }
670 GenericParam::Lifetime(l) => {
671 if l.lifetime.ident != "life0"
672 && l.lifetime.ident != "async_trait"
673 && !is_life(&l.lifetime)
674 {
675 abort!(
676 method.sig.generics.params.span(),
677 "savefile-abi does not support methods with lifetimes."
678 );
679 } else {
680 if l.lifetime.ident == "life0" {
681 life0_life_time += 1;
682 }
683 async_trait_life_time += 1;
684 }
685 }
686 }
687 }
688 }
689
690 let async_trait_macro_detected;
691 if life0_life_time >= 3 && async_trait_life_time >= 3 {
692 async_trait_macro_detected = true;
693 } else if life0_life_time == 0 && async_trait_life_time == 0 {
694 async_trait_macro_detected = false;
695 } else {
696 abort!(
697 item.span(),
698 "savefile-abi has heuristics that detects the use of the #[async_trait]-macro. This heuristic produced a partial result. It is possible that an incompatible version of async_trait crate has been used. Diagnostics: {} {}",
699 life0_life_time, async_trait_life_time
700 );
701 }
702
703 let method_defs = crate::savefile_abi::generate_method_definitions(
704 version,
705 trait_name.clone(),
706 method_number,
707 method_name,
708 ret_declaration,
709 ret_type,
710 no_return,
711 receiver_is_mut,
712 receiver_is_pin,
713 args,
714 &mut temp_name_generator,
715 &mut extra_definitions,
716 async_trait_macro_detected,
717 )?;
718 method_metadata.push(method_defs.method_metadata);
719 callee_method_trampoline.push(method_defs.callee_method_trampoline);
720 caller_method_trampoline.push(method_defs.caller_method_trampoline);
721 }
722 TraitItem::Type(t) => {
723 abort!(
724 t.span(),
725 "savefile_abi_exportable does not support associated types: {}",
726 t.ident
727 );
728 }
729 TraitItem::Macro(m) => {
730 abort!(
731 m.span(),
732 "savefile_abi_exportable does not support macro items: {:?}",
733 m
734 );
735 }
736 TraitItem::Verbatim(v) => {
737 abort!(
738 v.span(),
739 "Unsupported item in trait definition: {}",
740 v.to_token_stream()
741 );
742 }
743 x => abort!(
744 x.span(),
745 "Unsupported item in trait definition: {}",
746 x.to_token_stream()
747 ),
748 }
749 }
750
751 let abi_entry_light = Ident::new(&format!("abi_entry_light_{}", trait_name_str), Span::call_site());
752
753 let exports_for_trait = quote! {
754
755 unsafe extern "C" fn #abi_entry_light(flag: AbiProtocol) {
756 unsafe { abi_entry_light::<dyn #trait_name>(flag); }
757 }
758
759 #[automatically_derived]
760 #[doc(hidden)]
761 unsafe impl AbiExportable for dyn #trait_name {
762 const ABI_ENTRY : unsafe extern "C" fn (flag: AbiProtocol) = #abi_entry_light;
763 fn get_definition( version: u32) -> AbiTraitDefinition {
764 AbiTraitDefinition {
765 name: #trait_name_str.to_string(),
766 methods: vec! [ #(#method_metadata,)* ],
767 sync: #sync,
768 send: #send
769 }
770 }
771
772 fn get_latest_version() -> u32 {
773 #version
774 }
775
776 fn call(trait_object: TraitObject, method_number: u16, effective_version:u32, compatibility_mask: u64, data: &[u8], abi_result: *mut (), __savefile_internal_receiver: unsafe extern "C" fn(outcome: *const RawAbiCallResult, result_receiver: *mut ())) -> Result<(),SavefileError> {
777
778 let mut cursor = Cursor::new(data);
779
780 let mut deserializer = Deserializer {
781 file_version: cursor.read_u32::<LittleEndian>()?,
782 reader: &mut cursor,
783 ephemeral_state: HashMap::new(),
784 };
785
786 match method_number {
787 #(#callee_method_trampoline,)*
788 _ => {
789 return Err(SavefileError::general("Unknown method number"));
790 }
791 }
792 Ok(())
793 }
794 }
795
796 #[automatically_derived]
797 #[doc(hidden)]
798 impl #trait_name for AbiConnection<dyn #trait_name> {
799 #(#caller_method_trampoline)*
800 }
801 };
802
803 let input = TokenStream::from(input);
805 let extra_definitions: Vec<_> = extra_definitions.values().map(|(_, x)| x).collect();
806 let expanded = quote! {
807 #[allow(clippy::double_comparisons)]
808 #[allow(clippy::needless_question_mark)]
809 #[allow(unused_variables)]
810 #[allow(clippy::needless_late_init)]
811 #[allow(clippy::not_unsafe_ptr_arg_deref)]
812 #[allow(non_upper_case_globals)]
813 #[allow(clippy::manual_range_contains)]
814 #[allow(non_local_definitions)]
815 const _:() = {
816 #uses
817
818 #(#extra_definitions)*
819
820 #exports_for_trait
821
822 };
823
824 #input
825 };
826
827 Ok(expanded.into())
831}
832
833#[proc_macro]
834pub fn savefile_abi_export(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
835 match savefile_abi_export_impl(item) {
836 Ok(x) => x,
837 Err(err) => err.to_compile_error().into()
838 }
839}
840fn savefile_abi_export_impl(item: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenStream> {
841 let tokens = proc_macro2::TokenStream::from(item);
842
843 let mut tokens_iter = tokens.into_iter();
844
845 let Some(implementing_type) = tokens_iter.next() else {
846 abort!(Span::call_site(), "The macro savefile_abi_export! requires two parameters. The first parameter must be the implementing type, the second is the trait it implements.");
847 };
848 let Some(comma) = tokens_iter.next() else {
849 abort!(Span::call_site(), "The macro savefile_abi_export! requires two parameters. The first parameter must be the implementing type, the second is the trait it implements.");
850 };
851 if let TokenTree::Punct(p) = comma {
852 if p.as_char() != ',' {
853 abort!(p.span(), "Expected a comma (','). The macro savefile_abi_export! requires two parameters. The first parameter must be the implementing type, the second is the trait it implements, and these must be separated by a comma.");
854 }
855 } else {
856 abort!(comma.span(), "Expected a comma (','). The macro savefile_abi_export! requires two parameters. The first parameter must be the implementing type, the second is the trait it implements, and these must be separated by a comma.");
857 }
858 let Some(trait_type) = tokens_iter.next() else {
859 abort!(Span::call_site(), "The macro savefile_abi_export! requires two parameters. The first parameter must be the implementing type, the second is the trait it implements. Expected trait name.");
860 };
861
862 if let Some(extra) = tokens_iter.next() {
863 abort!(extra.span(), "Unexpected token. The macro savefile_abi_export! requires exactly two parameters. The first parameter must be the implementing type, the second is the trait it implements.");
864 }
865
866 let defspan = Span::call_site();
867 let uses = quote_spanned! { defspan =>
868 extern crate savefile_abi;
869 use savefile_abi::{AbiProtocol, AbiExportableImplementation, abi_entry,parse_return_value_impl};
870 };
871
872 let abi_entry = Ident::new(
873 ("abi_entry_".to_string() + &trait_type.to_string()).as_str(),
874 Span::call_site(),
875 );
876
877 let expanded = quote! {
878 #[allow(clippy::needless_question_mark)]
879 #[allow(clippy::double_comparisons)]
880 #[allow(non_local_definitions)]
881 const _:() = {
882 #uses
883 #[automatically_derived]
884 #[doc(hidden)]
885 unsafe impl AbiExportableImplementation for #implementing_type where #implementing_type: Default + #trait_type {
886 const ABI_ENTRY: unsafe extern "C" fn (AbiProtocol) = #abi_entry;
887 type AbiInterface = dyn #trait_type;
888
889 fn new() -> Box<Self::AbiInterface> {
890 std::boxed::Box::new(#implementing_type::default())
891 }
892 }
893 #[no_mangle]
894 unsafe extern "C" fn #abi_entry(flag: AbiProtocol) where #implementing_type: Default + #trait_type {
895 unsafe { abi_entry::<#implementing_type>(flag); }
896 }
897 };
898 };
899
900 Ok(expanded.into())
901}
902
903#[proc_macro_derive(
904 Savefile,
905 attributes(
906 savefile_unsafe_and_fast,
907 savefile_require_fast,
908 savefile_versions,
909 savefile_versions_as,
910 savefile_introspect_ignore,
911 savefile_introspect_key,
912 savefile_ignore,
913 savefile_default_val,
914 savefile_default_fn,
915 savefile_doc_hidden
916 )
917)]
918pub fn savefile(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
919 match savefile_impl(input) {
920 Ok(x) => x,
921 Err(err) => err.to_compile_error().into(),
922 }
923}
924fn savefile_impl(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenStream> {
925 let input: DeriveInput = syn::parse(input).expect("Expected valid rust code [Savefile]");
926
927 let s = serialize::savefile_derive_crate_serialize(input.clone())?;
928
929 let d = deserialize::savefile_derive_crate_deserialize(input.clone())?;
930
931 let w = savefile_derive_crate_withschema(input.clone())?;
932
933 let i = savefile_derive_crate_introspect(input.clone())?;
934
935 let r = derive_reprc_new(input)?;
936
937 let dummy_const = syn::Ident::new("_", proc_macro2::Span::call_site());
938
939 let expanded: TokenStream = quote! {
940 #s
941
942 #d
943
944 #i
945
946 #[allow(non_upper_case_globals)]
947 #[allow(clippy::double_comparisons)]
948 #[allow(non_camel_case_types)]
949 #[allow(clippy::manual_range_contains)]
950 const #dummy_const: () = {
951 extern crate savefile as _savefile;
952 use std::mem::MaybeUninit;
953 use savefile::prelude::Packed;
954
955 #w
956 #r
957 };
958 };
959 Ok(expanded.into())
962}
963#[proc_macro_derive(
964 SavefileNoIntrospect,
965 attributes(
966 savefile_unsafe_and_fast,
967 savefile_require_fast,
968 savefile_versions,
969 savefile_versions_as,
970 savefile_ignore,
971 savefile_introspect_ignore,
972 savefile_default_val,
973 savefile_default_fn
974 )
975)]
976pub fn savefile_no_introspect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
977 match savefile_no_introspect_impl(input) {
978 Ok(x) => x,
979 Err(err) => err.to_compile_error().into(),
980 }
981}
982
983fn savefile_no_introspect_impl(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenStream> {
984 let input: DeriveInput = syn::parse(input).expect("Expected valid rust code [SavefileNoIntrospect]");
985
986 let s = serialize::savefile_derive_crate_serialize(input.clone())?;
987
988 let d = deserialize::savefile_derive_crate_deserialize(input.clone())?;
989
990 let w = savefile_derive_crate_withschema(input.clone())?;
991
992 let r = derive_reprc_new(input)?;
993
994 let dummy_const = syn::Ident::new("_", proc_macro2::Span::call_site());
995
996 let expanded = quote! {
997 #s
998
999 #d
1000
1001 #[allow(non_upper_case_globals)]
1002 #[allow(clippy::double_comparisons)]
1003 #[allow(clippy::manual_range_contains)]
1004 const #dummy_const: () = {
1005 extern crate savefile as _savefile;
1006 use std::mem::MaybeUninit;
1007 use savefile::prelude::Packed;
1008
1009 #w
1010 #r
1011 };
1012 };
1013
1014 Ok(expanded.into())
1015}
1016
1017#[proc_macro_derive(
1018 SavefileIntrospectOnly,
1019 attributes(
1020 savefile_versions,
1021 savefile_versions_as,
1022 savefile_introspect_ignore,
1023 savefile_ignore,
1024 savefile_default_val,
1025 savefile_default_fn
1026 )
1027)]
1028pub fn savefile_introspect_only(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1029 match savefile_introspect_only_impl(input) {
1030 Ok(x) => x,
1031 Err(err) => err.to_compile_error().into()
1032 }
1033}
1034fn savefile_introspect_only_impl(input: proc_macro::TokenStream) -> syn::Result<proc_macro::TokenStream> {
1035 let input: DeriveInput = syn::parse(input).expect("Expected valid rust code [SavefileIntrospectOnly]");
1036
1037 let i = savefile_derive_crate_introspect(input)?;
1038
1039 let expanded = quote! {
1040 #i
1041 };
1042
1043 Ok(expanded.into())
1044}
1045
1046#[allow(non_snake_case)]
1047fn implement_reprc_hardcoded_false(name: syn::Ident, input: &DeriveInput) -> TokenStream {
1048 let defspan = proc_macro2::Span::call_site();
1049
1050 let generics = &input.generics;
1051 let doc_hidden = doc_hidden(&input.attrs);
1052 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1053 let extra_where = get_extra_where_clauses(input, where_clause, quote! {_savefile::prelude::WithSchema});
1054 let reprc = quote_spanned! {defspan=>
1055 _savefile::prelude::Packed
1056 };
1057 let isreprc = quote_spanned! {defspan=>
1058 _savefile::prelude::IsPacked
1059 };
1060 quote! {
1061
1062 #[automatically_derived]
1063 #doc_hidden
1064 impl #impl_generics #reprc for #name #ty_generics #where_clause #extra_where{
1065 #[allow(unused_comparisons,unused_variables, unused_variables)]
1066 unsafe fn repr_c_optimization_safe(file_version:u32) -> #isreprc {
1067 #isreprc::no()
1068 }
1069 }
1070
1071 }
1072}
1073
1074#[allow(non_snake_case)]
1075fn implement_reprc_struct(
1076 field_infos: Vec<FieldInfo>,
1077 input: &DeriveInput,
1078 name: syn::Ident,
1079 expect_fast: bool,
1080) -> syn::Result<TokenStream> {
1081 let generics = &input.generics;
1082 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1083 let extra_where = get_extra_where_clauses(input, where_clause, quote! {_savefile::prelude::Packed});
1084 let doc_hidden = doc_hidden(&input.attrs);
1085 let span = proc_macro2::Span::call_site();
1086 let defspan = proc_macro2::Span::call_site();
1087 let reprc = quote_spanned! {defspan=>
1088 _savefile::prelude::Packed
1089 };
1090 let isreprc = quote_spanned! {defspan=>
1091 _savefile::prelude::IsPacked
1092 };
1093 let offsetof = quote_spanned! {defspan=>
1094 _savefile::prelude::offset_of
1095 };
1096 let local_file_version = quote_spanned! { defspan => local_file_version};
1097 let mut min_safe_version = 0;
1099 let mut packed_outputs = Vec::new();
1100 let mut reprc_outputs = Vec::new();
1101
1102 for field in field_infos.windows(2) {
1103 let field_name1 = field[0].get_accessor();
1104 let field_name2 = field[1].get_accessor();
1105 let ty = field[0].ty;
1106 packed_outputs.push(quote!( (#offsetof!(#name #ty_generics, #field_name1) + std::mem::size_of::<#ty>() == #offsetof!(#name #ty_generics, #field_name2) )));
1107 }
1108 if field_infos.len() > 0 {
1109 if field_infos.len() == 1 {
1110 let ty = field_infos[0].ty;
1111 let field_name = field_infos[0].get_accessor();
1112 packed_outputs.push(quote!( (#offsetof!( #name #ty_generics, #field_name) == 0 )));
1113 packed_outputs.push(quote!( (#offsetof!( #name #ty_generics, #field_name) + std::mem::size_of::<#ty>() == std::mem::size_of::<#name #ty_generics>() )));
1114 } else {
1115 let first = field_infos.first().expect("field_infos.first()[2]").get_accessor();
1116 let last_field = field_infos.last().expect("field_infos.last()[2]");
1117 let last = last_field.get_accessor();
1118 let last_ty = &last_field.ty;
1119 packed_outputs.push(quote!( (#offsetof!(#name #ty_generics, #first) == 0 )));
1120 packed_outputs.push(quote!( (#offsetof!(#name #ty_generics, #last) + std::mem::size_of::<#last_ty>() == std::mem::size_of::<#name #ty_generics>() )));
1121 }
1122 }
1123
1124 for field in &field_infos {
1125 let verinfo = parse_attr_tag(field.attrs)?;
1126 if verinfo.ignore {
1127 continue;
1130 }
1131 let (field_from_version, field_to_version) = (verinfo.version_from, verinfo.version_to);
1132
1133 let removed = check_is_remove(field.ty);
1134 let field_type = &field.ty;
1135 if field_from_version == 0 && field_to_version == std::u32::MAX {
1136 if removed.is_removed() {
1137 if expect_fast {
1138 abort!(field.ty.span(), "The Removed type can only be used for removed fields. Use the savefile_version attribute to mark a field as only existing in previous versions.");
1139 } else {
1140 return Ok(implement_reprc_hardcoded_false(name, input));
1141 }
1142 }
1143 reprc_outputs
1144 .push(quote_spanned!( span => <#field_type as #reprc>::repr_c_optimization_safe(#local_file_version).is_yes()));
1145 } else {
1146 min_safe_version = min_safe_version.max(verinfo.min_safe_version());
1147
1148 if !removed.is_removed() {
1149 reprc_outputs.push(
1150 quote_spanned!( span => <#field_type as #reprc>::repr_c_optimization_safe(#local_file_version).is_yes()),
1151 );
1152 }
1153 }
1154 }
1155
1156 let require_packed = if expect_fast {
1157 quote!(
1158 const _: () = {
1159 if !PACKED {
1160 panic!("Memory layout not optimal - requires padding which disables savefile-optimization");
1161 }
1162 };
1163 )
1164 } else {
1165 quote!()
1166 };
1167
1168 let packed_storage = if generics.params.is_empty() == false {
1169 quote!(let)
1170 } else {
1171 quote!(const)
1172 };
1173
1174 Ok(quote! {
1175
1176 #[automatically_derived]
1177 #doc_hidden
1178 impl #impl_generics #reprc for #name #ty_generics #where_clause #extra_where {
1179 #[allow(unused_comparisons,unused_variables, unused_variables)]
1180 unsafe fn repr_c_optimization_safe(file_version:u32) -> #isreprc {
1181 let local_file_version = file_version;
1182 #packed_storage PACKED : bool = true #( && #packed_outputs)*;
1183 #require_packed
1184 if file_version >= #min_safe_version && PACKED #( && #reprc_outputs)*{
1185 unsafe { #isreprc::yes() }
1186 } else {
1187 #isreprc::no()
1188 }
1189 }
1190 }
1191 })
1192}
1193
1194#[derive(Debug)]
1195struct EnumSize {
1196 discriminant_size: u8,
1197 #[allow(unused)] repr_c: bool,
1199 explicit_size: bool,
1200}
1201
1202fn get_enum_size(attrs: &[syn::Attribute], actual_variants: usize) -> syn::Result<EnumSize> {
1203 let mut size_u8: Option<u8> = None;
1204 let mut repr_c_seen = false;
1205 let mut have_seen_explicit_size = false;
1206
1207 for attr in attrs.iter() {
1208 let path = attr.path();
1209
1210 if path.is_ident("repr") {
1211 if let Err(err) = attr.parse_nested_meta(|meta| {
1212 if meta.path.segments.len() != 1 {
1214 abort!(
1215 meta.path.span(),
1216 "Unsupported repr(X) attribute on enum: {}",
1217 meta.path.to_token_stream()
1218 );
1219 }
1220 match meta.path.segments[0].ident.to_string().as_str() {
1221 "C" => repr_c_seen = true,
1222 "u8" => {
1223 size_u8 = Some(1);
1224 have_seen_explicit_size = true;
1225 }
1226 "i8" => {
1227 size_u8 = Some(1);
1228 have_seen_explicit_size = true;
1229 }
1230 "u16" => {
1231 size_u8 = Some(2);
1232 have_seen_explicit_size = true;
1233 }
1234 "i16" => {
1235 size_u8 = Some(2);
1236 have_seen_explicit_size = true;
1237 }
1238 "u32" => {
1239 size_u8 = Some(4);
1240 have_seen_explicit_size = true;
1241 }
1242 "i32" => {
1243 size_u8 = Some(4);
1244 have_seen_explicit_size = true;
1245 }
1246 "u64" | "i64" => {
1247 abort!(
1248 meta.path.span(),
1249 "Savefile does not support enums with more than 2^32 variants."
1250 )
1251 }
1252 _ => abort!(
1253 meta.path.span(),
1254 "Unsupported repr(X) attribute on enum: {}",
1255 meta.path.to_token_stream()
1256 ),
1257 };
1258 Ok(())
1259 }) {
1260 return Err(err);
1261 };
1262 }
1263 }
1264
1265 if actual_variants >= u32::MAX as usize {
1266 abort_call_site!("The enum had an unreasonable number of variants");
1267 }
1268 let discriminant_size = size_u8.unwrap_or_else(|| {
1269 if actual_variants <= 256 {
1270 1
1271 } else if actual_variants <= 65536 {
1272 2
1273 } else {
1274 4
1275 }
1276 });
1277 Ok(EnumSize {
1278 discriminant_size,
1279 repr_c: repr_c_seen,
1280 explicit_size: have_seen_explicit_size,
1281 })
1282}
1283
1284#[proc_macro_derive(
1285 Packed,
1286 attributes(
1287 savefile_versions,
1288 savefile_versions_as,
1289 savefile_ignore,
1290 savefile_default_val,
1291 savefile_default_fn
1292 )
1293)]
1294pub fn reprc(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1295 syn::Error::new(Span::call_site(), "The #[derive(Packed)] style of unsafe performance opt-in has been removed. The performance gains are now available automatically for any packed struct.").to_compile_error().into()
1296}
1297fn derive_reprc_new(input: DeriveInput) -> syn::Result<TokenStream> {
1298 let name = &input.ident;
1299 let doc_hidden = doc_hidden(&input.attrs);
1300 let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl();
1301
1302 let mut opt_in_fast = false;
1303 for attr in input.attrs.iter() {
1304 if attr.path().is_ident("savefile_unsafe_and_fast") || attr.path().is_ident("savefile_require_fast") {
1305 opt_in_fast = true;
1306 }
1307 }
1320
1321 let expanded = match &input.data {
1326 &syn::Data::Enum(ref enum1) => {
1327 let enum_size = get_enum_size(&input.attrs, enum1.variants.len())?;
1328 let any_fields = enum1.variants.iter().any(|v| v.fields.len() > 0);
1329 if !enum_size.explicit_size {
1330 if opt_in_fast {
1331 if any_fields {
1332 abort_call_site!("The #[savefile_require_fast] requires an explicit #[repr(u8),C],#[repr(u16,C)] or #[repr(u32,C)], attribute.");
1333 } else {
1334 abort_call_site!("The #[savefile_require_fast] requires an explicit #[repr(u8)],#[repr(u16)] or #[repr(u32)], attribute.");
1335 }
1336 }
1337 return Ok(implement_reprc_hardcoded_false(name.clone(), &input));
1338 }
1339
1340 let mut conditions = vec![];
1341
1342 let mut min_safe_version: u32 = 0;
1343
1344 let mut unique_field_types = HashSet::new();
1345
1346 let fn_impl_generics = if !input.generics.params.is_empty() {
1347 quote! { :: #impl_generics}
1348 } else {
1349 quote! {}
1350 };
1351 for (variant_index, variant) in enum1.variants.iter().enumerate() {
1352 let mut attrs: Vec<_> = vec![];
1353
1354 let mut num_fields = 0usize;
1355 let mut field_types = vec![];
1356 match &variant.fields {
1357 &syn::Fields::Named(ref fields_named) => {
1358 for field in fields_named.named.iter() {
1359 attrs.push(&field.attrs);
1360 field_types.push(&field.ty);
1361 num_fields += 1;
1362 }
1363 }
1364 &syn::Fields::Unnamed(ref fields_unnamed) => {
1365 for field in fields_unnamed.unnamed.iter() {
1366 attrs.push(&field.attrs);
1367 field_types.push(&field.ty);
1368 num_fields += 1;
1369 }
1370 }
1371 &syn::Fields::Unit => {}
1372 }
1373 for i in 0usize..num_fields {
1374 let verinfo = parse_attr_tag(&attrs[i])?;
1375 if check_is_remove(&field_types[i]).is_removed() {
1376 if verinfo.version_to == u32::MAX {
1377 abort!(field_types[i].span(), "Removed fields must have a max version, provide one using #[savefile_versions=\"..N\"]")
1378 }
1379 min_safe_version = min_safe_version.max(verinfo.version_to + 1);
1380 }
1381 let typ = field_types[i].to_token_stream();
1382
1383 let variant_index = proc_macro2::Literal::u32_unsuffixed(variant_index as u32);
1384
1385 unique_field_types.insert(field_types[i].clone());
1386 if i == 0 {
1387 let discriminant_bytes = enum_size.discriminant_size as usize;
1388 conditions.push( quote!( (#discriminant_bytes == (get_variant_offsets #fn_impl_generics(#variant_index)[#i])) ) );
1389 }
1390 if i == num_fields - 1 {
1391 conditions.push(
1392 quote!( (std::mem::size_of::<#name #ty_generics>() == (get_variant_offsets #fn_impl_generics(#variant_index)[#i]) + std::mem::size_of::<#typ>()) )
1393 );
1394 } else {
1395 let n = i + 1;
1396 let end_offset_condition = quote!( (get_variant_offsets #fn_impl_generics(#variant_index)[#n] == (get_variant_offsets #fn_impl_generics(#variant_index)[#i]) + std::mem::size_of::<#typ>()) );
1397 conditions.push(quote!(#end_offset_condition));
1398 };
1399 }
1400
1401 for attr in attrs {
1402 let verinfo = parse_attr_tag(attr)?;
1403 if verinfo.ignore {
1404 if opt_in_fast {
1405 abort_call_site!(
1406 "The #[savefile_require_fast] attribute cannot be used for structures containing ignored fields"
1407 );
1408 } else {
1409 return Ok(implement_reprc_hardcoded_false(name.clone(), &input));
1410 }
1411 }
1412 min_safe_version = min_safe_version.max(verinfo.min_safe_version());
1413 }
1414 }
1415
1416 let defspan = proc_macro2::Span::call_site();
1417 let generics = &input.generics;
1418 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1419 let extra_where = get_extra_where_clauses(&input, where_clause, quote! {_savefile::prelude::Packed});
1420 let reprc = quote_spanned! { defspan=>
1421 _savefile::prelude::Packed
1422 };
1423 let isreprc = quote_spanned! {defspan=>
1424 _savefile::prelude::IsPacked
1425 };
1426
1427 if conditions.is_empty() {
1428 conditions.push(quote!(true));
1429 }
1430 let require_packed = if opt_in_fast {
1431 quote!(
1432 const _: () = {
1433 if !PACKED {
1434 panic!("Memory layout not optimal - requires padding which disables savefile-optimization");
1435 }
1436 };
1437 )
1438 } else {
1439 quote!()
1440 };
1441 let mut reprc_condition = vec![];
1442 for typ in unique_field_types {
1443 reprc_condition.push(quote!(
1444 <#typ as Packed>::repr_c_optimization_safe(file_version).is_yes()
1445 ));
1446 }
1447
1448 let packed_decl = if generics.params.is_empty() {
1449 quote! { const }
1450 } else {
1451 quote! { let }
1452 };
1453 let packed_constraints = if any_fields {
1454 quote!(
1455 #packed_decl PACKED : bool = true #( && #conditions)*;
1456 #require_packed
1457 if !PACKED {
1458 return #isreprc::no();
1459 }
1460 )
1461 } else {
1462 quote!()
1463 };
1464
1465 return Ok(quote! {
1466
1467 #[automatically_derived]
1468 #doc_hidden
1469 impl #impl_generics #reprc for #name #ty_generics #where_clause #extra_where {
1470 #[allow(unused_comparisons,unused_variables, unused_variables)]
1471 unsafe fn repr_c_optimization_safe(file_version:u32) -> #isreprc {
1472 let local_file_version = file_version;
1473
1474 #packed_constraints
1475
1476 if file_version >= #min_safe_version #( && #reprc_condition)* {
1477 unsafe { #isreprc::yes() }
1478 } else {
1479 #isreprc::no()
1480 }
1481 }
1482 }
1483 });
1484
1485 }
1487 &syn::Data::Struct(ref struc) => match &struc.fields {
1488 &syn::Fields::Named(ref namedfields) => {
1489 let field_infos: Vec<FieldInfo> = namedfields
1490 .named
1491 .iter()
1492 .enumerate()
1493 .map(|(field_index, field)| FieldInfo {
1494 ident: Some(field.ident.clone().expect("Expected identifier [8]")),
1495 field_span: field.ident.as_ref().unwrap().span(),
1496 index: field_index as u32,
1497 ty: &field.ty,
1498 attrs: &field.attrs,
1499 })
1500 .collect();
1501
1502 implement_reprc_struct(field_infos, &input, name.clone(), opt_in_fast)?
1503 }
1504 &syn::Fields::Unnamed(ref fields_unnamed) => {
1505 let field_infos: Vec<FieldInfo> = fields_unnamed
1506 .unnamed
1507 .iter()
1508 .enumerate()
1509 .map(|(idx, field)| FieldInfo {
1510 field_span: field.ty.span(),
1511 ident: None,
1512 index: idx as u32,
1513 ty: &field.ty,
1514 attrs: &field.attrs,
1515 })
1516 .collect();
1517
1518 implement_reprc_struct(field_infos, &input, name.clone(), opt_in_fast)?
1519 }
1520 &syn::Fields::Unit => implement_reprc_struct(Vec::new(), &input, name.clone(), opt_in_fast)?,
1521 },
1522 _ => {
1523 if opt_in_fast {
1524 abort_call_site!("Unsupported data type");
1525 }
1526 return Ok(implement_reprc_hardcoded_false(name.clone(), &input));
1527 }
1528 };
1529
1530 Ok(expanded)
1531}
1532
1533#[allow(non_snake_case)]
1534fn implement_introspect(
1535 field_infos: Vec<FieldInfo>,
1536 need_self: bool,
1537) -> syn::Result<(Vec<TokenStream>, Vec<TokenStream>, Option<TokenStream>)> {
1538 let span = proc_macro2::Span::call_site();
1539 let defspan = proc_macro2::Span::call_site();
1540
1541 let index1 = quote_spanned! { defspan => index };
1545 let introspect_item = quote_spanned! { defspan=>
1546 _savefile::prelude::introspect_item
1547 };
1548
1549 let mut fields = Vec::new();
1550 let mut fields_names = Vec::new();
1551 let mut introspect_key = None;
1552 let mut index_number = 0usize;
1553 for (idx, field) in field_infos.iter().enumerate() {
1554 let verinfo = parse_attr_tag(field.attrs)?;
1555 if verinfo.introspect_key && introspect_key.is_some() {
1556 abort!(
1557 field.field_span,
1558 "Type had more than one field with savefile_introspect_key - attribute"
1559 );
1560 }
1561 if verinfo.introspect_ignore {
1562 continue;
1563 }
1564 if need_self {
1565 let fieldname;
1566 let fieldname_raw;
1567
1568 let id = field.get_accessor();
1569 fieldname = quote! {&self.#id};
1570 fieldname_raw = quote! {#id};
1571
1572 fields.push(quote_spanned!( span => if #index1 == #index_number { return Some(#introspect_item(stringify!(#fieldname_raw).to_string(), #fieldname))}));
1573 if verinfo.introspect_key {
1574 let fieldname_raw2 = fieldname_raw.clone();
1575 introspect_key = Some(quote! {self.#fieldname_raw2});
1576 }
1577 fields_names.push(fieldname_raw);
1578 } else if let Some(id) = field.ident.clone() {
1579 let fieldname;
1580 let quoted_fieldname;
1581 let raw_fieldname = id.to_string();
1582 let id2 = id.clone();
1583 fieldname = id;
1584 quoted_fieldname = quote! { #fieldname };
1585 fields.push(quote_spanned!( span => if #index1 == #index_number { return Some(#introspect_item(#raw_fieldname.to_string(), #quoted_fieldname))}));
1586 fields_names.push(quoted_fieldname);
1587 if verinfo.introspect_key {
1588 introspect_key = Some(quote!(#id2))
1589 }
1590 } else {
1591 let fieldname;
1592 let quoted_fieldname;
1593 let raw_fieldname = idx.to_string();
1594 fieldname = Ident::new(&format!("v{}", idx), span);
1595 let fieldname2 = fieldname.clone();
1596 quoted_fieldname = quote! { #fieldname };
1597 fields.push(quote_spanned!( span => if #index1 == #index_number { return Some(#introspect_item(#raw_fieldname.to_string(), #quoted_fieldname))}));
1598 fields_names.push(quoted_fieldname);
1599 if verinfo.introspect_key {
1600 introspect_key = Some(quote!(#fieldname2))
1601 }
1602 }
1603
1604 index_number += 1;
1605 }
1606
1607 Ok((fields_names, fields, introspect_key))
1608}
1609
1610#[allow(non_snake_case)]
1611fn savefile_derive_crate_introspect(input: DeriveInput) -> syn::Result<TokenStream> {
1612 let name = &input.ident;
1613
1614 let generics = &input.generics;
1615 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
1616 let extra_where = get_extra_where_clauses(&input, where_clause, quote! {_savefile::prelude::Introspect});
1617 let doc_hidden = doc_hidden(&input.attrs);
1618 let span = proc_macro2::Span::call_site();
1619 let defspan = proc_macro2::Span::call_site();
1620 let introspect = quote_spanned! {defspan=>
1621 _savefile::prelude::Introspect
1622 };
1623 let introspect_item_type = quote_spanned! {defspan=>
1624 _savefile::prelude::IntrospectItem
1625 };
1626 let uses = quote_spanned! { defspan =>
1627 extern crate savefile as _savefile;
1628 };
1629
1630 let dummy_const = syn::Ident::new("_", proc_macro2::Span::call_site());
1637
1638 let expanded = match &input.data {
1639 &syn::Data::Enum(ref enum1) => {
1640 let mut variants = Vec::new();
1641 let mut value_variants = Vec::new();
1642 let mut len_variants = Vec::new();
1643 for variant in enum1.variants.iter() {
1644 let var_ident = variant.ident.clone();
1649 let variant_name = quote! { #var_ident };
1650 let variant_name_spanned = quote_spanned! { span => #variant_name};
1651
1652 let mut field_infos = Vec::new();
1653
1654 let return_value_name_str = format!("{}::{}", name, var_ident);
1655 let return_value_name = quote!(#return_value_name_str);
1656 match &variant.fields {
1657 &syn::Fields::Named(ref fields_named) => {
1658 for (idx, f) in fields_named.named.iter().enumerate() {
1659 field_infos.push(FieldInfo {
1660 ident: Some(f.ident.clone().expect("Expected identifier[9]")),
1661 field_span: f.ident.as_ref().unwrap().span(),
1662 index: idx as u32,
1663 ty: &f.ty,
1664 attrs: &f.attrs,
1665 });
1666 }
1667 let (fields_names, fields, introspect_key) = implement_introspect(field_infos, false)?;
1668 let fields_names1 = fields_names.clone();
1669 let fields_names2 = fields_names.clone();
1670 let fields_names3 = fields_names.clone();
1671 let num_fields = fields_names3.len();
1672 if let Some(introspect_key) = introspect_key {
1673 value_variants.push(quote!(#name::#variant_name_spanned{#(#fields_names,)*} => {
1674 #introspect_key.to_string()
1675 }
1676 ));
1677 } else {
1678 value_variants.push(quote!( #name::#variant_name_spanned{#(#fields_names2,)*} => {
1679 #return_value_name.to_string()
1680 } ));
1681 }
1682 variants.push(quote!( #name::#variant_name_spanned{#(#fields_names1,)*} => {
1683 #(#fields;)*
1684 } ));
1685 len_variants.push(quote!( #name::#variant_name_spanned{#(#fields_names3,)*} => {
1686 #num_fields
1687 } ));
1688 }
1689 &syn::Fields::Unnamed(ref fields_unnamed) => {
1690 for (idx, f) in fields_unnamed.unnamed.iter().enumerate() {
1691 field_infos.push(FieldInfo {
1692 field_span: f.ty.span(),
1693 ident: None,
1694 index: idx as u32,
1695 ty: &f.ty,
1696 attrs: &f.attrs,
1697 });
1698 }
1699 let (fields_names, fields, introspect_key) = implement_introspect(field_infos, false)?;
1700 let fields_names1 = fields_names.clone();
1701 let fields_names2 = fields_names.clone();
1702 let fields_names3 = fields_names.clone();
1703 let num_fields = fields_names3.len();
1704
1705 if let Some(introspect_key) = introspect_key {
1706 value_variants.push(quote!( #name::#variant_name_spanned(#(#fields_names1,)*) => {
1707 #introspect_key.to_string()
1708 }));
1709 } else {
1710 value_variants.push(
1711 quote!( #name::#variant_name_spanned(#(#fields_names2,)*) => #return_value_name.to_string() )
1712 );
1713 }
1714
1715 variants.push(quote!( #name::#variant_name_spanned(#(#fields_names,)*) => { #(#fields;)* } ));
1716 len_variants.push(quote!( #name::#variant_name_spanned(#(#fields_names3,)*) => {
1717 #num_fields
1718 } ));
1719 }
1720 &syn::Fields::Unit => {
1721 variants.push(quote! {
1723 #name::#variant_name_spanned => {}
1724 });
1725 value_variants.push(quote!( #name::#variant_name_spanned => #return_value_name.to_string() ));
1726 len_variants.push(quote!( #name::#variant_name_spanned => 0));
1727 }
1728 }
1729
1730 }
1732 quote! {
1733 #[allow(non_upper_case_globals)]
1734 #[allow(clippy::double_comparisons)]
1735 #[allow(clippy::manual_range_contains)]
1736 const #dummy_const: () = {
1737 #uses
1738
1739 #[automatically_derived]
1740 #doc_hidden
1741 impl #impl_generics #introspect for #name #ty_generics #where_clause #extra_where {
1742
1743 #[allow(unused_mut)]
1744 #[allow(unused_comparisons, unused_variables)]
1745 fn introspect_value(&self) -> String {
1746 match self {
1747 #(#value_variants,)*
1748 }
1749 }
1750 #[allow(unused_mut)]
1751 #[allow(unused_comparisons, unused_variables)]
1752 fn introspect_child(&self, index:usize) -> Option<Box<dyn #introspect_item_type+'_>> {
1753 match self {
1754 #(#variants,)*
1755 }
1756 return None;
1757 }
1758 #[allow(unused_mut)]
1759 #[allow(unused_comparisons, unused_variables)]
1760 fn introspect_len(&self) -> usize {
1761 match self {
1762 #(#len_variants,)*
1763 }
1764 }
1765
1766 }
1767 };
1768 }
1769 }
1770 &syn::Data::Struct(ref struc) => {
1771 let fields;
1772 match &struc.fields {
1773 &syn::Fields::Named(ref namedfields) => {
1774 let field_infos: Vec<FieldInfo> = namedfields
1775 .named
1776 .iter()
1777 .enumerate()
1778 .map(|(idx, field)| FieldInfo {
1779 ident: Some(field.ident.clone().expect("Expected identifier[10]")),
1780 field_span: field.ident.as_ref().unwrap().span(),
1781 ty: &field.ty,
1782 index: idx as u32,
1783 attrs: &field.attrs,
1784 })
1785 .collect();
1786
1787 fields = implement_introspect(field_infos, true)?;
1788 }
1789 &syn::Fields::Unnamed(ref fields_unnamed) => {
1790 let field_infos: Vec<FieldInfo> = fields_unnamed
1791 .unnamed
1792 .iter()
1793 .enumerate()
1794 .map(|(idx, f)| FieldInfo {
1795 ident: None,
1796 field_span: f.ty.span(),
1797 ty: &f.ty,
1798 index: idx as u32,
1799 attrs: &f.attrs,
1800 })
1801 .collect();
1802
1803 fields = implement_introspect(field_infos, true)?;
1804 }
1805 &syn::Fields::Unit => {
1806 fields = (Vec::new(), Vec::new(), None);
1807 }
1808 }
1809 let fields1 = fields.1;
1810 let introspect_key: Option<TokenStream> = fields.2;
1811 let field_count = fields1.len();
1812 let value_name;
1813 if let Some(introspect_key) = introspect_key {
1814 value_name = quote! { #introspect_key.to_string()};
1815 } else {
1816 value_name = quote! { stringify!(#name).to_string() };
1817 }
1818 quote! {
1819 #[allow(non_upper_case_globals)]
1820 #[allow(clippy::double_comparisons)]
1821 #[allow(clippy::manual_range_contains)]
1822 const #dummy_const: () = {
1823 #uses
1824
1825 #[automatically_derived]
1826 #doc_hidden
1827 impl #impl_generics #introspect for #name #ty_generics #where_clause #extra_where {
1828 #[allow(unused_comparisons)]
1829 #[allow(unused_mut, unused_variables)]
1830 fn introspect_value(&self) -> String {
1831 #value_name
1832 }
1833 #[allow(unused_comparisons)]
1834 #[allow(unused_mut, unused_variables)]
1835 fn introspect_child(&self, index: usize) -> Option<Box<dyn #introspect_item_type+'_>> {
1836 #(#fields1;)*
1837 return None;
1838 }
1839 fn introspect_len(&self) -> usize {
1840 #field_count
1841 }
1842 }
1843 };
1844 }
1845 }
1846 _ => {
1847 abort_call_site!("Unsupported datatype");
1848 }
1849 };
1850
1851 Ok(expanded)
1852}
1853
1854#[allow(non_snake_case)]
1855fn implement_withschema(
1856 structname: &str,
1857 field_infos: Vec<FieldInfo>,
1858 is_enum: FieldOffsetStrategy,
1859 generics: &Generics,
1860 ty_generics: &TypeGenerics,
1861 impl_generics: &ImplGenerics,
1862) -> syn::Result<Vec<TokenStream>> {
1863 let span = proc_macro2::Span::call_site();
1864 let defspan = proc_macro2::Span::call_site();
1865 let local_version = quote_spanned! { defspan => local_version};
1866 let Field = quote_spanned! { defspan => _savefile::prelude::Field };
1867 let WithSchema = quote_spanned! { defspan => _savefile::prelude::WithSchema };
1868 let fields1 = quote_spanned! { defspan => fields1 };
1869
1870 let structname = Ident::new(structname, defspan);
1871 let offset_of = quote_spanned! {defspan=>
1872 _savefile::prelude::offset_of
1873 };
1874
1875 let fn_impl_generics = if !generics.params.is_empty() {
1876 quote! { :: #impl_generics}
1877 } else {
1878 quote! {}
1879 };
1880 let mut fields = Vec::new();
1881 for (idx, field) in field_infos.iter().enumerate() {
1882 let verinfo = parse_attr_tag(field.attrs)?;
1883 if verinfo.ignore {
1884 continue;
1885 }
1886 let (field_from_version, field_to_version) = (verinfo.version_from, verinfo.version_to);
1887
1888 let offset;
1889 match is_enum {
1890 FieldOffsetStrategy::EnumWithKnownOffsets(variant_index) => {
1891 offset = quote! { Some(get_variant_offsets #fn_impl_generics (#variant_index)[#idx]) };
1892 }
1893 FieldOffsetStrategy::EnumWithUnknownOffsets => {
1894 offset = quote! { None };
1895 }
1896 FieldOffsetStrategy::Struct => {
1897 if let Some(name) = field.ident.clone() {
1898 offset = quote! { Some(#offset_of!(#structname #ty_generics, #name)) };
1899 } else {
1900 let idx = Index::from(idx);
1901 offset = quote! { Some(#offset_of!(#structname #ty_generics, #idx)) }
1902 };
1903 }
1904 }
1905
1906 let name_str = if let Some(name) = field.ident.clone() {
1907 name.to_string()
1908 } else {
1909 idx.to_string()
1910 };
1911 let removed = check_is_remove(field.ty);
1912 let field_type = &field.ty;
1913 if field_from_version == 0 && field_to_version == u32::MAX {
1914 if removed.is_removed() {
1915 abort!(
1916 field.ty.span(),
1917 "The Removed type can only be used for removed fields. Use the savefile_version attribute."
1918 );
1919 }
1920 fields.push(quote_spanned!( span => #fields1.push(unsafe{#Field::unsafe_new(#name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version, context)), #offset)} )));
1921 } else {
1922 let mut version_mappings = Vec::new();
1923 let offset = if field_to_version != u32::MAX {
1924 quote!(None)
1925 } else {
1926 offset
1927 };
1928 for dt in verinfo.deserialize_types.iter() {
1929 let dt_from = dt.from;
1930 let dt_to = dt.to;
1931 let dt_field_type = syn::Ident::new(&dt.serialized_type, span);
1932 version_mappings.push(quote!{
1934 if #local_version >= #dt_from && local_version <= #dt_to {
1935 #fields1.push(#Field ::new( #name_str.to_string(), std::boxed::Box::new(<#dt_field_type as #WithSchema>::schema(#local_version, context))) );
1936 }
1937 });
1938 }
1939
1940 fields.push(quote_spanned!( span =>
1941 #(#version_mappings)*
1942
1943 if #local_version >= #field_from_version && #local_version <= #field_to_version {
1944 #fields1.push(unsafe{#Field ::unsafe_new( #name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version, context)), #offset )} );
1945 }
1946 ));
1947 }
1948 }
1949 Ok(fields)
1950}
1951
1952enum FieldOffsetStrategy {
1953 Struct,
1954 EnumWithKnownOffsets(usize ),
1955 EnumWithUnknownOffsets,
1956}
1957
1958fn get_phantomdata_types_from_type(ty: &Type, mut in_phantom: bool, generic_params_to_constraint: &mut HashSet<Ident>) {
1959 match ty {
1960 Type::Array(a) => {
1961 get_phantomdata_types_from_type(&*a.elem, in_phantom, generic_params_to_constraint);
1962 }
1963 Type::BareFn(f) => {
1964 match &f.output {
1965 ReturnType::Default => {}
1966 ReturnType::Type(_, ty) => {
1967 get_phantomdata_types_from_type(&*ty, in_phantom, generic_params_to_constraint);
1968 }
1969 }
1970 for arg in &f.inputs {
1971 get_phantomdata_types_from_type(&arg.ty, in_phantom, generic_params_to_constraint);
1972 }
1973 }
1974 Type::Paren(ty) => {
1975 get_phantomdata_types_from_type(&*ty.elem, in_phantom, generic_params_to_constraint);
1976 }
1977 Type::Path(pd) => {
1978 if pd.path.segments.len() == 1 {
1979 let id = &pd.path.segments.last().unwrap().ident;
1980 if id == "PhantomData" {
1981 in_phantom = true;
1982 }
1983 if !in_phantom {
1984 generic_params_to_constraint.insert(id.clone());
1985 }
1986 }
1987 for param in &pd.path.segments {
1988 match ¶m.arguments {
1989 PathArguments::None => {}
1990 PathArguments::AngleBracketed(a) => {
1991 for arg in &a.args {
1992 match arg {
1993 GenericArgument::Type(t) => {
1994 get_phantomdata_types_from_type(&*t, in_phantom, generic_params_to_constraint);
1995 }
1996 GenericArgument::AssocType(a) => {
1997 get_phantomdata_types_from_type(&a.ty, in_phantom, generic_params_to_constraint);
1998 }
1999 _ => {}
2000 }
2001 }
2002 }
2003 PathArguments::Parenthesized(a) => {
2004 for arg in &a.inputs {
2005 get_phantomdata_types_from_type(arg, in_phantom, generic_params_to_constraint);
2006 }
2007 match &a.output {
2008 ReturnType::Default => {}
2009 ReturnType::Type(_, ty) => {
2010 get_phantomdata_types_from_type(&*ty, in_phantom, generic_params_to_constraint);
2011 }
2012 }
2013 }
2014 }
2015 }
2016 }
2017 Type::Ptr(_) => {}
2018 Type::Reference(r) => {
2019 get_phantomdata_types_from_type(&*r.elem, in_phantom, generic_params_to_constraint);
2020 }
2021 Type::Slice(s) => {
2022 get_phantomdata_types_from_type(&*s.elem, in_phantom, generic_params_to_constraint);
2023 }
2024 Type::TraitObject(to) => {}
2025 Tuple(typ) => {
2026 for t in &typ.elems {
2027 get_phantomdata_types_from_type(t, in_phantom, generic_params_to_constraint);
2028 }
2029 }
2030 _ => {}
2031 }
2032}
2033fn get_phantomdata_types(data: &syn::Data, in_phantom: bool, generic_params_to_constrain: &mut HashSet<Ident>) {
2034 match data {
2035 Data::Struct(s) => {
2036 for field in s.fields.iter() {
2037 get_phantomdata_types_from_type(&field.ty, in_phantom, generic_params_to_constrain);
2038 }
2039 }
2040 Data::Enum(v) => {
2041 for variant in v.variants.iter() {
2042 for field in variant.fields.iter() {
2043 get_phantomdata_types_from_type(&field.ty, in_phantom, generic_params_to_constrain);
2044 }
2045 }
2046 }
2047 Data::Union(_) => {}
2048 }
2049}
2050
2051#[allow(non_snake_case)]
2052fn savefile_derive_crate_withschema(input: DeriveInput) -> syn::Result<TokenStream> {
2053 let name = &input.ident;
2058 let doc_hidden = doc_hidden(&input.attrs);
2059
2060 let generics = &input.generics;
2061
2062 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
2063
2064 let extra_where = get_extra_where_clauses(&input, where_clause, quote! {_savefile::prelude::WithSchema});
2065
2066 let span = proc_macro2::Span::call_site();
2067 let defspan = proc_macro2::Span::call_site();
2068 let withschema = quote_spanned! {defspan=>
2069 _savefile::prelude::WithSchema
2070 };
2071
2072 let SchemaStruct = quote_spanned! { defspan => _savefile::prelude::SchemaStruct };
2073 let SchemaEnum = quote_spanned! { defspan => _savefile::prelude::SchemaEnum };
2074 let Schema = quote_spanned! { defspan => _savefile::prelude::Schema };
2075 let Field = quote_spanned! { defspan => _savefile::prelude::Field };
2076 let Variant = quote_spanned! { defspan => _savefile::prelude::Variant };
2077
2078 let expanded = match &input.data {
2081 &syn::Data::Enum(ref enum1) => {
2082 let max_variant_fields = enum1.variants.iter().map(|x| x.fields.len()).max().unwrap_or(0);
2083
2084 let enum_size = get_enum_size(&input.attrs, enum1.variants.len())?;
2085 let need_determine_offsets = enum_size.explicit_size;
2086
2087 let mut variants = Vec::new();
2088 let mut variant_field_offset_extractors = vec![];
2089 for (var_idx, variant) in enum1.variants.iter().enumerate() {
2090 let var_idx = var_idx as u8;
2094 let var_ident = variant.ident.clone();
2095 let variant_name = quote! { #var_ident };
2096 let variant_name_spanned = quote_spanned! { span => stringify!(#variant_name).to_string()};
2097
2098 let verinfo = parse_attr_tag(&variant.attrs)?;
2099 let (field_from_version, field_to_version) = (verinfo.version_from, verinfo.version_to);
2100
2101 if field_to_version != std::u32::MAX {
2102 abort!(
2103 variant.span(),
2104 "Savefile automatic derive does not support removal of enum values."
2105 );
2106 }
2107
2108 let mut field_infos = Vec::new();
2109
2110 let mut field_offset_extractors = vec![];
2111
2112 let offset_extractor_match_clause;
2113 match &variant.fields {
2114 &syn::Fields::Named(ref fields_named) => {
2115 let mut field_pattern = vec![];
2116 for (idx, f) in fields_named.named.iter().enumerate() {
2117 let field_name = f
2118 .ident
2119 .as_ref()
2120 .expect("Enum variant with named fields *must* actually have a name")
2121 .clone();
2122 field_offset_extractors.push(quote!(unsafe { (#field_name as *const _ as *const u8).offset_from(base_ptr) as usize }));
2123 field_pattern.push(field_name);
2124 field_infos.push(FieldInfo {
2125 ident: Some(f.ident.clone().expect("Expected identifier[1]")),
2126 field_span: f.ident.as_ref().unwrap().span(),
2127 ty: &f.ty,
2128 index: idx as u32,
2129 attrs: &f.attrs,
2130 });
2131 }
2132 offset_extractor_match_clause = quote! {#name::#var_ident { #(#field_pattern,)* } };
2133 }
2134 &syn::Fields::Unnamed(ref fields_unnamed) => {
2135 let mut field_pattern = vec![];
2136 for (idx, f) in fields_unnamed.unnamed.iter().enumerate() {
2137 let field_binding = Ident::new(&format!("x{}", idx), Span::call_site());
2138 field_pattern.push(field_binding.clone());
2139 field_offset_extractors.push(quote!(unsafe { (#field_binding as *const _ as *const u8).offset_from(base_ptr) as usize }));
2140 field_infos.push(FieldInfo {
2141 ident: None,
2142 field_span: f.ty.span(),
2143 index: idx as u32,
2144 ty: &f.ty,
2145 attrs: &f.attrs,
2146 });
2147 }
2148 offset_extractor_match_clause = quote! {#name::#var_ident ( #(#field_pattern,)* ) };
2149 }
2150 &syn::Fields::Unit => {
2151 offset_extractor_match_clause = quote! {#name::#var_ident};
2152 }
2154 }
2155 while field_offset_extractors.len() < max_variant_fields {
2156 field_offset_extractors.push(quote! {0});
2157 }
2158
2159 variant_field_offset_extractors.push(quote! {
2160 #offset_extractor_match_clause => {
2161 [ #(#field_offset_extractors,)* ]
2162 }
2163 });
2164
2165 let field_offset_strategy = if need_determine_offsets && field_infos.is_empty() == false {
2166 FieldOffsetStrategy::EnumWithKnownOffsets(var_idx as usize)
2167 } else {
2168 FieldOffsetStrategy::EnumWithUnknownOffsets
2169 };
2170
2171 let fields = implement_withschema(
2172 &name.to_string(),
2173 field_infos,
2174 field_offset_strategy,
2175 &generics,
2176 &ty_generics,
2177 &impl_generics,
2178 )?;
2179
2180 variants.push(quote! {
2181 (#field_from_version,
2182 #field_to_version,
2183 #Variant { name: #variant_name_spanned, discriminant: #var_idx, fields:
2184 {
2185 let mut fields1 = Vec::<#Field>::new();
2186 #(#fields;)*
2187 fields1
2188 }}
2189 )});
2190 }
2191
2192 let field_offset_impl;
2193 if need_determine_offsets {
2194 let varbuf_assign;
2195 if enum_size.discriminant_size == 1 {
2196 varbuf_assign = quote!( varbuf[0] = variant as u8; );
2197 } else if enum_size.discriminant_size == 2 {
2198 varbuf_assign = quote!(
2200 varbuf[0] = variant as u8;
2201 varbuf[1] = (variant>>8) as u8;
2202 );
2203 } else if enum_size.discriminant_size == 4 {
2204 varbuf_assign = quote!(
2206 varbuf[0] = variant as u8;
2207 varbuf[1] = (variant>>8) as u8;
2208 varbuf[2] = (variant>>16) as u8;
2209 varbuf[3] = (variant>>24) as u8;
2210 );
2211 } else {
2212 abort_call_site!("Unsupported enum size: {}", enum_size.discriminant_size);
2213 }
2214 let not_const_if_gen = if generics.params.is_empty() {
2215 quote! {const}
2216 } else {
2217 quote! {}
2218 };
2219 let conjure_variant;
2220 if generics.params.is_empty() {
2221 conjure_variant = quote! {
2222 let mut varbuf = [0u8;std::mem::size_of::<#name #ty_generics>()];
2223 #varbuf_assign
2224 let mut value : MaybeUninit<#name #ty_generics> = unsafe { std::mem::transmute(varbuf) };
2225 }
2226 } else {
2227 let discr_type;
2228 match enum_size.discriminant_size {
2229 1 => discr_type = quote! { u8 },
2230 2 => discr_type = quote! { u16 },
2231 4 => discr_type = quote! { u32 },
2232 _ => unreachable!(),
2233 }
2234 conjure_variant = quote! {
2235 let mut value = MaybeUninit::< #name #ty_generics >::uninit();
2236 let discr: *mut #discr_type = &mut value as *mut MaybeUninit<#name #ty_generics> as *mut #discr_type;
2237 unsafe {
2238 *discr = variant as #discr_type;
2239 }
2240 }
2241 }
2242
2243 field_offset_impl = quote! {
2244 #not_const_if_gen fn get_field_offset_impl #impl_generics (value: &#name #ty_generics) -> [usize;#max_variant_fields] {
2245 assert!(std::mem::size_of::<#name #ty_generics>()>0);
2246 let base_ptr = value as *const #name #ty_generics as *const u8;
2247 match value {
2248 #(#variant_field_offset_extractors)*
2249 }
2250 }
2251 #not_const_if_gen fn get_variant_offsets #impl_generics(variant: usize) -> [usize;#max_variant_fields] {
2252 #conjure_variant
2253 get_field_offset_impl(unsafe { &*(&value as *const MaybeUninit<#name #ty_generics> as *const #name #ty_generics) } )
2256 }
2257 };
2258 } else {
2259 field_offset_impl = quote! {};
2260 }
2261
2262 let discriminant_size = enum_size.discriminant_size;
2263 let has_explicit_repr = enum_size.repr_c;
2264
2265 quote! {
2266 #field_offset_impl
2267 #[automatically_derived]
2268 #doc_hidden
2269 impl #impl_generics #withschema for #name #ty_generics #where_clause #extra_where {
2270 #[allow(unused_mut)]
2271 #[allow(unused_comparisons, unused_variables)]
2272 fn schema(version:u32, context: &mut _savefile::prelude::WithSchemaContext) -> #Schema {
2273 let local_version = version;
2274
2275 #Schema::Enum (
2276 unsafe{#SchemaEnum::new_unsafe(
2277 stringify!(#name).to_string(),
2278 (vec![#(#variants),*]).into_iter().filter_map(|(fromver,tover,x)|{
2279 if local_version >= fromver && local_version <= tover {
2280 Some(x)
2281 } else {
2282 None
2283 }
2284 }).collect(),
2285 #discriminant_size,
2286 #has_explicit_repr,
2287 Some(std::mem::size_of::<#name #ty_generics>()),
2288 Some(std::mem::align_of::<#name #ty_generics>()),
2289 )}
2290 )
2291 }
2292 }
2293
2294 }
2295 }
2296 &syn::Data::Struct(ref struc) => {
2297 let fields;
2298 match &struc.fields {
2299 &syn::Fields::Named(ref namedfields) => {
2300 let field_infos: Vec<FieldInfo> = namedfields
2301 .named
2302 .iter()
2303 .enumerate()
2304 .map(|(idx, field)| FieldInfo {
2305 ident: Some(field.ident.clone().expect("Expected identifier[2]")),
2306 field_span: field.ident.span(),
2307 ty: &field.ty,
2308 index: idx as u32,
2309 attrs: &field.attrs,
2310 })
2311 .collect();
2312
2313 fields = implement_withschema(
2314 &name.to_string(),
2315 field_infos,
2316 FieldOffsetStrategy::Struct,
2317 &generics,
2318 &ty_generics,
2319 &impl_generics,
2320 )?;
2321 }
2322 &syn::Fields::Unnamed(ref fields_unnamed) => {
2323 let field_infos: Vec<FieldInfo> = fields_unnamed
2324 .unnamed
2325 .iter()
2326 .enumerate()
2327 .map(|(idx, f)| FieldInfo {
2328 field_span: f.ty.span(),
2329 ident: None,
2330 index: idx as u32,
2331 ty: &f.ty,
2332 attrs: &f.attrs,
2333 })
2334 .collect();
2335 fields = implement_withschema(
2336 &name.to_string(),
2337 field_infos,
2338 FieldOffsetStrategy::Struct,
2339 &generics,
2340 &ty_generics,
2341 &impl_generics,
2342 )?;
2343 }
2344 &syn::Fields::Unit => {
2345 fields = Vec::new();
2346 }
2347 }
2348 quote! {
2349 #[automatically_derived]
2350 #doc_hidden
2351 impl #impl_generics #withschema for #name #ty_generics #where_clause #extra_where {
2352
2353 #[allow(unused_comparisons)]
2354 #[allow(unused_mut, unused_variables)]
2355 fn schema(version:u32, context: &mut _savefile::prelude::WithSchemaContext) -> #Schema {
2356 let local_version = version;
2357 let mut fields1 = Vec::new();
2358 #(#fields;)* ;
2359 #Schema::Struct(unsafe{#SchemaStruct::new_unsafe(
2360 stringify!(#name).to_string(),
2361 fields1,
2362 Some(std::mem::size_of::<#name #ty_generics>()),
2363 Some(std::mem::align_of::<#name #ty_generics>()),
2364 )})
2365
2366 }
2367 }
2368 }
2369 }
2370 _ => {
2371 abort_call_site!("Unsupported datatype");
2372 }
2373 };
2374 Ok(expanded)
2378}