ink_codegen/generator/
metadata.rs1use crate::GenerateCode;
16use ::core::iter;
17use derive_more::From;
18use ir::{
19 Callable as _,
20 HexLiteral,
21 IsDocAttribute,
22};
23use proc_macro2::{
24 Ident,
25 TokenStream as TokenStream2,
26};
27use quote::{
28 quote,
29 quote_spanned,
30};
31use syn::{
32 parse_quote,
33 spanned::Spanned as _,
34};
35
36#[derive(From)]
38pub struct Metadata<'a> {
39 contract: &'a ir::Contract,
41}
42impl_as_ref_for_generator!(Metadata);
43
44impl GenerateCode for Metadata<'_> {
45 fn generate_code(&self) -> TokenStream2 {
46 let contract = self.generate_contract();
47 let layout = self.generate_layout();
48
49 quote! {
50 #[cfg(feature = "std")]
51 #[cfg(not(feature = "ink-as-dependency"))]
52 #[cfg(not(ink_abi = "sol"))]
53 const _: () = {
54 #[unsafe(no_mangle)]
55 pub fn __ink_generate_metadata() -> ::ink::metadata::InkProject {
56 let layout = #layout;
57 ::ink::metadata::layout::ValidateLayout::validate(&layout).unwrap_or_else(|error| {
58 ::core::panic!("metadata ink! generation failed: {}", error)
59 });
60 ::ink::metadata::InkProject::new(layout, #contract)
61 }
62 };
63 }
64 }
65}
66
67impl Metadata<'_> {
68 fn generate_layout(&self) -> TokenStream2 {
69 let storage_span = self.contract.module().storage().span();
70 let storage_ident = self.contract.module().storage().ident();
71 let key = quote! { <#storage_ident as ::ink::storage::traits::StorageKey>::KEY };
72
73 let layout_key = quote! {
74 <::ink::metadata::layout::LayoutKey
75 as ::core::convert::From<::ink::primitives::Key>>::from(#key)
76 };
77 quote_spanned!(storage_span=>
78 ::ink::metadata::layout::Layout::Root(::ink::metadata::layout::RootLayout::new(
81 #layout_key,
82 <#storage_ident as ::ink::storage::traits::StorageLayout>::layout(
83 &#key,
84 ),
85 ::ink::scale_info::meta_type::<#storage_ident>(),
86 ))
87 )
88 }
89
90 fn generate_contract(&self) -> TokenStream2 {
91 let constructors = self.generate_constructors();
92 let messages = self.generate_messages();
93 let docs = self
94 .contract
95 .module()
96 .attrs()
97 .iter()
98 .filter_map(|attr| attr.extract_docs());
99 let error_ty = syn::parse_quote! {
100 ::ink::LangError
101 };
102 let error = generate_type_spec(&error_ty);
103 let environment = self.generate_environment();
104 quote! {
105 ::ink::metadata::ContractSpec::new()
106 .constructors([
107 #( #constructors ),*
108 ])
109 .messages([
110 #( #messages ),*
111 ])
112 .events(
113 ::ink::collect_events()
114 )
115 .docs([
116 #( #docs ),*
117 ])
118 .lang_error(
119 #error
120 )
121 .environment(
122 #environment
123 )
124 .done()
125 }
126 }
127
128 #[allow(clippy::redundant_closure)] fn generate_constructors(&self) -> impl Iterator<Item = TokenStream2> + '_ {
131 self.contract
132 .module()
133 .impls()
134 .flat_map(|item_impl| item_impl.iter_constructors())
135 .map(|constructor| self.generate_constructor(constructor))
136 }
137
138 fn generate_constructor(
140 &self,
141 constructor: ir::CallableWithSelector<ir::Constructor>,
142 ) -> TokenStream2 {
143 let span = constructor.span();
144 let docs = constructor
145 .attrs()
146 .iter()
147 .filter_map(|attr| attr.extract_docs());
148 let selector_bytes = constructor.composed_selector().hex_lits();
149 let selector_id = constructor.composed_selector().into_be_u32();
150 let is_payable = constructor.is_payable();
151 let is_default = constructor.is_default();
152 let constructor = constructor.callable();
153 let name = constructor.normalized_name();
154 let args = constructor.inputs().map(Self::generate_dispatch_argument);
155 let storage_ident = self.contract.module().storage().ident();
156 let ret_ty = Self::generate_constructor_return_type(storage_ident, selector_id);
157 let cfg_attrs = constructor.get_cfg_attrs(span);
158 quote_spanned!(span=>
159 #( #cfg_attrs )*
160 ::ink::metadata::ConstructorSpec::from_label(#name)
161 .selector([
162 #( #selector_bytes ),*
163 ])
164 .args([
165 #( #args ),*
166 ])
167 .payable(#is_payable)
168 .default(#is_default)
169 .returns(#ret_ty)
170 .docs([
171 #( #docs ),*
172 ])
173 .done()
174 )
175 }
176
177 fn generate_dispatch_argument(pat_type: &syn::PatType) -> TokenStream2 {
179 let ident = match &*pat_type.pat {
180 syn::Pat::Ident(ident) => &ident.ident,
181 _ => unreachable!("encountered ink! dispatch input with missing identifier"),
182 };
183 let type_spec = generate_type_spec(&pat_type.ty);
184 quote! {
185 ::ink::metadata::MessageParamSpec::new(::core::stringify!(#ident))
186 .of_type(#type_spec)
187 .done()
188 }
189 }
190 fn generate_messages(&self) -> Vec<TokenStream2> {
192 let mut messages = Vec::new();
193 let inherent_messages = self.generate_inherent_messages();
194 let trait_messages = self.generate_trait_messages();
195 messages.extend(inherent_messages);
196 messages.extend(trait_messages);
197 messages
198 }
199
200 fn generate_inherent_messages(&self) -> Vec<TokenStream2> {
202 self.contract
203 .module()
204 .impls()
205 .filter(|item_impl| item_impl.trait_path().is_none())
206 .flat_map(|item_impl| item_impl.iter_messages())
207 .map(|message| {
208 let span = message.span();
209 let docs = message
210 .attrs()
211 .iter()
212 .filter_map(|attr| attr.extract_docs());
213 let selector_bytes = message.composed_selector().hex_lits();
214 let is_payable = message.is_payable();
215 let is_default = message.is_default();
216 let message = message.callable();
217 let mutates = message.receiver().is_ref_mut();
218 let name = message.normalized_name();
219 let args = message.inputs().map(Self::generate_dispatch_argument);
220 let cfg_attrs = message.get_cfg_attrs(span);
221 let ret_ty =
222 Self::generate_message_return_type(&message.wrapped_output());
223 quote_spanned!(span =>
224 #( #cfg_attrs )*
225 ::ink::metadata::MessageSpec::from_label(#name)
226 .selector([
227 #( #selector_bytes ),*
228 ])
229 .args([
230 #( #args ),*
231 ])
232 .returns(#ret_ty)
233 .mutates(#mutates)
234 .payable(#is_payable)
235 .default(#is_default)
236 .docs([
237 #( #docs ),*
238 ])
239 .done()
240 )
241 })
242 .collect()
243 }
244
245 fn generate_trait_messages(&self) -> Vec<TokenStream2> {
247 let storage_ident = self.contract.module().storage().ident();
248 self.contract
249 .module()
250 .impls()
251 .filter_map(|item_impl| {
252 item_impl
253 .trait_path()
254 .map(|trait_path| {
255 let trait_ident = item_impl.trait_ident().expect(
256 "must have an ink! trait identifier if it is an ink! trait implementation"
257 );
258 iter::repeat((trait_ident, trait_path)).zip(item_impl.iter_messages())
259 })
260 })
261 .flatten()
262 .map(|((trait_ident, trait_path), message)| {
263 let message_span = message.span();
264 let message_name = message
265 .normalized_name();
266 let message_docs = message
267 .attrs()
268 .iter()
269 .filter_map(|attr| attr.extract_docs());
270 let message_args = message
271 .inputs()
272 .map(Self::generate_dispatch_argument);
273 let cfg_attrs = message.get_cfg_attrs(message_span);
274 let mutates = message.receiver().is_ref_mut();
275 let local_id = message.local_id().hex_padded_suffixed();
276 let is_payable = quote! {{
277 <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
278 as #trait_path>::__ink_TraitInfo
279 as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE
280 }};
281 let selector = quote! {{
282 <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
283 as #trait_path>::__ink_TraitInfo
284 as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR
285 }};
286 let ret_ty = Self::generate_message_return_type(&message.wrapped_output());
287 let label = [trait_ident.to_string(), message_name].join("::");
288 quote_spanned!(message_span=>
289 #( #cfg_attrs )*
290 ::ink::metadata::MessageSpec::from_label(#label)
291 .selector(#selector)
292 .args([
293 #( #message_args ),*
294 ])
295 .returns(#ret_ty)
296 .mutates(#mutates)
297 .payable(#is_payable)
298 .docs([
299 #( #message_docs ),*
300 ])
301 .done()
302 )
303 })
304 .collect()
305 }
306
307 fn generate_message_return_type(ret_ty: &syn::Type) -> TokenStream2 {
309 let type_spec = generate_type_spec(ret_ty);
310 quote! {
311 ::ink::metadata::ReturnTypeSpec::new(#type_spec)
312 }
313 }
314
315 fn generate_constructor_return_type(
317 storage_ident: &Ident,
318 selector_id: u32,
319 ) -> TokenStream2 {
320 let span = storage_ident.span();
321 let constructor_info = quote_spanned!(span =>
322 < #storage_ident as ::ink::reflect::DispatchableConstructorInfo<#selector_id>>
323 );
324
325 quote_spanned!(span=>
326 ::ink::metadata::ReturnTypeSpec::new(if #constructor_info::IS_RESULT {
327 ::ink::metadata::TypeSpec::with_name_str::<
328 ::ink::ConstructorResult<::core::result::Result<(), #constructor_info::Error>>,
329 >("ink_primitives::ConstructorResult")
330 } else {
331 ::ink::metadata::TypeSpec::with_name_str::<
332 ::ink::ConstructorResult<()>,
333 >("ink_primitives::ConstructorResult")
334 })
335 )
336 }
337
338 fn generate_environment(&self) -> TokenStream2 {
339 let span = self.contract.module().span();
340
341 let account_id: syn::Type = parse_quote!(AccountId);
342 let balance: syn::Type = parse_quote!(Balance);
343 let hash: syn::Type = parse_quote!(Hash);
344 let timestamp: syn::Type = parse_quote!(Timestamp);
345 let block_number: syn::Type = parse_quote!(BlockNumber);
346
347 let account_id = generate_type_spec(&account_id);
348 let balance = generate_type_spec(&balance);
349 let hash = generate_type_spec(&hash);
350 let timestamp = generate_type_spec(×tamp);
351 let block_number = generate_type_spec(&block_number);
352 let buffer_size_const = quote!(::ink::env::BUFFER_SIZE);
353 quote_spanned!(span=>
354 ::ink::metadata::EnvironmentSpec::new()
355 .account_id(#account_id)
356 .balance(#balance)
357 .hash(#hash)
358 .timestamp(#timestamp)
359 .block_number(#block_number)
360 .native_to_eth_ratio(NATIVE_TO_ETH_RATIO)
361 .static_buffer_size(#buffer_size_const)
362 .done()
363 )
364 }
365}
366
367pub fn generate_type_spec(ty: &syn::Type) -> TokenStream2 {
369 fn without_display_name(ty: &syn::Type) -> TokenStream2 {
370 quote! { ::ink::metadata::TypeSpec::of_type::<#ty>() }
371 }
372
373 if let syn::Type::Path(type_path) = ty {
374 if type_path.qself.is_some() {
375 return without_display_name(ty)
376 }
377 let path = &type_path.path;
378 if path.segments.is_empty() {
379 return without_display_name(ty)
380 }
381 let segs = path
382 .segments
383 .iter()
384 .map(|seg| &seg.ident)
385 .collect::<Vec<_>>();
386 quote! {
387 ::ink::metadata::TypeSpec::with_name_segs::<#ty, _>(
388 ::core::iter::Iterator::map(
389 ::core::iter::IntoIterator::into_iter([ #( ::core::stringify!(#segs) ),* ]),
390 ::core::convert::AsRef::as_ref
391 )
392 )
393 }
394 } else {
395 without_display_name(ty)
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 fn extract_doc_attributes(attrs: &[syn::Attribute]) -> Vec<String> {
405 attrs
406 .iter()
407 .filter_map(|attr| attr.extract_docs())
408 .collect()
409 }
410
411 #[test]
412 fn extract_doc_comments_works() {
413 assert_eq!(
414 extract_doc_attributes(&[syn::parse_quote!( #[doc = r"content"] )]),
415 vec!["content".to_string()],
416 );
417 assert_eq!(
418 extract_doc_attributes(&[syn::parse_quote!(
419 )]),
421 vec![" content".to_string()],
422 );
423 assert_eq!(
424 extract_doc_attributes(&[syn::parse_quote!(
425 )]),
431 vec![
432 r"
433 * Multi-line comments
434 * may span many,
435 * many lines
436 "
437 .to_string()
438 ],
439 );
440 assert_eq!(
441 extract_doc_attributes(&[
442 syn::parse_quote!(
443 ),
445 syn::parse_quote!(
446 ),
448 syn::parse_quote!(
449 ),
451 syn::parse_quote!(
452 ),
454 ]),
455 vec![
456 " multiple".to_string(),
457 " single".to_string(),
458 " line".to_string(),
459 " comments".to_string(),
460 ],
461 );
462 assert_eq!(
463 extract_doc_attributes(&[
464 syn::parse_quote!( #[doc = r"a"] ),
465 syn::parse_quote!( #[non_doc] ),
466 syn::parse_quote!( #[doc = r"b"] ),
467 syn::parse_quote!( #[derive(NonDoc)] ),
468 syn::parse_quote!( #[doc = r"c"] ),
469 syn::parse_quote!( #[docker = false] ),
470 syn::parse_quote!( #[doc = r"d"] ),
471 syn::parse_quote!( #[doc(Nope)] ),
472 syn::parse_quote!( #[doc = r"e"] ),
473 ]),
474 vec![
475 "a".to_string(),
476 "b".to_string(),
477 "c".to_string(),
478 "d".to_string(),
479 "e".to_string(),
480 ],
481 )
482 }
483}