use crate::ir;
use core::fmt;
use proc_macro2::{
Ident,
Span,
};
use quote::ToTokens as _;
use syn::spanned::Spanned as _;
#[derive(Debug, Copy, Clone)]
pub enum CallableKind {
Message,
Constructor,
}
impl fmt::Display for CallableKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Message => write!(f, "message"),
Self::Constructor => write!(f, "constructor"),
}
}
}
#[derive(Debug)]
pub struct CallableWithSelector<'a, C> {
composed_selector: ir::Selector,
item_impl: &'a ir::ItemImpl,
callable: &'a C,
}
impl<C> Copy for CallableWithSelector<'_, C> {}
impl<C> Clone for CallableWithSelector<'_, C> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, C> CallableWithSelector<'a, C>
where
C: Callable,
{
pub(super) fn new(item_impl: &'a ir::ItemImpl, callable: &'a C) -> Self {
Self {
composed_selector: compose_selector(item_impl, callable),
item_impl,
callable,
}
}
}
impl<'a, C> CallableWithSelector<'a, C> {
pub fn composed_selector(&self) -> ir::Selector {
self.composed_selector
}
pub fn callable(&self) -> &'a C {
self.callable
}
pub fn item_impl(&self) -> &'a ir::ItemImpl {
self.item_impl
}
}
impl<'a, C> Callable for CallableWithSelector<'a, C>
where
C: Callable,
{
fn kind(&self) -> CallableKind {
<C as Callable>::kind(self.callable)
}
fn ident(&self) -> &Ident {
<C as Callable>::ident(self.callable)
}
fn user_provided_selector(&self) -> Option<&ir::Selector> {
<C as Callable>::user_provided_selector(self.callable)
}
fn is_payable(&self) -> bool {
<C as Callable>::is_payable(self.callable)
}
fn is_default(&self) -> bool {
<C as Callable>::is_default(self.callable)
}
fn has_wildcard_selector(&self) -> bool {
<C as Callable>::has_wildcard_selector(self.callable)
}
fn has_wildcard_complement_selector(&self) -> bool {
<C as Callable>::has_wildcard_complement_selector(self.callable)
}
fn visibility(&self) -> Visibility {
<C as Callable>::visibility(self.callable)
}
fn inputs(&self) -> InputsIter {
<C as Callable>::inputs(self.callable)
}
fn inputs_span(&self) -> Span {
<C as Callable>::inputs_span(self.callable)
}
fn statements(&self) -> &[syn::Stmt] {
<C as Callable>::statements(self.callable)
}
}
impl<'a, C> ::core::ops::Deref for CallableWithSelector<'a, C> {
type Target = C;
fn deref(&self) -> &Self::Target {
self.callable
}
}
pub trait Callable {
fn kind(&self) -> CallableKind;
fn ident(&self) -> &Ident;
fn user_provided_selector(&self) -> Option<&ir::Selector>;
fn is_payable(&self) -> bool;
fn is_default(&self) -> bool;
fn has_wildcard_selector(&self) -> bool;
fn has_wildcard_complement_selector(&self) -> bool;
fn visibility(&self) -> Visibility;
fn inputs(&self) -> InputsIter;
fn inputs_span(&self) -> Span;
fn statements(&self) -> &[syn::Stmt];
}
pub fn compose_selector<C>(item_impl: &ir::ItemImpl, callable: &C) -> ir::Selector
where
C: Callable,
{
if let Some(selector) = callable.user_provided_selector() {
return *selector
}
let callable_ident = callable.ident().to_string().into_bytes();
let namespace_bytes = item_impl
.namespace()
.map(|namespace| namespace.as_bytes().to_vec())
.unwrap_or_default();
let separator = &b"::"[..];
let joined = match item_impl.trait_path() {
None => {
if namespace_bytes.is_empty() {
callable_ident
} else {
[namespace_bytes, callable_ident].join(separator)
}
}
Some(path) => {
let path_bytes = if path.leading_colon.is_some() {
let mut str_repr = path.to_token_stream().to_string();
str_repr.retain(|c| !c.is_whitespace());
str_repr.into_bytes()
} else {
path.segments
.last()
.expect("encountered empty trait path")
.ident
.to_string()
.into_bytes()
};
if namespace_bytes.is_empty() {
[path_bytes, callable_ident].join(separator)
} else {
[namespace_bytes, path_bytes, callable_ident].join(separator)
}
}
};
ir::Selector::compute(&joined)
}
pub(super) fn ensure_callable_invariants(
method_item: &syn::ImplItemFn,
kind: CallableKind,
) -> Result<(), syn::Error> {
let bad_visibility = match &method_item.vis {
syn::Visibility::Inherited => None,
syn::Visibility::Restricted(vis_restricted) => Some(vis_restricted.span()),
syn::Visibility::Public(_) => None,
};
if let Some(bad_visibility) = bad_visibility {
return Err(format_err!(
bad_visibility,
"ink! {}s must have public or inherited visibility",
kind
))
}
if !method_item.sig.generics.params.is_empty() {
return Err(format_err_spanned!(
method_item.sig.generics.params,
"ink! {}s must not be generic",
kind,
))
}
if method_item.sig.constness.is_some() {
return Err(format_err_spanned!(
method_item.sig.constness,
"ink! {}s must not be const",
kind,
))
}
if method_item.sig.asyncness.is_some() {
return Err(format_err_spanned!(
method_item.sig.asyncness,
"ink! {}s must not be async",
kind,
))
}
if method_item.sig.unsafety.is_some() {
return Err(format_err_spanned!(
method_item.sig.unsafety,
"ink! {}s must not be unsafe",
kind,
))
}
if method_item.sig.abi.is_some() {
return Err(format_err_spanned!(
method_item.sig.abi,
"ink! {}s must not have explicit ABI",
kind,
))
}
if method_item.sig.variadic.is_some() {
return Err(format_err_spanned!(
method_item.sig.variadic,
"ink! {}s must not be variadic",
kind,
))
}
if let Some(arg) = method_item.sig.inputs.iter().find(|input| {
match input {
syn::FnArg::Typed(pat) => !matches!(*pat.pat, syn::Pat::Ident(_)),
_ => false,
}
}) {
return Err(format_err_spanned!(
arg,
"ink! {} arguments must have an identifier",
kind
))
}
Ok(())
}
#[derive(Debug, Clone)]
pub enum Visibility {
Public(syn::Token![pub]),
Inherited,
}
impl quote::ToTokens for Visibility {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::Public(vis_public) => vis_public.to_tokens(tokens),
Self::Inherited => (),
}
}
}
impl Visibility {
pub fn is_pub(&self) -> bool {
matches!(self, Self::Public(_))
}
pub fn is_inherited(&self) -> bool {
matches!(self, Self::Inherited)
}
pub fn span(&self) -> Option<Span> {
match self {
Self::Public(vis_public) => Some(vis_public.span()),
Self::Inherited => None,
}
}
}
pub struct InputsIter<'a> {
iter: syn::punctuated::Iter<'a, syn::FnArg>,
}
impl<'a> InputsIter<'a> {
pub(crate) fn new<P>(inputs: &'a syn::punctuated::Punctuated<syn::FnArg, P>) -> Self {
Self {
iter: inputs.iter(),
}
}
}
impl<'a> From<&'a ir::Message> for InputsIter<'a> {
fn from(message: &'a ir::Message) -> Self {
Self::new(&message.item.sig.inputs)
}
}
impl<'a> From<&'a ir::Constructor> for InputsIter<'a> {
fn from(constructor: &'a ir::Constructor) -> Self {
Self::new(&constructor.item.sig.inputs)
}
}
impl<'a> Iterator for InputsIter<'a> {
type Item = &'a syn::PatType;
fn next(&mut self) -> Option<Self::Item> {
'repeat: loop {
match self.iter.next() {
None => return None,
Some(syn::FnArg::Typed(pat_typed)) => return Some(pat_typed),
Some(syn::FnArg::Receiver(_)) => continue 'repeat,
}
}
}
}
impl<'a> ExactSizeIterator for InputsIter<'a> {
fn len(&self) -> usize {
self.iter.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Debug;
pub enum ExpectedSelector {
Raw([u8; 4]),
Blake2(Vec<u8>),
}
impl From<[u8; 4]> for ExpectedSelector {
fn from(raw_selector: [u8; 4]) -> Self {
ExpectedSelector::Raw(raw_selector)
}
}
impl From<Vec<u8>> for ExpectedSelector {
fn from(blake2_input: Vec<u8>) -> Self {
ExpectedSelector::Blake2(blake2_input)
}
}
impl ExpectedSelector {
pub fn expected_selector(self) -> ir::Selector {
match self {
Self::Raw(raw_selector) => ir::Selector::from(raw_selector),
Self::Blake2(blake2_input) => ir::Selector::compute(&blake2_input),
}
}
}
fn assert_compose_selector<C, S>(
item_impl: syn::ItemImpl,
item_method: syn::ImplItemFn,
expected_selector: S,
) where
C: Callable + TryFrom<syn::ImplItemFn>,
<C as TryFrom<syn::ImplItemFn>>::Error: Debug,
S: Into<ExpectedSelector>,
{
assert_eq!(
compose_selector(
&<ir::ItemImpl as TryFrom<syn::ItemImpl>>::try_from(item_impl).unwrap(),
&<C as TryFrom<syn::ImplItemFn>>::try_from(item_method).unwrap(),
),
expected_selector.into().expected_selector(),
)
}
#[test]
fn compose_selector_works() {
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl)]
impl MyStorage {}
},
syn::parse_quote! {
#[ink(message)]
fn my_message(&self) {}
},
b"my_message".to_vec(),
);
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl)]
impl MyTrait for MyStorage {}
},
syn::parse_quote! {
#[ink(message)]
fn my_message(&self) {}
},
b"MyTrait::my_message".to_vec(),
);
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl)]
impl ::my::full::path::MyTrait for MyStorage {}
},
syn::parse_quote! {
#[ink(message)]
fn my_message(&self) {}
},
b"::my::full::path::MyTrait::my_message".to_vec(),
);
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl, namespace = "my_namespace")]
impl MyStorage {}
},
syn::parse_quote! {
#[ink(message)]
fn my_message(&self) {}
},
b"my_namespace::my_message".to_vec(),
);
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl)]
impl MyTrait for MyStorage {}
},
syn::parse_quote! {
#[ink(message, selector = 0xDEADBEEF)]
fn my_message(&self) {}
},
[0xDE, 0xAD, 0xBE, 0xEF],
);
assert_compose_selector::<ir::Message, _>(
syn::parse_quote! {
#[ink(impl)]
impl relative::path_to::MyTrait for MyStorage {}
},
syn::parse_quote! {
#[ink(message)]
fn my_message(&self) {}
},
b"MyTrait::my_message".to_vec(),
);
}
}