ab_contracts_macros_impl/contract/
method.rs

1use crate::contract::common::{derive_ident_metadata, extract_ident_from_type};
2use ident_case::RenameRule;
3use proc_macro2::{Ident, Literal, Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned};
5use syn::spanned::Spanned;
6use syn::{
7    Attribute, Error, GenericArgument, Meta, Pat, PatType, PathArguments, ReturnType, Signature,
8    Token, Type, TypeTuple,
9};
10
11#[derive(Copy, Clone)]
12pub(super) enum MethodType {
13    Init,
14    Update,
15    View,
16}
17
18impl MethodType {
19    fn attr_str(self) -> &'static str {
20        match self {
21            MethodType::Init => "init",
22            MethodType::Update => "update",
23            MethodType::View => "view",
24        }
25    }
26}
27
28#[derive(Clone)]
29struct Env {
30    arg_name: Ident,
31    mutability: Option<Token![mut]>,
32}
33
34#[derive(Clone)]
35struct Tmp {
36    type_name: Type,
37    arg_name: Ident,
38    mutability: Option<Token![mut]>,
39}
40
41#[derive(Clone)]
42struct Slot {
43    with_address_arg: bool,
44    type_name: Type,
45    arg_name: Ident,
46    mutability: Option<Token![mut]>,
47}
48
49#[derive(Clone)]
50struct Input {
51    type_name: Type,
52    arg_name: Ident,
53}
54
55#[derive(Clone)]
56struct Output {
57    type_name: Type,
58    arg_name: Ident,
59    has_self: bool,
60}
61
62enum MethodReturnType {
63    /// The function doesn't have any return type defined
64    Unit(Type),
65    /// Returns a type without [`Result`]
66    Regular(Type),
67    /// Returns [`Result`], but [`Ok`] variant is `()`
68    ResultUnit(Type),
69    /// Returns [`Result`], but [`Ok`] variant is not `()`
70    Result(Type),
71}
72
73impl MethodReturnType {
74    fn unit_type() -> Type {
75        Type::Tuple(TypeTuple {
76            paren_token: Default::default(),
77            elems: Default::default(),
78        })
79    }
80
81    fn unit_return_type(&self) -> bool {
82        match self {
83            Self::Unit(_) | Self::ResultUnit(_) => true,
84            Self::Regular(_) | Self::Result(_) => false,
85        }
86    }
87
88    fn return_type(&self) -> &Type {
89        match self {
90            Self::Unit(ty) | Self::Regular(ty) | Self::ResultUnit(ty) | Self::Result(ty) => ty,
91        }
92    }
93}
94
95#[derive(Default)]
96pub(super) struct ExtTraitComponents {
97    pub(super) definition: TokenStream,
98    pub(super) r#impl: TokenStream,
99}
100
101pub(super) struct MethodDetails {
102    method_type: MethodType,
103    self_type: Type,
104    state: Option<Option<Token![mut]>>,
105    env: Option<Env>,
106    tmp: Option<Tmp>,
107    slots: Vec<Slot>,
108    inputs: Vec<Input>,
109    outputs: Vec<Output>,
110    return_type: MethodReturnType,
111}
112
113impl MethodDetails {
114    pub(super) fn new(method_type: MethodType, self_type: Type) -> Self {
115        Self {
116            method_type,
117            self_type,
118            state: None,
119            env: None,
120            tmp: None,
121            slots: Vec::new(),
122            inputs: Vec::new(),
123            outputs: Vec::new(),
124            return_type: MethodReturnType::Unit(MethodReturnType::unit_type()),
125        }
126    }
127
128    /// Returns `#[tmp]` type (or `()` if it is not used) if all methods have the same slots type
129    pub(super) fn tmp_type<'a, I>(iter: I) -> Option<Type>
130    where
131        I: Iterator<Item = &'a Self> + 'a,
132    {
133        let mut tmp_type = None;
134        for slot in iter.flat_map(|method_details| &method_details.tmp) {
135            match &tmp_type {
136                Some(tmp_type) => {
137                    if tmp_type != &slot.type_name {
138                        return None;
139                    }
140                }
141                None => {
142                    tmp_type.replace(slot.type_name.clone());
143                }
144            }
145        }
146
147        Some(tmp_type.unwrap_or_else(MethodReturnType::unit_type))
148    }
149
150    /// Returns `#[slot]` type (or `()` if it is not used) if all methods have the same slots type
151    pub(super) fn slot_type<'a, I>(iter: I) -> Option<Type>
152    where
153        I: Iterator<Item = &'a Self> + 'a,
154    {
155        let mut slot_type = None;
156        for slot in iter.flat_map(|method_details| &method_details.slots) {
157            match &slot_type {
158                Some(slot_type) => {
159                    if slot_type != &slot.type_name {
160                        return None;
161                    }
162                }
163                None => {
164                    slot_type.replace(slot.type_name.clone());
165                }
166            }
167        }
168
169        Some(slot_type.unwrap_or_else(MethodReturnType::unit_type))
170    }
171
172    pub(super) fn process_env_arg_ro(
173        &mut self,
174        input_span: Span,
175        pat_type: &PatType,
176    ) -> Result<(), Error> {
177        self.process_env_arg(input_span, pat_type, false)
178    }
179
180    pub(super) fn process_env_arg_rw(
181        &mut self,
182        input_span: Span,
183        pat_type: &PatType,
184    ) -> Result<(), Error> {
185        self.process_env_arg(input_span, pat_type, true)
186    }
187
188    fn process_env_arg(
189        &mut self,
190        input_span: Span,
191        pat_type: &PatType,
192        allow_mut: bool,
193    ) -> Result<(), Error> {
194        if self.env.is_some()
195            || self.tmp.is_some()
196            || !(self.inputs.is_empty() && self.outputs.is_empty())
197        {
198            return Err(Error::new(
199                input_span,
200                "`#[env]` must be the first non-Self argument and only appear once",
201            ));
202        }
203
204        if let Type::Reference(type_reference) = &*pat_type.ty
205            && let Type::Path(_type_path) = &*type_reference.elem
206            && let Pat::Ident(pat_ident) = &*pat_type.pat
207        {
208            if type_reference.mutability.is_some() && !allow_mut {
209                return Err(Error::new(
210                    input_span,
211                    "`#[env]` is not allowed to mutate data here",
212                ));
213            }
214
215            self.env.replace(Env {
216                arg_name: pat_ident.ident.clone(),
217                mutability: type_reference.mutability,
218            });
219            Ok(())
220        } else {
221            Err(Error::new(
222                pat_type.span(),
223                "`#[env]` must be a reference to `Env` type (can be shared or exclusive)",
224            ))
225        }
226    }
227
228    pub(super) fn process_state_arg_ro(
229        &mut self,
230        input_span: Span,
231        ty: &Type,
232    ) -> Result<(), Error> {
233        self.process_state_arg(input_span, ty, false)
234    }
235
236    pub(super) fn process_state_arg_rw(
237        &mut self,
238        input_span: Span,
239        ty: &Type,
240    ) -> Result<(), Error> {
241        self.process_state_arg(input_span, ty, true)
242    }
243
244    fn process_state_arg(
245        &mut self,
246        input_span: Span,
247        ty: &Type,
248        allow_mut: bool,
249    ) -> Result<(), Error> {
250        // Only accept `&self` or `&mut self`
251        if let Type::Reference(type_reference) = ty
252            && let Type::Path(type_path) = &*type_reference.elem
253            && type_path.path.is_ident("Self")
254        {
255            if type_reference.mutability.is_some() && !allow_mut {
256                return Err(Error::new(
257                    input_span,
258                    "`#[arg]` is not allowed to mutate data here",
259                ));
260            }
261
262            self.state.replace(type_reference.mutability);
263            Ok(())
264        } else {
265            Err(Error::new(
266                ty.span(),
267                "Can't consume `Self`, use `&self` or `&mut self` instead",
268            ))
269        }
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        // Check if input looks like `&Type` or `&mut Type`
285        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            // Check if input looks like `&Type` or `&mut Type`
340            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            // Check if input looks like `(&Address, &Type)` or `(&Address, &mut Type)`
364            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                // Ignore
399            }
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        // Ensure input looks like `&Type` or `&mut Type`, but not `Type`
423        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        // Ensure input looks like `&mut Type`
457        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            // Replace things like `MaybeData<Self>` with `MaybeData<#self_type>`
471            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        // Check if return type is `T` or `Result<T, ContractError>`
501        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                    // Check something with generic rather than a simple type
515                    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                    // Check for `-> Result<T, ContractError>`
523                    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                                // Swap `Self` for an actual struct name
544                                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                        // Swap `Self` for an actual struct name
555                        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            pub mod #original_method_name {
620                use super::*;
621
622                #guest_fn
623                #external_args_struct
624                #metadata
625            }
626        })
627    }
628
629    pub(super) fn generate_guest_trait_ffi(
630        &self,
631        fn_sig: &Signature,
632        trait_name: Option<&Ident>,
633    ) -> Result<TokenStream, Error> {
634        let original_method_name = &fn_sig.ident;
635
636        let external_args_struct = self.generate_external_args_struct(fn_sig, trait_name)?;
637        let metadata = self.generate_metadata(fn_sig, trait_name)?;
638
639        Ok(quote_spanned! {fn_sig.span() =>
640            pub mod #original_method_name {
641                use super::*;
642
643                #external_args_struct
644                #metadata
645            }
646        })
647    }
648
649    pub(super) fn generate_guest_fn(
650        &self,
651        fn_sig: &Signature,
652        trait_name: Option<&Ident>,
653    ) -> Result<TokenStream, Error> {
654        let self_type = &self.self_type;
655
656        // `internal_args_pointers` will generate pointers in `InternalArgs` fields
657        let mut internal_args_pointers = Vec::new();
658        // `preparation` will generate code that is used before calling original function
659        let mut preparation = Vec::new();
660        // `original_fn_args` will generate arguments for calling original method implementation
661        let mut original_fn_args = Vec::new();
662
663        // Optional state argument with pointer and size (+ capacity if mutable)
664        if let Some(mutability) = self.state {
665            internal_args_pointers.push(quote! {
666                pub self_ptr: ::core::ptr::NonNull<
667                    <#self_type as ::ab_contracts_macros::__private::IoType>::PointerType,
668                >,
669            });
670
671            if mutability.is_some() {
672                internal_args_pointers.push(quote! {
673                    /// Size of the contents `self_ptr` points to
674                    pub self_size: *mut ::core::primitive::u32,
675                    /// Capacity of the allocated memory following `self_ptr` points to
676                    pub self_capacity: ::core::ptr::NonNull<::core::primitive::u32>,
677                });
678
679                original_fn_args.push(quote! {&mut *{
680                    // Ensure state type implements `IoType`, which is required for crossing
681                    // host/guest boundary
682                    const _: () = {
683                        const fn assert_impl_io_type<T>()
684                        where
685                            T: ::ab_contracts_macros::__private::IoType,
686                        {}
687                        assert_impl_io_type::<#self_type>();
688                    };
689
690                    <#self_type as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
691                        &mut args.self_ptr,
692                        &mut args.self_size,
693                        args.self_capacity.read(),
694                    )
695                }});
696            } else {
697                internal_args_pointers.push(quote! {
698                    /// Size of the contents `self_ptr` points to
699                    pub self_size: ::core::ptr::NonNull<::core::primitive::u32>,
700                });
701
702                original_fn_args.push(quote! {&*{
703                    // Ensure state type implements `IoType`, which is required for crossing
704                    // host/guest boundary
705                    const _: () = {
706                        const fn assert_impl_io_type<T>()
707                        where
708                            T: ::ab_contracts_macros::__private::IoType,
709                        {}
710                        assert_impl_io_type::<#self_type>();
711                    };
712
713                    <#self_type as ::ab_contracts_macros::__private::IoType>::from_ptr(
714                        &args.self_ptr,
715                        args.self_size.as_ref(),
716                        // Size matches capacity for immutable inputs
717                        args.self_size.read(),
718                    )
719                }});
720            }
721        }
722
723        // Optional environment argument with just a pointer
724        if let Some(env) = &self.env {
725            let ptr_field = format_ident!("{}_ptr", env.arg_name);
726            let assert_msg = format!("`{ptr_field}` pointer is misaligned");
727            let mutability = env.mutability;
728
729            internal_args_pointers.push(quote! {
730                // Use `Env` to check if method argument had the correct type at compile time
731                pub #ptr_field: ::core::ptr::NonNull<::ab_contracts_macros::__private::Env<'internal_args>>,
732            });
733
734            if mutability.is_some() {
735                original_fn_args.push(quote! {{
736                    debug_assert!(args.#ptr_field.is_aligned(), #assert_msg);
737                    args.#ptr_field.as_mut()
738                }});
739            } else {
740                original_fn_args.push(quote! {{
741                    debug_assert!(args.#ptr_field.is_aligned(), #assert_msg);
742                    args.#ptr_field.as_ref()
743                }});
744            }
745        }
746
747        // Optional tmp argument with pointer and size (+ capacity if mutable)
748        //
749        // Also asserting that type is safe for memory copying.
750        if let Some(tmp) = &self.tmp {
751            let type_name = &tmp.type_name;
752            let mutability = tmp.mutability;
753            let ptr_field = format_ident!("{}_ptr", tmp.arg_name);
754            let size_field = format_ident!("{}_size", tmp.arg_name);
755            let size_doc = format!("Size of the contents `{ptr_field}` points to");
756            let capacity_field = format_ident!("{}_capacity", tmp.arg_name);
757            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
758
759            internal_args_pointers.push(quote! {
760                pub #ptr_field: ::core::ptr::NonNull<
761                    <
762                        // Make sure `#[tmp]` type matches expected type
763                        <#self_type as ::ab_contracts_macros::__private::Contract>::Tmp as ::ab_contracts_macros::__private::IoType
764                    >::PointerType,
765                >,
766            });
767
768            if mutability.is_some() {
769                internal_args_pointers.push(quote! {
770                    #[doc = #size_doc]
771                    pub #size_field: *mut ::core::primitive::u32,
772                    #[doc = #capacity_doc]
773                    pub #capacity_field: ::core::ptr::NonNull<::core::primitive::u32>,
774                });
775
776                original_fn_args.push(quote! {&mut *{
777                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
778                    // of tmp that might be removed or not present and implies implementation of
779                    // `IoType`, which is required for crossing host/guest boundary
780                    const _: () = {
781                        const fn assert_impl_io_type_optional<T>()
782                        where
783                            T: ::ab_contracts_macros::__private::IoTypeOptional,
784                        {}
785                        assert_impl_io_type_optional::<#type_name>();
786                    };
787
788                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
789                        &mut args.#ptr_field,
790                        &mut args.#size_field,
791                        args.#capacity_field.read(),
792                    )
793                }});
794            } else {
795                internal_args_pointers.push(quote! {
796                    #[doc = #size_doc]
797                    pub #size_field: ::core::ptr::NonNull<::core::primitive::u32>,
798                });
799
800                original_fn_args.push(quote! {&*{
801                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
802                    // of tmp that might be removed or not present and implies implementation of
803                    // `IoType`, which is required for crossing host/guest boundary
804                    const _: () = {
805                        const fn assert_impl_io_type_optional<T>()
806                        where
807                            T: ::ab_contracts_macros::__private::IoTypeOptional,
808                        {}
809                        assert_impl_io_type_optional::<#type_name>();
810                    };
811
812                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
813                        &args.#ptr_field,
814                        args.#size_field.as_ref(),
815                        // Size matches capacity for immutable inputs
816                        args.#size_field.read(),
817                    )
818                }});
819            }
820        }
821
822        // Slot arguments with:
823        // * in case address is used: pointer to address, pointer to slot and size (+ capacity if
824        //   mutable)
825        // * in case address is not used: pointer to slot and size (+ capacity if mutable)
826        //
827        // Also asserting that type is safe for memory copying.
828        for slot in &self.slots {
829            let type_name = &slot.type_name;
830            let mutability = slot.mutability;
831            let address_ptr_field = format_ident!("{}_address_ptr", slot.arg_name);
832            let ptr_field = format_ident!("{}_ptr", slot.arg_name);
833            let size_field = format_ident!("{}_size", slot.arg_name);
834            let size_doc = format!("Size of the contents `{ptr_field}` points to");
835            let capacity_field = format_ident!("{}_capacity", slot.arg_name);
836            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
837
838            internal_args_pointers.push(quote! {
839                // Use `Address` to check if method argument had the correct type at compile time
840                pub #address_ptr_field: ::core::ptr::NonNull<::ab_contracts_macros::__private::Address>,
841                pub #ptr_field: ::core::ptr::NonNull<
842                    <
843                        // Make sure `#[slot]` type matches expected type
844                        <#self_type as ::ab_contracts_macros::__private::Contract>::Slot as ::ab_contracts_macros::__private::IoType
845                    >::PointerType,
846                >,
847            });
848
849            let arg_extraction = if mutability.is_some() {
850                internal_args_pointers.push(quote! {
851                    #[doc = #size_doc]
852                    pub #size_field: *mut ::core::primitive::u32,
853                    #[doc = #capacity_doc]
854                    pub #capacity_field: ::core::ptr::NonNull<::core::primitive::u32>,
855                });
856
857                quote! {&mut *{
858                    // Ensure slot type implements `IoTypeOptional`, which is required for handling
859                    // of slot that might be removed or not present and implies implementation of
860                    // `IoType`, which is required for crossing host/guest boundary
861                    const _: () = {
862                        const fn assert_impl_io_type_optional<T>()
863                        where
864                            T: ::ab_contracts_macros::__private::IoTypeOptional,
865                        {}
866                        assert_impl_io_type_optional::<#type_name>();
867                    };
868
869                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
870                        &mut args.#ptr_field,
871                        &mut args.#size_field,
872                        args.#capacity_field.read(),
873                    )
874                }}
875            } else {
876                internal_args_pointers.push(quote! {
877                    #[doc = #size_doc]
878                    pub #size_field: ::core::ptr::NonNull<::core::primitive::u32>,
879                });
880
881                quote! {&*{
882                    // Ensure slot type implements `IoTypeOptional`, which is required for handling
883                    // of slot that might be removed or not present and implies implementation of
884                    // `IoType`, which is required for crossing host/guest boundary
885                    const _: () = {
886                        const fn assert_impl_io_type_optional<T>()
887                        where
888                            T: ::ab_contracts_macros::__private::IoTypeOptional,
889                        {}
890                        assert_impl_io_type_optional::<#type_name>();
891                    };
892
893                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
894                        &args.#ptr_field,
895                        args.#size_field.as_ref(),
896                        // Size matches capacity for immutable inputs
897                        args.#size_field.read(),
898                    )
899                }}
900            };
901
902            if slot.with_address_arg {
903                original_fn_args.push(quote! {
904                    (
905                        &<::ab_contracts_macros::__private::Address as ::ab_contracts_macros::__private::IoType>::from_ptr(
906                            &args.#address_ptr_field,
907                            &<::ab_contracts_macros::__private::Address as ::ab_contracts_macros::__private::TrivialType>::SIZE,
908                            <::ab_contracts_macros::__private::Address as ::ab_contracts_macros::__private::TrivialType>::SIZE,
909                        ),
910                        #arg_extraction,
911                    )
912                });
913            } else {
914                original_fn_args.push(arg_extraction);
915            }
916        }
917
918        // Inputs with a pointer and size.
919        // Also asserting that type is safe for memory copying.
920        for input in &self.inputs {
921            let type_name = &input.type_name;
922            let arg_name = &input.arg_name;
923            let ptr_field = format_ident!("{arg_name}_ptr");
924            let size_field = format_ident!("{arg_name}_size");
925            let size_doc = format!("Size of the contents `{ptr_field}` points to");
926
927            internal_args_pointers.push(quote! {
928                pub #ptr_field: ::core::ptr::NonNull<
929                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
930                >,
931                #[doc = #size_doc]
932                pub #size_field: ::core::ptr::NonNull<::core::primitive::u32>,
933            });
934
935            original_fn_args.push(quote! {&*{
936                // Ensure input type implements `IoType`, which is required for crossing host/guest
937                // boundary
938                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_ptr(
947                    &args.#ptr_field,
948                    args.#size_field.as_ref(),
949                    // Size matches capacity for immutable inputs
950                    args.#size_field.read(),
951                )
952            }});
953        }
954
955        // Outputs with a pointer, size and capacity.
956        // Also asserting that type is safe for memory copying.
957        for output in &self.outputs {
958            let type_name = &output.type_name;
959            let arg_name = &output.arg_name;
960            let ptr_field = format_ident!("{arg_name}_ptr");
961            let size_field = format_ident!("{arg_name}_size");
962            let size_doc = format!("Size of the contents `{ptr_field}` points to");
963            let capacity_field = format_ident!("{arg_name}_capacity");
964            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
965
966            internal_args_pointers.push(quote! {
967                pub #ptr_field: ::core::ptr::NonNull<
968                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
969                >,
970                #[doc = #size_doc]
971                pub #size_field: *mut ::core::primitive::u32,
972                #[doc = #capacity_doc]
973                pub #capacity_field: ::core::ptr::NonNull<::core::primitive::u32>,
974            });
975
976            original_fn_args.push(quote! {&mut *{
977                // Ensure output type implements `IoTypeOptional`, which is required for handling of
978                // the initially uninitialized type and implies implementation of `IoType`, which is
979                // required for crossing host/guest boundary
980                const _: () = {
981                    const fn assert_impl_io_type_optional<T>()
982                    where
983                        T: ::ab_contracts_macros::__private::IoTypeOptional,
984                    {}
985                    assert_impl_io_type_optional::<#type_name>();
986                };
987
988                <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
989                    &mut args.#ptr_field,
990                    &mut args.#size_field,
991                    args.#capacity_field.read(),
992                )
993            }});
994        }
995
996        let original_method_name = &fn_sig.ident;
997        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
998        let return_type = self.return_type.return_type();
999
1000        let internal_args_struct = {
1001            // Result can be used through return type or argument, for argument no special handling
1002            // of the return type is needed. Similarly, it is skipped for a unit return type.
1003            if !self.return_type.unit_return_type() {
1004                internal_args_pointers.push(quote! {
1005                    pub ok_result_ptr: ::core::ptr::NonNull<#return_type>,
1006                    /// The size of the contents `ok_result_ptr` points to
1007                    pub ok_result_size: *mut ::core::primitive::u32,
1008                    /// Capacity of the allocated memory `ok_result_ptr` points to
1009                    pub ok_result_capacity: ::core::ptr::NonNull<::core::primitive::u32>,
1010                });
1011
1012                // Ensure return type implements not only `IoType`, which is required for crossing
1013                // host/guest boundary, but also `TrivialType` and result handling is trivial.
1014                // `#[output]` must be used for a variable size result.
1015                preparation.push(quote! {
1016                    debug_assert!(
1017                        args.ok_result_ptr.is_aligned(),
1018                        "`ok_result_ptr` pointer is misaligned"
1019                    );
1020                    if !args.ok_result_size.is_null() {
1021                        debug_assert_eq!(
1022                            args.ok_result_size.read(),
1023                            0,
1024                            "`ok_result_size` must be zero initially",
1025                        );
1026                    }
1027                    debug_assert!(
1028                        args.ok_result_capacity.read() >=
1029                            <#return_type as ::ab_contracts_macros::__private::TrivialType>::SIZE,
1030                        "`ok_result_capacity` specified is invalid",
1031                    );
1032                });
1033            }
1034            let args_struct_doc = format!(
1035                "Data structure containing expected input to [`{ffi_fn_name}()`], it is used \
1036                internally by the contract, there should be no need to construct it explicitly \
1037                except maybe in contract's own tests"
1038            );
1039            quote_spanned! {fn_sig.span() =>
1040                #[doc = #args_struct_doc]
1041                #[derive(::core::fmt::Debug)]
1042                #[repr(C)]
1043                pub struct InternalArgs<'internal_args>
1044                {
1045                    #( #internal_args_pointers )*
1046                    _phantom: ::core::marker::PhantomData<&'internal_args ()>,
1047                }
1048            }
1049        };
1050
1051        let result_var_name = format_ident!("result");
1052        let guest_fn = {
1053            // Depending on whether `T` or `Result<T, ContractError>` is used as return type,
1054            // generate different code for result handling
1055            let result_handling = match &self.return_type {
1056                MethodReturnType::Unit(_) => {
1057                    quote! {
1058                        // Return exit code
1059                        ::ab_contracts_macros::__private::ExitCode::ok()
1060                    }
1061                }
1062                MethodReturnType::Regular(_) => {
1063                    quote! {
1064                        // Size ight be a null pointer for trivial types
1065                        if !args.ok_result_size.is_null() {
1066                            args.ok_result_size.write(
1067                                <#return_type as ::ab_contracts_macros::__private::TrivialType>::SIZE,
1068                            );
1069                        }
1070                        args.ok_result_ptr.write(#result_var_name);
1071                        // Return exit code
1072                        ::ab_contracts_macros::__private::ExitCode::ok()
1073                    }
1074                }
1075                MethodReturnType::ResultUnit(_) => {
1076                    quote! {
1077                        // Return exit code
1078                        match #result_var_name {
1079                            Ok(()) => ::ab_contracts_macros::__private::ExitCode::ok(),
1080                            Err(error) => error.exit_code(),
1081                        }
1082                    }
1083                }
1084                MethodReturnType::Result(_) => {
1085                    quote! {
1086                        // Write a result into `InternalArgs` if there is any, return exit code
1087                        match #result_var_name {
1088                            Ok(result) => {
1089                                // Size ight be a null pointer for trivial types
1090                                if !args.ok_result_size.is_null() {
1091                                    args.ok_result_size.write(
1092                                        <#return_type as ::ab_contracts_macros::__private::TrivialType>::SIZE,
1093                                    );
1094                                }
1095                                args.ok_result_ptr.write(result);
1096                                // Return exit code
1097                                ::ab_contracts_macros::__private::ExitCode::ok()
1098                            }
1099                            Err(error) => error.exit_code(),
1100                        }
1101                    }
1102                }
1103            };
1104
1105            let full_struct_name = if let Some(trait_name) = trait_name {
1106                quote! { <#self_type as #trait_name> }
1107            } else {
1108                quote! { #self_type }
1109            };
1110
1111            // Generate FFI function with original name (hiding original implementation), but
1112            // exported as shortcut name
1113            quote_spanned! {fn_sig.span() =>
1114                /// FFI interface into a method, called by the host.
1115                ///
1116                /// NOTE: Calling this function directly shouldn't be necessary except maybe in
1117                /// contract's own tests.
1118                ///
1119                /// # Safety
1120                ///
1121                /// Caller must ensure the provided pointer corresponds to expected ABI.
1122                #[cfg_attr(feature = "guest", unsafe(no_mangle))]
1123                #[allow(clippy::new_ret_no_self, reason = "Method was re-written for FFI purposes without `Self`")]
1124                #[allow(clippy::absurd_extreme_comparisons, reason = "Macro-generated code doesn't know the size upfront")]
1125                pub unsafe extern "C" fn #ffi_fn_name(
1126                    mut args: ::core::ptr::NonNull<InternalArgs<'_>>,
1127                ) -> ::ab_contracts_macros::__private::ExitCode {
1128                    // SAFETY: Must be upheld by the caller (executor)
1129                    unsafe {
1130                        debug_assert!(args.is_aligned(), "`args` pointer is misaligned");
1131                        let args = args.as_mut();
1132
1133                        #( #preparation )*
1134
1135                        // Call inner function via normal Rust API
1136                        #[allow(
1137                            unused_variables,
1138                            reason = "Sometimes result is `()`"
1139                        )]
1140                        #[allow(
1141                            clippy::let_unit_value,
1142                            reason = "Sometimes result is `()`"
1143                        )]
1144                        let #result_var_name = #full_struct_name::#original_method_name(
1145                            #( #original_fn_args, )*
1146                        );
1147
1148                        #result_handling
1149                    }
1150                }
1151            }
1152        };
1153
1154        let fn_pointer_static = {
1155            let adapter_ffi_fn_name = format_ident!("{ffi_fn_name}_adapter");
1156            let args_struct_name =
1157                derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1158
1159            quote! {
1160                #[doc(hidden)]
1161                pub mod fn_pointer {
1162                    use super::*;
1163
1164                    unsafe extern "C" fn #adapter_ffi_fn_name(
1165                        ptr: ::core::ptr::NonNull<::core::ptr::NonNull<::core::ffi::c_void>>,
1166                    ) -> ::ab_contracts_macros::__private::ExitCode {
1167                        // SAFETY: Caller must ensure correct ABI of the void pointer, little can be
1168                        // done here
1169                        unsafe { #ffi_fn_name(ptr.cast::<InternalArgs<'_>>()) }
1170                    }
1171
1172                    pub const METHOD_FN_POINTER: ::ab_contracts_macros::__private::NativeExecutorContactMethod =
1173                        ::ab_contracts_macros::__private::NativeExecutorContactMethod {
1174                            method_fingerprint: &<#args_struct_name as ::ab_contracts_macros::__private::ExternalArgs>::FINGERPRINT,
1175                            method_metadata: METADATA,
1176                            ffi_fn: #adapter_ffi_fn_name,
1177                        };
1178                }
1179            }
1180        };
1181
1182        Ok(quote! {
1183            #internal_args_struct
1184            #guest_fn
1185            #fn_pointer_static
1186        })
1187    }
1188
1189    fn generate_external_args_struct(
1190        &self,
1191        fn_sig: &Signature,
1192        trait_name: Option<&Ident>,
1193    ) -> Result<TokenStream, Error> {
1194        let self_type = &self.self_type;
1195        let original_method_name = &fn_sig.ident;
1196
1197        let args_struct_name =
1198            derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1199        // `external_args_pointers` will generate pointers in `ExternalArgs` fields
1200        let mut external_args_fields = Vec::new();
1201        // Arguments of `::new()` method
1202        let mut method_args = Vec::new();
1203        // Fields set on `Self` in `::new()` method
1204        let mut method_args_fields = Vec::new();
1205
1206        // For slots in external args only address is needed
1207        for slot in &self.slots {
1208            let arg_name = &slot.arg_name;
1209            let ptr_field = format_ident!("{arg_name}_ptr");
1210
1211            external_args_fields.push(quote! {
1212                pub #ptr_field: ::core::ptr::NonNull<::ab_contracts_macros::__private::Address>,
1213            });
1214
1215            method_args.push(quote! {
1216                #arg_name: &::ab_contracts_macros::__private::Address,
1217            });
1218            method_args_fields.push(quote! {
1219                // TODO: Use `NonNull::from_ref()` once stable
1220                #ptr_field: ::core::ptr::NonNull::from(#arg_name),
1221            });
1222        }
1223
1224        // Inputs with a pointer and size
1225        for input in &self.inputs {
1226            let type_name = &input.type_name;
1227            let arg_name = &input.arg_name;
1228            let ptr_field = format_ident!("{arg_name}_ptr");
1229            let size_field = format_ident!("{arg_name}_size");
1230            let size_doc = format!("Size of the contents `{ptr_field}` points to");
1231
1232            external_args_fields.push(quote! {
1233                pub #ptr_field: ::core::ptr::NonNull<
1234                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1235                >,
1236                #[doc = #size_doc]
1237                pub #size_field: ::core::ptr::NonNull<::core::primitive::u32>,
1238            });
1239
1240            method_args.push(quote! {
1241                #arg_name: &#type_name,
1242            });
1243            method_args_fields.push(quote! {
1244                // SAFETY: This pointer is used as input to FFI call, and underlying data
1245                // will not be modified, also the pointer will not outlive the reference
1246                // from which it was created despite copying
1247                #ptr_field: unsafe {
1248                    *::ab_contracts_macros::__private::IoType::as_ptr(#arg_name)
1249                },
1250                // SAFETY: This pointer is used as input to FFI call, and underlying data
1251                // will not be modified, also the pointer will not outlive the reference
1252                // from which it was created despite copying
1253                #size_field: unsafe {
1254                    *::ab_contracts_macros::__private::IoType::size_ptr(#arg_name)
1255                },
1256            });
1257        }
1258
1259        // Outputs with a pointer, size and capacity
1260        let mut outputs_iter = self.outputs.iter().peekable();
1261        while let Some(output) = outputs_iter.next() {
1262            let type_name = &output.type_name;
1263            let arg_name = &output.arg_name;
1264            let ptr_field = format_ident!("{arg_name}_ptr");
1265            let size_field = format_ident!("{arg_name}_size");
1266            let size_doc = format!("Size of the contents `{ptr_field}` points to");
1267            let capacity_field = format_ident!("{arg_name}_capacity");
1268            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
1269
1270            // Initializer's return type will be `()` for caller of `#[init]`, state is stored by
1271            // the host and not returned to the caller, hence no explicit argument is needed
1272            if outputs_iter.is_empty()
1273                && self.return_type.unit_return_type()
1274                && matches!(self.method_type, MethodType::Init)
1275            {
1276                continue;
1277            }
1278
1279            external_args_fields.push(quote! {
1280                pub #ptr_field: ::core::ptr::NonNull<
1281                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1282                >,
1283                #[doc = #size_doc]
1284                pub #size_field: *mut ::core::primitive::u32,
1285                #[doc = #capacity_doc]
1286                pub #capacity_field: ::core::ptr::NonNull<::core::primitive::u32>,
1287            });
1288
1289            method_args.push(quote! {
1290                #arg_name: &mut #type_name,
1291            });
1292            method_args_fields.push(quote! {
1293                // SAFETY: This pointer is used as input to FFI call, and underlying data will only
1294                // be modified there, also the pointer will not outlive the reference from which it
1295                // was created despite copying
1296                #ptr_field: unsafe {
1297                    *::ab_contracts_macros::__private::IoType::as_mut_ptr(#arg_name)
1298                },
1299                // SAFETY: This pointer is used as input to FFI call, and underlying data will only
1300                // be modified there, also the pointer will not outlive the reference from which it
1301                // was created despite copying
1302                #size_field: unsafe {
1303                    *::ab_contracts_macros::__private::IoType::size_mut_ptr(#arg_name)
1304                },
1305                // SAFETY: This pointer is used as input to FFI call, and underlying data will not
1306                // be modified, also the pointer will not outlive the reference from which it was
1307                // created despite copying
1308                #capacity_field: unsafe {
1309                    *::ab_contracts_macros::__private::IoType::capacity_ptr(#arg_name)
1310                },
1311            });
1312        }
1313
1314        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1315
1316        // Initializer's return type will be `()` for caller of `#[init]` since the state is stored
1317        // by the host and not returned to the caller and explicit argument is not needed in
1318        // `ExternalArgs` struct. Similarly, it is skipped for a unit return type.
1319        if !(matches!(self.method_type, MethodType::Init) || self.return_type.unit_return_type()) {
1320            let return_type = &self.return_type.return_type();
1321
1322            external_args_fields.push(quote! {
1323                pub ok_result_ptr: ::core::ptr::NonNull<#return_type>,
1324                /// Size of the contents `ok_result_ptr` points to
1325                pub ok_result_size: *mut ::core::primitive::u32,
1326                /// Capacity of the allocated memory `ok_result_ptr` points to
1327                pub ok_result_capacity: ::core::ptr::NonNull<::core::primitive::u32>,
1328            });
1329
1330            method_args.push(quote! {
1331                ok_result: &mut ::core::mem::MaybeUninit<#return_type>,
1332                ok_result_size: &mut ::core::primitive::u32,
1333            });
1334            method_args_fields.push(quote! {
1335                // SAFETY: Pointer created from an allocated struct
1336                ok_result_ptr: unsafe {
1337                    ::core::ptr::NonNull::new_unchecked(ok_result.as_mut_ptr())
1338                },
1339                ok_result_size: ::core::ptr::from_mut(ok_result_size),
1340                // This is for `TrivialType` and will never be modified
1341                // TODO: Use `NonNull::from_ref()` once stable
1342                ok_result_capacity: ::core::ptr::NonNull::from(
1343                    &<#return_type as ::ab_contracts_macros::__private::TrivialType>::SIZE,
1344                ),
1345            });
1346        }
1347        let args_struct_doc = format!(
1348            "Data structure containing expected input for external method invocation, eventually \
1349            calling `{ffi_fn_name}()` on the other side by the host.\n\n\
1350            This can be used with [`Env`](::ab_contracts_macros::__private::Env), though there are \
1351            helper methods on this provided by extension trait that allow not dealing with this \
1352            struct directly in simpler cases."
1353        );
1354
1355        Ok(quote_spanned! {fn_sig.span() =>
1356            #[doc = #args_struct_doc]
1357            #[derive(::core::fmt::Debug)]
1358            #[repr(C)]
1359            pub struct #args_struct_name {
1360                #( #external_args_fields )*
1361            }
1362
1363            #[automatically_derived]
1364            unsafe impl ::ab_contracts_macros::__private::ExternalArgs for #args_struct_name {
1365                const FINGERPRINT: ::ab_contracts_macros::__private::MethodFingerprint =
1366                    ::ab_contracts_macros::__private::MethodFingerprint::new(METADATA)
1367                        .expect("Metadata is statically correct; qed");
1368                const METADATA: &[::core::primitive::u8] = METADATA;
1369            }
1370
1371            impl #args_struct_name {
1372                /// Create a new instance
1373                #[allow(
1374                    clippy::new_without_default,
1375                    reason = "Do not want `Default` in auto-generated code"
1376                )]
1377                pub fn new(
1378                    #( #method_args )*
1379                ) -> Self {
1380                    Self {
1381                        #( #method_args_fields )*
1382                    }
1383                }
1384            }
1385        })
1386    }
1387
1388    fn generate_metadata(
1389        &self,
1390        fn_sig: &Signature,
1391        trait_name: Option<&Ident>,
1392    ) -> Result<TokenStream, Error> {
1393        let self_type = &self.self_type;
1394        // `method_metadata` will generate metadata about method arguments, each element in this
1395        // vector corresponds to one argument
1396        let mut method_metadata = Vec::new();
1397
1398        if let Some(env) = &self.env {
1399            let env_metadata_type = if env.mutability.is_some() {
1400                "EnvRw"
1401            } else {
1402                "EnvRo"
1403            };
1404
1405            let env_metadata_type = format_ident!("{env_metadata_type}");
1406            method_metadata.push(quote! {
1407                &[::ab_contracts_macros::__private::ContractMetadataKind::#env_metadata_type as ::core::primitive::u8],
1408            });
1409        }
1410
1411        if let Some(tmp) = &self.tmp {
1412            let tmp_metadata_type = if tmp.mutability.is_some() {
1413                "TmpRw"
1414            } else {
1415                "TmpRo"
1416            };
1417
1418            let tmp_metadata_type = format_ident!("{tmp_metadata_type}");
1419            let arg_name_metadata = derive_ident_metadata(&tmp.arg_name)?;
1420            method_metadata.push(quote! {
1421                &[::ab_contracts_macros::__private::ContractMetadataKind::#tmp_metadata_type as ::core::primitive::u8],
1422                #arg_name_metadata,
1423            });
1424        }
1425
1426        for slot in &self.slots {
1427            let slot_metadata_type = if slot.mutability.is_some() {
1428                "SlotRw"
1429            } else {
1430                "SlotRo"
1431            };
1432
1433            let slot_metadata_type = format_ident!("{slot_metadata_type}");
1434            let arg_name_metadata = derive_ident_metadata(&slot.arg_name)?;
1435            method_metadata.push(quote! {
1436                &[::ab_contracts_macros::__private::ContractMetadataKind::#slot_metadata_type as ::core::primitive::u8],
1437                #arg_name_metadata,
1438            });
1439        }
1440
1441        for input in &self.inputs {
1442            let io_metadata_type = format_ident!("Input");
1443            let arg_name_metadata = derive_ident_metadata(&input.arg_name)?;
1444            let type_name = &input.type_name;
1445
1446            method_metadata.push(quote! {
1447                &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1448                #arg_name_metadata,
1449                <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1450            });
1451        }
1452
1453        let mut outputs_iter = self.outputs.iter().peekable();
1454        while let Some(output) = outputs_iter.next() {
1455            let io_metadata_type = "Output";
1456
1457            let io_metadata_type = format_ident!("{io_metadata_type}");
1458            let arg_name_metadata = derive_ident_metadata(&output.arg_name)?;
1459            // Skip type metadata for `#[init]`'s last output since it is known statically
1460            let with_type_metadata = if outputs_iter.is_empty()
1461                && self.return_type.unit_return_type()
1462                && matches!(self.method_type, MethodType::Init)
1463            {
1464                None
1465            } else {
1466                let type_name = &output.type_name;
1467                Some(quote! {
1468                    <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1469                })
1470            };
1471            method_metadata.push(quote! {
1472                &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1473                #arg_name_metadata,
1474                #with_type_metadata
1475            });
1476        }
1477
1478        // Skipped if return type is unit
1479        if !self.return_type.unit_return_type() {
1480            // There isn't an explicit name in case of the return type
1481            let arg_name_metadata = Literal::u8_unsuffixed(0);
1482            // Skip type metadata for `#[init]`'s result since it is known statically
1483            let with_type_metadata = if matches!(self.method_type, MethodType::Init) {
1484                None
1485            } else {
1486                let return_type = self.return_type.return_type();
1487                Some(quote! {
1488                    <#return_type as ::ab_contracts_macros::__private::IoType>::METADATA,
1489                })
1490            };
1491            method_metadata.push(quote! {
1492                &[
1493                    ::ab_contracts_macros::__private::ContractMetadataKind::Output as ::core::primitive::u8,
1494                    #arg_name_metadata,
1495                ],
1496                #with_type_metadata
1497            });
1498        }
1499
1500        let method_type = match self.method_type {
1501            MethodType::Init => "Init",
1502            MethodType::Update => {
1503                if let Some(mutable) = &self.state {
1504                    if mutable.is_some() {
1505                        "UpdateStatefulRw"
1506                    } else {
1507                        "UpdateStatefulRo"
1508                    }
1509                } else {
1510                    "UpdateStateless"
1511                }
1512            }
1513            MethodType::View => {
1514                if let Some(mutable) = &self.state {
1515                    if mutable.is_some() {
1516                        return Err(Error::new(
1517                            fn_sig.span(),
1518                            "Stateful view methods are not supported",
1519                        ));
1520                    }
1521
1522                    "ViewStateful"
1523                } else {
1524                    "ViewStateless"
1525                }
1526            }
1527        };
1528
1529        let method_type = format_ident!("{method_type}");
1530        let number_of_arguments = u8::try_from(method_metadata.len()).map_err(|_error| {
1531            Error::new(
1532                fn_sig.span(),
1533                format!("Number of arguments must not be more than {}", u8::MAX),
1534            )
1535        })?;
1536        let total_number_of_arguments =
1537            Literal::u8_unsuffixed(number_of_arguments.saturating_add(1));
1538        let number_of_arguments = Literal::u8_unsuffixed(number_of_arguments);
1539
1540        let original_method_name = &fn_sig.ident;
1541        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1542        let method_name_metadata = derive_ident_metadata(&ffi_fn_name)?;
1543        Ok(quote_spanned! {fn_sig.span() =>
1544            #[expect(
1545                clippy::assertions_on_constants,
1546                reason = "Auto-generated compile-time check"
1547            )]
1548            const fn metadata()
1549                -> ([::core::primitive::u8; ::ab_contracts_macros::__private::MAX_METADATA_CAPACITY], usize)
1550            {
1551                assert!(
1552                    #total_number_of_arguments <= ::ab_contracts_macros::__private::MAX_TOTAL_METHOD_ARGS,
1553                    "Too many arguments"
1554                );
1555                ::ab_contracts_macros::__private::concat_metadata_sources(&[
1556                    &[::ab_contracts_macros::__private::ContractMetadataKind::#method_type as ::core::primitive::u8],
1557                    #method_name_metadata,
1558                    &[#number_of_arguments],
1559                    #( #method_metadata )*
1560                ])
1561            }
1562
1563            /// Method metadata, see [`ContractMetadataKind`] for encoding details
1564            ///
1565            /// [`ContractMetadataKind`]: ::ab_contracts_macros::__private::ContractMetadataKind
1566            // Strange syntax to allow Rust to extend the lifetime of metadata scratch automatically
1567            pub const METADATA: &[::core::primitive::u8] =
1568                metadata()
1569                    .0
1570                    .split_at(metadata().1)
1571                    .0;
1572        })
1573    }
1574
1575    pub(super) fn generate_trait_ext_components(
1576        &self,
1577        fn_sig: &Signature,
1578        fn_attrs: &[Attribute],
1579        trait_name: Option<&Ident>,
1580    ) -> Result<ExtTraitComponents, Error> {
1581        let self_type = &self.self_type;
1582
1583        let mut preparation = Vec::new();
1584        let mut method_args = Vec::new();
1585        let mut external_args_args = Vec::new();
1586        let mut result_processing = Vec::new();
1587
1588        // Address of the contract
1589        method_args.push(quote_spanned! {fn_sig.span() =>
1590            contract: ::ab_contracts_macros::__private::Address,
1591        });
1592
1593        // For each slot argument generate an address argument
1594        for slot in &self.slots {
1595            let arg_name = &slot.arg_name;
1596
1597            method_args.push(quote_spanned! {fn_sig.span() =>
1598                #arg_name: &::ab_contracts_macros::__private::Address,
1599            });
1600            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1601        }
1602
1603        // For each input argument, generate a corresponding read-only argument
1604        for input in &self.inputs {
1605            let type_name = &input.type_name;
1606            let arg_name = &input.arg_name;
1607
1608            method_args.push(quote_spanned! {fn_sig.span() =>
1609                #arg_name: &#type_name,
1610            });
1611            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1612        }
1613
1614        // For each output argument, generate a corresponding write-only argument
1615        let mut outputs_iter = self.outputs.iter().peekable();
1616        while let Some(output) = outputs_iter.next() {
1617            let type_name = &output.type_name;
1618            let arg_name = &output.arg_name;
1619
1620            // Initializer's return type will be `()` for caller of `#[init]`, state is stored by
1621            // the host and not returned to the caller
1622            if outputs_iter.is_empty()
1623                && self.return_type.unit_return_type()
1624                && matches!(self.method_type, MethodType::Init)
1625            {
1626                continue;
1627            }
1628
1629            method_args.push(quote_spanned! {fn_sig.span() =>
1630                #arg_name: &mut #type_name,
1631            });
1632            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1633        }
1634
1635        let original_method_name = &fn_sig.ident;
1636        let ext_method_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1637        // Non-`#[view]` methods can only be called on `&mut Env`
1638        let env_self = if matches!(self.method_type, MethodType::View) {
1639            quote_spanned! {fn_sig.span() => &self }
1640        } else {
1641            quote_spanned! {fn_sig.span() => &mut self }
1642        };
1643        // `#[view]` methods do not require explicit method context
1644        let method_context_arg = (!matches!(self.method_type, MethodType::View)).then(|| {
1645            quote_spanned! {fn_sig.span() =>
1646                method_context: ::ab_contracts_macros::__private::MethodContext,
1647            }
1648        });
1649        // Initializer's return type will be `()` for caller of `#[init]` since the state is stored
1650        // by the host and not returned to the caller. Similarly, it is skipped for a unit return
1651        // type.
1652        let method_signature = if matches!(self.method_type, MethodType::Init)
1653            || self.return_type.unit_return_type()
1654        {
1655            quote_spanned! {fn_sig.span() =>
1656                #[allow(dead_code, reason = "Macro-generated")]
1657                fn #ext_method_name(
1658                    #env_self,
1659                    #method_context_arg
1660                    #( #method_args )*
1661                ) -> ::core::result::Result<(), ::ab_contracts_macros::__private::ContractError>
1662            }
1663        } else {
1664            let return_type = self.return_type.return_type();
1665
1666            preparation.push(quote_spanned! {fn_sig.span() =>
1667                let mut ok_result = ::core::mem::MaybeUninit::uninit();
1668                // While this will not change for `TrivialType`, the pointer will be written to and
1669                // as such, the value needs to be given
1670                let mut ok_result_size =
1671                    <#return_type as ::ab_contracts_macros::__private::TrivialType>::SIZE;
1672            });
1673            external_args_args.push(quote_spanned! {fn_sig.span() =>
1674                &mut ok_result,
1675                &mut ok_result_size
1676            });
1677            result_processing.push(quote_spanned! {fn_sig.span() =>
1678                // This is fine for `TrivialType` types
1679                ok_result.assume_init()
1680            });
1681
1682            quote_spanned! {fn_sig.span() =>
1683                #[allow(dead_code, reason = "Macro-generated")]
1684                fn #ext_method_name(
1685                    #env_self,
1686                    #method_context_arg
1687                    #( #method_args )*
1688                ) -> ::core::result::Result<
1689                    #return_type,
1690                    ::ab_contracts_macros::__private::ContractError,
1691                >
1692            }
1693        };
1694
1695        let attrs = fn_attrs.iter().filter(|attr| {
1696            let path = match &attr.meta {
1697                Meta::Path(path) => path,
1698                Meta::List(list) => &list.path,
1699                Meta::NameValue(name_value) => &name_value.path,
1700            };
1701
1702            if let Some(ident) = path.get_ident() {
1703                ident == "doc" || ident == "allow" || ident == "expect"
1704            } else {
1705                false
1706            }
1707        });
1708        let definition = quote_spanned! {fn_sig.span() =>
1709            #[allow(
1710                clippy::too_many_arguments,
1711                reason = "Generated code may have more arguments that source code"
1712            )]
1713            #( #attrs )*
1714            #method_signature;
1715        };
1716
1717        let args_struct_name =
1718            derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1719        // `#[view]` methods do not require explicit method context
1720        let method_context_value = if matches!(self.method_type, MethodType::View) {
1721            quote_spanned! {fn_sig.span() =>
1722                ::ab_contracts_macros::__private::MethodContext::Reset
1723            }
1724        } else {
1725            quote_spanned! {fn_sig.span() =>
1726                method_context
1727            }
1728        };
1729        let r#impl = quote_spanned! {fn_sig.span() =>
1730            #[inline]
1731            #method_signature {
1732                #( #preparation )*
1733
1734                let mut args = #original_method_name::#args_struct_name::new(
1735                    #( #external_args_args, )*
1736                );
1737
1738                self.call(contract, &mut args, #method_context_value)?;
1739
1740                // SAFETY: The non-error result above indicates successful storing of the result
1741                #[allow(
1742                    unused_unsafe,
1743                    reason = "Sometimes there is no result to process and block is empty"
1744                )]
1745                #[allow(
1746                    clippy::let_unit_value,
1747                    reason = "Sometimes there is no result to process and block is empty"
1748                )]
1749                let result = unsafe {
1750                    #( #result_processing )*
1751                };
1752
1753                Ok(result)
1754            }
1755        };
1756
1757        Ok(ExtTraitComponents { definition, r#impl })
1758    }
1759}
1760
1761fn extract_arg_name(mut pat: &Pat) -> Option<Ident> {
1762    loop {
1763        match pat {
1764            Pat::Ident(pat_ident) => {
1765                return Some(pat_ident.ident.clone());
1766            }
1767            Pat::Reference(pat_reference) => {
1768                pat = &pat_reference.pat;
1769            }
1770            _ => {
1771                return None;
1772            }
1773        }
1774    }
1775}
1776
1777fn derive_ffi_fn_name(
1778    type_name: &Type,
1779    trait_name: Option<&Ident>,
1780    method_name: &Ident,
1781) -> Result<Ident, Error> {
1782    let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1783        Error::new(
1784            type_name.span(),
1785            "`#[contract]` must be applied to a simple struct without generics",
1786        )
1787    })?;
1788    let ffi_fn_prefix =
1789        RenameRule::SnakeCase.apply_to_variant(trait_name.unwrap_or(type_name).to_string());
1790
1791    Ok(format_ident!("{ffi_fn_prefix}_{method_name}"))
1792}
1793
1794fn derive_external_args_struct_name(
1795    type_name: &Type,
1796    trait_name: Option<&Ident>,
1797    method_name: &Ident,
1798) -> Result<Ident, Error> {
1799    let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1800        Error::new(
1801            type_name.span(),
1802            "`#[contract]` must be applied to a simple struct without generics",
1803        )
1804    })?;
1805    Ok(format_ident!(
1806        "{}{}Args",
1807        trait_name.unwrap_or(type_name),
1808        RenameRule::PascalCase.apply_to_field(method_name.to_string())
1809    ))
1810}