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