Skip to main content

ab_contracts_macros_impl/
contract.rs

1mod common;
2mod init;
3mod method;
4mod update;
5mod view;
6
7use crate::contract::common::{derive_ident_metadata, extract_ident_from_type};
8use crate::contract::init::process_init_fn;
9use crate::contract::method::{ExtTraitComponents, MethodDetails};
10use crate::contract::update::{process_update_fn, process_update_fn_definition};
11use crate::contract::view::{process_view_fn, process_view_fn_definition};
12use ab_contracts_common::METADATA_STATIC_NAME_PREFIX;
13use heck::ToSnakeCase;
14use proc_macro2::{Ident, Literal, Span, TokenStream};
15use quote::{format_ident, quote};
16use std::collections::HashMap;
17use syn::spanned::Spanned;
18use syn::{
19    Error, ImplItem, ImplItemFn, ItemImpl, ItemTrait, Meta, TraitItem, TraitItemConst, TraitItemFn,
20    Type, Visibility, parse_quote, parse2,
21};
22
23#[derive(Default)]
24struct MethodOutput {
25    guest_ffi: TokenStream,
26    trait_ext_components: ExtTraitComponents,
27}
28
29struct Method {
30    /// As authored in source code
31    original_ident: Ident,
32    methods_details: MethodDetails,
33}
34
35#[derive(Default)]
36struct ContractDetails {
37    methods: Vec<Method>,
38}
39
40pub(super) fn contract(item: TokenStream) -> Result<TokenStream, Error> {
41    if let Ok(item_trait) = parse2::<ItemTrait>(item.clone()) {
42        // Trait definition
43        return process_trait_definition(item_trait);
44    }
45
46    let error_message = "`#[contract]` must be applied to struct implementation, trait definition or trait \
47        implementation";
48
49    let item_impl =
50        parse2::<ItemImpl>(item).map_err(|error| Error::new(error.span(), error_message))?;
51
52    if let Some((_not, path, _for)) = &item_impl.trait_ {
53        let trait_name = path
54            .get_ident()
55            .ok_or_else(|| Error::new(path.span(), error_message))?
56            .clone();
57        // Trait implementation
58        process_trait_impl(item_impl, &trait_name)
59    } else {
60        // Implementation of a struct
61        process_struct_impl(item_impl)
62    }
63}
64
65fn process_trait_definition(mut item_trait: ItemTrait) -> Result<TokenStream, Error> {
66    let trait_name = &item_trait.ident;
67
68    if !item_trait.generics.params.is_empty() {
69        return Err(Error::new(
70            item_trait.generics.span(),
71            "`#[contract]` does not support generics",
72        ));
73    }
74
75    let mut guest_ffis = Vec::with_capacity(item_trait.items.len());
76    let mut trait_ext_components = Vec::with_capacity(item_trait.items.len());
77    let mut contract_details = ContractDetails::default();
78
79    for item in &mut item_trait.items {
80        if let TraitItem::Fn(trait_item_fn) = item {
81            let method_output =
82                process_fn_definition(trait_name, trait_item_fn, &mut contract_details)?;
83            guest_ffis.push(method_output.guest_ffi);
84            trait_ext_components.push(method_output.trait_ext_components);
85
86            // This is needed to make trait itself object safe, which is in turn used as a hack for
87            // some APIs
88            if let Some(where_clause) = &mut trait_item_fn.sig.generics.where_clause {
89                where_clause.predicates.push(parse_quote! {
90                    Self: ::core::marker::Sized
91                });
92            } else {
93                trait_item_fn
94                    .sig
95                    .generics
96                    .where_clause
97                    .replace(parse_quote! {
98                        where
99                            Self: ::core::marker::Sized
100                    });
101            }
102        }
103    }
104
105    let metadata_const = generate_trait_metadata(&contract_details, trait_name, item_trait.span())?;
106    let ext_trait = generate_extension_trait(trait_name, &trait_ext_components);
107
108    Ok(quote! {
109        #item_trait
110
111        // `dyn ContractTrait` here is a bit of a hack that allows treating a trait as a type. These
112        // constants specifically can't be implemented on a trait itself because that'll make trait
113        // not object safe, which is needed for `ContractTrait` that uses a similar hack with
114        // `dyn ContractTrait`.
115        impl ::ab_contracts_macros::__private::ContractTraitDefinition for dyn #trait_name {
116            #[cfg(feature = "guest")]
117            #[doc(hidden)]
118            const GUEST_FEATURE_ENABLED: () = ();
119            #metadata_const
120        }
121
122        #ext_trait
123
124        /// FFI code generated by procedural macro
125        #[expect(clippy::wildcard_imports, reason = "Macro-generated")]
126        pub mod ffi {
127            use super::*;
128
129            #( #guest_ffis )*
130        }
131    })
132}
133
134fn process_trait_impl(mut item_impl: ItemImpl, trait_name: &Ident) -> Result<TokenStream, Error> {
135    let struct_name = item_impl.self_ty.as_ref();
136
137    if !item_impl.generics.params.is_empty() {
138        return Err(Error::new(
139            item_impl.generics.span(),
140            "`#[contract]` does not support generics",
141        ));
142    }
143
144    let mut guest_ffis = Vec::with_capacity(item_impl.items.len());
145    let mut contract_details = ContractDetails::default();
146
147    for item in &mut item_impl.items {
148        match item {
149            ImplItem::Fn(impl_item_fn) => {
150                let method_output = process_fn(
151                    struct_name.clone(),
152                    Some(trait_name),
153                    impl_item_fn,
154                    &mut contract_details,
155                )?;
156                guest_ffis.push(method_output.guest_ffi);
157
158                if let Some(where_clause) = &mut impl_item_fn.sig.generics.where_clause {
159                    where_clause.predicates.push(parse_quote! {
160                        Self: ::core::marker::Sized
161                    });
162                } else {
163                    impl_item_fn
164                        .sig
165                        .generics
166                        .where_clause
167                        .replace(parse_quote! {
168                            where
169                                Self: ::core::marker::Sized
170                        });
171                }
172            }
173            ImplItem::Const(impl_item_const) if impl_item_const.ident == "METADATA" => {
174                return Err(Error::new(
175                    impl_item_const.span(),
176                    "`#[contract]` doesn't allow overriding `METADATA` constant",
177                ));
178            }
179            _ => {
180                // Ignore
181            }
182        }
183    }
184
185    let static_name = format_ident!("{METADATA_STATIC_NAME_PREFIX}{}", trait_name);
186    let ffi_mod_ident = format_ident!("{}_ffi", trait_name.to_string().to_snake_case());
187    let metadata_const = generate_trait_metadata(&contract_details, trait_name, item_impl.span())?;
188    let method_fn_pointers_const = {
189        let methods = contract_details
190            .methods
191            .iter()
192            .map(|method| &method.original_ident);
193
194        quote! {
195            #[doc(hidden)]
196            const NATIVE_EXECUTOR_METHODS: &[::ab_contracts_macros::__private::NativeExecutorContactMethod] = &[
197                #( #ffi_mod_ident::#methods::fn_pointer::METHOD_FN_POINTER, )*
198            ];
199        }
200    };
201
202    Ok(quote! {
203        /// Contribute trait metadata to contract's metadata
204        ///
205        /// Enabled with `guest` feature to appear in the final binary.
206        ///
207        /// See [`Contract::MAIN_CONTRACT_METADATA`] for details.
208        ///
209        /// [`Contract::MAIN_CONTRACT_METADATA`]: ::ab_contracts_macros::__private::Contract::MAIN_CONTRACT_METADATA
210        #[cfg(feature = "guest")]
211        #[used]
212        #[unsafe(no_mangle)]
213        #[cfg_attr(
214            target_env = "abundance",
215            unsafe(link_section = "ab-contract-metadata")
216        )]
217        static #static_name: [::core::primitive::u8; <dyn #trait_name as ::ab_contracts_macros::__private::ContractTraitDefinition>::METADATA.len()] = unsafe {
218            *<dyn #trait_name as ::ab_contracts_macros::__private::ContractTraitDefinition>::METADATA.as_ptr().cast()
219        };
220
221        // Sanity check that trait implementation fully matches trait definition
222        const _: () = {
223            // Import as `ffi` for generated metadata constant to pick up a correct version
224            use #ffi_mod_ident as ffi;
225            #metadata_const
226
227            // Comparing compact metadata to allow argument name differences and similar things
228            // TODO: This two-step awkwardness because simple comparison doesn't work in const
229            //  environment yet
230            let (impl_compact_metadata, impl_compact_metadata_size) =
231                ::ab_contracts_macros::__private::ContractMetadataKind::compact(METADATA)
232                    .expect("Generated metadata is correct; qed");
233            let (def_compact_metadata, def_compact_metadata_size) =
234                ::ab_contracts_macros::__private::ContractMetadataKind::compact(
235                    <dyn #trait_name as ::ab_contracts_macros::__private::ContractTraitDefinition>::METADATA,
236                )
237                    .expect("Generated metadata is correct; qed");
238            assert!(
239                impl_compact_metadata_size == def_compact_metadata_size,
240                "Trait implementation must match trait definition exactly"
241            );
242            let mut i = 0;
243            while impl_compact_metadata_size > i {
244                assert!(
245                    impl_compact_metadata[i] == def_compact_metadata[i],
246                    "Trait implementation must match trait definition exactly"
247                );
248                i += 1;
249            }
250        };
251
252        // Ensure `guest` feature is enabled for crate with trait definition
253        #[cfg(feature = "guest")]
254        const _: () = <dyn #trait_name as ::ab_contracts_macros::__private::ContractTraitDefinition>::GUEST_FEATURE_ENABLED;
255
256        #item_impl
257
258        // `dyn ContractTrait` here is a bit of a hack that allows treating a trait as a type for
259        // convenient API in native execution environment
260        impl ::ab_contracts_macros::__private::ContractTrait<dyn #trait_name> for #struct_name {
261            #method_fn_pointers_const
262        }
263
264        /// FFI code generated by procedural macro
265        #[expect(clippy::wildcard_imports, reason = "Macro-generated")]
266        pub mod #ffi_mod_ident {
267            use super::*;
268
269            #( #guest_ffis )*
270        }
271    })
272}
273
274fn generate_trait_metadata(
275    contract_details: &ContractDetails,
276    trait_name: &Ident,
277    span: Span,
278) -> Result<TraitItemConst, Error> {
279    let num_methods = u8::try_from(contract_details.methods.len()).map_err(|_error| {
280        Error::new(
281            span,
282            format!("Trait can't have more than {} methods", u8::MAX),
283        )
284    })?;
285    let num_methods = Literal::u8_unsuffixed(num_methods);
286    let methods = contract_details
287        .methods
288        .iter()
289        .map(|method| &method.original_ident);
290    let trait_name_metadata = derive_ident_metadata(trait_name)?;
291
292    // Encodes the following:
293    // * Type: trait definition
294    // * Length of trait name in bytes (u8)
295    // * Trait name as UTF-8 bytes
296    // * Number of methods
297    // * Metadata of methods
298    Ok(parse_quote! {
299        /// Trait metadata, see [`ContractMetadataKind`] for encoding details
300        ///
301        /// [`ContractMetadataKind`]: ::ab_contracts_macros::__private::ContractMetadataKind
302        const METADATA: &[::core::primitive::u8] = {
303            const fn metadata()
304                -> ([::core::primitive::u8; ::ab_contracts_macros::__private::MAX_METADATA_CAPACITY], usize)
305            {
306                ::ab_contracts_macros::__private::concat_metadata_sources(&[
307                    &[::ab_contracts_macros::__private::ContractMetadataKind::Trait as ::core::primitive::u8],
308                    #trait_name_metadata,
309                    &[#num_methods],
310                    #( ffi::#methods::METADATA, )*
311                ])
312            }
313
314            // Strange syntax to allow Rust to extend the lifetime of metadata scratch
315            // automatically
316            metadata()
317                .0
318                .split_at(metadata().1)
319                .0
320        };
321    })
322}
323
324fn process_struct_impl(mut item_impl: ItemImpl) -> Result<TokenStream, Error> {
325    let struct_name = item_impl.self_ty.as_ref();
326
327    if !item_impl.generics.params.is_empty() {
328        return Err(Error::new(
329            item_impl.generics.span(),
330            "`#[contract]` does not support generics",
331        ));
332    }
333
334    // Needed for arguments
335    item_impl.attrs.extend([
336        parse_quote! { #[expect(clippy::allow_attributes, reason = "Attribute below")] },
337        parse_quote! { #[allow(clippy::trivially_copy_pass_by_ref, reason = "API requirement")] },
338    ]);
339
340    let mut guest_ffis = Vec::with_capacity(item_impl.items.len());
341    let mut trait_ext_components = Vec::with_capacity(item_impl.items.len());
342    let mut contract_details = ContractDetails::default();
343
344    for item in &mut item_impl.items {
345        if let ImplItem::Fn(impl_item_fn) = item {
346            let method_output = process_fn(
347                struct_name.clone(),
348                None,
349                impl_item_fn,
350                &mut contract_details,
351            )?;
352            guest_ffis.push(method_output.guest_ffi);
353            trait_ext_components.push(method_output.trait_ext_components);
354        }
355    }
356
357    let maybe_slot_type = MethodDetails::slot_type(
358        contract_details
359            .methods
360            .iter()
361            .map(|method| &method.methods_details),
362    );
363    let Some(slot_type) = maybe_slot_type else {
364        return Err(Error::new(
365            item_impl.span(),
366            "All `#[slot]` arguments must be of the same type in all methods of a contract",
367        ));
368    };
369
370    let maybe_tmp_type = MethodDetails::tmp_type(
371        contract_details
372            .methods
373            .iter()
374            .map(|method| &method.methods_details),
375    );
376
377    let Some(tmp_type) = maybe_tmp_type else {
378        return Err(Error::new(
379            item_impl.span(),
380            "All `#[tmp]` arguments must be of the same type in all methods of a contract",
381        ));
382    };
383
384    let metadata_const = {
385        let num_methods = u8::try_from(contract_details.methods.len()).map_err(|_error| {
386            Error::new(
387                item_impl.span(),
388                format!("Struct can't have more than {} methods", u8::MAX),
389            )
390        })?;
391        let num_methods = Literal::u8_unsuffixed(num_methods);
392        let methods = contract_details
393            .methods
394            .iter()
395            .map(|method| &method.original_ident);
396
397        // Encodes the following:
398        // * Type: contract
399        // * Metadata of the state type
400        // * Number of methods
401        // * Metadata of methods
402        quote! {
403            const MAIN_CONTRACT_METADATA: &[::core::primitive::u8] = {
404                const fn metadata()
405                    -> ([::core::primitive::u8; ::ab_contracts_macros::__private::MAX_METADATA_CAPACITY], usize)
406                {
407                    ::ab_contracts_macros::__private::concat_metadata_sources(&[
408                        &[::ab_contracts_macros::__private::ContractMetadataKind::Contract as ::core::primitive::u8],
409                        <#struct_name as ::ab_contracts_macros::__private::IoType>::METADATA,
410                        <#slot_type as ::ab_contracts_macros::__private::IoType>::METADATA,
411                        <#tmp_type as ::ab_contracts_macros::__private::IoType>::METADATA,
412                        &[#num_methods],
413                        #( ffi::#methods::METADATA, )*
414                    ])
415                }
416
417                // Strange syntax to allow Rust to extend the lifetime of metadata scratch
418                // automatically
419                metadata()
420                    .0
421                    .split_at(metadata().1)
422                    .0
423            };
424        }
425    };
426    let method_fn_pointers_const = {
427        let methods = contract_details
428            .methods
429            .iter()
430            .map(|method| &method.original_ident);
431
432        quote! {
433            #[doc(hidden)]
434            const NATIVE_EXECUTOR_METHODS: &[::ab_contracts_macros::__private::NativeExecutorContactMethod] = &[
435                #( ffi::#methods::fn_pointer::METHOD_FN_POINTER, )*
436            ];
437        }
438    };
439
440    let struct_name_ident = extract_ident_from_type(struct_name).ok_or_else(|| {
441        Error::new(
442            struct_name.span(),
443            "`#[contract]` must be applied to simple struct implementation",
444        )
445    })?;
446
447    let ext_trait = generate_extension_trait(struct_name_ident, &trait_ext_components);
448
449    let struct_name_str = struct_name_ident.to_string();
450    let static_name = format_ident!("{METADATA_STATIC_NAME_PREFIX}{}", struct_name_str);
451    Ok(quote! {
452        /// Main contract metadata
453        ///
454        /// Enabled with `guest` feature to appear in the final binary, also prevents from
455        /// `guest` feature being enabled in dependencies at the same time since that'll cause
456        /// duplicated symbols.
457        ///
458        /// See [`Contract::MAIN_CONTRACT_METADATA`] for details.
459        ///
460        /// [`Contract::MAIN_CONTRACT_METADATA`]: ::ab_contracts_macros::__private::Contract::MAIN_CONTRACT_METADATA
461        #[cfg(feature = "guest")]
462        #[used]
463        #[unsafe(no_mangle)]
464        #[cfg_attr(
465            target_env = "abundance",
466            unsafe(link_section = "ab-contract-metadata")
467        )]
468        static #static_name: [
469            ::core::primitive::u8;
470            <#struct_name as ::ab_contracts_macros::__private::Contract>::MAIN_CONTRACT_METADATA
471                .len()
472        ] = unsafe {
473            *<#struct_name as ::ab_contracts_macros::__private::Contract>::MAIN_CONTRACT_METADATA
474                .as_ptr()
475                .cast()
476        };
477
478        impl ::ab_contracts_macros::__private::Contract for #struct_name {
479            #metadata_const
480            #method_fn_pointers_const
481            #[doc(hidden)]
482            const CODE: &::core::primitive::str = ::ab_contracts_macros::__private::concatcp!(
483                #struct_name_str,
484                '[',
485                ::core::env!("CARGO_PKG_NAME"),
486                '/',
487                ::core::file!(),
488                ':',
489                ::core::line!(),
490                ':',
491                ::core::column!(),
492                ']',
493            );
494            // Ensure `guest` feature is enabled for `ab-contracts-common` crate
495            #[cfg(feature = "guest")]
496            #[doc(hidden)]
497            const GUEST_FEATURE_ENABLED: () = ();
498            type Slot = #slot_type;
499            type Tmp = #tmp_type;
500
501            fn code() -> impl ::core::ops::Deref<
502                Target = ::ab_contracts_macros::__private::VariableBytes<
503                    { ::ab_contracts_macros::__private::MAX_CODE_SIZE },
504                >,
505            > {
506                const fn code_bytes() -> &'static [::core::primitive::u8] {
507                    <#struct_name as ::ab_contracts_macros::__private::Contract>::CODE.as_bytes()
508                }
509
510                const fn code_size() -> ::core::primitive::u32 {
511                    code_bytes().len() as ::core::primitive::u32
512                }
513
514                static CODE_SIZE: ::core::primitive::u32 = code_size();
515
516                ::ab_contracts_macros::__private::VariableBytes::from_buffer(
517                    code_bytes(),
518                    &CODE_SIZE
519                )
520            }
521        }
522
523        #item_impl
524
525        #ext_trait
526
527        /// FFI code generated by procedural macro
528        #[expect(clippy::wildcard_imports, reason = "Macro-generated")]
529        pub mod ffi {
530            use super::*;
531
532            #( #guest_ffis )*
533        }
534    })
535}
536
537fn process_fn_definition(
538    trait_name: &Ident,
539    trait_item_fn: &mut TraitItemFn,
540    contract_details: &mut ContractDetails,
541) -> Result<MethodOutput, Error> {
542    let supported_attrs = HashMap::<_, fn(_, _, _, _) -> _>::from_iter([
543        (format_ident!("update"), process_update_fn_definition as _),
544        (format_ident!("view"), process_view_fn_definition as _),
545    ]);
546    let mut attrs = trait_item_fn.attrs.extract_if(.., |attr| match &attr.meta {
547        Meta::Path(path) => {
548            path.leading_colon.is_none()
549                && path.segments.len() == 1
550                && supported_attrs.contains_key(&path.segments[0].ident)
551        }
552        Meta::List(_meta_list) => false,
553        Meta::NameValue(_meta_name_value) => false,
554    });
555
556    let Some(attr) = attrs.next() else {
557        drop(attrs);
558
559        // Return an unmodified original if no recognized arguments are present
560        return Ok(MethodOutput::default());
561    };
562
563    if let Some(next_attr) = attrs.take(1).next() {
564        return Err(Error::new(
565            next_attr.span(),
566            format!(
567                "The method `{}` can only have one of `#[update]` or `#[view]` attributes specified",
568                trait_item_fn.sig.ident
569            ),
570        ));
571    }
572
573    // Make sure the method doesn't have customized ABI
574    if let Some(abi) = &trait_item_fn.sig.abi {
575        return Err(Error::new(
576            abi.span(),
577            format!(
578                "The method `{}` with `#[{}]` attribute must have default ABI",
579                trait_item_fn.sig.ident,
580                attr.meta.path().segments[0].ident
581            ),
582        ));
583    }
584
585    if trait_item_fn.default.is_some() {
586        return Err(Error::new(
587            trait_item_fn.span(),
588            "`#[contract]` does not support `#[update]` or `#[view]` methods with default implementation \
589            in trait definition",
590        ));
591    }
592
593    let processor = supported_attrs
594        .get(&attr.path().segments[0].ident)
595        .expect("Matched above to be one of the supported attributes; qed");
596    processor(
597        trait_name,
598        &mut trait_item_fn.sig,
599        trait_item_fn.attrs.as_slice(),
600        contract_details,
601    )
602}
603
604fn process_fn(
605    struct_name: Type,
606    trait_name: Option<&Ident>,
607    impl_item_fn: &mut ImplItemFn,
608    contract_details: &mut ContractDetails,
609) -> Result<MethodOutput, Error> {
610    let supported_attrs = HashMap::<_, fn(_, _, _, _, _) -> _>::from_iter([
611        (format_ident!("init"), process_init_fn as _),
612        (format_ident!("update"), process_update_fn as _),
613        (format_ident!("view"), process_view_fn as _),
614    ]);
615    let mut attrs = impl_item_fn.attrs.extract_if(.., |attr| match &attr.meta {
616        Meta::Path(path) => {
617            path.leading_colon.is_none()
618                && path.segments.len() == 1
619                && supported_attrs.contains_key(&path.segments[0].ident)
620        }
621        Meta::List(_meta_list) => false,
622        Meta::NameValue(_meta_name_value) => false,
623    });
624
625    let Some(attr) = attrs.next() else {
626        drop(attrs);
627
628        // Return an unmodified original if no recognized arguments are present
629        return Ok(MethodOutput::default());
630    };
631
632    if let Some(next_attr) = attrs.take(1).next() {
633        return Err(Error::new(
634            next_attr.span(),
635            format!(
636                "The method `{}` can only have one of `#[init]`, `#[update]` or `#[view]` attributes specified",
637                impl_item_fn.sig.ident
638            ),
639        ));
640    }
641
642    // Make sure the method is public if not a trait impl
643    if !(matches!(impl_item_fn.vis, Visibility::Public(_)) || trait_name.is_some()) {
644        return Err(Error::new(
645            impl_item_fn.sig.span(),
646            format!(
647                "The method `{}` with `#[{}]` attribute must be public",
648                impl_item_fn.sig.ident,
649                attr.meta.path().segments[0].ident
650            ),
651        ));
652    }
653
654    // Make sure the method doesn't have customized ABI
655    if let Some(abi) = &impl_item_fn.sig.abi {
656        return Err(Error::new(
657            abi.span(),
658            format!(
659                "The method `{}` with `#[{}]` attribute must have default ABI",
660                impl_item_fn.sig.ident,
661                attr.meta.path().segments[0].ident
662            ),
663        ));
664    }
665
666    let processor = supported_attrs
667        .get(&attr.path().segments[0].ident)
668        .expect("Matched above to be one of the supported attributes; qed");
669    processor(
670        struct_name,
671        trait_name,
672        &mut impl_item_fn.sig,
673        impl_item_fn.attrs.as_slice(),
674        contract_details,
675    )
676}
677
678fn generate_extension_trait(
679    ident: &Ident,
680    trait_ext_components: &[ExtTraitComponents],
681) -> TokenStream {
682    let trait_name = format_ident!("{ident}Ext");
683    let trait_doc = format!(
684        "Extension trait that provides helper methods for calling [`{ident}`]'s methods on \
685        [`Env`](::ab_contracts_macros::__private::Env) for convenience purposes"
686    );
687    let definitions = trait_ext_components
688        .iter()
689        .map(|components| &components.definition);
690    let impls = trait_ext_components
691        .iter()
692        .map(|components| &components.r#impl);
693
694    quote! {
695        #[expect(clippy::wildcard_imports, reason = "Macro-generated")]
696        use ffi::*;
697
698        #[doc = #trait_doc]
699        #[automatically_derived]
700        #[expect(clippy::allow_attributes, reason = "Attribute below")]
701        #[allow(clippy::trivially_copy_pass_by_ref, reason = "API requirement")]
702        pub trait #trait_name {
703            #( #definitions )*
704        }
705
706        #[automatically_derived]
707        impl #trait_name for ::ab_contracts_macros::__private::Env<'_> {
708            #( #impls )*
709        }
710    }
711}