use super::{
ensure_callable_invariants,
Callable,
CallableKind,
InputsIter,
Visibility,
};
use crate::ir;
use core::convert::TryFrom;
use proc_macro2::{
Ident,
Span,
};
use syn::spanned::Spanned as _;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Receiver {
Ref,
RefMut,
}
impl quote::ToTokens for Receiver {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let receiver = match self {
Self::Ref => quote::quote! { &self },
Self::RefMut => quote::quote! { &mut self },
};
tokens.extend(receiver);
}
}
impl Receiver {
pub fn is_ref(self) -> bool {
matches!(self, Self::Ref)
}
pub fn is_ref_mut(self) -> bool {
matches!(self, Self::RefMut)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Message {
pub(super) item: syn::ImplItemMethod,
is_payable: bool,
selector: Option<ir::Selector>,
}
impl quote::ToTokens for Message {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.item.to_tokens(tokens)
}
}
impl Message {
fn ensure_receiver_is_self_ref(
method_item: &syn::ImplItemMethod,
) -> Result<(), syn::Error> {
let mut fn_args = method_item.sig.inputs.iter();
fn bail(span: Span) -> syn::Error {
format_err!(
span,
"pro! messages must have `&self` or `&mut self` receiver",
)
}
match fn_args.next() {
None => return Err(bail(method_item.span())),
Some(syn::FnArg::Typed(pat_typed)) => return Err(bail(pat_typed.span())),
Some(syn::FnArg::Receiver(receiver)) => {
if receiver.reference.is_none() {
return Err(bail(receiver.span()))
}
}
}
Ok(())
}
fn ensure_not_return_self(
method_item: &syn::ImplItemMethod,
) -> Result<(), syn::Error> {
match &method_item.sig.output {
syn::ReturnType::Default => (),
syn::ReturnType::Type(_arrow, ret_type) => {
if let syn::Type::Path(type_path) = &**ret_type {
if type_path.path.is_ident("Self") {
return Err(format_err!(
ret_type,
"pro! messages must not return `Self`"
))
}
}
}
}
Ok(())
}
fn sanitize_attributes(
method_item: &syn::ImplItemMethod,
) -> Result<(ir::ProAttribute, Vec<syn::Attribute>), syn::Error> {
ir::sanitize_attributes(
method_item.span(),
method_item.attrs.clone(),
&ir::AttributeArgKind::Message,
|arg| {
match arg.kind() {
ir::AttributeArg::Message
| ir::AttributeArg::Payable
| ir::AttributeArg::Selector(_) => Ok(()),
_ => Err(None),
}
},
)
}
}
impl TryFrom<syn::ImplItemMethod> for Message {
type Error = syn::Error;
fn try_from(method_item: syn::ImplItemMethod) -> Result<Self, Self::Error> {
ensure_callable_invariants(&method_item, CallableKind::Message)?;
Self::ensure_receiver_is_self_ref(&method_item)?;
Self::ensure_not_return_self(&method_item)?;
let (pro_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?;
let is_payable = pro_attrs.is_payable();
let selector = pro_attrs.selector();
Ok(Self {
is_payable,
selector,
item: syn::ImplItemMethod {
attrs: other_attrs,
..method_item
},
})
}
}
impl Callable for Message {
fn kind(&self) -> CallableKind {
CallableKind::Message
}
fn ident(&self) -> &Ident {
&self.item.sig.ident
}
fn user_provided_selector(&self) -> Option<&ir::Selector> {
self.selector.as_ref()
}
fn is_payable(&self) -> bool {
self.is_payable
}
fn visibility(&self) -> Visibility {
match &self.item.vis {
syn::Visibility::Public(vis_public) => Visibility::Public(vis_public.clone()),
syn::Visibility::Inherited => Visibility::Inherited,
_ => unreachable!("encountered invalid visibility for pro! message"),
}
}
fn inputs(&self) -> InputsIter {
InputsIter::from(self)
}
fn inputs_span(&self) -> Span {
self.item.sig.inputs.span()
}
fn statements(&self) -> &[syn::Stmt] {
&self.item.block.stmts
}
}
impl Message {
pub fn attrs(&self) -> &[syn::Attribute] {
&self.item.attrs
}
pub fn receiver(&self) -> Receiver {
match self.item.sig.inputs.iter().next() {
Some(syn::FnArg::Receiver(receiver)) => {
debug_assert!(receiver.reference.is_some());
if receiver.mutability.is_some() {
Receiver::RefMut
} else {
Receiver::Ref
}
}
_ => unreachable!("encountered invalid receiver argument for pro! message"),
}
}
pub fn output(&self) -> Option<&syn::Type> {
match &self.item.sig.output {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, return_type) => Some(return_type),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_works() {
let test_inputs: Vec<(Option<syn::Type>, syn::ImplItemMethod)> = vec![
(
None,
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
),
(
Some(syn::parse_quote! { i32 }),
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) -> i32 {}
},
),
(
Some(syn::parse_quote! { (i32, u64, bool) }),
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) -> (i32, u64, bool) {}
},
),
];
for (expected_output, item_method) in test_inputs {
let actual_output = <ir::Message as TryFrom<_>>::try_from(item_method)
.unwrap()
.output()
.cloned();
assert_eq!(actual_output, expected_output);
}
}
#[test]
fn inputs_works() {
macro_rules! expected_inputs {
( $( $name:ident: $ty:ty ),* ) => {{
vec![
$(
syn::parse_quote! {
$name: $ty
}
),*
]
}};
}
let test_inputs: Vec<(Vec<syn::FnArg>, syn::ImplItemMethod)> = vec![
(
expected_inputs!(),
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
),
(
expected_inputs!(a: i32),
syn::parse_quote! {
#[pro(message)]
fn my_message(&self, a: i32) {}
},
),
(
expected_inputs!(a: i32, b: u64, c: [u8; 32]),
syn::parse_quote! {
#[pro(message)]
fn my_message(&self, a: i32, b: u64, c: [u8; 32]) {}
},
),
];
for (expected_inputs, item_method) in test_inputs {
let actual_inputs = <ir::Message as TryFrom<_>>::try_from(item_method)
.unwrap()
.inputs()
.cloned()
.map(|pat_type| syn::FnArg::Typed(pat_type))
.collect::<Vec<_>>();
assert_eq!(actual_inputs, expected_inputs);
}
}
#[test]
fn is_payable_works() {
let test_inputs: Vec<(bool, syn::ImplItemMethod)> = vec![
(
false,
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
),
(
true,
syn::parse_quote! {
#[pro(message, payable)]
pub fn my_message(&self) {}
},
),
(
true,
syn::parse_quote! {
#[pro(message)]
#[pro(payable)]
pub fn my_message(&self) {}
},
),
(
true,
syn::parse_quote! {
#[pro(message)]
#[pro(selector = "0xDEADBEEF", payable)]
pub fn my_message(&self) {}
},
),
];
for (expect_payable, item_method) in test_inputs {
let is_payable = <ir::Message as TryFrom<_>>::try_from(item_method)
.unwrap()
.is_payable();
assert_eq!(is_payable, expect_payable);
}
}
#[test]
fn receiver_works() {
let test_inputs: Vec<(Receiver, syn::ImplItemMethod)> = vec![
(
Receiver::Ref,
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
),
(
Receiver::RefMut,
syn::parse_quote! {
#[pro(message, payable)]
fn my_message(&mut self) {}
},
),
];
for (expected_receiver, item_method) in test_inputs {
let actual_receiver = <ir::Message as TryFrom<_>>::try_from(item_method)
.unwrap()
.receiver();
assert_eq!(actual_receiver, expected_receiver);
}
}
#[test]
fn visibility_works() {
let test_inputs: Vec<(bool, syn::ImplItemMethod)> = vec![
(
false,
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
),
(
true,
syn::parse_quote! {
#[pro(message)]
pub fn my_message(&self) {}
},
),
(
false,
syn::parse_quote! {
#[pro(message)]
fn my_message(&mut self) {}
},
),
(
true,
syn::parse_quote! {
#[pro(message)]
pub fn my_message(&mut self) {}
},
),
];
for (is_pub, item_method) in test_inputs {
let visibility = <ir::Message as TryFrom<_>>::try_from(item_method)
.unwrap()
.visibility();
assert_eq!(visibility.is_pub(), is_pub);
assert_eq!(visibility.is_inherited(), !is_pub);
}
}
#[test]
fn try_from_works() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(&mut self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message(&mut self) {}
},
syn::parse_quote! {
#[pro(message, payable)]
fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message, payable)]
fn my_message(&mut self) {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(&self, input1: i32, input2: i64, input3: u32, input4: u64) -> bool {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(&mut self, input1: i32, input2: i64, input3: u32, input4: u64) -> bool {}
},
];
for item_method in item_methods {
assert!(<ir::Message as TryFrom<_>>::try_from(item_method).is_ok());
}
}
fn assert_try_from_fails(item_method: syn::ImplItemMethod, expected_err: &str) {
assert_eq!(
<ir::Message as TryFrom<_>>::try_from(item_method)
.map_err(|err| err.to_string()),
Err(expected_err.to_string()),
);
}
#[test]
fn try_from_generics_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
fn my_message<T>(&self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message<T>(&self) {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message<T>(&mut self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message<T>(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must not be generic")
}
}
#[test]
fn try_from_receiver_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
fn my_message() {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message(mut self) {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(this: &Self) {}
},
syn::parse_quote! {
#[pro(message)]
pub fn my_message(this: &mut Self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"pro! messages must have `&self` or `&mut self` receiver",
)
}
}
#[test]
fn try_from_const_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
const fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
const fn my_message(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must not be const")
}
}
#[test]
fn try_from_async_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
async fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
async fn my_message(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must not be async")
}
}
#[test]
fn try_from_unsafe_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
unsafe fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
unsafe fn my_message(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must not be unsafe")
}
}
#[test]
fn try_from_explicit_abi_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
extern "C" fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
extern "C" fn my_message(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must have explicit ABI")
}
}
#[test]
fn try_from_variadic_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
fn my_message(&self, ...) {}
},
syn::parse_quote! {
#[pro(message)]
fn my_message(&mut self, ...) {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! messages must not be variadic")
}
}
#[test]
fn try_from_visibility_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message)]
crate fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
crate fn my_message(&mut self) {}
},
syn::parse_quote! {
#[pro(message)]
pub(in my::path) fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
pub(in my::path) fn my_message(&mut self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"pro! messages must have public or inherited visibility",
)
}
}
#[test]
fn conflicting_attributes_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(message, storage)]
fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message, namespace = "my_namespace")]
fn my_message(&self) {}
},
syn::parse_quote! {
#[pro(message)]
#[pro(event)]
fn my_message(&self) {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"encountered conflicting pro! attribute argument",
)
}
}
}