use crate::{
error::ExtError as _,
ir,
ir::{
ExtensionId,
Selector,
},
};
use core::{
convert::TryFrom,
result::Result,
};
use proc_macro2::{
Group as Group2,
Ident,
Span,
TokenStream as TokenStream2,
TokenTree as TokenTree2,
};
use std::collections::HashMap;
use syn::spanned::Spanned;
pub trait IsDocAttribute {
fn is_doc_attribute(&self) -> bool;
fn extract_docs(&self) -> Option<String>;
}
impl IsDocAttribute for syn::Attribute {
fn is_doc_attribute(&self) -> bool {
self.path.is_ident("doc")
}
fn extract_docs(&self) -> Option<String> {
if !self.is_doc_attribute() {
return None
}
if let Ok(syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(lit_str),
..
})) = self.parse_meta()
{
return Some(lit_str.value())
}
None
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Attribute {
Ink(InkAttribute),
Other(syn::Attribute),
}
pub trait Attrs {
fn attrs(&self) -> &[syn::Attribute];
}
impl Attrs for syn::ImplItem {
fn attrs(&self) -> &[syn::Attribute] {
match self {
syn::ImplItem::Const(item) => &item.attrs,
syn::ImplItem::Method(item) => &item.attrs,
syn::ImplItem::Type(item) => &item.attrs,
syn::ImplItem::Macro(item) => &item.attrs,
_ => &[],
}
}
}
impl Attrs for syn::Item {
fn attrs(&self) -> &[syn::Attribute] {
use syn::Item;
match self {
Item::Const(syn::ItemConst { attrs, .. })
| Item::Enum(syn::ItemEnum { attrs, .. })
| Item::ExternCrate(syn::ItemExternCrate { attrs, .. })
| Item::Fn(syn::ItemFn { attrs, .. })
| Item::ForeignMod(syn::ItemForeignMod { attrs, .. })
| Item::Impl(syn::ItemImpl { attrs, .. })
| Item::Macro(syn::ItemMacro { attrs, .. })
| Item::Macro2(syn::ItemMacro2 { attrs, .. })
| Item::Mod(syn::ItemMod { attrs, .. })
| Item::Static(syn::ItemStatic { attrs, .. })
| Item::Struct(syn::ItemStruct { attrs, .. })
| Item::Trait(syn::ItemTrait { attrs, .. })
| Item::TraitAlias(syn::ItemTraitAlias { attrs, .. })
| Item::Type(syn::ItemType { attrs, .. })
| Item::Union(syn::ItemUnion { attrs, .. })
| Item::Use(syn::ItemUse { attrs, .. }) => attrs,
_ => &[],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InkAttribute {
args: Vec<AttributeFrag>,
}
impl Spanned for InkAttribute {
fn span(&self) -> Span {
self.args
.iter()
.map(|arg| arg.span())
.fold(self.first().span(), |fst, snd| {
fst.join(snd).unwrap_or_else(|| self.first().span())
})
}
}
impl InkAttribute {
pub fn ensure_first(&self, expected: &AttributeArgKind) -> Result<(), syn::Error> {
if &self.first().arg.kind() != expected {
return Err(format_err!(
self.span(),
"unexpected first ink! attribute argument",
))
}
Ok(())
}
fn ensure_no_duplicate_args<'a, A>(args: A) -> Result<(), syn::Error>
where
A: IntoIterator<Item = &'a ir::AttributeFrag>,
{
use crate::error::ExtError as _;
use std::collections::HashSet;
let mut seen: HashSet<&AttributeFrag> = HashSet::new();
let mut seen2: HashMap<AttributeArgKind, Span> = HashMap::new();
for arg in args.into_iter() {
if let Some(seen) = seen.get(arg) {
return Err(format_err!(
arg.span(),
"encountered duplicate ink! attribute arguments"
)
.into_combine(format_err!(
seen.span(),
"first equal ink! attribute argument here"
)))
}
if let Some(seen) = seen2.get(&arg.kind().kind()) {
return Err(format_err!(
arg.span(),
"encountered ink! attribute arguments with equal kinds"
)
.into_combine(format_err!(
*seen,
"first equal ink! attribute argument with equal kind here"
)))
}
seen.insert(arg);
seen2.insert(arg.kind().kind(), arg.span());
}
Ok(())
}
pub fn from_expanded<A>(attrs: A) -> Result<Self, syn::Error>
where
A: IntoIterator<Item = Self>,
{
let args = attrs
.into_iter()
.flat_map(|attr| attr.args)
.collect::<Vec<_>>();
if args.is_empty() {
return Err(format_err!(
Span::call_site(),
"encountered unexpected empty expanded ink! attribute arguments",
))
}
Self::ensure_no_duplicate_args(&args)?;
Ok(Self { args })
}
pub fn first(&self) -> &AttributeFrag {
self.args
.first()
.expect("encountered invalid empty ink! attribute list")
}
pub fn args(&self) -> ::core::slice::Iter<AttributeFrag> {
self.args.iter()
}
pub fn namespace(&self) -> Option<ir::Namespace> {
self.args().find_map(|arg| {
if let ir::AttributeArg::Namespace(namespace) = arg.kind() {
return Some(namespace.clone())
}
None
})
}
pub fn selector(&self) -> Option<SelectorOrWildcard> {
self.args().find_map(|arg| {
if let ir::AttributeArg::Selector(selector) = arg.kind() {
return Some(*selector)
}
None
})
}
pub fn is_payable(&self) -> bool {
self.args()
.any(|arg| matches!(arg.kind(), AttributeArg::Payable))
}
pub fn has_wildcard_selector(&self) -> bool {
self.args().any(|arg| {
matches!(
arg.kind(),
AttributeArg::Selector(SelectorOrWildcard::Wildcard)
)
})
}
pub fn is_anonymous(&self) -> bool {
self.args()
.any(|arg| matches!(arg.kind(), AttributeArg::Anonymous))
}
pub fn is_handle_status(&self) -> bool {
!self
.args()
.any(|arg| matches!(arg.kind(), AttributeArg::HandleStatus(false)))
}
pub fn is_returns_result(&self) -> bool {
!self
.args()
.any(|arg| matches!(arg.kind(), AttributeArg::ReturnsResult(false)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AttributeFrag {
pub ast: syn::Meta,
pub arg: AttributeArg,
}
impl AttributeFrag {
pub fn kind(&self) -> &AttributeArg {
&self.arg
}
}
impl Spanned for AttributeFrag {
fn span(&self) -> Span {
self.ast.span()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AttributeArgKind {
Storage,
Event,
Anonymous,
Topic,
Message,
Constructor,
Payable,
Selector,
Extension,
Namespace,
Implementation,
HandleStatus,
ReturnsResult,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AttributeArg {
Storage,
Event,
Anonymous,
Topic,
Message,
Constructor,
Payable,
Selector(SelectorOrWildcard),
Namespace(Namespace),
Implementation,
Extension(ExtensionId),
HandleStatus(bool),
ReturnsResult(bool),
}
impl core::fmt::Display for AttributeArgKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
match self {
Self::Storage => write!(f, "storage"),
Self::Event => write!(f, "event"),
Self::Anonymous => write!(f, "anonymous"),
Self::Topic => write!(f, "topic"),
Self::Message => write!(f, "message"),
Self::Constructor => write!(f, "constructor"),
Self::Payable => write!(f, "payable"),
Self::Selector => {
write!(f, "selector = S:[u8; 4] || _")
}
Self::Extension => {
write!(f, "extension = N:u32)")
}
Self::Namespace => {
write!(f, "namespace = N:string")
}
Self::Implementation => write!(f, "impl"),
Self::HandleStatus => write!(f, "handle_status"),
Self::ReturnsResult => write!(f, "returns_result"),
}
}
}
impl AttributeArg {
pub fn kind(&self) -> AttributeArgKind {
match self {
Self::Storage => AttributeArgKind::Storage,
Self::Event => AttributeArgKind::Event,
Self::Anonymous => AttributeArgKind::Anonymous,
Self::Topic => AttributeArgKind::Topic,
Self::Message => AttributeArgKind::Message,
Self::Constructor => AttributeArgKind::Constructor,
Self::Payable => AttributeArgKind::Payable,
Self::Selector(_) => AttributeArgKind::Selector,
Self::Extension(_) => AttributeArgKind::Extension,
Self::Namespace(_) => AttributeArgKind::Namespace,
Self::Implementation => AttributeArgKind::Implementation,
Self::HandleStatus(_) => AttributeArgKind::HandleStatus,
Self::ReturnsResult(_) => AttributeArgKind::ReturnsResult,
}
}
}
impl core::fmt::Display for AttributeArg {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
match self {
Self::Storage => write!(f, "storage"),
Self::Event => write!(f, "event"),
Self::Anonymous => write!(f, "anonymous"),
Self::Topic => write!(f, "topic"),
Self::Message => write!(f, "message"),
Self::Constructor => write!(f, "constructor"),
Self::Payable => write!(f, "payable"),
Self::Selector(selector) => core::fmt::Display::fmt(&selector, f),
Self::Extension(extension) => {
write!(f, "extension = {:?}", extension.into_u32())
}
Self::Namespace(namespace) => {
write!(f, "namespace = {:?}", namespace.as_bytes())
}
Self::Implementation => write!(f, "impl"),
Self::HandleStatus(value) => write!(f, "handle_status = {:?}", value),
Self::ReturnsResult(value) => write!(f, "returns_result = {:?}", value),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SelectorOrWildcard {
Wildcard,
UserProvided(ir::Selector),
}
impl SelectorOrWildcard {
fn selector(bytes: [u8; 4]) -> SelectorOrWildcard {
SelectorOrWildcard::UserProvided(Selector::from(bytes))
}
}
impl core::fmt::Display for SelectorOrWildcard {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
match self {
Self::UserProvided(selector) => core::fmt::Debug::fmt(&selector, f),
Self::Wildcard => write!(f, "_"),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Namespace {
bytes: Vec<u8>,
}
impl From<Vec<u8>> for Namespace {
fn from(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}
impl Namespace {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
pub fn contains_ink_attributes<'a, I>(attrs: I) -> bool
where
I: IntoIterator<Item = &'a syn::Attribute>,
{
attrs.into_iter().any(|attr| attr.path.is_ident("ink"))
}
pub fn first_ink_attribute<'a, I>(
attrs: I,
) -> Result<Option<ir::InkAttribute>, syn::Error>
where
I: IntoIterator<Item = &'a syn::Attribute>,
{
let first = attrs.into_iter().find(|attr| attr.path.is_ident("ink"));
match first {
None => Ok(None),
Some(ink_attr) => InkAttribute::try_from(ink_attr.clone()).map(Some),
}
}
pub fn partition_attributes<I>(
attrs: I,
) -> Result<(Vec<InkAttribute>, Vec<syn::Attribute>), syn::Error>
where
I: IntoIterator<Item = syn::Attribute>,
{
use either::Either;
use itertools::Itertools as _;
let (ink_attrs, others) = attrs
.into_iter()
.map(<Attribute as TryFrom<_>>::try_from)
.collect::<Result<Vec<Attribute>, syn::Error>>()?
.into_iter()
.partition_map(|attr| {
match attr {
Attribute::Ink(ink_attr) => Either::Left(ink_attr),
Attribute::Other(other_attr) => Either::Right(other_attr),
}
});
Attribute::ensure_no_duplicate_attrs(&ink_attrs)?;
Ok((ink_attrs, others))
}
pub fn sanitize_attributes<I, C>(
parent_span: Span,
attrs: I,
is_valid_first: &ir::AttributeArgKind,
is_conflicting_attr: C,
) -> Result<(InkAttribute, Vec<syn::Attribute>), syn::Error>
where
I: IntoIterator<Item = syn::Attribute>,
C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
{
let (ink_attrs, other_attrs) = ir::partition_attributes(attrs)?;
let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(parent_span, "at this invocation",))
})?;
normalized.ensure_first(is_valid_first).map_err(|err| {
err.into_combine(format_err!(
parent_span,
"expected {} as first ink! attribute argument",
is_valid_first,
))
})?;
normalized.ensure_no_conflicts(is_conflicting_attr)?;
Ok((normalized, other_attrs))
}
pub fn sanitize_optional_attributes<I, C>(
parent_span: Span,
attrs: I,
is_conflicting_attr: C,
) -> Result<(Option<InkAttribute>, Vec<syn::Attribute>), syn::Error>
where
I: IntoIterator<Item = syn::Attribute>,
C: FnMut(&ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
{
let (ink_attrs, rust_attrs) = ir::partition_attributes(attrs)?;
if ink_attrs.is_empty() {
return Ok((None, rust_attrs))
}
let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(parent_span, "at this invocation",))
})?;
normalized.ensure_no_conflicts(is_conflicting_attr)?;
Ok((Some(normalized), rust_attrs))
}
impl Attribute {
fn ensure_no_duplicate_attrs<'a, I>(attrs: I) -> Result<(), syn::Error>
where
I: IntoIterator<Item = &'a InkAttribute>,
{
use std::collections::HashSet;
let mut seen: HashSet<&InkAttribute> = HashSet::new();
for attr in attrs.into_iter() {
if let Some(seen) = seen.get(attr) {
use crate::error::ExtError as _;
return Err(format_err!(
attr.span(),
"encountered duplicate ink! attribute"
)
.into_combine(format_err!(seen.span(), "first ink! attribute here")))
}
seen.insert(attr);
}
Ok(())
}
}
impl TryFrom<syn::Attribute> for Attribute {
type Error = syn::Error;
fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
if attr.path.is_ident("ink") {
return <InkAttribute as TryFrom<_>>::try_from(attr).map(Into::into)
}
Ok(Attribute::Other(attr))
}
}
impl From<InkAttribute> for Attribute {
fn from(ink_attribute: InkAttribute) -> Self {
Attribute::Ink(ink_attribute)
}
}
fn transform_wildcard_selector_to_string(group: Group2) -> TokenTree2 {
let mut found_selector = false;
let mut found_equal = false;
let new_group: TokenStream2 = group
.stream()
.into_iter()
.map(|tt| {
match tt {
TokenTree2::Group(grp) => transform_wildcard_selector_to_string(grp),
TokenTree2::Ident(ident)
if found_selector && found_equal && ident == "_" =>
{
let mut lit = proc_macro2::Literal::string("_");
lit.set_span(ident.span());
found_selector = false;
found_equal = false;
TokenTree2::Literal(lit)
}
TokenTree2::Ident(ident) if ident == "selector" => {
found_selector = true;
TokenTree2::Ident(ident)
}
TokenTree2::Punct(punct) if punct.as_char() == '=' => {
found_equal = true;
TokenTree2::Punct(punct)
}
_ => tt,
}
})
.collect();
TokenTree2::Group(Group2::new(group.delimiter(), new_group))
}
impl TryFrom<syn::Attribute> for InkAttribute {
type Error = syn::Error;
fn try_from(mut attr: syn::Attribute) -> Result<Self, Self::Error> {
if !attr.path.is_ident("ink") {
return Err(format_err_spanned!(attr, "unexpected non-ink! attribute"))
}
let ts: TokenStream2 = attr
.tokens
.into_iter()
.map(|tt| {
match tt {
TokenTree2::Group(grp) => transform_wildcard_selector_to_string(grp),
_ => tt,
}
})
.collect();
attr.tokens = ts;
match attr.parse_meta().map_err(|_| {
format_err_spanned!(attr, "unexpected ink! attribute structure")
})? {
syn::Meta::List(meta_list) => {
let args = meta_list
.nested
.into_iter()
.map(<AttributeFrag as TryFrom<_>>::try_from)
.collect::<Result<Vec<_>, syn::Error>>()?;
Self::ensure_no_duplicate_args(&args)?;
if args.is_empty() {
return Err(format_err_spanned!(
attr,
"encountered unsupported empty ink! attribute"
))
}
Ok(InkAttribute { args })
}
_ => Err(format_err_spanned!(attr, "unknown ink! attribute")),
}
}
}
impl InkAttribute {
pub fn ensure_no_conflicts<'a, P>(
&'a self,
mut is_conflicting: P,
) -> Result<(), syn::Error>
where
P: FnMut(&'a ir::AttributeFrag) -> Result<(), Option<syn::Error>>,
{
let mut err: Option<syn::Error> = None;
for arg in self.args() {
if let Err(reason) = is_conflicting(arg) {
let conflict_err = format_err!(
arg.span(),
"encountered conflicting ink! attribute argument",
);
match &mut err {
Some(err) => {
err.combine(conflict_err);
}
None => {
err = Some(conflict_err);
}
}
if let Some(reason) = reason {
err.as_mut()
.expect("must be `Some` at this point")
.combine(reason);
}
}
}
if let Some(err) = err {
return Err(err)
}
Ok(())
}
}
impl TryFrom<syn::NestedMeta> for AttributeFrag {
type Error = syn::Error;
fn try_from(nested_meta: syn::NestedMeta) -> Result<Self, Self::Error> {
match nested_meta {
syn::NestedMeta::Meta(meta) => {
match &meta {
syn::Meta::NameValue(name_value) => {
if name_value.path.is_ident("selector") {
if let syn::Lit::Str(lit_str) = &name_value.lit {
let argument = lit_str.value();
if argument != "_" {
return Err(format_err!(
name_value,
"#[ink(selector = ..)] attributes with string inputs are deprecated. \
use an integer instead, e.g. #[ink(selector = 1)] or #[ink(selector = 0xC0DECAFE)]."
))
}
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::Selector(SelectorOrWildcard::Wildcard),
})
}
if let syn::Lit::Int(lit_int) = &name_value.lit {
let selector_u32 = lit_int.base10_parse::<u32>()
.map_err(|error| {
format_err_spanned!(
lit_int,
"selector value out of range. selector must be a valid `u32` integer: {}",
error
)
})?;
let selector = Selector::from(selector_u32.to_be_bytes());
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::Selector(SelectorOrWildcard::UserProvided(selector)),
})
}
return Err(format_err!(name_value, "expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"))
}
if name_value.path.is_ident("namespace") {
if let syn::Lit::Str(lit_str) = &name_value.lit {
let argument = lit_str.value();
syn::parse_str::<syn::Ident>(&argument)
.map_err(|_error| format_err!(
lit_str,
"encountered invalid Rust identifier for namespace argument",
))?;
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::Namespace(
Namespace::from(argument.into_bytes()),
),
})
}
return Err(format_err!(name_value, "expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]"))
}
if name_value.path.is_ident("extension") {
if let syn::Lit::Int(lit_int) = &name_value.lit {
let id = lit_int.base10_parse::<u32>().map_err(|parse_err| {
format_err!(
name_value,
"could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
).into_combine(parse_err)
})?;
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::Extension(
ExtensionId::from_u32(id),
),
})
}
return Err(format_err!(name_value, "expected `u32` integer type for `N` in #[ink(extension = N)]"))
}
if name_value.path.is_ident("handle_status") {
if let syn::Lit::Bool(lit_bool) = &name_value.lit {
let value = lit_bool.value;
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::HandleStatus(value),
})
}
return Err(format_err!(name_value, "expected `bool` value type for `flag` in #[ink(handle_status = flag)]"))
}
if name_value.path.is_ident("returns_result") {
if let syn::Lit::Bool(lit_bool) = &name_value.lit {
let value = lit_bool.value;
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::ReturnsResult(value),
})
}
return Err(format_err!(name_value, "expected `bool` value type for `flag` in #[ink(returns_result = flag)]"))
}
Err(format_err_spanned!(
meta,
"unknown ink! attribute argument (name = value)",
))
}
syn::Meta::Path(path) => {
path
.get_ident()
.map(Ident::to_string)
.ok_or_else(|| format_err_spanned!(meta, "unknown ink! attribute (path)"))
.and_then(|ident| match ident.as_str() {
"storage" => Ok(AttributeArg::Storage),
"message" => Ok(AttributeArg::Message),
"constructor" => Ok(AttributeArg::Constructor),
"event" => Ok(AttributeArg::Event),
"anonymous" => Ok(AttributeArg::Anonymous),
"topic" => Ok(AttributeArg::Topic),
"payable" => Ok(AttributeArg::Payable),
"impl" => Ok(AttributeArg::Implementation),
"selector" => Err(format_err!(
meta,
"encountered #[ink(selector)] that is missing its u32 parameter. \
Did you mean #[ink(selector = value: u32)] ?"
)),
"namespace" => Err(format_err!(
meta,
"encountered #[ink(namespace)] that is missing its string parameter. \
Did you mean #[ink(namespace = name: str)] ?"
)),
"extension" => Err(format_err!(
meta,
"encountered #[ink(extension)] that is missing its `id` parameter. \
Did you mean #[ink(extension = id: u32)] ?"
)),
"handle_status" => Err(format_err!(
meta,
"encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
Did you mean #[ink(handle_status = flag: bool)] ?"
)),
"returns_result" => Err(format_err!(
meta,
"encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \
Did you mean #[ink(returns_result = flag: bool)] ?"
)),
_ => Err(format_err_spanned!(
meta, "unknown ink! attribute (path)"
))
})
.map(|kind| AttributeFrag { ast: meta, arg: kind, })
}
syn::Meta::List(_) => {
Err(format_err_spanned!(
meta,
"unknown ink! attribute argument (list)"
))
}
}
}
syn::NestedMeta::Lit(_) => {
Err(format_err_spanned!(
nested_meta,
"unknown ink! attribute argument (literal)"
))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contains_ink_attributes_works() {
assert!(!contains_ink_attributes(&[]));
assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink] }]));
assert!(contains_ink_attributes(&[syn::parse_quote! { #[ink(..)] }]));
assert!(contains_ink_attributes(&[
syn::parse_quote! { #[inline] },
syn::parse_quote! { #[likely] },
syn::parse_quote! { #[ink(storage)] },
]));
assert!(!contains_ink_attributes(&[
syn::parse_quote! { #[inline] },
syn::parse_quote! { #[likely] },
]));
}
fn assert_first_ink_attribute(
input: &[syn::Attribute],
expected: Result<Option<Vec<ir::AttributeArg>>, &'static str>,
) {
assert_eq!(
first_ink_attribute(input)
.map(|maybe_attr: Option<ir::InkAttribute>| {
maybe_attr.map(|attr: ir::InkAttribute| {
attr.args.into_iter().map(|arg| arg.arg).collect::<Vec<_>>()
})
})
.map_err(|err| err.to_string()),
expected.map_err(ToString::to_string),
)
}
#[test]
fn first_ink_attribute_works() {
assert_first_ink_attribute(&[], Ok(None));
assert_first_ink_attribute(
&[syn::parse_quote! { #[ink(storage)] }],
Ok(Some(vec![AttributeArg::Storage])),
);
assert_first_ink_attribute(
&[syn::parse_quote! { #[ink(invalid)] }],
Err("unknown ink! attribute (path)"),
);
}
mod test {
use crate::ir;
#[derive(Debug, PartialEq, Eq)]
pub enum Attribute {
Ink(Vec<ir::AttributeArg>),
Other(syn::Attribute),
}
impl From<ir::Attribute> for Attribute {
fn from(attr: ir::Attribute) -> Self {
match attr {
ir::Attribute::Ink(ink_attr) => {
Self::Ink(
ink_attr
.args
.into_iter()
.map(|arg| arg.arg)
.collect::<Vec<_>>(),
)
}
ir::Attribute::Other(other_attr) => Self::Other(other_attr),
}
}
}
impl From<ir::InkAttribute> for Attribute {
fn from(ink_attr: ir::InkAttribute) -> Self {
Attribute::from(ir::Attribute::Ink(ink_attr))
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InkAttribute {
args: Vec<ir::AttributeArg>,
}
impl From<ir::InkAttribute> for InkAttribute {
fn from(ink_attr: ir::InkAttribute) -> Self {
Self {
args: ink_attr
.args
.into_iter()
.map(|arg| arg.arg)
.collect::<Vec<_>>(),
}
}
}
impl<I> From<I> for InkAttribute
where
I: IntoIterator<Item = ir::AttributeArg>,
{
fn from(args: I) -> Self {
Self {
args: args.into_iter().collect::<Vec<_>>(),
}
}
}
}
fn assert_attribute_try_from(
input: syn::Attribute,
expected: Result<test::Attribute, &'static str>,
) {
assert_eq!(
<ir::Attribute as TryFrom<_>>::try_from(input)
.map(test::Attribute::from)
.map_err(|err| err.to_string()),
expected.map_err(ToString::to_string),
)
}
#[test]
fn storage_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(storage)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Storage])),
);
}
#[test]
fn impl_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(impl)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Implementation])),
);
}
#[test]
fn selector_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(selector = 42)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
SelectorOrWildcard::UserProvided(Selector::from([0, 0, 0, 42])),
)])),
);
assert_attribute_try_from(
syn::parse_quote! {
#[ink(selector = 0xDEADBEEF)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
SelectorOrWildcard::selector([0xDE, 0xAD, 0xBE, 0xEF]),
)])),
);
}
#[test]
fn selector_negative_number() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(selector = -1)]
},
Err(
"selector value out of range. selector must be a valid `u32` integer: \
invalid digit found in string",
),
);
}
#[test]
fn selector_out_of_range() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(selector = 0xFFFF_FFFF_FFFF_FFFF)]
},
Err(
"selector value out of range. \
selector must be a valid `u32` integer: number too large to fit in target type"
),
);
}
#[test]
fn selector_invalid_type() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(selector = true)]
},
Err("expected 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"),
);
}
#[test]
fn namespace_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(namespace = "my_namespace")]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Namespace(
Namespace::from("my_namespace".to_string().into_bytes()),
)])),
);
}
#[test]
fn namespace_invalid_identifier() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(namespace = "::invalid_identifier")]
},
Err("encountered invalid Rust identifier for namespace argument"),
);
}
#[test]
fn namespace_invalid_type() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(namespace = 42)]
},
Err("expected string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]"),
);
}
#[test]
fn namespace_missing_parameter() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(namespace)]
},
Err(
"encountered #[ink(namespace)] that is missing its string parameter. \
Did you mean #[ink(namespace = name: str)] ?",
),
);
}
#[test]
fn extension_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(extension = 42)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::Extension(
ExtensionId::from_u32(42),
)])),
);
}
#[test]
fn extension_invalid_value_type() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(extension = "string")]
},
Err("expected `u32` integer type for `N` in #[ink(extension = N)]"),
);
}
#[test]
fn extension_negative_integer() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(extension = -1)]
},
Err("could not parse `N` in `#[ink(extension = N)]` into a `u32` integer"),
);
}
#[test]
fn extension_too_big_integer() {
let max_u32_plus_1 = (u32::MAX as u64) + 1;
assert_attribute_try_from(
syn::parse_quote! {
#[ink(extension = #max_u32_plus_1)]
},
Err("could not parse `N` in `#[ink(extension = N)]` into a `u32` integer"),
);
}
#[test]
fn extension_missing_parameter() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(extension)]
},
Err(
"encountered #[ink(extension)] that is missing its `id` parameter. \
Did you mean #[ink(extension = id: u32)] ?",
),
);
}
#[test]
fn handle_status_works() {
fn expected_ok(value: bool) -> Result<test::Attribute, &'static str> {
Ok(test::Attribute::Ink(vec![AttributeArg::HandleStatus(
value,
)]))
}
assert_attribute_try_from(
syn::parse_quote! {
#[ink(handle_status = true)]
},
expected_ok(true),
);
assert_attribute_try_from(
syn::parse_quote! {
#[ink(handle_status = false)]
},
expected_ok(false),
);
}
#[test]
fn handle_status_missing_parameter() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(handle_status)]
},
Err(
"encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
Did you mean #[ink(handle_status = flag: bool)] ?",
),
);
}
#[test]
fn handle_status_invalid_parameter_type() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(handle_status = "string")]
},
Err("expected `bool` value type for `flag` in #[ink(handle_status = flag)]"),
);
}
#[test]
fn returns_result_works() {
fn expected_ok(value: bool) -> Result<test::Attribute, &'static str> {
Ok(test::Attribute::Ink(vec![AttributeArg::ReturnsResult(
value,
)]))
}
assert_attribute_try_from(
syn::parse_quote! {
#[ink(returns_result = true)]
},
expected_ok(true),
);
assert_attribute_try_from(
syn::parse_quote! {
#[ink(returns_result = false)]
},
expected_ok(false),
);
}
#[test]
fn returns_result_missing_parameter() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(returns_result)]
},
Err(
"encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \
Did you mean #[ink(returns_result = flag: bool)] ?",
),
);
}
#[test]
fn returns_result_invalid_parameter_type() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(returns_result = "string")]
},
Err("expected `bool` value type for `flag` in #[ink(returns_result = flag)]"),
);
}
#[test]
fn compound_mixed_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(message, namespace = "my_namespace")]
},
Ok(test::Attribute::Ink(vec![
AttributeArg::Message,
AttributeArg::Namespace(Namespace::from(
"my_namespace".to_string().into_bytes(),
)),
])),
)
}
#[test]
fn compound_simple_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(
storage,
message,
constructor,
event,
topic,
payable,
impl,
)]
},
Ok(test::Attribute::Ink(vec![
AttributeArg::Storage,
AttributeArg::Message,
AttributeArg::Constructor,
AttributeArg::Event,
AttributeArg::Topic,
AttributeArg::Payable,
AttributeArg::Implementation,
])),
);
}
#[test]
fn non_ink_attribute_works() {
let attr: syn::Attribute = syn::parse_quote! {
#[non_ink(message)]
};
assert_attribute_try_from(attr.clone(), Ok(test::Attribute::Other(attr)));
}
#[test]
fn empty_ink_attribute_fails() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink]
},
Err("unknown ink! attribute"),
);
assert_attribute_try_from(
syn::parse_quote! {
#[ink()]
},
Err("encountered unsupported empty ink! attribute"),
);
}
#[test]
fn duplicate_flags_fails() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(message, message)]
},
Err("encountered duplicate ink! attribute arguments"),
);
}
fn assert_parition_attributes(
input: Vec<syn::Attribute>,
expected: Result<(Vec<test::InkAttribute>, Vec<syn::Attribute>), &'static str>,
) {
assert_eq!(
partition_attributes(input)
.map(|(ink_attr, other_attr)| {
(
ink_attr
.into_iter()
.map(test::InkAttribute::from)
.collect::<Vec<_>>(),
other_attr,
)
})
.map_err(|err| err.to_string()),
expected.map_err(ToString::to_string)
);
}
#[test]
fn parition_attributes_works() {
assert_parition_attributes(
vec![
syn::parse_quote! { #[ink(message)] },
syn::parse_quote! { #[non_ink_attribute] },
],
Ok((
vec![test::InkAttribute::from(vec![AttributeArg::Message])],
vec![syn::parse_quote! { #[non_ink_attribute] }],
)),
)
}
#[test]
fn parition_duplicates_fails() {
assert_parition_attributes(
vec![
syn::parse_quote! { #[ink(message)] },
syn::parse_quote! { #[ink(message)] },
],
Err("encountered duplicate ink! attribute"),
)
}
}