use proc_macro2::TokenStream;
use syn::{parse_quote, spanned::Spanned, Fields};
pub fn ident_from_impl(impl_code: &syn::ItemImpl) -> syn::Result<syn::Ident> {
last_segment_ident(&impl_code.self_ty)
}
pub fn ident_from_struct(struct_code: &syn::ItemStruct) -> syn::Ident {
struct_code.ident.clone()
}
pub fn function_name(sig: &syn::Signature) -> String {
sig.ident.to_string()
}
pub fn function_arg_names(sig: &syn::Signature) -> Vec<syn::Ident> {
sig.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(syn::PatType {
pat: box syn::Pat::Ident(pat),
..
}) => Some(pat.ident.clone()),
_ => None
})
.collect()
}
pub fn function_named_args(sig: &syn::Signature) -> Vec<&syn::FnArg> {
sig.inputs
.iter()
.filter(|arg| {
matches!(
arg,
syn::FnArg::Typed(syn::PatType {
pat: box syn::Pat::Ident(_),
..
})
)
})
.collect::<Vec<_>>()
}
pub fn function_typed_args(sig: &syn::Signature) -> Vec<syn::PatType> {
sig.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(pat_type) => Some(pat_type.clone()),
_ => None
})
.collect()
}
pub fn receiver_arg(sig: &syn::Signature) -> Option<syn::Receiver> {
sig.inputs.iter().find_map(|arg| match arg {
syn::FnArg::Receiver(receiver) => Some(receiver.clone()),
_ => None
})
}
pub fn function_return_type(sig: &syn::Signature) -> syn::ReturnType {
sig.output.clone()
}
pub fn struct_fields_ident(item: &syn::ItemStruct) -> syn::Result<Vec<syn::Ident>> {
map_fields(item, |f| {
f.ident.clone().ok_or(syn::Error::new(
f.span(),
"Invalid field. Module fields must be named"
))
})
}
pub fn struct_typed_fields(item: &syn::ItemStruct) -> syn::Result<Vec<(syn::Ident, syn::Type)>> {
map_fields(item, |f| {
f.ident
.clone()
.ok_or(syn::Error::new_spanned(
f,
"Invalid field. Module fields must be named"
))
.map(|i| (i, f.ty.clone()))
})
}
fn map_fields<T, F: FnMut(&syn::Field) -> syn::Result<T>>(
item: &syn::ItemStruct,
f: F
) -> syn::Result<Vec<T>> {
if item.fields.is_empty() {
return Ok(vec![]);
}
if let syn::Fields::Named(named) = &item.fields {
named.named.iter().map(f).collect()
} else {
Err(syn::Error::new_spanned(
&item.fields,
"Invalid fields. Module fields must be named"
))
}
}
pub fn visibility_pub() -> syn::Visibility {
parse_quote!(pub)
}
pub fn visibility_default() -> syn::Visibility {
parse_quote!()
}
pub fn docs_attrs(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.cloned()
.collect()
}
pub fn string_docs(attrs: &[syn::Attribute]) -> Vec<String> {
let attrs = docs_attrs(attrs);
let mut docs = Vec::new();
for attr in attrs {
if let syn::Meta::NameValue(nv) = &attr.meta {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(str),
..
}) = &nv.value
{
docs.push(str.value());
}
}
}
docs
}
pub fn last_segment_ident(ty: &syn::Type) -> syn::Result<syn::Ident> {
match ty {
syn::Type::Path(type_path) => type_path
.path
.segments
.last()
.map(|seg| seg.ident.clone())
.ok_or(syn::Error::new(type_path.span(), "Invalid type path")),
ty => Err(syn::Error::new(
ty.span(),
"Only support impl for type path"
))
}
}
pub fn as_casted_ty_stream(ty: &syn::Type, as_ty: syn::Type) -> TokenStream {
let ty = match ty {
syn::Type::Path(type_path) => {
let mut segments: syn::punctuated::Punctuated<syn::PathSegment, syn::Token![::]> =
type_path.path.segments.clone();
if let Some(ps) = segments.first_mut() {
if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments {
let generic_arg: syn::AngleBracketedGenericArguments = parse_quote!(::#ab);
ps.arguments = syn::PathArguments::AngleBracketed(generic_arg);
}
}
syn::Type::Path(syn::TypePath {
path: syn::Path {
leading_colon: None,
segments
},
..type_path.clone()
})
}
_ => ty.clone()
};
parse_quote!(<#ty as #as_ty>)
}
pub fn is_ref(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Reference(_))
}
pub fn extract_unit_variants(input: &syn::ItemEnum) -> syn::Result<Vec<syn::Variant>> {
let variants = &input.variants;
let is_valid = variants
.iter()
.all(|v| matches!(v.fields, syn::Fields::Unit));
if is_valid {
Ok(variants.into_iter().cloned().collect())
} else {
Err(syn::Error::new_spanned(
variants,
"Expected a unit enum variant."
))
}
}
pub fn transform_variants<F: Fn(String, Fields, u16, Vec<String>) -> TokenStream>(
variants: &[syn::Variant],
f: F
) -> TokenStream {
let mut discriminant = 0u16;
let variants = variants.iter().map(|v| {
let docs = string_docs(&v.attrs);
let name = v.ident.to_string();
let fields = v.fields.clone();
if let Some((_, syn::Expr::Lit(lit))) = &v.discriminant {
if let syn::Lit::Int(int) = &lit.lit {
discriminant = int.base10_parse().unwrap();
}
};
let result = f(name, fields, discriminant, docs);
discriminant += 1;
result
});
quote::quote!(odra::prelude::vec![#(#variants)*])
}