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