ink_macro/event/
metadata.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use ink_ir::{
16    EventConfig,
17    IsDocAttribute,
18};
19use proc_macro2::TokenStream as TokenStream2;
20use quote::quote_spanned;
21use syn::spanned::Spanned;
22
23/// Derives the `ink::Event` trait for the given `struct`.
24pub fn event_metadata_derive(mut s: synstructure::Structure) -> TokenStream2 {
25    s.bind_with(|_| synstructure::BindStyle::Move)
26        .add_bounds(synstructure::AddBounds::Fields)
27        .underscore_const(true);
28    match &s.ast().data {
29        syn::Data::Struct(_) => {
30            event_metadata_derive_struct(s).unwrap_or_else(|err| err.to_compile_error())
31        }
32        _ => {
33            syn::Error::new(
34                s.ast().span(),
35                "can only derive `EventMetadata` for Rust `struct` items",
36            )
37            .to_compile_error()
38        }
39    }
40}
41
42/// `Event` derive implementation for `struct` types.
43fn event_metadata_derive_struct(s: synstructure::Structure) -> syn::Result<TokenStream2> {
44    assert_eq!(s.variants().len(), 1, "can only operate on structs");
45    let span = s.ast().span();
46
47    let variant = &s.variants()[0];
48    let ident = variant.ast().ident;
49
50    let config = EventConfig::try_from(variant.ast().attrs)?;
51    let name = config
52        .name()
53        .map(ToString::to_string)
54        .unwrap_or_else(|| variant.ast().ident.to_string());
55
56    let docs = variant
57        .ast()
58        .attrs
59        .iter()
60        .filter_map(|attr| attr.extract_docs());
61
62    let args = variant.bindings().iter().map( |field| {
63        let field_ty = &field.ast().ty;
64        let field_span = field_ty.span();
65        if let Some(field_name) = field.ast().ident.as_ref() {
66            let indexed = super::has_ink_topic_attribute(field)?;
67            let docs = field
68                .ast()
69                .attrs
70                .iter()
71                .filter_map(|attr| attr.extract_docs());
72            let ty_spec = ink_codegen::generate_type_spec(field_ty);
73            Ok(quote_spanned!(field_span =>
74                ::ink::metadata::EventParamSpec::new(::core::stringify!(#field_name))
75                    .of_type(#ty_spec)
76                    .indexed(#indexed)
77                    .docs([ #( #docs ),* ])
78                    .done()
79            ))
80        } else {
81            Err(syn::Error::new(
82                field_span,
83                "can only derive `EventMetadata` for Rust `struct` items with named fields",
84            ))
85        }
86    }).collect::<syn::Result<Vec<_>>>()?;
87
88    Ok(s.bound_impl(
89        quote_spanned!(span=> ::ink::metadata::EventMetadata),
90        quote_spanned!(span=>
91            const MODULE_PATH: &'static str = ::core::module_path!();
92
93            fn event_spec() -> ::ink::metadata::EventSpec {
94               // register this event metadata function in the distributed slice for combining all
95               // events referenced in the contract binary.
96               #[::ink::linkme::distributed_slice(::ink::CONTRACT_EVENTS)]
97               #[linkme(crate = ::ink::linkme)]
98               static EVENT_METADATA: fn() -> ::ink::metadata::EventSpec =
99                   <#ident as ::ink::metadata::EventMetadata>::event_spec;
100
101                ::ink::metadata::EventSpec::new(#name)
102                    .module_path(::core::module_path!())
103                    .signature_topic(
104                        <Self as ::ink::env::Event<::ink::abi::Ink>>::SIGNATURE_TOPIC
105                    )
106                    .args([
107                       #( #args ),*
108                    ])
109                    .docs([
110                       #( #docs ),*
111                    ])
112                    .done()
113            }
114        ),
115    ))
116}