use crate::{
error::ExtError as _,
ir,
ir::idents_lint,
Callable,
};
use proc_macro2::{
Ident,
Span,
};
use quote::TokenStreamExt as _;
use std::collections::HashMap;
use syn::{
spanned::Spanned,
token,
};
#[derive(Debug, PartialEq, Eq)]
pub struct ItemMod {
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
mod_token: token::Mod,
ident: Ident,
brace: token::Brace,
items: Vec<ir::Item>,
}
impl ItemMod {
fn ensure_storage_struct_quantity(
module_span: Span,
items: &[ir::Item],
) -> Result<(), syn::Error> {
let storage_iter = items
.iter()
.filter(|item| matches!(item, ir::Item::Ink(ir::InkItem::Storage(_))));
if storage_iter.clone().next().is_none() {
return Err(format_err!(module_span, "missing ink! storage struct",))
}
if storage_iter.clone().count() >= 2 {
let mut error = format_err!(
module_span,
"encountered multiple ink! storage structs, expected exactly one"
);
for storage in storage_iter {
error.combine(format_err!(storage, "ink! storage struct here"))
}
return Err(error)
}
Ok(())
}
fn ensure_contains_message(
module_span: Span,
items: &[ir::Item],
) -> Result<(), syn::Error> {
let found_message = items
.iter()
.filter_map(|item| {
match item {
ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
Some(item_impl.iter_messages())
}
_ => None,
}
})
.any(|mut messages| messages.next().is_some());
if !found_message {
return Err(format_err!(module_span, "missing ink! message"))
}
Ok(())
}
fn ensure_contains_constructor(
module_span: Span,
items: &[ir::Item],
) -> Result<(), syn::Error> {
let found_constructor = items
.iter()
.filter_map(|item| {
match item {
ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
Some(item_impl.iter_constructors())
}
_ => None,
}
})
.any(|mut constructors| constructors.next().is_some());
if !found_constructor {
return Err(format_err!(module_span, "missing ink! constructor"))
}
Ok(())
}
fn ensure_no_overlapping_selectors(items: &[ir::Item]) -> Result<(), syn::Error> {
let mut messages = <HashMap<ir::Selector, &ir::Message>>::new();
let mut constructors = <HashMap<ir::Selector, &ir::Constructor>>::new();
for item_impl in items
.iter()
.filter_map(ir::Item::map_ink_item)
.filter_map(ir::InkItem::filter_map_impl_block)
{
use std::collections::hash_map::Entry;
fn compose_error(
first_span: Span,
second_span: Span,
selector: ir::Selector,
kind: &str,
) -> syn::Error {
format_err!(
second_span,
"encountered ink! {}s with overlapping selectors (= {:02X?})\n\
hint: use #[ink(selector = S:u32)] on the callable or \
#[ink(namespace = N:string)] on the implementation block to \
disambiguate overlapping selectors.",
kind,
selector.to_bytes(),
)
.into_combine(format_err!(
first_span,
"first ink! {} with overlapping selector here",
kind,
))
}
for message in item_impl.iter_messages() {
let selector = message.composed_selector();
match messages.entry(selector) {
Entry::Occupied(overlap) => {
return Err(compose_error(
overlap.get().span(),
message.callable().span(),
selector,
"message",
))
}
Entry::Vacant(vacant) => {
vacant.insert(message.callable());
}
}
}
for constructor in item_impl.iter_constructors() {
let selector = constructor.composed_selector();
match constructors.entry(selector) {
Entry::Occupied(overlap) => {
return Err(compose_error(
overlap.get().span(),
constructor.callable().span(),
selector,
"constructor",
))
}
Entry::Vacant(vacant) => {
vacant.insert(constructor.callable());
}
}
}
}
Ok(())
}
fn ensure_valid_wildcard_selector_usage(
items: &[ir::Item],
) -> Result<(), syn::Error> {
let mut wildcard_selector: Option<&ir::Message> = None;
let mut other_messages = Vec::new();
for item_impl in items
.iter()
.filter_map(ir::Item::map_ink_item)
.filter_map(ir::InkItem::filter_map_impl_block)
{
for message in item_impl.iter_messages() {
if !message.has_wildcard_selector() {
other_messages.push(message);
continue
}
match wildcard_selector {
None => wildcard_selector = Some(message.callable()),
Some(overlap) => {
let err = format_err!(
message.callable().span(),
"encountered ink! messages with overlapping wildcard selectors",
);
let overlap_err = format_err!(
overlap.span(),
"first ink! message with overlapping wildcard selector here",
);
return Err(err.into_combine(overlap_err))
}
}
}
if let Some(wildcard) = wildcard_selector {
match other_messages.len() as u32 {
0 => return Err(format_err!(
wildcard.span(),
"missing definition of another message with TODO in tandem with a wildcard \
selector",
)),
1 => {
if !other_messages[0].callable().has_wildcard_complement_selector() {
return Err(format_err!(
other_messages[0].callable().span(),
"when using a wildcard selector `selector = _` for an ink! message \
then the other message must use the wildcard complement `selector = @`"
))
}
}
2.. => {
let mut combined = format_err!(
wildcard.span(),
"exactly one other message must be defined together with a wildcard selector",
);
for message in &other_messages {
if !message.callable().has_wildcard_complement_selector() {
combined.combine(
format_err!(
message.callable().span(),
"additional message not permitted together with a wildcard selector",
)
)
}
}
return Err(combined)
}
}
} else {
for message in &other_messages {
if message.callable().has_wildcard_complement_selector() {
return Err(format_err!(
message.callable().span(),
"encountered ink! message with wildcard complement `selector = @` but no \
wildcard `selector = _` defined"
));
}
}
}
let mut wildcard_selector: Option<&ir::Constructor> = None;
for constructor in item_impl.iter_constructors() {
if !constructor.has_wildcard_selector() {
continue
}
match wildcard_selector {
None => wildcard_selector = Some(constructor.callable()),
Some(overlap) => {
return Err(format_err!(
constructor.callable().span(),
"encountered ink! constructor with overlapping wildcard selectors",
)
.into_combine(format_err!(
overlap.span(),
"first ink! constructor with overlapping wildcard selector here",
)))
}
}
}
}
Ok(())
}
}
impl TryFrom<syn::ItemMod> for ItemMod {
type Error = syn::Error;
fn try_from(module: syn::ItemMod) -> Result<Self, Self::Error> {
let module_span = module.span();
idents_lint::ensure_no_ink_identifiers(&module)?;
let (brace, items) = match module.content {
Some((brace, items)) => (brace, items),
None => {
return Err(format_err_spanned!(
module,
"out-of-line ink! modules are not supported, use `#[ink::contract] mod name {{ ... }}`",
))
}
};
let (ink_attrs, other_attrs) = ir::partition_attributes(module.attrs)?;
if !ink_attrs.is_empty() {
let mut error = format_err!(
module_span,
"encountered invalid ink! attributes on ink! module"
);
for ink_attr in ink_attrs {
error.combine(format_err!(
ink_attr.span(),
"invalid ink! attribute on module"
))
}
return Err(error)
}
let items = items
.into_iter()
.map(<ir::Item as TryFrom<syn::Item>>::try_from)
.collect::<Result<Vec<_>, syn::Error>>()?;
Self::ensure_storage_struct_quantity(module_span, &items)?;
Self::ensure_contains_message(module_span, &items)?;
Self::ensure_contains_constructor(module_span, &items)?;
Self::ensure_no_overlapping_selectors(&items)?;
Self::ensure_valid_wildcard_selector_usage(&items)?;
Ok(Self {
attrs: other_attrs,
vis: module.vis,
mod_token: module.mod_token,
ident: module.ident,
brace,
items,
})
}
}
impl quote::ToTokens for ItemMod {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.append_all(
self.attrs
.iter()
.filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
);
self.vis.to_tokens(tokens);
self.mod_token.to_tokens(tokens);
self.ident.to_tokens(tokens);
self.brace.surround(tokens, |tokens| {
tokens.append_all(
self.attrs
.iter()
.filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
);
tokens.append_all(&self.items);
});
}
}
impl ItemMod {
pub fn ident(&self) -> &Ident {
&self.ident
}
pub fn storage(&self) -> &ir::Storage {
let mut iter = IterInkItems::new(self)
.filter_map(|ink_item| ink_item.filter_map_storage_item());
let storage = iter
.next()
.expect("encountered ink! module without a storage struct");
assert!(
iter.next().is_none(),
"encountered multiple storage structs in ink! module"
);
storage
}
pub fn items(&self) -> &[ir::Item] {
self.items.as_slice()
}
pub fn impls(&self) -> IterItemImpls {
IterItemImpls::new(self)
}
pub fn events(&self) -> IterEvents {
IterEvents::new(self)
}
pub fn attrs(&self) -> &[syn::Attribute] {
&self.attrs
}
pub fn vis(&self) -> &syn::Visibility {
&self.vis
}
}
pub struct IterInkItems<'a> {
items_iter: core::slice::Iter<'a, ir::Item>,
}
impl<'a> IterInkItems<'a> {
fn new(ink_module: &'a ItemMod) -> Self {
Self {
items_iter: ink_module.items.iter(),
}
}
}
impl<'a> Iterator for IterInkItems<'a> {
type Item = &'a ir::InkItem;
fn next(&mut self) -> Option<Self::Item> {
'repeat: loop {
match self.items_iter.next() {
None => return None,
Some(item) => {
if let Some(event) = item.map_ink_item() {
return Some(event)
}
continue 'repeat
}
}
}
}
}
pub struct IterEvents<'a> {
items_iter: IterInkItems<'a>,
}
impl<'a> IterEvents<'a> {
fn new(ink_module: &'a ItemMod) -> Self {
Self {
items_iter: IterInkItems::new(ink_module),
}
}
}
impl<'a> Iterator for IterEvents<'a> {
type Item = &'a ir::Event;
fn next(&mut self) -> Option<Self::Item> {
'repeat: loop {
match self.items_iter.next() {
None => return None,
Some(ink_item) => {
if let Some(event) = ink_item.filter_map_event_item() {
return Some(event)
}
continue 'repeat
}
}
}
}
}
pub struct IterItemImpls<'a> {
items_iter: IterInkItems<'a>,
}
impl<'a> IterItemImpls<'a> {
fn new(ink_module: &'a ItemMod) -> Self {
Self {
items_iter: IterInkItems::new(ink_module),
}
}
}
impl<'a> Iterator for IterItemImpls<'a> {
type Item = &'a ir::ItemImpl;
fn next(&mut self) -> Option<Self::Item> {
'repeat: loop {
match self.items_iter.next() {
None => return None,
Some(ink_item) => {
if let Some(event) = ink_item.filter_map_impl_block() {
return Some(event)
}
continue 'repeat
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate as ir;
#[test]
fn item_mod_try_from_works() {
let item_mods: Vec<syn::ItemMod> = vec![
syn::parse_quote! {
mod minimal {
#[ink(storage)]
pub struct Minimal {}
impl Minimal {
#[ink(constructor)]
pub fn new() -> Self {}
#[ink(message)]
pub fn minimal_message(&self) {}
}
}
},
syn::parse_quote! {
mod flipper {
#[ink(storage)]
pub struct Flipper {
value: bool,
}
impl Default for Flipper {
#[ink(constructor)]
fn default() -> Self {
Self { value: false }
}
}
impl Flipper {
#[ink(message)]
pub fn flip(&mut self) {
self.value = !self.value
}
#[ink(message)]
pub fn get(&self) -> bool {
self.value
}
}
}
},
];
for item_mod in item_mods {
assert!(<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod).is_ok())
}
}
fn assert_fail(item_mod: syn::ItemMod, expected_err: &str) {
assert_eq!(
<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(item_mod)
.map_err(|err| err.to_string()),
Err(expected_err.to_string()),
);
}
#[test]
fn missing_storage_struct_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
"missing ink! storage struct",
)
}
#[test]
fn multiple_storage_struct_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyFirstStorage {}
#[ink(storage)]
pub struct MySecondStorage {}
impl MyFirstStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
"encountered multiple ink! storage structs, expected exactly one",
)
}
#[test]
fn missing_constructor_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
"missing ink! constructor",
)
}
#[test]
fn missing_message_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
}
}
},
"missing ink! message",
)
}
#[test]
fn invalid_out_of_line_module_fails() {
assert_fail(
syn::parse_quote! {
mod my_module;
},
"out-of-line ink! modules are not supported, use `#[ink::contract] mod name { ... }`",
)
}
#[test]
fn conflicting_attributes_fails() {
assert_fail(
syn::parse_quote! {
#[ink(namespace = "my_namespace")]
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
"encountered invalid ink! attributes on ink! module",
)
}
#[test]
fn overlapping_messages_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = 0xDEADBEEF)]
pub fn my_message_1(&self) {}
}
impl MyStorage {
#[ink(message, selector = 0xDEADBEEF)]
pub fn my_message_2(&self) {}
}
}
},
"encountered ink! messages with overlapping selectors (= [DE, AD, BE, EF])\n\
hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
on the implementation block to disambiguate overlapping selectors.",
);
}
#[test]
fn overlapping_constructors_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor, selector = 0xDEADBEEF)]
pub fn my_constructor_1() -> Self {}
#[ink(message)]
pub fn my_message_1(&self) {}
}
impl MyStorage {
#[ink(constructor, selector = 0xDEADBEEF)]
pub fn my_constructor_2() -> Self {}
}
}
},
"encountered ink! constructors with overlapping selectors (= [DE, AD, BE, EF])\n\
hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
on the implementation block to disambiguate overlapping selectors.",
);
}
#[test]
fn overlapping_trait_impls_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl first::MyTrait for MyStorage {
#[ink(constructor)]
fn my_constructor() -> Self {}
#[ink(message)]
fn my_message(&self) {}
}
impl second::MyTrait for MyStorage {
#[ink(message)]
fn my_message(&self) {}
}
}
},
"encountered ink! messages with overlapping selectors (= [04, C4, 94, 46])\n\
hint: use #[ink(selector = S:u32)] on the callable or #[ink(namespace = N:string)] \
on the implementation block to disambiguate overlapping selectors.",
);
}
#[test]
fn allow_overlap_between_messages_and_constructors() {
assert!(
<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor, selector = 0xDEADBEEF)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = 0xDEADBEEF)]
pub fn my_message(&self) {}
}
}
})
.is_ok()
);
}
#[test]
fn overlapping_wildcard_selectors_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn my_message1(&self) {}
#[ink(message, selector = _)]
pub fn my_message2(&self) {}
}
}
},
"encountered ink! messages with overlapping wildcard selectors",
);
}
#[test]
fn wildcard_selector_on_constructor_works() {
assert!(
<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor, selector = _)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
})
.is_ok()
);
}
#[test]
fn overlap_between_wildcard_selector_and_composed_selector_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _, selector = 0xCAFEBABE)]
pub fn my_message(&self) {}
}
}
},
"encountered ink! attribute arguments with equal kinds",
);
}
#[test]
fn wildcard_selector_and_one_other_message_with_well_known_selector_works() {
assert!(
<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
#[ink(message, selector = 0x9BAE9D5E)]
pub fn wildcard_complement_message(&self) {}
}
}
})
.is_ok()
);
}
#[test]
fn wildcard_selector_and_one_other_message_with_wildcard_complement_selector_works() {
assert!(
<ir::ItemMod as TryFrom<syn::ItemMod>>::try_from(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
#[ink(message, selector = @)]
pub fn wildcard_complement_message(&self) {}
}
}
})
.is_ok()
);
}
#[test]
fn wildcard_selector_without_other_message_fails() {
assert_fail(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
}
}
},
"missing definition of another message with TODO in tandem with a wildcard selector"
)
}
#[test]
fn wildcard_selector_and_one_other_message_without_well_known_selector_fails() {
assert_fail(syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
#[ink(message)]
pub fn other_message_without_well_known_selector(&self) {}
}
}
},
"when using a wildcard selector `selector = _` for an ink! message then the other \
message must use the wildcard complement `selector = @`"
);
}
#[test]
fn wildcard_selector_with_two_other_messages() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
#[ink(message, selector = 0x00000000)]
pub fn wildcard_complement_message(&self) {}
#[ink(message)]
pub fn another_message_not_allowed(&self) {}
}
}
},
"exactly one other message must be defined together with a wildcard selector",
);
}
#[test]
fn wildcard_selector_with_many_other_messages() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = _)]
pub fn fallback(&self) {}
#[ink(message, selector = @)]
pub fn wildcard_complement(&self) {}
#[ink(message)]
pub fn another_message_not_allowed1(&self) {}
#[ink(message)]
pub fn another_message_not_allowed2(&self) {}
#[ink(message)]
pub fn another_message_not_allowed3(&self) {}
}
}
},
"exactly one other message must be defined together with a wildcard selector",
);
}
#[test]
fn wildcard_complement_used_without_wildcard_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = @)]
pub fn uses_reserved_wildcard_other_message_selector(&self) {}
}
}
},
"encountered ink! message with wildcard complement `selector = @` but no \
wildcard `selector = _` defined",
)
}
#[test]
fn wildcard_reserved_selector_used_without_wildcard_fails() {
assert_fail(
syn::parse_quote! {
mod my_module {
#[ink(storage)]
pub struct MyStorage {}
impl MyStorage {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message, selector = 0x9BAE9D5E)]
pub fn uses_reserved_wildcard_other_message_selector(&self) {}
}
}
},
"encountered ink! message with wildcard complement `selector = @` but no \
wildcard `selector = _` defined",
)
}
}