use crate::{
error::ExtError,
ir,
ir::idents_lint,
};
use core::slice::Iter as SliceIter;
use proc_macro2::TokenStream as TokenStream2;
use std::collections::HashMap;
use syn::{
spanned::Spanned as _,
Result,
};
#[derive(Debug, PartialEq, Eq)]
pub struct ChainExtension {
item: syn::ItemTrait,
error_code: syn::TraitItemType,
methods: Vec<ChainExtensionMethod>,
}
impl ChainExtension {
pub fn attrs(&self) -> Vec<syn::Attribute> {
let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
.expect("encountered unexpected invalid attributes for ink! chain extension");
attrs
}
pub fn span(&self) -> proc_macro2::Span {
self.item.span()
}
pub fn ident(&self) -> &proc_macro2::Ident {
&self.item.ident
}
pub fn iter_methods(&self) -> SliceIter<ChainExtensionMethod> {
self.methods.iter()
}
pub fn error_code(&self) -> &syn::Type {
self.error_code
.default
.as_ref()
.map(|(_token, ty)| ty)
.expect("unexpected missing default type for error code")
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChainExtensionMethod {
item: syn::TraitItemMethod,
id: ExtensionId,
handle_status: bool,
returns_result: bool,
}
impl ChainExtensionMethod {
pub fn attrs(&self) -> Vec<syn::Attribute> {
let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
.expect(
"encountered unexpected invalid attributes for ink! chain extension method",
);
attrs
}
pub fn span(&self) -> proc_macro2::Span {
self.item.span()
}
pub fn ident(&self) -> &proc_macro2::Ident {
&self.item.sig.ident
}
pub fn sig(&self) -> &syn::Signature {
&self.item.sig
}
pub fn id(&self) -> ExtensionId {
self.id
}
pub fn inputs(&self) -> ChainExtensionMethodInputs {
ChainExtensionMethodInputs {
iter: self.item.sig.inputs.iter(),
}
}
pub fn handle_status(&self) -> bool {
self.handle_status
}
pub fn returns_result(&self) -> bool {
self.returns_result
}
}
pub struct ChainExtensionMethodInputs<'a> {
iter: syn::punctuated::Iter<'a, syn::FnArg>,
}
impl<'a> Iterator for ChainExtensionMethodInputs<'a> {
type Item = &'a syn::PatType;
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
let item = self.iter.next()?;
match item {
syn::FnArg::Receiver(receiver) => {
panic!("encountered unexpected receiver in chain extension method input: {:?}", receiver)
}
syn::FnArg::Typed(pat_type) => Some(pat_type),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtensionId {
index: u32,
}
impl ExtensionId {
pub fn from_u32(index: u32) -> Self {
Self { index }
}
pub fn into_u32(self) -> u32 {
self.index
}
}
impl TryFrom<syn::ItemTrait> for ChainExtension {
type Error = syn::Error;
fn try_from(item_trait: syn::ItemTrait) -> core::result::Result<Self, Self::Error> {
idents_lint::ensure_no_ink_identifiers(&item_trait)?;
Self::analyse_properties(&item_trait)?;
let (error_code, methods) = Self::analyse_items(&item_trait)?;
Ok(Self {
item: item_trait,
error_code,
methods,
})
}
}
impl ChainExtension {
pub fn new(attr: TokenStream2, input: TokenStream2) -> Result<Self> {
if !attr.is_empty() {
return Err(format_err_spanned!(
attr,
"unexpected attribute input for ink! chain extension"
))
}
let item_trait = syn::parse2::<syn::ItemTrait>(input)?;
ChainExtension::try_from(item_trait)
}
fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
if let Some(unsafety) = &item_trait.unsafety {
return Err(format_err_spanned!(
unsafety,
"ink! chain extensions cannot be unsafe"
))
}
if let Some(auto) = &item_trait.auto_token {
return Err(format_err_spanned!(
auto,
"ink! chain extensions cannot be automatically implemented traits"
))
}
if !item_trait.generics.params.is_empty() {
return Err(format_err_spanned!(
item_trait.generics.params,
"ink! chain extensions must not be generic"
))
}
if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
return Err(format_err_spanned!(
item_trait.vis,
"ink! chain extensions must have public visibility"
))
}
if !item_trait.supertraits.is_empty() {
return Err(format_err_spanned!(
item_trait.supertraits,
"ink! chain extensions with super-traits are not supported, yet"
))
}
Ok(())
}
fn analyse_error_code(
item_type: &syn::TraitItemType,
previous: &mut Option<syn::TraitItemType>,
) -> Result<()> {
if item_type.ident != "ErrorCode" {
return Err(format_err_spanned!(
item_type.ident,
"chain extensions expect an associated type with name `ErrorCode` but found {}",
item_type.ident,
))
}
if !item_type.generics.params.is_empty() {
return Err(format_err_spanned!(
item_type.generics,
"generic chain extension `ErrorCode` types are not supported",
))
}
if !item_type.bounds.is_empty() {
return Err(format_err_spanned!(
item_type.bounds,
"bounded chain extension `ErrorCode` types are not supported",
))
}
if item_type.default.is_none() {
return Err(format_err_spanned!(
item_type,
"expected a default type for the ink! chain extension ErrorCode",
))
}
match previous {
Some(previous_error_code) => {
return Err(format_err_spanned!(
item_type,
"encountered duplicate `ErrorCode` associated types for the chain extension",
)).map_err(|err| err.into_combine(format_err_spanned!(
previous_error_code,
"first `ErrorCode` associated type here",
)))
}
None => {
*previous = Some(item_type.clone());
}
}
Ok(())
}
fn analyse_items(
item_trait: &syn::ItemTrait,
) -> Result<(syn::TraitItemType, Vec<ChainExtensionMethod>)> {
let mut methods = Vec::new();
let mut seen_ids = HashMap::new();
let mut error_code = None;
for trait_item in &item_trait.items {
match trait_item {
syn::TraitItem::Const(const_trait_item) => {
return Err(format_err_spanned!(
const_trait_item,
"associated constants in ink! chain extensions are not supported, yet"
))
}
syn::TraitItem::Macro(macro_trait_item) => {
return Err(format_err_spanned!(
macro_trait_item,
"macros in ink! chain extensions are not supported"
))
}
syn::TraitItem::Type(type_trait_item) => {
Self::analyse_error_code(type_trait_item, &mut error_code)?;
}
syn::TraitItem::Verbatim(verbatim) => {
return Err(format_err_spanned!(
verbatim,
"encountered unsupported item in ink! chain extensions"
))
}
syn::TraitItem::Method(method_trait_item) => {
let method = Self::analyse_methods(method_trait_item)?;
let method_id = method.id();
if let Some(previous) = seen_ids.get(&method_id) {
return Err(format_err!(
method.span(),
"encountered duplicate extension identifiers for the same chain extension",
).into_combine(format_err!(
*previous,
"previous duplicate extension identifier here",
)))
}
seen_ids.insert(method_id, method.span());
methods.push(method);
}
unknown => {
return Err(format_err_spanned!(
unknown,
"encountered unknown or unsupported item in ink! chain extensions"
))
}
}
}
let error_code = match error_code {
Some(error_code) => error_code,
None => {
return Err(format_err_spanned!(
item_trait,
"missing ErrorCode associated type from ink! chain extension",
))
}
};
Ok((error_code, methods))
}
fn analyse_methods(method: &syn::TraitItemMethod) -> Result<ChainExtensionMethod> {
if let Some(default_impl) = &method.default {
return Err(format_err_spanned!(
default_impl,
"ink! chain extension methods with default implementations are not supported"
))
}
if let Some(constness) = &method.sig.constness {
return Err(format_err_spanned!(
constness,
"const ink! chain extension methods are not supported"
))
}
if let Some(asyncness) = &method.sig.asyncness {
return Err(format_err_spanned!(
asyncness,
"async ink! chain extension methods are not supported"
))
}
if let Some(unsafety) = &method.sig.unsafety {
return Err(format_err_spanned!(
unsafety,
"unsafe ink! chain extension methods are not supported"
))
}
if let Some(abi) = &method.sig.abi {
return Err(format_err_spanned!(
abi,
"ink! chain extension methods with non default ABI are not supported"
))
}
if let Some(variadic) = &method.sig.variadic {
return Err(format_err_spanned!(
variadic,
"variadic ink! chain extension methods are not supported"
))
}
if !method.sig.generics.params.is_empty() {
return Err(format_err_spanned!(
method.sig.generics.params,
"generic ink! chain extension methods are not supported"
))
}
match ir::first_ink_attribute(&method.attrs)?
.map(|attr| attr.first().kind().clone()) {
Some(ir::AttributeArg::Extension(extension)) => {
Self::analyse_chain_extension_method(method, extension)
}
Some(_unsupported) => {
Err(format_err_spanned!(
method,
"encountered unsupported ink! attribute for ink! chain extension method. expected #[ink(extension = N: u32)] attribute"
))
}
None => {
Err(format_err_spanned!(
method,
"missing #[ink(extension = N: u32)] flag on ink! chain extension method"
))
}
}
}
fn analyse_chain_extension_method(
item_method: &syn::TraitItemMethod,
extension: ExtensionId,
) -> Result<ChainExtensionMethod> {
let (ink_attrs, _) = ir::sanitize_attributes(
item_method.span(),
item_method.attrs.clone(),
&ir::AttributeArgKind::Extension,
|arg| {
match arg.kind() {
ir::AttributeArg::Extension(_)
| ir::AttributeArg::HandleStatus(_)
| ir::AttributeArg::ReturnsResult(_) => Ok(()),
_ => Err(None),
}
},
)?;
if let Some(receiver) = item_method.sig.receiver() {
return Err(format_err_spanned!(
receiver,
"ink! chain extension method must not have a `self` receiver",
))
}
let result = ChainExtensionMethod {
id: extension,
item: item_method.clone(),
handle_status: ink_attrs.is_handle_status(),
returns_result: ink_attrs.is_returns_result(),
};
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_ink_chain_extension_eq_err {
( error: $err_str:literal, $($chain_extension:tt)* ) => {
assert_eq!(
<ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
$( $chain_extension )*
})
.map_err(|err| err.to_string()),
Err(
$err_str.to_string()
)
)
};
}
#[test]
fn unsafe_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions cannot be unsafe",
pub unsafe trait MyChainExtension {}
);
}
#[test]
fn auto_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions cannot be automatically implemented traits",
pub auto trait MyChainExtension {}
);
}
#[test]
fn non_pub_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must have public visibility",
trait MyChainExtension {}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must have public visibility",
pub(crate) trait MyChainExtension {}
);
}
#[test]
fn generic_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must not be generic",
pub trait MyChainExtension<T> {}
);
}
#[test]
fn chain_extension_with_supertraits_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions with super-traits are not supported, yet",
pub trait MyChainExtension: SuperChainExtension {}
);
}
#[test]
fn chain_extension_containing_const_item_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "associated constants in ink! chain extensions are not supported, yet",
pub trait MyChainExtension {
const T: i32;
}
);
}
#[test]
fn chain_extension_containing_invalid_associated_type_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "chain extensions expect an associated type with name `ErrorCode` but found Type",
pub trait MyChainExtension {
type Type;
}
);
}
#[test]
fn chain_extension_with_invalid_error_code() {
assert_ink_chain_extension_eq_err!(
error: "chain extensions expect an associated type with name `ErrorCode` but found IncorrectName",
pub trait MyChainExtension {
type IncorrectName = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "generic chain extension `ErrorCode` types are not supported",
pub trait MyChainExtension {
type ErrorCode<T> = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "bounded chain extension `ErrorCode` types are not supported",
pub trait MyChainExtension {
type ErrorCode: Copy = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "expected a default type for the ink! chain extension ErrorCode",
pub trait MyChainExtension {
type ErrorCode;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate `ErrorCode` associated types for the chain extension",
pub trait MyChainExtension {
type ErrorCode = ();
type ErrorCode = ();
}
);
}
#[test]
fn chain_extension_containing_macro_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "macros in ink! chain extensions are not supported",
pub trait MyChainExtension {
my_macro_call!();
}
);
}
#[test]
fn chain_extension_containing_non_flagged_method_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_1(&self);
}
);
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_2(&mut self);
}
);
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_3() -> Self;
}
);
}
#[test]
fn chain_extension_containing_default_implemented_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension methods with default implementations are not supported",
pub trait MyChainExtension {
#[ink(constructor)]
fn default_implemented() -> Self {}
}
);
}
#[test]
fn chain_extension_containing_const_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "const ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
const fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_async_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "async ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
async fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_unsafe_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "unsafe ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
unsafe fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_methods_using_explicit_abi_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension methods with non default ABI are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
extern fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_variadic_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "variadic ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
fn const_constructor(...) -> Self;
}
);
}
#[test]
fn chain_extension_containing_generic_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "generic ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(extension = 1)]
fn const_constructor<T>() -> Self;
}
);
}
#[test]
fn chain_extension_containing_method_with_unsupported_ink_attribute_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "\
encountered unsupported ink! attribute for ink! chain extension method. \
expected #[ink(extension = N: u32)] attribute",
pub trait MyChainExtension {
#[ink(message)]
fn unsupported_ink_attribute(&self);
}
);
assert_ink_chain_extension_eq_err!(
error: "unknown ink! attribute (path)",
pub trait MyChainExtension {
#[ink(unknown)]
fn unknown_ink_attribute(&self);
}
);
}
#[test]
fn chain_extension_containing_method_with_invalid_marker() {
assert_ink_chain_extension_eq_err!(
error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
pub trait MyChainExtension {
#[ink(extension = -1)]
fn has_self_receiver();
}
);
let too_large = (u32::MAX as u64) + 1;
assert_ink_chain_extension_eq_err!(
error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
pub trait MyChainExtension {
#[ink(extension = #too_large)]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "expected `u32` integer type for `N` in #[ink(extension = N)]",
pub trait MyChainExtension {
#[ink(extension = "Hello, World!")]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered #[ink(extension)] that is missing its `id` parameter. \
Did you mean #[ink(extension = id: u32)] ?",
pub trait MyChainExtension {
#[ink(extension)]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate ink! attribute",
pub trait MyChainExtension {
#[ink(extension = 42)]
#[ink(extension = 42)]
fn duplicate_attributes() -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered ink! attribute arguments with equal kinds",
pub trait MyChainExtension {
#[ink(extension = 1)]
#[ink(extension = 2)]
fn duplicate_attributes() -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered conflicting ink! attribute argument",
pub trait MyChainExtension {
#[ink(extension = 1)]
#[ink(message)]
fn conflicting_attributes() -> Self;
}
);
}
#[test]
fn chain_extension_containing_method_with_self_receiver_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn has_self_receiver(&self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn has_self_receiver(&mut self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn has_self_receiver(self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn has_self_receiver(self: &Self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn has_self_receiver(self: Self) -> Self;
}
);
}
#[test]
fn chain_extension_with_overlapping_extension_ids() {
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate extension identifiers for the same chain extension",
pub trait MyChainExtension {
#[ink(extension = 1)]
fn same_id_1();
#[ink(extension = 1)]
fn same_id_2();
}
);
}
#[test]
fn chain_extension_is_ok() {
let chain_extension = <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1)]
fn extension_1();
#[ink(extension = 2)]
fn extension_2(input: i32);
#[ink(extension = 3)]
fn extension_3() -> i32;
#[ink(extension = 4)]
fn extension_4(input: i32) -> i32;
#[ink(extension = 5)]
fn extension_5(in1: i8, in2: i16, in3: i32, in4: i64) -> (u8, u16, u32, u64);
}
}).unwrap();
assert_eq!(chain_extension.methods.len(), 5);
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.id())
.zip(1..=5u32)
{
assert_eq!(actual.index, expected);
}
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.ident().to_string())
.zip(
[
"extension_1",
"extension_2",
"extension_3",
"extension_4",
"extension_5",
]
.iter()
.map(ToString::to_string),
)
{
assert_eq!(actual, expected);
}
}
#[test]
fn chain_extension_with_params_is_ok() {
let chain_extension =
<ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(extension = 1, handle_status = false)]
fn extension_a();
#[ink(extension = 2, returns_result = false)]
fn extension_b();
#[ink(extension = 3, handle_status = false, returns_result = false)]
fn extension_c();
#[ink(extension = 4)]
#[ink(handle_status = false)]
fn extension_d();
#[ink(extension = 5)]
#[ink(returns_result = false)]
fn extension_e();
#[ink(extension = 6)]
#[ink(handle_status = false)]
#[ink(returns_result = false)]
fn extension_f();
}
})
.unwrap();
let expected_methods = 6;
assert_eq!(chain_extension.methods.len(), expected_methods);
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.id())
.zip(1..=expected_methods as u32)
{
assert_eq!(actual.index, expected);
}
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.ident().to_string())
.zip(
[
"extension_a",
"extension_b",
"extension_c",
"extension_d",
"extension_e",
"extension_f",
]
.iter()
.map(ToString::to_string),
)
{
assert_eq!(actual, expected);
}
}
}