1use ink_codegen::generate_code;
16use ink_ir::{
17 ast,
18 format_err_spanned,
19 utils::duplicate_config_err,
20};
21use ink_primitives::abi::Abi;
22use proc_macro2::TokenStream as TokenStream2;
23use quote::{
24 format_ident,
25 quote,
26 quote_spanned,
27};
28
29pub fn analyze(config: TokenStream2, input: TokenStream2) -> TokenStream2 {
30 match analyze_or_err(config, input) {
31 Ok(tokens) => tokens,
32 Err(err) => err.to_compile_error(),
33 }
34}
35
36pub fn analyze_or_err(
37 config: TokenStream2,
38 input: TokenStream2,
39) -> syn::Result<TokenStream2> {
40 let config = Config::parse(config)?;
42 let trait_def = ink_ir::InkTraitDefinition::new(TokenStream2::new(), input)?;
44 let trait_def_impl = generate_code((&trait_def, config.abi));
45
46 let span = trait_def.item().span();
47 let trait_name = trait_def.item().ident();
48 let contract_ref_name = format_ident!("{trait_name}Ref");
49 let abi_ty = match config.abi.unwrap_or({
50 #[cfg(not(ink_abi = "sol"))]
51 {
52 Abi::Ink
53 }
54
55 #[cfg(ink_abi = "sol")]
56 {
57 Abi::Sol
58 }
59 }) {
60 Abi::Ink => quote!(::ink::abi::Ink),
61 Abi::Sol => quote!(::ink::abi::Sol),
62 };
63 let env = config
64 .env
65 .unwrap_or_else(|| syn::parse_quote! { ::ink::env::DefaultEnvironment });
66 Ok(quote_spanned!(span =>
67 #trait_def_impl
69
70 type #contract_ref_name =
72 <<::ink::reflect::TraitDefinitionRegistry<#env> as #trait_name>
73 ::__ink_TraitInfo as ::ink::codegen::TraitCallForwarder>::Forwarder<#abi_ty>;
74 ))
75}
76
77#[derive(Debug, PartialEq, Eq, Default)]
79struct Config {
80 abi: Option<Abi>,
82 env: Option<syn::Path>,
88}
89
90impl Config {
91 fn parse(config: TokenStream2) -> syn::Result<Config> {
93 let args = syn::parse2::<ast::AttributeArgs>(config)?;
94 let mut abi_info: Option<(Abi, ast::MetaNameValue)> = None;
95 let mut env_info: Option<(syn::Path, ast::MetaNameValue)> = None;
96 for arg in args.into_iter() {
97 if arg.name().is_ident("abi") {
98 if let Some((_, ast)) = abi_info {
99 return Err(duplicate_config_err(ast, arg, "abi", "contract"));
100 }
101 let arg_info = arg
102 .name_value()
103 .zip(arg.value().and_then(ast::MetaValue::to_string));
104 if let Some((name_value, abi_str)) = arg_info {
105 let abi = match abi_str.as_str() {
106 "ink" => Abi::Ink,
107 "sol" => Abi::Sol,
108 _ => {
109 return Err(format_err_spanned!(
110 arg,
111 "expected one of `ink` or `sol` for `abi` ink! configuration argument",
112 ))
113 }
114 };
115 abi_info = Some((abi, name_value.clone()));
116 } else {
117 return Err(format_err_spanned!(
118 arg,
119 "expected a string literal value for `abi` ink! configuration argument",
120 ));
121 }
122 } else if arg.name().is_ident("env") {
123 if let Some((_, ast)) = env_info {
124 return Err(duplicate_config_err(ast, arg, "env", "contract"));
125 }
126 let arg_info = arg
127 .name_value()
128 .zip(arg.value().and_then(ast::MetaValue::as_path));
129 if let Some((name_value, path)) = arg_info {
130 env_info = Some((path.clone(), name_value.clone()))
131 } else {
132 return Err(format_err_spanned!(
133 arg,
134 "expected a path value for `env` ink! configuration argument",
135 ));
136 }
137 } else {
138 return Err(format_err_spanned!(
139 arg,
140 "encountered unknown or unsupported ink! configuration argument",
141 ));
142 }
143 }
144 Ok(Config {
145 abi: abi_info.map(|(abi, _)| abi),
146 env: env_info.map(|(path, _)| path),
147 })
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 fn assert_try_from(input: TokenStream2, expected: Result<Config, &'static str>) {
158 assert_eq!(
159 Config::parse(input).map_err(|err| err.to_string()),
160 expected.map_err(ToString::to_string),
161 );
162 }
163
164 #[test]
165 fn empty_config_works() {
166 assert_try_from(quote! {}, Ok(Config::default()))
167 }
168
169 #[test]
170 fn abi_works() {
171 assert_try_from(
172 quote! {
173 abi = "ink"
174 },
175 Ok(Config {
176 abi: Some(Abi::Ink),
177 env: None,
178 }),
179 );
180 assert_try_from(
181 quote! {
182 abi = "sol"
183 },
184 Ok(Config {
185 abi: Some(Abi::Sol),
186 env: None,
187 }),
188 );
189 }
190
191 #[test]
192 fn abi_invalid_value_fails() {
193 assert_try_from(
194 quote! { abi = "move" },
195 Err("expected one of `ink` or `sol` for `abi` ink! configuration argument"),
196 );
197 assert_try_from(
198 quote! { abi = 1u8 },
199 Err("expected a string literal value for `abi` ink! configuration argument"),
200 );
201 }
202
203 #[test]
204 fn abi_missing_value_fails() {
205 assert_try_from(
206 syn::parse_quote! { abi },
207 Err("expected a string literal value for `abi` ink! configuration argument"),
208 );
209 }
210
211 #[test]
212 fn env_works() {
213 assert_try_from(
214 quote! {
215 env = ::my::env::Types
216 },
217 Ok(Config {
218 abi: None,
219 env: Some(syn::parse_quote! { ::my::env::Types }),
220 }),
221 )
222 }
223
224 #[test]
225 fn env_invalid_value_fails() {
226 assert_try_from(
227 quote! { env = "invalid" },
228 Err("expected a path value for `env` ink! configuration argument"),
229 );
230 }
231
232 #[test]
233 fn env_missing_value_fails() {
234 assert_try_from(
235 quote! { env },
236 Err("expected a path value for `env` ink! configuration argument"),
237 );
238 }
239
240 #[test]
241 fn unknown_arg_fails() {
242 assert_try_from(
243 quote! { unknown = argument },
244 Err("encountered unknown or unsupported ink! configuration argument"),
245 );
246 }
247
248 #[test]
249 fn duplicate_args_fails() {
250 assert_try_from(
251 quote! {
252 abi = "ink",
253 abi = "sol",
254 },
255 Err("encountered duplicate ink! contract `abi` configuration argument"),
256 );
257 assert_try_from(
258 quote! {
259 env = ::my::env::Types,
260 env = ::my::other::env::Types,
261 },
262 Err("encountered duplicate ink! contract `env` configuration argument"),
263 );
264 }
265}