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