ab_contracts_macros_impl/contract/
view.rs1use 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 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 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}