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 #[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
154 .name()
155 .map(ToString::to_string)
156 .unwrap_or_else(|| constructor.ident().to_string());
157 let args = constructor.inputs().map(Self::generate_dispatch_argument);
158 let storage_ident = self.contract.module().storage().ident();
159 let ret_ty = Self::generate_constructor_return_type(storage_ident, selector_id);
160 let cfg_attrs = constructor.get_cfg_attrs(span);
161 quote_spanned!(span=>
162 #( #cfg_attrs )*
163 ::ink::metadata::ConstructorSpec::from_label(#name)
164 .selector([
165 #( #selector_bytes ),*
166 ])
167 .args([
168 #( #args ),*
169 ])
170 .payable(#is_payable)
171 .default(#is_default)
172 .returns(#ret_ty)
173 .docs([
174 #( #docs ),*
175 ])
176 .done()
177 )
178 }
179
180 fn generate_dispatch_argument(pat_type: &syn::PatType) -> TokenStream2 {
182 let ident = match &*pat_type.pat {
183 syn::Pat::Ident(ident) => &ident.ident,
184 _ => unreachable!("encountered ink! dispatch input with missing identifier"),
185 };
186 let type_spec = generate_type_spec(&pat_type.ty);
187 quote! {
188 ::ink::metadata::MessageParamSpec::new(::core::stringify!(#ident))
189 .of_type(#type_spec)
190 .done()
191 }
192 }
193 fn generate_messages(&self) -> Vec<TokenStream2> {
195 let mut messages = Vec::new();
196 let inherent_messages = self.generate_inherent_messages();
197 let trait_messages = self.generate_trait_messages();
198 messages.extend(inherent_messages);
199 messages.extend(trait_messages);
200 messages
201 }
202
203 fn generate_inherent_messages(&self) -> Vec<TokenStream2> {
205 self.contract
206 .module()
207 .impls()
208 .filter(|item_impl| item_impl.trait_path().is_none())
209 .flat_map(|item_impl| item_impl.iter_messages())
210 .map(|message| {
211 let span = message.span();
212 let docs = message
213 .attrs()
214 .iter()
215 .filter_map(|attr| attr.extract_docs());
216 let selector_bytes = message.composed_selector().hex_lits();
217 let is_payable = message.is_payable();
218 let is_default = message.is_default();
219 let message = message.callable();
220 let mutates = message.receiver().is_ref_mut();
221 let name = message
222 .name()
223 .map(ToString::to_string)
224 .unwrap_or_else(|| message.ident().to_string());
225 let args = message.inputs().map(Self::generate_dispatch_argument);
226 let cfg_attrs = message.get_cfg_attrs(span);
227 let ret_ty =
228 Self::generate_message_return_type(&message.wrapped_output());
229 quote_spanned!(span =>
230 #( #cfg_attrs )*
231 ::ink::metadata::MessageSpec::from_label(#name)
232 .selector([
233 #( #selector_bytes ),*
234 ])
235 .args([
236 #( #args ),*
237 ])
238 .returns(#ret_ty)
239 .mutates(#mutates)
240 .payable(#is_payable)
241 .default(#is_default)
242 .docs([
243 #( #docs ),*
244 ])
245 .done()
246 )
247 })
248 .collect()
249 }
250
251 fn generate_trait_messages(&self) -> Vec<TokenStream2> {
253 let storage_ident = self.contract.module().storage().ident();
254 self.contract
255 .module()
256 .impls()
257 .filter_map(|item_impl| {
258 item_impl
259 .trait_path()
260 .map(|trait_path| {
261 let trait_ident = item_impl.trait_ident().expect(
262 "must have an ink! trait identifier if it is an ink! trait implementation"
263 );
264 iter::repeat((trait_ident, trait_path)).zip(item_impl.iter_messages())
265 })
266 })
267 .flatten()
268 .map(|((trait_ident, trait_path), message)| {
269 let message_span = message.span();
270 let message_name = message
271 .name()
272 .map(ToString::to_string)
273 .unwrap_or_else(|| message.ident().to_string());
274 let message_docs = message
275 .attrs()
276 .iter()
277 .filter_map(|attr| attr.extract_docs());
278 let message_args = message
279 .inputs()
280 .map(Self::generate_dispatch_argument);
281 let cfg_attrs = message.get_cfg_attrs(message_span);
282 let mutates = message.receiver().is_ref_mut();
283 let local_id = message.local_id().hex_padded_suffixed();
284 let is_payable = quote! {{
285 <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
286 as #trait_path>::__ink_TraitInfo
287 as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE
288 }};
289 let selector = quote! {{
290 <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
291 as #trait_path>::__ink_TraitInfo
292 as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR
293 }};
294 let ret_ty = Self::generate_message_return_type(&message.wrapped_output());
295 let label = [trait_ident.to_string(), message_name].join("::");
296 quote_spanned!(message_span=>
297 #( #cfg_attrs )*
298 ::ink::metadata::MessageSpec::from_label(#label)
299 .selector(#selector)
300 .args([
301 #( #message_args ),*
302 ])
303 .returns(#ret_ty)
304 .mutates(#mutates)
305 .payable(#is_payable)
306 .docs([
307 #( #message_docs ),*
308 ])
309 .done()
310 )
311 })
312 .collect()
313 }
314
315 fn generate_message_return_type(ret_ty: &syn::Type) -> TokenStream2 {
317 let type_spec = generate_type_spec(ret_ty);
318 quote! {
319 ::ink::metadata::ReturnTypeSpec::new(#type_spec)
320 }
321 }
322
323 fn generate_constructor_return_type(
325 storage_ident: &Ident,
326 selector_id: u32,
327 ) -> TokenStream2 {
328 let span = storage_ident.span();
329 let constructor_info = quote_spanned!(span =>
330 < #storage_ident as ::ink::reflect::DispatchableConstructorInfo<#selector_id>>
331 );
332
333 quote_spanned!(span=>
334 ::ink::metadata::ReturnTypeSpec::new(if #constructor_info::IS_RESULT {
335 ::ink::metadata::TypeSpec::with_name_str::<
336 ::ink::ConstructorResult<::core::result::Result<(), #constructor_info::Error>>,
337 >("ink_primitives::ConstructorResult")
338 } else {
339 ::ink::metadata::TypeSpec::with_name_str::<
340 ::ink::ConstructorResult<()>,
341 >("ink_primitives::ConstructorResult")
342 })
343 )
344 }
345
346 fn generate_environment(&self) -> TokenStream2 {
347 let span = self.contract.module().span();
348
349 let account_id: syn::Type = parse_quote!(AccountId);
350 let balance: syn::Type = parse_quote!(Balance);
351 let hash: syn::Type = parse_quote!(Hash);
352 let timestamp: syn::Type = parse_quote!(Timestamp);
353 let block_number: syn::Type = parse_quote!(BlockNumber);
354 let chain_extension: syn::Type = parse_quote!(ChainExtension);
355
356 let account_id = generate_type_spec(&account_id);
357 let balance = generate_type_spec(&balance);
358 let hash = generate_type_spec(&hash);
359 let timestamp = generate_type_spec(×tamp);
360 let block_number = generate_type_spec(&block_number);
361 let chain_extension = generate_type_spec(&chain_extension);
362 let buffer_size_const = quote!(::ink::env::BUFFER_SIZE);
363 quote_spanned!(span=>
364 ::ink::metadata::EnvironmentSpec::new()
365 .account_id(#account_id)
366 .balance(#balance)
367 .hash(#hash)
368 .timestamp(#timestamp)
369 .block_number(#block_number)
370 .chain_extension(#chain_extension)
371 .max_event_topics(MAX_EVENT_TOPICS)
372 .static_buffer_size(#buffer_size_const)
373 .done()
374 )
375 }
376}
377
378pub fn generate_type_spec(ty: &syn::Type) -> TokenStream2 {
380 fn without_display_name(ty: &syn::Type) -> TokenStream2 {
381 quote! { ::ink::metadata::TypeSpec::of_type::<#ty>() }
382 }
383
384 if let syn::Type::Path(type_path) = ty {
385 if type_path.qself.is_some() {
386 return without_display_name(ty)
387 }
388 let path = &type_path.path;
389 if path.segments.is_empty() {
390 return without_display_name(ty)
391 }
392 let segs = path
393 .segments
394 .iter()
395 .map(|seg| &seg.ident)
396 .collect::<Vec<_>>();
397 quote! {
398 ::ink::metadata::TypeSpec::with_name_segs::<#ty, _>(
399 ::core::iter::Iterator::map(
400 ::core::iter::IntoIterator::into_iter([ #( ::core::stringify!(#segs) ),* ]),
401 ::core::convert::AsRef::as_ref
402 )
403 )
404 }
405 } else {
406 without_display_name(ty)
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 fn extract_doc_attributes(attrs: &[syn::Attribute]) -> Vec<String> {
416 attrs
417 .iter()
418 .filter_map(|attr| attr.extract_docs())
419 .collect()
420 }
421
422 #[test]
423 fn extract_doc_comments_works() {
424 assert_eq!(
425 extract_doc_attributes(&[syn::parse_quote!( #[doc = r"content"] )]),
426 vec!["content".to_string()],
427 );
428 assert_eq!(
429 extract_doc_attributes(&[syn::parse_quote!(
430 )]),
432 vec![" content".to_string()],
433 );
434 assert_eq!(
435 extract_doc_attributes(&[syn::parse_quote!(
436 )]),
442 vec![r"
443 * Multi-line comments
444 * may span many,
445 * many lines
446 "
447 .to_string()],
448 );
449 assert_eq!(
450 extract_doc_attributes(&[
451 syn::parse_quote!(
452 ),
454 syn::parse_quote!(
455 ),
457 syn::parse_quote!(
458 ),
460 syn::parse_quote!(
461 ),
463 ]),
464 vec![
465 " multiple".to_string(),
466 " single".to_string(),
467 " line".to_string(),
468 " comments".to_string(),
469 ],
470 );
471 assert_eq!(
472 extract_doc_attributes(&[
473 syn::parse_quote!( #[doc = r"a"] ),
474 syn::parse_quote!( #[non_doc] ),
475 syn::parse_quote!( #[doc = r"b"] ),
476 syn::parse_quote!( #[derive(NonDoc)] ),
477 syn::parse_quote!( #[doc = r"c"] ),
478 syn::parse_quote!( #[docker = false] ),
479 syn::parse_quote!( #[doc = r"d"] ),
480 syn::parse_quote!( #[doc(Nope)] ),
481 syn::parse_quote!( #[doc = r"e"] ),
482 ]),
483 vec![
484 "a".to_string(),
485 "b".to_string(),
486 "c".to_string(),
487 "d".to_string(),
488 "e".to_string(),
489 ],
490 )
491 }
492}