1use crate::contract::common::{derive_ident_metadata, extract_ident_from_type};
2use heck::{ToSnakeCase, ToUpperCamelCase};
3use proc_macro2::{Ident, Literal, Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::token::Paren;
8use syn::{
9 Attribute, Error, GenericArgument, Meta, Pat, PatType, PathArguments, Receiver, ReceiverKind,
10 ReturnType, Signature, Token, Type, TypeTuple,
11};
12
13#[derive(Copy, Clone)]
14pub(super) enum MethodType {
15 Init,
16 Update,
17 View,
18}
19
20impl MethodType {
21 fn attr_str(self) -> &'static str {
22 match self {
23 MethodType::Init => "init",
24 MethodType::Update => "update",
25 MethodType::View => "view",
26 }
27 }
28}
29
30#[derive(Clone)]
31struct Env {
32 arg_name: Ident,
33 mutability: Option<Token![mut]>,
34}
35
36#[derive(Clone)]
37struct Tmp {
38 type_name: Type,
39 arg_name: Ident,
40 mutability: Option<Token![mut]>,
41}
42
43#[derive(Clone)]
44struct Slot {
45 with_address_arg: bool,
46 type_name: Type,
47 arg_name: Ident,
48 mutability: Option<Token![mut]>,
49}
50
51#[derive(Clone)]
52struct Input {
53 type_name: Type,
54 arg_name: Ident,
55}
56
57#[derive(Clone)]
58struct Output {
59 type_name: Type,
60 arg_name: Ident,
61 has_self: bool,
62}
63
64enum MethodReturnType {
65 Unit(Type),
67 Regular(Type),
69 ResultUnit(Type),
71 Result(Type),
73}
74
75impl MethodReturnType {
76 fn unit_type() -> Type {
77 Type::Tuple(TypeTuple {
78 attrs: Vec::new(),
79 paren_token: Paren::default(),
80 elems: Punctuated::default(),
81 })
82 }
83
84 fn unit_return_type(&self) -> bool {
85 match self {
86 Self::Unit(_) | Self::ResultUnit(_) => true,
87 Self::Regular(_) | Self::Result(_) => false,
88 }
89 }
90
91 fn return_type(&self) -> &Type {
92 match self {
93 Self::Unit(ty) | Self::Regular(ty) | Self::ResultUnit(ty) | Self::Result(ty) => ty,
94 }
95 }
96}
97
98#[derive(Default)]
99pub(super) struct ExtTraitComponents {
100 pub(super) definition: TokenStream,
101 pub(super) r#impl: TokenStream,
102}
103
104pub(super) struct MethodDetails {
105 method_type: MethodType,
106 self_type: Type,
107 #[expect(clippy::option_option, reason = "Intentional storing of syn's type")]
108 state: Option<Option<Token![mut]>>,
109 env: Option<Env>,
110 tmp: Option<Tmp>,
111 slots: Vec<Slot>,
112 inputs: Vec<Input>,
113 outputs: Vec<Output>,
114 return_type: MethodReturnType,
115}
116
117impl MethodDetails {
118 pub(super) fn new(method_type: MethodType, self_type: Type) -> Self {
119 Self {
120 method_type,
121 self_type,
122 state: None,
123 env: None,
124 tmp: None,
125 slots: Vec::new(),
126 inputs: Vec::new(),
127 outputs: Vec::new(),
128 return_type: MethodReturnType::Unit(MethodReturnType::unit_type()),
129 }
130 }
131
132 pub(super) fn tmp_type<'a, I>(iter: I) -> Option<Type>
134 where
135 I: Iterator<Item = &'a Self> + 'a,
136 {
137 let mut tmp_type = None;
138 for slot in iter.flat_map(|method_details| &method_details.tmp) {
139 match &tmp_type {
140 Some(tmp_type) => {
141 if tmp_type != &slot.type_name {
142 return None;
143 }
144 }
145 None => {
146 tmp_type.replace(slot.type_name.clone());
147 }
148 }
149 }
150
151 Some(tmp_type.unwrap_or_else(MethodReturnType::unit_type))
152 }
153
154 pub(super) fn slot_type<'a, I>(iter: I) -> Option<Type>
156 where
157 I: Iterator<Item = &'a Self> + 'a,
158 {
159 let mut slot_type = None;
160 for slot in iter.flat_map(|method_details| &method_details.slots) {
161 match &slot_type {
162 Some(slot_type) => {
163 if slot_type != &slot.type_name {
164 return None;
165 }
166 }
167 None => {
168 slot_type.replace(slot.type_name.clone());
169 }
170 }
171 }
172
173 Some(slot_type.unwrap_or_else(MethodReturnType::unit_type))
174 }
175
176 pub(super) fn process_env_arg_ro(
177 &mut self,
178 input_span: Span,
179 pat_type: &PatType,
180 ) -> Result<(), Error> {
181 self.process_env_arg(input_span, pat_type, false)
182 }
183
184 pub(super) fn process_env_arg_rw(
185 &mut self,
186 input_span: Span,
187 pat_type: &PatType,
188 ) -> Result<(), Error> {
189 self.process_env_arg(input_span, pat_type, true)
190 }
191
192 fn process_env_arg(
193 &mut self,
194 input_span: Span,
195 pat_type: &PatType,
196 allow_mut: bool,
197 ) -> Result<(), Error> {
198 if self.env.is_some()
199 || self.tmp.is_some()
200 || !(self.inputs.is_empty() && self.outputs.is_empty())
201 {
202 return Err(Error::new(
203 input_span,
204 "`#[env]` must be the first non-Self argument and only appear once",
205 ));
206 }
207
208 if let Type::Reference(type_reference) = &*pat_type.ty
209 && let Type::Path(_type_path) = &*type_reference.elem
210 && let Pat::Ident(pat_ident) = &*pat_type.pat
211 {
212 if type_reference.mutability.is_some() && !allow_mut {
213 return Err(Error::new(
214 input_span,
215 "`#[env]` is not allowed to mutate data here",
216 ));
217 }
218
219 self.env.replace(Env {
220 arg_name: pat_ident.ident.clone(),
221 mutability: type_reference.mutability,
222 });
223 Ok(())
224 } else {
225 Err(Error::new(
226 pat_type.span(),
227 "`#[env]` must be a reference to `Env` type (can be shared or exclusive)",
228 ))
229 }
230 }
231
232 pub(super) fn process_state_arg_ro(
233 &mut self,
234 input_span: Span,
235 receiver: &Receiver,
236 ) -> Result<(), Error> {
237 self.process_state_arg(input_span, receiver, false)
238 }
239
240 pub(super) fn process_state_arg_rw(
241 &mut self,
242 input_span: Span,
243 receiver: &Receiver,
244 ) -> Result<(), Error> {
245 self.process_state_arg(input_span, receiver, true)
246 }
247
248 fn process_state_arg(
249 &mut self,
250 input_span: Span,
251 receiver: &Receiver,
252 allow_mut: bool,
253 ) -> Result<(), Error> {
254 let ReceiverKind::Reference(_, _, mutability) = receiver.kind else {
256 return Err(Error::new(
257 input_span,
258 "Can't consume `Self`, use `&self` or `&mut self` instead",
259 ));
260 };
261 if mutability.is_some() && !allow_mut {
262 return Err(Error::new(
263 input_span,
264 "`#[arg]` is not allowed to mutate data here",
265 ));
266 }
267
268 self.state.replace(mutability);
269 Ok(())
270 }
271
272 pub(super) fn process_tmp_arg(
273 &mut self,
274 input_span: Span,
275 pat_type: &PatType,
276 ) -> Result<(), Error> {
277 if self.tmp.is_some() || !(self.inputs.is_empty() && self.outputs.is_empty()) {
278 return Err(Error::new(
279 input_span,
280 "`#[tmp]` must appear only once before any `#[input]` or `#[output]`",
281 ));
282 }
283
284 if let Type::Reference(type_reference) = &*pat_type.ty {
286 let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
287 return Err(Error::new(
288 pat_type.span(),
289 "`#[tmp]` argument name must be either a simple variable or a reference",
290 ));
291 };
292
293 self.tmp.replace(Tmp {
294 type_name: type_reference.elem.as_ref().clone(),
295 arg_name,
296 mutability: type_reference.mutability,
297 });
298
299 return Ok(());
300 }
301
302 Err(Error::new(
303 pat_type.span(),
304 "`#[tmp]` must be a reference to a type implementing `IoTypeOptional` (can be \
305 shared or exclusive) like `&MaybeData<Slot>` or `&mut VariableBytes<1024>`",
306 ))
307 }
308
309 pub(super) fn process_slot_arg_ro(
310 &mut self,
311 input_span: Span,
312 pat_type: &PatType,
313 ) -> Result<(), Error> {
314 self.process_slot_arg(input_span, pat_type, false)
315 }
316
317 pub(super) fn process_slot_arg_rw(
318 &mut self,
319 input_span: Span,
320 pat_type: &PatType,
321 ) -> Result<(), Error> {
322 self.process_slot_arg(input_span, pat_type, true)
323 }
324
325 fn process_slot_arg(
326 &mut self,
327 input_span: Span,
328 pat_type: &PatType,
329 allow_mut: bool,
330 ) -> Result<(), Error> {
331 if !(self.inputs.is_empty() && self.outputs.is_empty()) {
332 return Err(Error::new(
333 input_span,
334 "`#[slot]` must appear before any `#[input]` or `#[output]`",
335 ));
336 }
337
338 match &*pat_type.ty {
339 Type::Reference(type_reference) => {
341 if type_reference.mutability.is_some() && !allow_mut {
342 return Err(Error::new(
343 input_span,
344 "`#[slot]` is not allowed to mutate data here",
345 ));
346 }
347
348 let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
349 return Err(Error::new(
350 pat_type.span(),
351 "`#[slot]` argument name must be either a simple variable or a reference",
352 ));
353 };
354
355 self.slots.push(Slot {
356 with_address_arg: false,
357 type_name: type_reference.elem.as_ref().clone(),
358 arg_name,
359 mutability: type_reference.mutability,
360 });
361 return Ok(());
362 }
363 Type::Tuple(type_tuple) => {
365 if type_tuple.elems.len() == 2
366 && let Type::Reference(address_type) =
367 type_tuple.elems.first().expect("Checked above; qed")
368 && address_type.mutability.is_none()
369 && let Type::Reference(outer_slot_type) =
370 type_tuple.elems.last().expect("Checked above; qed")
371 && let Pat::Tuple(pat_tuple) = &*pat_type.pat
372 && pat_tuple.elems.len() == 2
373 && let Some(slot_arg) = extract_arg_name(&pat_tuple.elems[1])
374 {
375 if outer_slot_type.mutability.is_some() && !allow_mut {
376 return Err(Error::new(
377 input_span,
378 "`#[slot]` is not allowed to mutate data here",
379 ));
380 }
381
382 self.slots.push(Slot {
383 with_address_arg: true,
384 type_name: outer_slot_type.elem.as_ref().clone(),
385 arg_name: slot_arg,
386 mutability: outer_slot_type.mutability,
387 });
388 return Ok(());
389 }
390
391 return Err(Error::new(
392 pat_type.span(),
393 "`#[slot]` with address must be a tuple of arguments, each of which is \
394 either a simple variable or a reference",
395 ));
396 }
397 _ => {
398 }
400 }
401
402 Err(Error::new(
403 pat_type.span(),
404 "`#[slot]` must be a reference to a type implementing `IoTypeOptional` (can be \
405 shared or exclusive) like `&MaybeData<Slot>` or a tuple of references to address and \
406 to slot type like `(&Address, &mut VariableBytes<1024>)`",
407 ))
408 }
409
410 pub(super) fn process_input_arg(
411 &mut self,
412 input_span: Span,
413 pat_type: &PatType,
414 ) -> Result<(), Error> {
415 if !self.outputs.is_empty() {
416 return Err(Error::new(
417 input_span,
418 "`#[input]` must appear before any `#[output]`",
419 ));
420 }
421
422 if let Type::Reference(type_reference) = &*pat_type.ty {
424 let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
425 return Err(Error::new(
426 pat_type.span(),
427 "`#[input]` argument name must be either a simple variable or a reference",
428 ));
429 };
430 if type_reference.mutability.is_some() {
431 return Err(Error::new(
432 input_span,
433 "`#[input]` must be a shared reference",
434 ));
435 }
436
437 self.inputs.push(Input {
438 type_name: type_reference.elem.as_ref().clone(),
439 arg_name,
440 });
441
442 Ok(())
443 } else {
444 Err(Error::new(
445 pat_type.span(),
446 "`#[input]` must be a shared reference to a type",
447 ))
448 }
449 }
450
451 pub(super) fn process_output_arg(
452 &mut self,
453 _input_span: Span,
454 pat_type: &PatType,
455 ) -> Result<(), Error> {
456 if let Type::Reference(type_reference) = &*pat_type.ty
458 && type_reference.mutability.is_some()
459 {
460 let Pat::Ident(pat_ident) = &*pat_type.pat else {
461 return Err(Error::new(
462 pat_type.span(),
463 "`#[output]` argument name must be an exclusive reference",
464 ));
465 };
466
467 let mut type_name = type_reference.elem.as_ref().clone();
468 let mut has_self = false;
469
470 if let Type::Path(type_path) = &mut type_name
472 && let Some(path_segment) = type_path.path.segments.first_mut()
473 && let PathArguments::AngleBracketed(generic_arguments) =
474 &mut path_segment.arguments
475 && let Some(GenericArgument::Type(first_generic_argument)) =
476 generic_arguments.args.first_mut()
477 && let Type::Path(type_path) = &first_generic_argument
478 && type_path.path.is_ident("Self")
479 {
480 *first_generic_argument = self.self_type.clone();
481 has_self = true;
482 }
483
484 self.outputs.push(Output {
485 type_name,
486 arg_name: pat_ident.ident.clone(),
487 has_self,
488 });
489 Ok(())
490 } else {
491 Err(Error::new(
492 pat_type.span(),
493 "`#[output]` must be an exclusive reference to a type implementing \
494 `IoTypeOptional`, likely `MaybeData` container",
495 ))
496 }
497 }
498
499 pub(super) fn process_return(&mut self, output: &ReturnType) -> Result<(), Error> {
500 let error_message = format!(
502 "`#[{}]` must return `()` or `T` or `Result<T, ContractError>",
503 self.method_type.attr_str()
504 );
505 match output {
506 ReturnType::Default => {
507 self.set_return_type(MethodReturnType::Unit(MethodReturnType::unit_type()));
508 }
509 ReturnType::Type(_r_arrow, return_type) => match return_type.as_ref() {
510 Type::Array(_type_array) => {
511 self.set_return_type(MethodReturnType::Regular(return_type.as_ref().clone()));
512 }
513 Type::Path(type_path) => {
514 let Some(last_path_segment) = type_path.path.segments.last() else {
516 self.set_return_type(MethodReturnType::Regular(
517 return_type.as_ref().clone(),
518 ));
519 return Ok(());
520 };
521
522 if last_path_segment.ident == "Result" {
524 if let PathArguments::AngleBracketed(result_arguments) =
525 &last_path_segment.arguments
526 && result_arguments.args.len() == 2
527 && let GenericArgument::Type(ok_type) = &result_arguments.args[0]
528 && let GenericArgument::Type(error_type) = &result_arguments.args[1]
529 && let Type::Path(error_path) = error_type
530 && error_path
531 .path
532 .segments
533 .last()
534 .is_some_and(|s| s.ident == "ContractError")
535 {
536 if let Type::Path(ok_path) = ok_type
537 && ok_path
538 .path
539 .segments
540 .first()
541 .is_some_and(|s| s.ident == "Self")
542 {
543 self.set_return_type(MethodReturnType::Result(
545 self.self_type.clone(),
546 ));
547 } else {
548 self.set_return_type(MethodReturnType::Result(ok_type.clone()));
549 }
550 } else {
551 return Err(Error::new(return_type.span(), error_message));
552 }
553 } else if last_path_segment.ident == "Self" {
554 self.set_return_type(MethodReturnType::Regular(self.self_type.clone()));
556 } else {
557 self.set_return_type(MethodReturnType::Regular(
558 return_type.as_ref().clone(),
559 ));
560 }
561 }
562 return_type => {
563 return Err(Error::new(return_type.span(), error_message));
564 }
565 },
566 }
567
568 Ok(())
569 }
570
571 fn set_return_type(&mut self, return_type: MethodReturnType) {
572 let unit_type = MethodReturnType::unit_type();
573 self.return_type = match return_type {
574 MethodReturnType::Unit(ty) => MethodReturnType::Unit(ty),
575 MethodReturnType::Regular(ty) => {
576 if ty == unit_type {
577 MethodReturnType::Unit(ty)
578 } else {
579 MethodReturnType::Regular(ty)
580 }
581 }
582 MethodReturnType::ResultUnit(ty) => MethodReturnType::ResultUnit(ty),
583 MethodReturnType::Result(ty) => {
584 if ty == unit_type {
585 MethodReturnType::ResultUnit(ty)
586 } else {
587 MethodReturnType::Result(ty)
588 }
589 }
590 };
591 }
592
593 pub(super) fn generate_guest_ffi(
594 &self,
595 fn_sig: &Signature,
596 trait_name: Option<&Ident>,
597 ) -> Result<TokenStream, Error> {
598 let self_type = &self.self_type;
599 if matches!(self.method_type, MethodType::Init) {
600 let self_return_type = self.return_type.return_type() == self_type;
601 let self_last_output_type = self.outputs.last().is_some_and(|output| output.has_self);
602
603 if !(self_return_type || self_last_output_type) {
604 return Err(Error::new(
605 fn_sig.span(),
606 "`#[init]` must have `Self` as either return type or last `#[output]` \
607 argument",
608 ));
609 }
610 }
611
612 let original_method_name = &fn_sig.ident;
613
614 let guest_fn = self.generate_guest_fn(fn_sig, trait_name)?;
615 let external_args_struct = self.generate_external_args_struct(fn_sig, trait_name)?;
616 let metadata = self.generate_metadata(fn_sig, trait_name)?;
617
618 Ok(quote_spanned! {fn_sig.span() =>
619 #[expect(clippy::inline_modules, reason = "Macro-generated")]
620 pub mod #original_method_name {
621 use super::*;
622
623 #guest_fn
624 #external_args_struct
625 #metadata
626 }
627 })
628 }
629
630 pub(super) fn generate_guest_trait_ffi(
631 &self,
632 fn_sig: &Signature,
633 trait_name: Option<&Ident>,
634 ) -> Result<TokenStream, Error> {
635 let original_method_name = &fn_sig.ident;
636
637 let external_args_struct = self.generate_external_args_struct(fn_sig, trait_name)?;
638 let metadata = self.generate_metadata(fn_sig, trait_name)?;
639
640 Ok(quote_spanned! {fn_sig.span() =>
641 #[expect(clippy::inline_modules, reason = "Macro-generated")]
642 pub mod #original_method_name {
643 use super::*;
644
645 #external_args_struct
646 #metadata
647 }
648 })
649 }
650
651 pub(super) fn generate_guest_fn(
652 &self,
653 fn_sig: &Signature,
654 trait_name: Option<&Ident>,
655 ) -> Result<TokenStream, Error> {
656 let self_type = &self.self_type;
657
658 let mut internal_args_pointers = Vec::new();
660 let mut preparation = Vec::new();
662 let mut original_fn_args = Vec::new();
664
665 if let Some(mutability) = self.state {
667 internal_args_pointers.push(quote! {
668 pub self_ptr: ::core::ptr::NonNull<
669 <#self_type as ::ab_contracts_macros::__private::IoType>::PointerType,
670 >,
671 pub self_size: ::core::primitive::u32,
673 pub self_capacity: ::core::primitive::u32,
675 });
676
677 if mutability.is_some() {
678 original_fn_args.push(quote! {&mut *{
679 const {
682 const fn assert_impl_io_type<T>()
683 where
684 T: ::ab_contracts_macros::__private::IoType,
685 {}
686 assert_impl_io_type::<#self_type>();
687 }
688
689 <#self_type as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
690 &mut args.self_ptr,
691 &mut args.self_size,
692 args.self_capacity,
693 )
694 }});
695 } else {
696 original_fn_args.push(quote! {&*{
697 const {
700 const fn assert_impl_io_type<T>()
701 where
702 T: ::ab_contracts_macros::__private::IoType,
703 {}
704 assert_impl_io_type::<#self_type>();
705 }
706
707 <#self_type as ::ab_contracts_macros::__private::IoType>::from_ptr(
708 &args.self_ptr,
709 &args.self_size,
710 args.self_capacity,
711 )
712 }});
713 }
714 }
715
716 if let Some(env) = &self.env {
718 let env_field = &env.arg_name;
719 let mutability = env.mutability;
720
721 internal_args_pointers.push(quote! {
722 pub #env_field: &'internal_args #mutability ::ab_contracts_macros::__private::Env<'internal_args>,
724 });
725
726 original_fn_args.push(quote! { args.#env_field });
727 }
728
729 if let Some(tmp) = &self.tmp {
731 let type_name = &tmp.type_name;
732 let mutability = tmp.mutability;
733 let ptr_field = format_ident!("{}_ptr", tmp.arg_name);
734 let size_field = format_ident!("{}_size", tmp.arg_name);
735 let size_doc = format!("Size of the contents `{ptr_field}` points to");
736 let capacity_field = format_ident!("{}_capacity", tmp.arg_name);
737 let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
738
739 internal_args_pointers.push(quote! {
740 pub #ptr_field: ::core::ptr::NonNull<
741 <
742 <#self_type as ::ab_contracts_macros::__private::Contract>::Tmp as ::ab_contracts_macros::__private::IoType
744 >::PointerType,
745 >,
746 #[doc = #size_doc]
747 pub #size_field: ::core::primitive::u32,
748 #[doc = #capacity_doc]
749 pub #capacity_field: ::core::primitive::u32,
750 });
751
752 if mutability.is_some() {
753 original_fn_args.push(quote! {&mut *{
754 const {
758 const fn assert_impl_io_type_optional<T>()
759 where
760 T: ::ab_contracts_macros::__private::IoTypeOptional,
761 {}
762 assert_impl_io_type_optional::<#type_name>();
763 }
764
765 <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
766 &mut args.#ptr_field,
767 &mut args.#size_field,
768 args.#capacity_field,
769 )
770 }});
771 } else {
772 original_fn_args.push(quote! {&*{
773 const {
777 const fn assert_impl_io_type_optional<T>()
778 where
779 T: ::ab_contracts_macros::__private::IoTypeOptional,
780 {}
781 assert_impl_io_type_optional::<#type_name>();
782 }
783
784 <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
785 &args.#ptr_field,
786 &args.#size_field,
787 args.#capacity_field,
788 )
789 }});
790 }
791 }
792
793 for slot in &self.slots {
797 let type_name = &slot.type_name;
798 let mutability = slot.mutability;
799 let address_field = format_ident!("{}_address", slot.arg_name);
800 let ptr_field = format_ident!("{}_ptr", slot.arg_name);
801 let size_field = format_ident!("{}_size", slot.arg_name);
802 let size_doc = format!("Size of the contents `{ptr_field}` points to");
803 let capacity_field = format_ident!("{}_capacity", slot.arg_name);
804 let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
805
806 internal_args_pointers.push(quote! {
807 pub #address_field: &'internal_args ::ab_contracts_macros::__private::Address,
810 pub #ptr_field: ::core::ptr::NonNull<
811 <
812 <#self_type as ::ab_contracts_macros::__private::Contract>::Slot as ::ab_contracts_macros::__private::IoType
814 >::PointerType,
815 >,
816 #[doc = #size_doc]
817 pub #size_field: ::core::primitive::u32,
818 #[doc = #capacity_doc]
819 pub #capacity_field: ::core::primitive::u32,
820 });
821
822 let arg_extraction = if mutability.is_some() {
823 quote! {&mut *{
824 const {
829 const fn assert_impl_io_type_optional<T>()
830 where
831 T: ::ab_contracts_macros::__private::IoTypeOptional,
832 {}
833 assert_impl_io_type_optional::<#type_name>();
834 }
835
836 <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
837 &mut args.#ptr_field,
838 &mut args.#size_field,
839 args.#capacity_field,
840 )
841 }}
842 } else {
843 quote! {&*{
844 const {
849 const fn assert_impl_io_type_optional<T>()
850 where
851 T: ::ab_contracts_macros::__private::IoTypeOptional,
852 {}
853 assert_impl_io_type_optional::<#type_name>();
854 }
855
856 <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
857 &args.#ptr_field,
858 &args.#size_field,
859 args.#capacity_field,
860 )
861 }}
862 };
863
864 if slot.with_address_arg {
865 original_fn_args.push(quote! {
866 (
867 args.#address_field,
868 #arg_extraction,
869 )
870 });
871 } else {
872 original_fn_args.push(arg_extraction);
873 }
874 }
875
876 for input in &self.inputs {
878 let type_name = &input.type_name;
879 let arg_name = &input.arg_name;
880 let ptr_field = format_ident!("{arg_name}_ptr");
881 let size_field = format_ident!("{arg_name}_size");
882 let size_doc = format!("Size of the contents `{ptr_field}` points to");
883 let capacity_field = format_ident!("{arg_name}_capacity");
884 let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
885
886 internal_args_pointers.push(quote! {
887 pub #ptr_field: ::core::ptr::NonNull<
888 <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
889 >,
890 #[doc = #size_doc]
891 pub #size_field: ::core::primitive::u32,
892 #[doc = #capacity_doc]
893 pub #capacity_field: ::core::primitive::u32,
894 });
895
896 original_fn_args.push(quote! {&*{
897 const {
900 const fn assert_impl_io_type<T>()
901 where
902 T: ::ab_contracts_macros::__private::IoType,
903 {}
904 assert_impl_io_type::<#type_name>();
905 }
906
907 <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
908 &args.#ptr_field,
909 &args.#size_field,
910 args.#capacity_field,
911 )
912 }});
913 }
914
915 for output in &self.outputs {
917 let type_name = &output.type_name;
918 let arg_name = &output.arg_name;
919 let ptr_field = format_ident!("{arg_name}_ptr");
920 let size_field = format_ident!("{arg_name}_size");
921 let size_doc = format!("Size of the contents `{ptr_field}` points to");
922 let capacity_field = format_ident!("{arg_name}_capacity");
923 let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
924
925 internal_args_pointers.push(quote! {
926 pub #ptr_field: ::core::ptr::NonNull<
927 <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
928 >,
929 #[doc = #size_doc]
930 pub #size_field: ::core::primitive::u32,
931 #[doc = #capacity_doc]
932 pub #capacity_field: ::core::primitive::u32,
933 });
934
935 original_fn_args.push(quote! {&mut *{
936 const {
939 const fn assert_impl_io_type<T>()
940 where
941 T: ::ab_contracts_macros::__private::IoType,
942 {}
943 assert_impl_io_type::<#type_name>();
944 }
945
946 <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
947 &mut args.#ptr_field,
948 &mut args.#size_field,
949 args.#capacity_field,
950 )
951 }});
952 }
953
954 let original_method_name = &fn_sig.ident;
955 let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
956 let return_type = self.return_type.return_type();
957
958 let internal_args_struct = {
959 if !self.return_type.unit_return_type() {
961 internal_args_pointers.push(quote! {
962 pub ok_result: &'internal_args mut ::core::mem::MaybeUninit<#return_type>,
963 });
964
965 preparation.push(quote! {
966 const {
971 const fn assert_impl_trivial_type<T>()
972 where
973 T: ::ab_contracts_macros::__private::IoType,
974 {}
975 assert_impl_trivial_type::<#return_type>();
976 }
977 });
978 }
979 let args_struct_doc = format!(
980 "Data structure containing expected input to [`{ffi_fn_name}()`], it is used \
981 internally by the contract, there should be no need to construct it explicitly \
982 except maybe in contract's own tests"
983 );
984 quote_spanned! {fn_sig.span() =>
985 #[doc = #args_struct_doc]
986 #[derive(::core::fmt::Debug)]
987 #[repr(C)]
988 #[expect(
989 clippy::partial_pub_fields,
990 reason = "Not needed to be constructed publicly, but does need to always store \
991 the lifetime"
992 )]
993 #[allow(clippy::pub_underscore_fields, reason = "Comes from user-provided arguments")]
994 pub struct InternalArgs<'internal_args>
995 {
996 #( #internal_args_pointers )*
997 lifetime: ::core::marker::PhantomData<&'internal_args ()>,
998 }
999 }
1000 };
1001
1002 let result_var_name = format_ident!("result");
1003 let guest_fn = {
1004 let result_handling = match &self.return_type {
1007 MethodReturnType::Unit(_) => {
1008 quote! {
1009 ::ab_contracts_macros::__private::ExitCode::ok()
1011 }
1012 }
1013 MethodReturnType::Regular(_) => {
1014 quote! {
1015 args.ok_result.write(#result_var_name);
1016 ::ab_contracts_macros::__private::ExitCode::ok()
1018 }
1019 }
1020 MethodReturnType::ResultUnit(_) => {
1021 quote! {
1022 match #result_var_name {
1024 Ok(()) => ::ab_contracts_macros::__private::ExitCode::ok(),
1025 Err(error) => error.exit_code(),
1026 }
1027 }
1028 }
1029 MethodReturnType::Result(_) => {
1030 quote! {
1031 match #result_var_name {
1033 Ok(result) => {
1034 args.ok_result.write(result);
1035 ::ab_contracts_macros::__private::ExitCode::ok()
1037 }
1038 Err(error) => error.exit_code(),
1039 }
1040 }
1041 }
1042 };
1043
1044 let full_struct_name = if let Some(trait_name) = trait_name {
1045 quote! { <#self_type as #trait_name> }
1046 } else {
1047 quote! { #self_type }
1048 };
1049
1050 quote_spanned! {fn_sig.span() =>
1052 #[cfg_attr(feature = "guest", unsafe(no_mangle))]
1061 #[allow(clippy::new_ret_no_self, reason = "Method was re-written for FFI purposes without `Self`")]
1062 #[allow(clippy::absurd_extreme_comparisons, reason = "Macro-generated code doesn't know the size upfront")]
1063 pub unsafe extern "C" fn #ffi_fn_name(
1064 args: &mut InternalArgs<'_>,
1065 ) -> ::ab_contracts_macros::__private::ExitCode {
1066 #( #preparation )*
1067
1068 #[allow(
1070 unused_variables,
1071 reason = "Sometimes result is `()`"
1072 )]
1073 #[allow(
1074 clippy::let_unit_value,
1075 reason = "Sometimes result is `()`"
1076 )]
1077 let #result_var_name = #full_struct_name::#original_method_name(
1078 #( #original_fn_args, )*
1079 );
1080
1081 #result_handling
1082 }
1083 }
1084 };
1085
1086 let fn_pointer_static = {
1087 let adapter_ffi_fn_name = format_ident!("{ffi_fn_name}_adapter");
1088 let args_struct_name =
1089 derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1090
1091 quote! {
1092 #[doc(hidden)]
1093 #[expect(clippy::inline_modules, reason = "Macro-generated")]
1094 #[expect(clippy::wildcard_imports, reason = "Macro-generated")]
1095 pub mod fn_pointer {
1096 use super::*;
1097
1098 unsafe extern "C" fn #adapter_ffi_fn_name(
1099 args_ptr: ::core::ptr::NonNull<::core::ffi::c_void>,
1100 ) -> ::ab_contracts_macros::__private::ExitCode {
1101 unsafe {
1104 let mut args = args_ptr.cast::<InternalArgs<'_>>();
1105 debug_assert!(args.is_aligned(), "`args` pointer is misaligned");
1106 let args = args.as_mut();
1107 #ffi_fn_name(args)
1108 }
1109 }
1110
1111 pub const METHOD_FN_POINTER: ::ab_contracts_macros::__private::NativeExecutorContactMethod =
1112 ::ab_contracts_macros::__private::NativeExecutorContactMethod {
1113 method_fingerprint: &<#args_struct_name<'_> as ::ab_contracts_macros::__private::ExternalArgs>::FINGERPRINT,
1114 method_metadata: METADATA,
1115 ffi_fn: #adapter_ffi_fn_name,
1116 };
1117 }
1118 }
1119 };
1120
1121 Ok(quote! {
1122 #internal_args_struct
1123 #guest_fn
1124 #fn_pointer_static
1125 })
1126 }
1127
1128 fn generate_external_args_struct(
1129 &self,
1130 fn_sig: &Signature,
1131 trait_name: Option<&Ident>,
1132 ) -> Result<TokenStream, Error> {
1133 let self_type = &self.self_type;
1134 let original_method_name = &fn_sig.ident;
1135
1136 let args_struct_name =
1137 derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1138 let mut external_args_fields = Vec::new();
1140 let mut method_args = Vec::new();
1142 let mut method_args_fields = Vec::new();
1144
1145 for slot in &self.slots {
1147 let arg_name = &slot.arg_name;
1148 let ptr_field = format_ident!("{arg_name}_ptr");
1149
1150 external_args_fields.push(quote! {
1151 pub #ptr_field: ::core::ptr::NonNull<::ab_contracts_macros::__private::Address>,
1152 });
1153
1154 method_args.push(quote! {
1155 #arg_name: &'external_args ::ab_contracts_macros::__private::Address,
1156 });
1157 method_args_fields.push(quote! {
1158 #ptr_field: ::core::ptr::NonNull::from_ref(#arg_name),
1159 });
1160 }
1161
1162 for input in &self.inputs {
1164 let type_name = &input.type_name;
1165 let arg_name = &input.arg_name;
1166 let ptr_field = format_ident!("{arg_name}_ptr");
1167 let size_field = format_ident!("{arg_name}_size");
1168 let size_doc = format!("Size of the contents `{ptr_field}` points to");
1169 let capacity_field = format_ident!("{arg_name}_capacity");
1170 let capacity_doc = format!("Capacity of the contents `{ptr_field}` points to");
1171
1172 external_args_fields.push(quote! {
1173 pub #ptr_field: ::core::ptr::NonNull<
1174 <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1175 >,
1176 #[doc = #size_doc]
1177 pub #size_field: ::core::primitive::u32,
1178 #[doc = #capacity_doc]
1179 pub #capacity_field: ::core::primitive::u32,
1180 });
1181
1182 method_args.push(quote! {
1183 #arg_name: &'external_args #type_name,
1184 });
1185 method_args_fields.push(quote! {
1186 #ptr_field: unsafe {
1190 *::ab_contracts_macros::__private::IoType::as_ptr(#arg_name)
1191 },
1192 #size_field: ::ab_contracts_macros::__private::IoType::size(#arg_name),
1193 #capacity_field: ::ab_contracts_macros::__private::IoType::capacity(#arg_name),
1194 });
1195 }
1196
1197 let mut outputs_iter = self.outputs.iter().peekable();
1199 while let Some(output) = outputs_iter.next() {
1200 let type_name = &output.type_name;
1201 let arg_name = &output.arg_name;
1202 let ptr_field = format_ident!("{arg_name}_ptr");
1203 let size_field = format_ident!("{arg_name}_size");
1204 let size_doc = format!("Size of the contents `{ptr_field}` points to");
1205 let capacity_field = format_ident!("{arg_name}_capacity");
1206 let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
1207
1208 if outputs_iter.is_empty()
1211 && self.return_type.unit_return_type()
1212 && matches!(self.method_type, MethodType::Init)
1213 {
1214 continue;
1215 }
1216
1217 external_args_fields.push(quote! {
1218 pub #ptr_field: ::core::ptr::NonNull<
1219 <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1220 >,
1221 #[doc = #size_doc]
1222 pub #size_field: ::core::primitive::u32,
1223 #[doc = #capacity_doc]
1224 pub #capacity_field: ::core::primitive::u32,
1225 });
1226
1227 method_args.push(quote! {
1228 #arg_name: &'external_args mut #type_name,
1229 });
1230 method_args_fields.push(quote! {
1231 #ptr_field: unsafe {
1235 *::ab_contracts_macros::__private::IoType::as_mut_ptr(#arg_name)
1236 },
1237 #size_field: ::ab_contracts_macros::__private::IoType::size(#arg_name),
1238 #capacity_field: ::ab_contracts_macros::__private::IoType::capacity(#arg_name),
1239 });
1240 }
1241
1242 let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1243
1244 if !(matches!(self.method_type, MethodType::Init) || self.return_type.unit_return_type()) {
1248 let return_type = &self.return_type.return_type();
1249
1250 external_args_fields.push(quote! {
1251 pub ok_result: &'external_args mut ::core::mem::MaybeUninit<#return_type>,
1252 });
1253
1254 method_args.push(quote! {
1255 ok_result: &'external_args mut ::core::mem::MaybeUninit<#return_type>,
1256 });
1257 method_args_fields.push(quote! {
1258 ok_result,
1259 });
1260 }
1261 let args_struct_doc = format!(
1262 "Data structure containing expected input for external method invocation, eventually \
1263 calling `{ffi_fn_name}()` on the other side by the host.\n\n\
1264 This can be used with [`Env`](::ab_contracts_macros::__private::Env), though there are \
1265 helper methods on this provided by extension trait that allow not dealing with this \
1266 struct directly in simpler cases."
1267 );
1268
1269 Ok(quote_spanned! {fn_sig.span() =>
1270 #[doc = #args_struct_doc]
1271 #[derive(::core::fmt::Debug)]
1272 #[repr(C)]
1273 #[allow(clippy::pub_underscore_fields, reason = "Comes from user-provided arguments")]
1274 #[allow(rustdoc::redundant_explicit_links, reason = "Macro-generated")]
1275 pub struct #args_struct_name<'external_args> {
1276 #( #external_args_fields )*
1277 pub _lifetime: ::core::marker::PhantomData<&'external_args ()>,
1279 }
1280
1281 #[automatically_derived]
1282 unsafe impl ::ab_contracts_macros::__private::ExternalArgs for #args_struct_name<'_> {
1283 const FINGERPRINT: ::ab_contracts_macros::__private::MethodFingerprint =
1284 ::ab_contracts_macros::__private::MethodFingerprint::new(METADATA)
1285 .expect("Metadata is statically correct; qed");
1286 const METADATA: &'static [::core::primitive::u8] = METADATA;
1287 }
1288
1289 impl<'external_args> #args_struct_name<'external_args> {
1290 #[allow(
1294 clippy::new_without_default,
1295 reason = "Do not want `Default` in auto-generated code"
1296 )]
1297 #[allow(clippy::used_underscore_binding, reason = "Macro-generated")]
1298 pub fn new(
1299 #( #method_args )*
1300 ) -> Self {
1301 Self {
1302 #( #method_args_fields )*
1303 _lifetime: ::core::marker::PhantomData,
1304 }
1305 }
1306 }
1307 })
1308 }
1309
1310 fn generate_metadata(
1311 &self,
1312 fn_sig: &Signature,
1313 trait_name: Option<&Ident>,
1314 ) -> Result<TokenStream, Error> {
1315 let self_type = &self.self_type;
1316 let mut method_metadata = Vec::new();
1319
1320 if let Some(env) = &self.env {
1321 let env_metadata_type = if env.mutability.is_some() {
1322 "EnvRw"
1323 } else {
1324 "EnvRo"
1325 };
1326
1327 let env_metadata_type = format_ident!("{env_metadata_type}");
1328 method_metadata.push(quote! {
1329 &[::ab_contracts_macros::__private::ContractMetadataKind::#env_metadata_type as ::core::primitive::u8],
1330 });
1331 }
1332
1333 if let Some(tmp) = &self.tmp {
1334 let tmp_metadata_type = if tmp.mutability.is_some() {
1335 "TmpRw"
1336 } else {
1337 "TmpRo"
1338 };
1339
1340 let tmp_metadata_type = format_ident!("{tmp_metadata_type}");
1341 let arg_name_metadata = derive_ident_metadata(&tmp.arg_name)?;
1342 method_metadata.push(quote! {
1343 &[::ab_contracts_macros::__private::ContractMetadataKind::#tmp_metadata_type as ::core::primitive::u8],
1344 #arg_name_metadata,
1345 });
1346 }
1347
1348 for slot in &self.slots {
1349 let slot_metadata_type = if slot.mutability.is_some() {
1350 "SlotRw"
1351 } else {
1352 "SlotRo"
1353 };
1354
1355 let slot_metadata_type = format_ident!("{slot_metadata_type}");
1356 let arg_name_metadata = derive_ident_metadata(&slot.arg_name)?;
1357 method_metadata.push(quote! {
1358 &[::ab_contracts_macros::__private::ContractMetadataKind::#slot_metadata_type as ::core::primitive::u8],
1359 #arg_name_metadata,
1360 });
1361 }
1362
1363 for input in &self.inputs {
1364 let io_metadata_type = format_ident!("Input");
1365 let arg_name_metadata = derive_ident_metadata(&input.arg_name)?;
1366 let type_name = &input.type_name;
1367
1368 method_metadata.push(quote! {
1369 &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1370 #arg_name_metadata,
1371 <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1372 });
1373 }
1374
1375 let mut outputs_iter = self.outputs.iter().peekable();
1376 while let Some(output) = outputs_iter.next() {
1377 let io_metadata_type = "Output";
1378
1379 let io_metadata_type = format_ident!("{io_metadata_type}");
1380 let arg_name_metadata = derive_ident_metadata(&output.arg_name)?;
1381 let with_type_metadata = if outputs_iter.is_empty()
1383 && self.return_type.unit_return_type()
1384 && matches!(self.method_type, MethodType::Init)
1385 {
1386 None
1387 } else {
1388 let type_name = &output.type_name;
1389 Some(quote! {
1390 <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1391 })
1392 };
1393 method_metadata.push(quote! {
1394 &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1395 #arg_name_metadata,
1396 #with_type_metadata
1397 });
1398 }
1399
1400 if !self.return_type.unit_return_type() {
1402 let arg_name_metadata = Literal::u8_unsuffixed(0);
1404 let with_type_metadata = if matches!(self.method_type, MethodType::Init) {
1406 None
1407 } else {
1408 let return_type = self.return_type.return_type();
1409 Some(quote! {
1410 <#return_type as ::ab_contracts_macros::__private::IoType>::METADATA,
1411 })
1412 };
1413 method_metadata.push(quote! {
1414 &[
1415 ::ab_contracts_macros::__private::ContractMetadataKind::Return as ::core::primitive::u8,
1416 #arg_name_metadata,
1417 ],
1418 #with_type_metadata
1419 });
1420 }
1421
1422 let method_type = match self.method_type {
1423 MethodType::Init => "Init",
1424 MethodType::Update => {
1425 if let Some(mutable) = &self.state {
1426 if mutable.is_some() {
1427 "UpdateStatefulRw"
1428 } else {
1429 "UpdateStatefulRo"
1430 }
1431 } else {
1432 "UpdateStateless"
1433 }
1434 }
1435 MethodType::View => {
1436 if let Some(mutable) = &self.state {
1437 if mutable.is_some() {
1438 return Err(Error::new(
1439 fn_sig.span(),
1440 "Stateful view methods are not supported",
1441 ));
1442 }
1443
1444 "ViewStateful"
1445 } else {
1446 "ViewStateless"
1447 }
1448 }
1449 };
1450
1451 let method_type = format_ident!("{method_type}");
1452 let number_of_arguments = u8::try_from(method_metadata.len()).map_err(|_error| {
1453 Error::new(
1454 fn_sig.span(),
1455 format!("Number of arguments must not be more than {}", u8::MAX),
1456 )
1457 })?;
1458 let total_number_of_arguments =
1459 Literal::u8_unsuffixed(number_of_arguments.saturating_add(1));
1460 let number_of_arguments = Literal::u8_unsuffixed(number_of_arguments);
1461
1462 let original_method_name = &fn_sig.ident;
1463 let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1464 let method_name_metadata = derive_ident_metadata(&ffi_fn_name)?;
1465 Ok(quote_spanned! {fn_sig.span() =>
1466 #[expect(
1467 clippy::assertions_on_constants,
1468 reason = "Auto-generated compile-time check"
1469 )]
1470 const fn metadata()
1471 -> ([::core::primitive::u8; ::ab_contracts_macros::__private::MAX_METADATA_CAPACITY], usize)
1472 {
1473 assert!(
1474 #total_number_of_arguments <= ::ab_contracts_macros::__private::MAX_TOTAL_METHOD_ARGS,
1475 "Too many arguments"
1476 );
1477 ::ab_contracts_macros::__private::concat_metadata_sources(&[
1478 &[::ab_contracts_macros::__private::ContractMetadataKind::#method_type as ::core::primitive::u8],
1479 #method_name_metadata,
1480 &[#number_of_arguments],
1481 #( #method_metadata )*
1482 ])
1483 }
1484
1485 pub const METADATA: &[::core::primitive::u8] =
1490 metadata()
1491 .0
1492 .split_at(metadata().1)
1493 .0;
1494 })
1495 }
1496
1497 pub(super) fn generate_trait_ext_components(
1498 &self,
1499 fn_sig: &Signature,
1500 fn_attrs: &[Attribute],
1501 trait_name: Option<&Ident>,
1502 ) -> Result<ExtTraitComponents, Error> {
1503 let self_type = &self.self_type;
1504
1505 let mut preparation = Vec::new();
1506 let mut method_args = Vec::new();
1507 let mut external_args_args = Vec::new();
1508 let mut result_processing_before = Vec::new();
1509 let mut result_processing_after = Vec::new();
1510
1511 method_args.push(quote_spanned! {fn_sig.span() =>
1513 contract: ::ab_contracts_macros::__private::Address,
1514 });
1515
1516 for slot in &self.slots {
1518 let arg_name = &slot.arg_name;
1519
1520 method_args.push(quote_spanned! {fn_sig.span() =>
1521 #arg_name: &::ab_contracts_macros::__private::Address,
1522 });
1523 external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1524 }
1525
1526 for input in &self.inputs {
1528 let type_name = &input.type_name;
1529 let arg_name = &input.arg_name;
1530
1531 method_args.push(quote_spanned! {fn_sig.span() =>
1532 #arg_name: &#type_name,
1533 });
1534 external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1535 }
1536
1537 let mut outputs_iter = self.outputs.iter().peekable();
1539 while let Some(output) = outputs_iter.next() {
1540 let type_name = &output.type_name;
1541 let arg_name = &output.arg_name;
1542 let size_var = format_ident!("macro_{arg_name}_size");
1543 let size_field = format_ident!("{arg_name}_size");
1544
1545 if outputs_iter.is_empty()
1548 && self.return_type.unit_return_type()
1549 && matches!(self.method_type, MethodType::Init)
1550 {
1551 continue;
1552 }
1553
1554 method_args.push(quote_spanned! {fn_sig.span() =>
1555 #arg_name: &mut #type_name,
1556 });
1557 external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1558 result_processing_before.push(quote_spanned! {fn_sig.span() =>
1559 let #size_var = args.#size_field;
1560 });
1561 result_processing_after.push(quote_spanned! {fn_sig.span() =>
1562 unsafe {
1564 ::ab_contracts_macros::__private::IoType::set_size(#arg_name, #size_var);
1565 }
1566 });
1567 }
1568
1569 let original_method_name = &fn_sig.ident;
1570 let ext_method_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1571 let env_self = if matches!(self.method_type, MethodType::View) {
1573 quote_spanned! {fn_sig.span() => &self }
1574 } else {
1575 quote_spanned! {fn_sig.span() => &mut self }
1576 };
1577 let method_context_arg = (!matches!(self.method_type, MethodType::View)).then(|| {
1579 quote_spanned! {fn_sig.span() =>
1580 method_context: ::ab_contracts_macros::__private::MethodContext,
1581 }
1582 });
1583 let method_signature = if matches!(self.method_type, MethodType::Init)
1587 || self.return_type.unit_return_type()
1588 {
1589 quote_spanned! {fn_sig.span() =>
1590 #[allow(dead_code, reason = "Macro-generated")]
1591 #[allow(clippy::used_underscore_binding, reason = "Macro-generated")]
1592 #[allow(clippy::semicolon_if_nothing_returned, reason = "Macro-generated")]
1593 fn #ext_method_name(
1594 #env_self,
1595 #method_context_arg
1596 #( #method_args )*
1597 ) -> ::core::result::Result<(), ::ab_contracts_macros::__private::ContractError>
1598 }
1599 } else {
1600 let return_type = self.return_type.return_type();
1601
1602 preparation.push(quote_spanned! {fn_sig.span() =>
1603 const {
1608 const fn assert_impl_trivial_type<T>()
1609 where
1610 T: ::ab_contracts_macros::__private::IoType,
1611 {}
1612 assert_impl_trivial_type::<#return_type>();
1613 }
1614
1615 let mut ok_result = ::core::mem::MaybeUninit::uninit();
1616 });
1617 external_args_args.push(quote_spanned! {fn_sig.span() =>
1618 &mut ok_result
1619 });
1620 result_processing_after.push(quote_spanned! {fn_sig.span() =>
1621 unsafe {
1623 ok_result.assume_init()
1624 }
1625 });
1626
1627 quote_spanned! {fn_sig.span() =>
1628 #[allow(dead_code, reason = "Macro-generated")]
1629 #[allow(clippy::used_underscore_binding, reason = "Macro-generated")]
1630 fn #ext_method_name(
1631 #env_self,
1632 #method_context_arg
1633 #( #method_args )*
1634 ) -> ::core::result::Result<
1635 #return_type,
1636 ::ab_contracts_macros::__private::ContractError,
1637 >
1638 }
1639 };
1640
1641 let attrs = fn_attrs.iter().filter(|attr| {
1642 let path = match &attr.meta {
1643 Meta::Path(path) => path,
1644 Meta::List(list) => &list.path,
1645 Meta::NameValue(name_value) => &name_value.path,
1646 };
1647
1648 if let Some(ident) = path.get_ident() {
1649 ident == "doc" || ident == "allow" || ident == "expect"
1650 } else {
1651 false
1652 }
1653 });
1654 let definition = quote_spanned! {fn_sig.span() =>
1655 #[allow(
1656 clippy::too_many_arguments,
1657 reason = "Generated code may have more arguments that source code"
1658 )]
1659 #( #attrs )*
1660 #method_signature;
1661 };
1662
1663 let args_struct_name =
1664 derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1665 let method_context_value = if matches!(self.method_type, MethodType::View) {
1667 quote_spanned! {fn_sig.span() =>
1668 ::ab_contracts_macros::__private::MethodContext::Reset
1669 }
1670 } else {
1671 quote_spanned! {fn_sig.span() =>
1672 method_context
1673 }
1674 };
1675 let r#impl = quote_spanned! {fn_sig.span() =>
1676 #[inline]
1677 #method_signature {
1678 #( #preparation )*
1679
1680 let mut args = #original_method_name::#args_struct_name::new(
1681 #( #external_args_args, )*
1682 );
1683
1684 self.call(contract, &mut args, #method_context_value)?;
1685
1686 #( #result_processing_before )*
1687
1688 #[allow(
1689 clippy::let_unit_value,
1690 reason = "Sometimes there is no result to process and block is empty"
1691 )]
1692 let result = {
1693 #( #result_processing_after )*
1694 };
1695
1696 Ok(result)
1697 }
1698 };
1699
1700 Ok(ExtTraitComponents { definition, r#impl })
1701 }
1702}
1703
1704fn extract_arg_name(mut pat: &Pat) -> Option<Ident> {
1705 loop {
1706 match pat {
1707 Pat::Ident(pat_ident) => {
1708 return Some(pat_ident.ident.clone());
1709 }
1710 Pat::Reference(pat_reference) => {
1711 pat = &pat_reference.pat;
1712 }
1713 _ => {
1714 return None;
1715 }
1716 }
1717 }
1718}
1719
1720fn derive_ffi_fn_name(
1721 type_name: &Type,
1722 trait_name: Option<&Ident>,
1723 method_name: &Ident,
1724) -> Result<Ident, Error> {
1725 let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1726 Error::new(
1727 type_name.span(),
1728 "`#[contract]` must be applied to a simple struct without generics",
1729 )
1730 })?;
1731 let ffi_fn_prefix = trait_name.unwrap_or(type_name).to_string().to_snake_case();
1732
1733 Ok(format_ident!("{ffi_fn_prefix}_{method_name}"))
1734}
1735
1736fn derive_external_args_struct_name(
1737 type_name: &Type,
1738 trait_name: Option<&Ident>,
1739 method_name: &Ident,
1740) -> Result<Ident, Error> {
1741 let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1742 Error::new(
1743 type_name.span(),
1744 "`#[contract]` must be applied to a simple struct without generics",
1745 )
1746 })?;
1747 Ok(format_ident!(
1748 "{}{}Args",
1749 trait_name.unwrap_or(type_name),
1750 method_name.to_string().to_upper_camel_case()
1751 ))
1752}