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, PartialEq, Eq)]
pub struct Constructor {
pub(super) item: syn::ImplItemMethod,
selector: Option<ir::Selector>,
}
impl quote::ToTokens for Constructor {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.item.to_tokens(tokens)
}
}
impl Constructor {
fn type_is_self_val(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Path(syn::TypePath {
qself: None,
path
}) if path.is_ident("Self"))
}
fn ensure_valid_return_type(
method_item: &syn::ImplItemMethod,
) -> Result<(), syn::Error> {
match &method_item.sig.output {
syn::ReturnType::Default => {
return Err(format_err_spanned!(
&method_item.sig,
"missing return for pro! constructor",
))
}
syn::ReturnType::Type(_, return_type) => {
if !Self::type_is_self_val(return_type.as_ref()) {
return Err(format_err_spanned!(
return_type,
"pro! constructors must return Self",
))
}
}
}
Ok(())
}
fn ensure_no_self_receiver(
method_item: &syn::ImplItemMethod,
) -> Result<(), syn::Error> {
match method_item.sig.inputs.iter().next() {
None | Some(syn::FnArg::Typed(_)) => (),
Some(syn::FnArg::Receiver(receiver)) => {
return Err(format_err_spanned!(
receiver,
"pro! constructors must have no `self` receiver",
))
}
}
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::Constructor,
|arg| {
match arg.kind() {
ir::AttributeArg::Constructor | ir::AttributeArg::Selector(_) => {
Ok(())
}
ir::AttributeArg::Payable => {
Err(Some(format_err!(
arg.span(),
"constructors are implicitly payable"
)))
}
_ => Err(None),
}
},
)
}
}
impl TryFrom<syn::ImplItemMethod> for Constructor {
type Error = syn::Error;
fn try_from(method_item: syn::ImplItemMethod) -> Result<Self, Self::Error> {
ensure_callable_invariants(&method_item, CallableKind::Constructor)?;
Self::ensure_valid_return_type(&method_item)?;
Self::ensure_no_self_receiver(&method_item)?;
let (pro_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?;
let selector = pro_attrs.selector();
Ok(Constructor {
selector,
item: syn::ImplItemMethod {
attrs: other_attrs,
..method_item
},
})
}
}
impl Callable for Constructor {
fn kind(&self) -> CallableKind {
CallableKind::Constructor
}
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 {
true
}
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! constructor"),
}
}
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 Constructor {
pub fn attrs(&self) -> &[syn::Attribute] {
&self.item.attrs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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(constructor)]
fn my_constructor() -> Self {}
},
),
(
expected_inputs!(a: i32),
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(a: i32) -> Self {}
},
),
(
expected_inputs!(a: i32, b: u64, c: [u8; 32]),
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(a: i32, b: u64, c: [u8; 32]) -> Self {}
},
),
];
for (expected_inputs, item_method) in test_inputs {
let actual_inputs = <ir::Constructor 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 visibility_works() {
let test_inputs: Vec<(bool, syn::ImplItemMethod)> = vec![
(
false,
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor() -> Self {}
},
),
(
true,
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() -> Self {}
},
),
];
for (is_pub, item_method) in test_inputs {
let visibility = <ir::Constructor 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(constructor)]
fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(input1: i32, input2: i64, input3: u32, input4: u64) -> Self {}
},
];
for item_method in item_methods {
assert!(<ir::Constructor as TryFrom<_>>::try_from(item_method).is_ok());
}
}
fn assert_try_from_fails(item_method: syn::ImplItemMethod, expected_err: &str) {
assert_eq!(
<ir::Constructor as TryFrom<_>>::try_from(item_method)
.map_err(|err| err.to_string()),
Err(expected_err.to_string()),
);
}
#[test]
fn try_from_missing_return_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor() {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "missing return for pro! constructor")
}
}
#[test]
fn try_from_invalid_return_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor() -> &Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() -> &mut Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() -> i32 {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor() -> Result<Self, ()> {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must return Self")
}
}
#[test]
fn try_from_invalid_self_receiver_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(&self) -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor(&mut self) -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor(self) -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor(mut self) -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"pro! constructors must have no `self` receiver",
)
}
}
#[test]
fn try_from_generics_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor<T>() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub fn my_constructor<T>() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must not be generic")
}
}
#[test]
fn try_from_const_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
const fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub const fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must not be const")
}
}
#[test]
fn try_from_async_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
async fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
async fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must not be async")
}
}
#[test]
fn try_from_unsafe_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
unsafe fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
unsafe fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must not be unsafe")
}
}
#[test]
fn try_from_explicit_abi_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
extern "C" fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
extern "C" fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must have explicit ABI")
}
}
#[test]
fn try_from_variadic_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(...) -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
fn my_constructor(...) -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(item_method, "pro! constructors must not be variadic")
}
}
#[test]
fn try_from_visibility_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor)]
crate fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
pub(in my::path) fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"pro! constructors must have public or inherited visibility",
)
}
}
#[test]
fn conflicting_attributes_fails() {
let item_methods: Vec<syn::ImplItemMethod> = vec![
syn::parse_quote! {
#[pro(constructor, storage)]
fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor, namespace = "my_namespace")]
fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
#[pro(event)]
fn my_constructor() -> Self {}
},
syn::parse_quote! {
#[pro(constructor)]
#[pro(payable)]
fn my_constructor() -> Self {}
},
];
for item_method in item_methods {
assert_try_from_fails(
item_method,
"encountered conflicting pro! attribute argument",
)
}
}
}