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