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