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