use core::cmp::Ordering;
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Error;
pub fn cmp_sig_by_name(a: &syn::Signature, b: &syn::Signature) -> Ordering {
cmp_ident(&a.ident, &b.ident)
}
pub fn cmp_ident(a: &syn::Ident, b: &syn::Ident) -> Ordering {
a.to_string().cmp(&b.to_string())
}
pub fn is_receiver(fn_arg: &syn::FnArg) -> bool {
matches!(fn_arg, syn::FnArg::Receiver(_))
}
pub fn arg_pat(fn_arg: &syn::FnArg) -> Option<&syn::Pat> {
match fn_arg {
syn::FnArg::Typed(pat_type) => Some(&pat_type.pat),
syn::FnArg::Receiver(_) => None,
}
}
pub fn method_args(
sig: &syn::Signature,
) -> impl Iterator<Item = (&syn::Ident, &syn::Type)> {
fn arg_from_pat_type(
pat_type: &syn::PatType,
) -> (&syn::Ident, &syn::Type) {
let ident = if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
&pat_ident.ident
} else {
panic!(
"internal error: non-ident arg pattern should have been \
rejected by validate_method_arg_patterns"
);
};
let ty = pat_type.ty.as_ref();
(ident, ty)
}
sig.inputs.iter().filter_map(|fn_arg| match fn_arg {
syn::FnArg::Typed(pat_type) => Some(arg_from_pat_type(pat_type)),
_ => None,
})
}
pub fn method_arg_names(
sig: &syn::Signature,
) -> impl Iterator<Item = &syn::Ident> {
method_args(sig).map(|(ident, _ty)| ident)
}
pub fn method_arg_name_list(sig: &syn::Signature) -> TokenStream {
let idents = method_arg_names(sig);
quote! { #( #idents ),* }
}
pub fn error<S, M>(spanned: &S, msg: M) -> Error
where
S: syn::spanned::Spanned,
M: AsRef<str>,
{
Error::new(spanned.span(), msg.as_ref())
}
pub fn validate_no_generics(
generics: &syn::Generics,
allow_lifetimes: bool,
subject: &str,
) -> Result<(), Error> {
for param in &generics.params {
if allow_lifetimes && matches!(param, syn::GenericParam::Lifetime(_)) {
continue;
}
let msg = if allow_lifetimes {
format!(
"generic parameters are not supported on {subject} \
(lifetimes are allowed)"
)
} else {
format!(
"generic parameters (including lifetimes) are not \
supported on {subject}"
)
};
return Err(error(param, msg));
}
Ok(())
}
pub fn validate_version_string(
span: proc_macro2::Span,
version: &str,
) -> Result<(), Error> {
if thruput_contract_util::semver::try_major_version_from_string(version)
.is_none()
{
return Err(Error::new(
span,
format!(
"invalid version {version:?}: expected a full semver \
triplet like \"1.0.0\" (optionally with -PRERELEASE \
and +BUILD suffixes)"
),
));
}
Ok(())
}
pub fn validate_ident_string(
span: proc_macro2::Span,
value: &str,
attr: &str,
) -> Result<(), Error> {
if syn::parse_str::<syn::Ident>(value).is_err() {
return Err(Error::new(
span,
format!(
"{attr} = {value:?} must be a valid Rust identifier \
(it is used to name generated items)"
),
));
}
Ok(())
}
pub fn validate_spec_name(
span: proc_macro2::Span,
name: &str,
) -> Result<(), Error> {
if name.contains("::") {
return Err(Error::new(
span,
format!(
"name = {name:?} must not contain `::` (it replaces only \
the final segment of the spec id; the module path comes \
from where the type is declared)"
),
));
}
Ok(())
}
pub fn validate_wire_crate_name(
span: proc_macro2::Span,
name: &str,
) -> syn::Result<()> {
if name.is_empty()
|| !name
.bytes()
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
{
return Err(syn::Error::new(
span,
"wire_crate must be a non-empty package label containing only ASCII letters, digits, '-' or '_'",
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_spec_name_without_a_path_separator_is_accepted() {
validate_spec_name(proc_macro2::Span::call_site(), "SpecPayload")
.unwrap();
}
#[test]
fn a_spec_name_carrying_a_path_separator_anywhere_is_rejected() {
for name in ["spelled::Out", "::Out", "Out::", "::", "a::b::C"] {
let err = validate_spec_name(proc_macro2::Span::call_site(), name)
.unwrap_err();
assert!(
err.to_string().contains("must not contain `::`"),
"{name:?}: {err}"
);
}
}
}