mod config;
mod signature_topic;
use config::EventConfig;
use proc_macro2::{
Span,
TokenStream as TokenStream2,
};
use quote::ToTokens;
use syn::spanned::Spanned as _;
use crate::{
error::ExtError,
ir,
utils::extract_cfg_attributes,
};
pub use signature_topic::SignatureTopicArg;
#[derive(Debug, PartialEq, Eq)]
pub struct Event {
item: syn::ItemStruct,
config: EventConfig,
}
impl Event {
pub fn new(config: TokenStream2, item: TokenStream2) -> Result<Self, syn::Error> {
let item = syn::parse2::<syn::ItemStruct>(item.clone()).map_err(|err| {
err.into_combine(format_err_spanned!(
item,
"event definition must be a `struct`",
))
})?;
let parsed_config = syn::parse2::<crate::ast::AttributeArgs>(config)?;
let config = EventConfig::try_from(parsed_config)?;
for attr in &item.attrs {
if attr.path().to_token_stream().to_string().contains("event") {
return Err(format_err_spanned!(
attr,
"only one `ink::event` is allowed",
));
}
}
Ok(Self { item, config })
}
pub fn item(&self) -> &syn::ItemStruct {
&self.item
}
pub(super) fn is_ink_event(
item_struct: &syn::ItemStruct,
) -> Result<bool, syn::Error> {
if !ir::contains_ink_attributes(&item_struct.attrs) {
return Ok(false);
}
let attr = ir::first_ink_attribute(&item_struct.attrs)?
.expect("missing expected ink! attribute for struct");
Ok(matches!(attr.first().kind(), ir::AttributeArg::Event))
}
pub fn anonymous(&self) -> bool {
self.config.anonymous()
}
pub fn signature_topic_hex(&self) -> Option<&str> {
self.config.signature_topic_hex()
}
pub fn get_cfg_attrs(&self, span: Span) -> Vec<TokenStream2> {
extract_cfg_attributes(&self.item.attrs, span)
}
}
impl ToTokens for Event {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.item.to_tokens(tokens)
}
}
impl TryFrom<syn::ItemStruct> for Event {
type Error = syn::Error;
fn try_from(item_struct: syn::ItemStruct) -> Result<Self, Self::Error> {
let struct_span = item_struct.span();
let (ink_attrs, other_attrs) = ir::sanitize_attributes(
struct_span,
item_struct.attrs.clone(),
&ir::AttributeArgKind::Event,
|arg| {
match arg.kind() {
ir::AttributeArg::Event
| ir::AttributeArg::SignatureTopic(_)
| ir::AttributeArg::Anonymous => Ok(()),
_ => Err(None),
}
},
)?;
if ink_attrs.is_anonymous() && ink_attrs.signature_topic_hex().is_some() {
return Err(format_err_spanned!(
item_struct,
"cannot use use `anonymous` with `signature_topic`",
));
}
Ok(Self {
item: syn::ItemStruct {
attrs: other_attrs,
..item_struct
},
config: EventConfig::new(
ink_attrs.is_anonymous(),
ink_attrs.signature_topic_hex(),
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_try_from_works() {
let s = "11".repeat(32);
let item_struct: syn::ItemStruct = syn::parse_quote! {
#[ink(event)]
#[ink(signature_topic = #s)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
};
assert!(Event::try_from(item_struct).is_ok());
}
fn assert_try_from_fails(item_struct: syn::ItemStruct, expected: &str) {
assert_eq!(
Event::try_from(item_struct).map_err(|err| err.to_string()),
Err(expected.to_string())
)
}
#[test]
fn conflicting_struct_attributes_fails() {
assert_try_from_fails(
syn::parse_quote! {
#[ink(event)]
#[ink(storage)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"encountered conflicting ink! attribute argument",
)
}
#[test]
fn duplicate_struct_attributes_fails() {
assert_try_from_fails(
syn::parse_quote! {
#[ink(event)]
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"encountered duplicate ink! attribute",
);
assert_try_from_fails(
syn::parse_quote! {
#[ink(event)]
#[ink(anonymous)]
#[ink(anonymous)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"encountered duplicate ink! attribute",
);
let s = "11".repeat(32);
assert_try_from_fails(
syn::parse_quote! {
#[ink(event)]
#[ink(signature_topic = #s)]
#[ink(signature_topic = #s)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"encountered duplicate ink! attribute",
);
}
#[test]
fn wrong_first_struct_attribute_fails() {
assert_try_from_fails(
syn::parse_quote! {
#[ink(storage)]
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"unexpected first ink! attribute argument",
)
}
#[test]
fn missing_event_attribute_fails() {
assert_try_from_fails(
syn::parse_quote! {
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"encountered unexpected empty expanded ink! attribute arguments",
)
}
#[test]
fn anonymous_event_works() {
fn assert_anonymous_event(event: syn::ItemStruct) {
match Event::try_from(event) {
Ok(event) => {
assert!(event.anonymous());
}
Err(_) => panic!("encountered unexpected invalid anonymous event"),
}
}
assert_anonymous_event(syn::parse_quote! {
#[ink(event)]
#[ink(anonymous)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
});
assert_anonymous_event(syn::parse_quote! {
#[ink(event, anonymous)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
});
}
#[test]
fn signature_conflict_fails() {
let s = "11".repeat(32);
assert_try_from_fails(
syn::parse_quote! {
#[ink(event)]
#[ink(anonymous)]
#[ink(signature_topic = #s)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
},
"cannot use use `anonymous` with `signature_topic`",
)
}
}