ab_contracts_macros_impl/contract/
view.rs

1use crate::contract::method::{MethodDetails, MethodType};
2use crate::contract::{ContractDetails, Method, MethodOutput};
3use proc_macro2::Ident;
4use quote::format_ident;
5use std::collections::HashMap;
6use syn::spanned::Spanned;
7use syn::{Attribute, Error, FnArg, Meta, Signature, Type, parse_quote};
8
9pub(super) fn process_view_fn(
10    self_type: Type,
11    trait_name: Option<&Ident>,
12    fn_sig: &mut Signature,
13    fn_attrs: &[Attribute],
14    contract_details: &mut ContractDetails,
15) -> Result<MethodOutput, Error> {
16    let mut methods_details = MethodDetails::new(MethodType::View, self_type);
17
18    for input in &mut fn_sig.inputs {
19        let input_span = input.span();
20        // TODO: Moving this outside of the loop causes confusing lifetime issues
21        // TODO: Simplify `-> Result<(), Error>` to `-> _` once https://github.com/rust-lang/rust/issues/135864 is resolved
22        let supported_attrs = HashMap::<_, fn(_, _, _) -> Result<(), Error>>::from_iter([
23            (format_ident!("env"), MethodDetails::process_env_arg_ro as _),
24            (
25                format_ident!("slot"),
26                MethodDetails::process_slot_arg_ro as _,
27            ),
28            (
29                format_ident!("input"),
30                MethodDetails::process_input_arg as _,
31            ),
32            (
33                format_ident!("output"),
34                MethodDetails::process_output_arg as _,
35            ),
36        ]);
37
38        match input {
39            FnArg::Receiver(receiver) => {
40                if receiver.reference.is_none() {
41                    return Err(Error::new(
42                        fn_sig.span(),
43                        "`#[view]` can't consume `Self` or `&mut self`, use `&self` instead",
44                    ));
45                }
46
47                methods_details.process_state_arg_ro(input_span, &receiver.ty)?;
48            }
49            FnArg::Typed(pat_type) => {
50                let mut attrs = pat_type.attrs.extract_if(.., |attr| match &attr.meta {
51                    Meta::Path(path) => {
52                        path.leading_colon.is_none()
53                            && path.segments.len() == 1
54                            && supported_attrs.contains_key(&path.segments[0].ident)
55                    }
56                    Meta::List(_meta_list) => false,
57                    Meta::NameValue(_meta_name_value) => false,
58                });
59
60                let Some(attr) = attrs.next() else {
61                    return Err(Error::new(
62                        input_span,
63                        "Each `#[view]` argument (except `&self`) must be annotated with \
64                        exactly one of: `#[env]`, `#[slot]`, `#[input]` or `#[output]`, in that \
65                        order",
66                    ));
67                };
68
69                if let Some(next_attr) = attrs.take(1).next() {
70                    return Err(Error::new(
71                        next_attr.span(),
72                        "Each `#[view]` argument (except `&self`) must be annotated with \
73                        exactly one of: `#[env]`, `#[slot]`, `#[input]` or `#[output]`, in that \
74                        order",
75                    ));
76                }
77
78                let processor = supported_attrs
79                    .get(&attr.path().segments[0].ident)
80                    .expect("Matched above to be one of the supported attributes; qed");
81
82                processor(&mut methods_details, input_span, &*pat_type)?;
83            }
84        }
85    }
86
87    methods_details.process_return(&fn_sig.output)?;
88
89    let guest_ffi = methods_details.generate_guest_ffi(fn_sig, trait_name)?;
90    let trait_ext_components =
91        methods_details.generate_trait_ext_components(fn_sig, fn_attrs, trait_name)?;
92
93    contract_details.methods.push(Method {
94        original_ident: fn_sig.ident.clone(),
95        methods_details,
96    });
97
98    Ok(MethodOutput {
99        guest_ffi,
100        trait_ext_components,
101    })
102}
103
104pub(super) fn process_view_fn_definition(
105    trait_name: &Ident,
106    fn_sig: &mut Signature,
107    fn_attrs: &[Attribute],
108    contract_details: &mut ContractDetails,
109) -> Result<MethodOutput, Error> {
110    let mut methods_details =
111        MethodDetails::new(MethodType::View, Type::Path(parse_quote! { #trait_name }));
112
113    for input in &mut fn_sig.inputs {
114        let input_span = input.span();
115        // TODO: Moving this outside of the loop causes confusing lifetime issues
116        // TODO: Simplify `-> Result<(), Error>` to `-> _` once https://github.com/rust-lang/rust/issues/135864 is resolved
117        let supported_attrs = HashMap::<_, fn(_, _, _) -> Result<(), Error>>::from_iter([
118            (format_ident!("env"), MethodDetails::process_env_arg_ro as _),
119            (
120                format_ident!("input"),
121                MethodDetails::process_input_arg as _,
122            ),
123            (
124                format_ident!("output"),
125                MethodDetails::process_output_arg as _,
126            ),
127        ]);
128
129        match input {
130            FnArg::Receiver(_receiver) => {
131                return Err(Error::new(
132                    fn_sig.span(),
133                    "`#[view]` in trait definition can't access state through `Self` directly, \
134                    call implementation method through `Env` instead if necessary",
135                ));
136            }
137            FnArg::Typed(pat_type) => {
138                let mut attrs = pat_type.attrs.extract_if(.., |attr| match &attr.meta {
139                    Meta::Path(path) => {
140                        path.leading_colon.is_none()
141                            && path.segments.len() == 1
142                            && supported_attrs.contains_key(&path.segments[0].ident)
143                    }
144                    Meta::List(_meta_list) => false,
145                    Meta::NameValue(_meta_name_value) => false,
146                });
147
148                let Some(attr) = attrs.next() else {
149                    return Err(Error::new(
150                        input_span,
151                        "Each `#[view]` argument must be annotated with exactly one of: \
152                        `#[env]`, `#[input]` or `#[output]`, in that order",
153                    ));
154                };
155
156                if let Some(next_attr) = attrs.take(1).next() {
157                    return Err(Error::new(
158                        next_attr.span(),
159                        "Each `#[view]` argument must be annotated with exactly one of: \
160                        `#[env]`, `#[input]` or `#[output]`, in that order",
161                    ));
162                }
163
164                let processor = supported_attrs
165                    .get(&attr.path().segments[0].ident)
166                    .expect("Matched above to be one of the supported attributes; qed");
167
168                processor(&mut methods_details, input_span, &*pat_type)?;
169            }
170        }
171    }
172
173    methods_details.process_return(&fn_sig.output)?;
174
175    let guest_ffi = methods_details.generate_guest_trait_ffi(fn_sig, Some(trait_name))?;
176    let trait_ext_components =
177        methods_details.generate_trait_ext_components(fn_sig, fn_attrs, Some(trait_name))?;
178
179    contract_details.methods.push(Method {
180        original_ident: fn_sig.ident.clone(),
181        methods_details,
182    });
183
184    Ok(MethodOutput {
185        guest_ffi,
186        trait_ext_components,
187    })
188}