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