use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{
parse2, Error, FnArg, GenericArgument, Pat, PatType, Path, PathArguments, Result, ReturnType,
Signature, Type,
};
pub fn get_futurize_macro() -> Path {
parse2(quote::quote!(::zephyrus::macros::futurize)).unwrap()
}
pub fn get_parse_trait() -> Path {
parse2(quote::quote!(::zephyrus::prelude::Parse)).unwrap()
}
pub fn get_command_path() -> Path {
parse2(quote::quote!(::zephyrus::command::Command)).unwrap()
}
pub fn get_path(t: &Type) -> Result<&Path> {
match t {
Type::Path(p) => Ok(&p.path),
Type::Reference(r) => get_path(&r.elem),
_ => Err(Error::new(
t.span(),
"parameter must be a path to a context type",
)),
}
}
pub fn get_path_mut(t: &mut Type) -> Result<&mut Path> {
match t {
Type::Path(p) => Ok(&mut p.path),
Type::Reference(r) => get_path_mut(&mut r.elem),
_ => Err(Error::new(
t.span(),
"parameter must be a path to a context type",
)),
}
}
pub fn get_pat(arg: &FnArg) -> Result<&PatType> {
match arg {
FnArg::Typed(t) => Ok(t),
_ => Err(Error::new(
arg.span(),
"`self` parameter is not allowed here",
)),
}
}
pub fn get_pat_mut(arg: &mut FnArg) -> Result<&mut PatType> {
match arg {
FnArg::Typed(t) => Ok(t),
_ => Err(Error::new(
arg.span(),
"`self` parameter is not allowed here",
)),
}
}
pub fn get_ident(p: &Pat) -> Result<Ident> {
match p {
Pat::Ident(pi) => Ok(pi.ident.clone()),
_ => Err(Error::new(p.span(), "parameter must have an identifier")),
}
}
pub fn get_generic_arguments(path: &Path) -> Result<impl Iterator<Item = &GenericArgument> + '_> {
match &path.segments.last().unwrap().arguments {
PathArguments::None => Ok(Vec::new().into_iter()),
PathArguments::AngleBracketed(arguments) => {
Ok(arguments.args.iter().collect::<Vec<_>>().into_iter())
}
_ => Err(Error::new(
path.span(),
"context type cannot have generic parameters in parenthesis",
)),
}
}
pub fn get_context_type_and_ident(sig: &Signature) -> Result<(Ident, Type)> {
let ctx = match sig.inputs.iter().next() {
None => {
return Err(Error::new(
sig.inputs.span(),
"Expected SlashContext as first paramenter",
))
}
Some(c) => get_pat(c)?,
};
let ctx_ident = get_ident(&ctx.pat)?;
let path = get_path(&ctx.ty)?;
let mut args = get_generic_arguments(path)?;
let ty = loop {
match args.next() {
Some(GenericArgument::Lifetime(_)) => (),
Some(a) => {
break match a {
GenericArgument::Type(t) => {
if let Type::Infer(_) = t {
return Err(Error::new(
sig.inputs.span(),
"SlashContext must have a known type",
));
} else {
t.clone()
}
}
_ => {
return Err(Error::new(
sig.inputs.span(),
"SlashContext type must be a type",
))
}
}
}
None => {
return Err(Error::new(
sig.inputs.span(),
"SlashContext type must be set",
))
}
}
};
Ok((ctx_ident, ty))
}
pub fn check_return_type(ret: &ReturnType, out: TokenStream) -> Result<()> {
let ty = match &ret {
ReturnType::Default => syn::parse2::<Type>(quote::quote!(()))?,
ReturnType::Type(_, ty) => syn::parse2::<Type>(quote::quote!(#ty))?,
};
let out = parse2(quote::quote!(#out))?;
if ty != out {
return Err(Error::new(
ret.span(),
format!(
"Expected {} as return type, got {}",
out.to_token_stream(),
ty.to_token_stream()
),
));
}
Ok(())
}