Skip to main content

ab_contracts_macros_impl/contract/
method.rs

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    /// The function doesn't have any return type defined
66    Unit(Type),
67    /// Returns a type without [`Result`]
68    Regular(Type),
69    /// Returns [`Result`], but [`Ok`] variant is `()`
70    ResultUnit(Type),
71    /// Returns [`Result`], but [`Ok`] variant is not `()`
72    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    /// Returns `#[tmp]` type (or `()` if it is not used) if all methods have the same slots type
133    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    /// Returns `#[slot]` type (or `()` if it is not used) if all methods have the same slots type
155    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        // Only accept `&self` or `&mut self`
255        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        // 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            #[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        // `internal_args_pointers` will generate pointers in `InternalArgs` fields
659        let mut internal_args_pointers = Vec::new();
660        // `preparation` will generate code used before calling the original function
661        let mut preparation = Vec::new();
662        // `original_fn_args` will generate arguments for calling original method implementation
663        let mut original_fn_args = Vec::new();
664
665        // Optional state argument
666        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                /// Size of the contents `self_ptr` points to
672                pub self_size: ::core::primitive::u32,
673                /// Capacity of the allocated memory `self_ptr` points to
674                pub self_capacity: ::core::primitive::u32,
675            });
676
677            if mutability.is_some() {
678                original_fn_args.push(quote! {&mut *{
679                    // Ensure the state type implements `IoType`, which is required for crossing the
680                    // host/guest boundary
681                    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                    // Ensure the state type implements `IoType`, which is required for crossing the
698                    // host/guest boundary
699                    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        // Optional environment argument
717        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                // Use `Env` to check if the method argument had the correct type at compile time
723                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        // Optional tmp argument
730        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                        // Make sure `#[tmp]` type matches expected type
743                        <#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                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
755                    // of tmp that might be removed or not present and implies implementation of
756                    // `IoType`, which is required for crossing the host/guest boundary
757                    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                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
774                    // of tmp that might be removed or not present and implies implementation of
775                    // `IoType`, which is required for crossing the host/guest boundary
776                    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        // Slot arguments with:
794        // * in case address is used: a pointer to address, a pointer to slot and size + capacity
795        // * in case address is not used: a pointer to slot and size + capacity
796        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                // Use `Address` to check if the method argument had the correct type at compile
808                // time
809                pub #address_field: &'internal_args ::ab_contracts_macros::__private::Address,
810                pub #ptr_field: ::core::ptr::NonNull<
811                    <
812                        // Make sure the `#[slot]` type matches the expected type
813                        <#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                    // Ensure the slot type implements `IoTypeOptional`, which is required for
825                    // handling of slot that might be removed or not present and implies
826                    // implementation of `IoType`, which is required for crossing the host/guest
827                    // boundary
828                    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                    // Ensure the slot type implements `IoTypeOptional`, which is required for
845                    // handling of slot that might be removed or not present and implies
846                    // implementation of `IoType`, which is required for crossing the host/guest
847                    // boundary
848                    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        // Inputs
877        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                // Ensure the input type implements `IoType`, which is required for crossing the
898                // host/guest boundary
899                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        // Outputs
916        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                // Ensure the output type implements `IoType`, which is required for crossing the
937                // host/guest 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_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            // No special handling of the return type is needed for a unit return type
960            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                    // Ensure the return type implements not only `IoType`, which is required for
967                    // crossing host/guest boundary, but also `TrivialType` and result handling is
968                    // trivial without the need to worry about size and capacity.
969                    // `#[output]` must be used for a variable size result.
970                    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            // Depending on whether `T` or `Result<T, ContractError>` is used as a return type,
1005            // generate different code for result handling
1006            let result_handling = match &self.return_type {
1007                MethodReturnType::Unit(_) => {
1008                    quote! {
1009                        // Return exit code
1010                        ::ab_contracts_macros::__private::ExitCode::ok()
1011                    }
1012                }
1013                MethodReturnType::Regular(_) => {
1014                    quote! {
1015                        args.ok_result.write(#result_var_name);
1016                        // Return exit code
1017                        ::ab_contracts_macros::__private::ExitCode::ok()
1018                    }
1019                }
1020                MethodReturnType::ResultUnit(_) => {
1021                    quote! {
1022                        // Return exit code
1023                        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                        // Write a result into `InternalArgs` if there is any, return exit code
1032                        match #result_var_name {
1033                            Ok(result) => {
1034                                args.ok_result.write(result);
1035                                // Return exit code
1036                                ::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            // Generate FFI function with the original name
1051            quote_spanned! {fn_sig.span() =>
1052                /// FFI interface into a method, called by the host.
1053                ///
1054                /// NOTE: Calling this function directly shouldn't be necessary except maybe in
1055                /// contract's own tests.
1056                ///
1057                /// # Safety
1058                ///
1059                /// Caller must ensure the provided pointer corresponds to the expected ABI.
1060                #[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                    // Call inner function via normal Rust API
1069                    #[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                        // SAFETY: Caller must ensure correct ABI of the void pointer, little can be
1102                        // done here
1103                        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        // `external_args_pointers` will generate pointers in `ExternalArgs` fields
1139        let mut external_args_fields = Vec::new();
1140        // Arguments of `::new()` method
1141        let mut method_args = Vec::new();
1142        // Fields set on `Self` in `::new()` method
1143        let mut method_args_fields = Vec::new();
1144
1145        // For slots in external args only the address pointer is needed
1146        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        // Inputs with pointers to data and size
1163        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                // SAFETY: This pointer is used as input to FFI call, and underlying data
1187                // will not be modified, also the pointer will not outlive the reference
1188                // from which it was created despite copying
1189                #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        // Outputs with pointers to data, size and capacity
1198        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            // Initializer's return type will be `()` for caller of `#[init]`, state is stored by
1209            // the host and not returned to the caller, hence no explicit argument is needed
1210            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                // SAFETY: This pointer is used as input to FFI call, and underlying data will only
1232                // be modified there, also the pointer will not outlive the reference from which it
1233                // was created despite copying
1234                #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        // Initializer's return type will be `()` for the caller of `#[init]` since the state is
1245        // stored by the host and not returned to the caller and explicit argument is not needed in
1246        // the `ExternalArgs` struct. Similarly, it is skipped for a unit return type.
1247        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                /// Lifetime of the struct
1278                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                /// Create a new instance.
1291                ///
1292                /// NOTE: Make sure to query updated sizes of arguments after calling the contract.
1293                #[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        // `method_metadata` will generate metadata about method arguments, each element in this
1317        // vector corresponds to one argument
1318        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            // Skip type metadata for `#[init]`'s last output since it is known statically
1382            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        // Skipped if return type is unit
1401        if !self.return_type.unit_return_type() {
1402            // There isn't an explicit name in case of the return type
1403            let arg_name_metadata = Literal::u8_unsuffixed(0);
1404            // Skip type metadata for `#[init]`'s result since it is known statically
1405            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            /// Method metadata, see [`ContractMetadataKind`] for encoding details
1486            ///
1487            /// [`ContractMetadataKind`]: ::ab_contracts_macros::__private::ContractMetadataKind
1488            // Strange syntax to allow Rust to extend the lifetime of metadata scratch automatically
1489            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        // Address of the contract
1512        method_args.push(quote_spanned! {fn_sig.span() =>
1513            contract: ::ab_contracts_macros::__private::Address,
1514        });
1515
1516        // For each slot argument generate an address argument
1517        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 each input argument, generate a corresponding read-only argument
1527        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        // For each output argument, generate a corresponding write-only argument
1538        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            // Initializer's return type will be `()` for the caller of `#[init]`, state is stored
1546            // by the host and not returned to the caller
1547            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                // SAFETY: The host updates size correctly
1563                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        // Non-`#[view]` methods can only be called on `&mut Env`
1572        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        // `#[view]` methods do not require explicit method context
1578        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        // Initializer's return type will be `()` for the caller of `#[init]` since the state is
1584        // stored by the host and not returned to the caller. Similarly, it is skipped for a unit
1585        // return type.
1586        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                // Ensure the return type implements not only `IoType`, which is required for
1604                // crossing host/guest boundary, but also `TrivialType` and result handling is
1605                // trivial without the need to worry about size and capacity.
1606                // `#[output]` must be used for a variable size result.
1607                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                // SAFETY: The non-error result indicates successful storing of the result
1622                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        // `#[view]` methods do not require explicit method context
1666        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}