use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, Token};
#[derive(Debug)]
pub struct MethodProto {
pub name: &'static str,
pub args: &'static [&'static str],
pub proto: &'static str,
pub with_self: bool,
pub with_result: bool,
}
impl MethodProto {
const EMPTY_ARGS: &'static [&'static str] = &[];
pub const fn new(name: &'static str, proto: &'static str) -> Self {
MethodProto {
name,
proto,
args: MethodProto::EMPTY_ARGS,
with_self: false,
with_result: true,
}
}
pub const fn args(mut self, args: &'static [&'static str]) -> MethodProto {
self.args = args;
self
}
pub const fn has_self(mut self) -> MethodProto {
self.with_self = true;
self
}
pub const fn no_result(mut self) -> MethodProto {
self.with_result = false;
self
}
}
pub(crate) fn impl_method_proto(
cls: &syn::Type,
sig: &mut syn::Signature,
module: &syn::Path,
meth: &MethodProto,
) -> syn::Result<TokenStream> {
let proto: syn::Path = syn::parse_str(meth.proto).unwrap();
let mut impl_types = Vec::new();
for (i, arg) in meth.args.iter().enumerate() {
let idx = if meth.with_self { i + 1 } else { i };
let arg_name = syn::Ident::new(arg, Span::call_site());
let input = match &mut sig.inputs[idx] {
syn::FnArg::Typed(input) => input,
receiver @ syn::FnArg::Receiver(_) => {
bail_spanned!(receiver.span() => "unexpected receiver in #[pyproto]")
}
};
let decl = syn::parse_quote! { <#cls as #module::#proto<'p>>::#arg_name };
let mut arg_ty = match crate::utils::option_type_argument(&input.ty) {
Some(arg_ty) => {
let arg_ty = arg_ty.clone();
*input.ty = syn::parse_quote! { Option<#decl> };
arg_ty
}
None => std::mem::replace(&mut *input.ty, decl),
};
insert_lifetime(&mut arg_ty);
impl_types.push(quote! {type #arg_name = #arg_ty;});
}
if meth.with_self {
modify_self_ty(sig);
}
let res_type_def = if meth.with_result {
let ret_ty = match &sig.output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => {
let mut ty = ty.clone();
insert_lifetime(&mut ty);
ty.to_token_stream()
}
};
sig.output = syn::parse_quote! { -> <#cls as #module::#proto<'p>>::Result };
quote! { type Result = #ret_ty; }
} else {
proc_macro2::TokenStream::new()
};
Ok(quote! {
impl<'p> #module::#proto<'p> for #cls {
#(#impl_types)*
#res_type_def
}
})
}
fn insert_lifetime(ty: &mut syn::Type) {
fn insert_lifetime_for_path(path: &mut syn::TypePath) {
if let Some(seg) = path.path.segments.last_mut() {
if let syn::PathArguments::AngleBracketed(ref mut args) = seg.arguments {
let mut has_lifetime = false;
for arg in &mut args.args {
match arg {
syn::GenericArgument::Type(ref mut ty) => insert_lifetime(ty),
syn::GenericArgument::Lifetime(_) => has_lifetime = true,
_ => {}
}
}
if !has_lifetime && (seg.ident == "PyRef" || seg.ident == "PyRefMut") {
args.args.insert(0, syn::parse_quote! {'p});
}
}
}
}
match ty {
syn::Type::Reference(ref mut r) => {
r.lifetime.get_or_insert(syn::parse_quote! {'p});
insert_lifetime(&mut *r.elem);
}
syn::Type::Path(ref mut path) => insert_lifetime_for_path(path),
_ => {}
}
}
fn modify_self_ty(sig: &mut syn::Signature) {
match sig.inputs[0] {
syn::FnArg::Receiver(ref mut slf) => {
slf.reference = Some((Token), syn::parse_quote! {'p}));
}
syn::FnArg::Typed(_) => {}
}
}