use crate::konst::ConstSpec;
use crate::method::{FnArg, FnSpec, FnType, SelfType};
use crate::utils;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::ext::IdentExt;
pub enum PropertyType<'a> {
Descriptor(&'a syn::Field),
Function(&'a FnSpec<'a>),
}
pub fn gen_py_method(
cls: &syn::Type,
sig: &mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<TokenStream> {
check_generic(sig)?;
let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?;
Ok(match &spec.tp {
FnType::Fn(self_ty) => impl_py_method_def(&spec, &impl_wrap(cls, &spec, self_ty, true)),
FnType::FnNew => impl_py_method_def_new(&spec, &impl_wrap_new(cls, &spec)),
FnType::FnCall(self_ty) => {
impl_py_method_def_call(&spec, &impl_wrap(cls, &spec, self_ty, false))
}
FnType::FnClass => impl_py_method_def_class(&spec, &impl_wrap_class(cls, &spec)),
FnType::FnStatic => impl_py_method_def_static(&spec, &impl_wrap_static(cls, &spec)),
FnType::ClassAttribute => {
impl_py_method_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
}
FnType::Getter(self_ty) => impl_py_getter_def(
&spec.python_name,
&spec.doc,
&impl_wrap_getter(cls, PropertyType::Function(&spec), self_ty)?,
),
FnType::Setter(self_ty) => impl_py_setter_def(
&spec.python_name,
&spec.doc,
&impl_wrap_setter(cls, PropertyType::Function(&spec), self_ty)?,
),
})
}
fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
let err_msg = |typ| format!("A Python method can't have a generic {} parameter", typ);
for param in &sig.generics.params {
match param {
syn::GenericParam::Lifetime(_) => {}
syn::GenericParam::Type(_) => {
return Err(syn::Error::new_spanned(param, err_msg("type")));
}
syn::GenericParam::Const(_) => {
return Err(syn::Error::new_spanned(param, err_msg("const")));
}
}
}
Ok(())
}
pub fn gen_py_const(
cls: &syn::Type,
name: &syn::Ident,
attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<Option<TokenStream>> {
let spec = ConstSpec::parse(name, attrs)?;
if spec.is_class_attr {
let wrapper = quote! {
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
pyo3::IntoPy::into_py(#cls::#name, py)
}
};
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
}
Ok(None)
}
pub fn impl_wrap(
cls: &syn::Type,
spec: &FnSpec<'_>,
self_ty: &SelfType,
noargs: bool,
) -> TokenStream {
let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls);
impl_wrap_common(cls, spec, noargs, slf, body)
}
fn impl_wrap_common(
cls: &syn::Type,
spec: &FnSpec<'_>,
noargs: bool,
slf: TokenStream,
body: TokenStream,
) -> TokenStream {
let python_name = &spec.python_name;
if spec.args.is_empty() && noargs {
quote! {
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(
stringify!(#cls), ".", stringify!(#python_name), "()");
pyo3::callback_body_without_convert!(_py, {
#slf
pyo3::callback::convert(_py, #body)
})
}
}
} else {
let body = impl_arg_params(&spec, body);
quote! {
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(
stringify!(#cls), ".", stringify!(#python_name), "()");
pyo3::callback_body_without_convert!(_py, {
#slf
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
pyo3::callback::convert(_py, #body)
})
}
}
}
}
pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
let python_name = &spec.python_name;
let cb = impl_call(cls, &spec);
let body = impl_arg_params(&spec, cb);
let slf = self_ty.receiver(cls);
quote! {
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
#slf
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
pyo3::callback::convert(_py, #body)
})
}
}
}
pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name;
let python_name = &spec.python_name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(#(#names),*) };
let body = impl_arg_params(spec, cb);
quote! {
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
subtype: *mut pyo3::ffi::PyTypeObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
use pyo3::type_object::PyTypeInfo;
use std::convert::TryFrom;
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
let initializer = pyo3::PyClassInitializer::try_from(#body)?;
let cell = initializer.create_cell_from_subtype(_py, subtype)?;
Ok(cell as *mut pyo3::ffi::PyObject)
})
}
}
}
pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name;
let python_name = &spec.python_name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(&_cls, #(#names),*) };
let body = impl_arg_params(spec, cb);
quote! {
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_cls: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
let _cls = pyo3::types::PyType::from_type_ptr(_py, _cls as *mut pyo3::ffi::PyTypeObject);
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
pyo3::callback::convert(_py, #body)
})
}
}
}
pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name;
let python_name = &spec.python_name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(#(#names),*) };
let body = impl_arg_params(spec, cb);
quote! {
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
pyo3::callback::convert(_py, #body)
})
}
}
}
pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name;
let cb = quote! { #cls::#name() };
quote! {
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
pyo3::IntoPy::into_py(#cb, py)
}
}
}
fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
let (py_arg, args) = split_off_python_arg(&spec.args);
if !args.is_empty() {
return Err(syn::Error::new_spanned(
args[0].ty,
"Getter function can only have one argument of type pyo3::Python",
));
}
let name = &spec.name;
let fncall = if py_arg.is_some() {
quote!(#cls::#name(_slf, _py))
} else {
quote!(#cls::#name(_slf))
};
Ok(fncall)
}
pub(crate) fn impl_wrap_getter(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
) -> syn::Result<TokenStream> {
let (python_name, getter_impl) = match property_type {
PropertyType::Descriptor(field) => {
let name = field.ident.as_ref().unwrap();
(
name.unraw(),
quote!({
_slf.#name.clone()
}),
)
}
PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_getter(cls, spec)?),
};
let slf = self_ty.receiver(cls);
Ok(quote! {
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
{
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
#slf
pyo3::callback::convert(_py, #getter_impl)
})
}
})
}
fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream> {
let (py_arg, args) = split_off_python_arg(&spec.args);
if args.is_empty() {
return Err(syn::Error::new_spanned(
&spec.name,
"Setter function expected to have one argument",
));
} else if args.len() > 1 {
return Err(syn::Error::new_spanned(
&args[1].ty,
"Setter function can have at most two arguments: one of pyo3::Python, and one other",
));
}
let name = &spec.name;
let fncall = if py_arg.is_some() {
quote!(#cls::#name(_slf, _py, _val))
} else {
quote!(#cls::#name(_slf, _val))
};
Ok(fncall)
}
pub(crate) fn impl_wrap_setter(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
) -> syn::Result<TokenStream> {
let (python_name, setter_impl) = match property_type {
PropertyType::Descriptor(field) => {
let name = field.ident.as_ref().unwrap();
(name.unraw(), quote!({ _slf.#name = _val; }))
}
PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_setter(cls, spec)?),
};
let slf = self_ty.receiver(cls);
Ok(quote! {
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_value: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> pyo3::libc::c_int
{
const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()");
pyo3::callback_body_without_convert!(_py, {
#slf
let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value);
let _val = pyo3::FromPyObject::extract(_value)?;
pyo3::callback::convert(_py, #setter_impl)
})
}
})
}
pub fn get_arg_names(spec: &FnSpec) -> Vec<syn::Ident> {
(0..spec.args.len())
.map(|pos| syn::Ident::new(&format!("arg{}", pos), Span::call_site()))
.collect()
}
fn impl_call(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let fname = &spec.name;
let names = get_arg_names(spec);
quote! { #cls::#fname(_slf, #(#names),*) }
}
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
if spec.args.is_empty() {
return quote! {
#body
};
}
let mut params = Vec::new();
for arg in spec.args.iter() {
if arg.py || spec.is_args(&arg.name) || spec.is_kwargs(&arg.name) {
continue;
}
let name = arg.name;
let kwonly = spec.is_kw_only(&arg.name);
let opt = arg.optional.is_some() || spec.default_value(&arg.name).is_some();
params.push(quote! {
pyo3::derive_utils::ParamDescription {
name: stringify!(#name),
is_optional: #opt,
kw_only: #kwonly
}
});
}
let mut param_conversion = Vec::new();
let mut option_pos = 0;
for (idx, arg) in spec.args.iter().enumerate() {
param_conversion.push(impl_arg_param(&arg, &spec, idx, &mut option_pos));
}
let (mut accept_args, mut accept_kwargs) = (false, false);
for s in spec.attrs.iter() {
use crate::pyfunction::Argument;
match s {
Argument::VarArgs(_) => accept_args = true,
Argument::KeywordArgs(_) => accept_kwargs = true,
_ => continue,
}
}
let num_normal_params = params.len();
quote! {{
const PARAMS: &'static [pyo3::derive_utils::ParamDescription] = &[
#(#params),*
];
let mut output = [None; #num_normal_params];
let mut _args = _args;
let mut _kwargs = _kwargs;
let (_args, _kwargs) = pyo3::derive_utils::parse_fn_args(
Some(_LOCATION),
PARAMS,
_args,
_kwargs,
#accept_args,
#accept_kwargs,
&mut output
)?;
#(#param_conversion)*
#body
}}
}
fn impl_arg_param(
arg: &FnArg<'_>,
spec: &FnSpec<'_>,
idx: usize,
option_pos: &mut usize,
) -> TokenStream {
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
if arg.py {
return quote! {
let #arg_name = _py;
};
}
let ty = arg.ty;
let name = arg.name;
if spec.is_args(&name) {
return quote! {
let #arg_name = <#ty as pyo3::FromPyObject>::extract(_args.as_ref())?;
};
} else if spec.is_kwargs(&name) {
return quote! {
let #arg_name = _kwargs;
};
}
let arg_value = quote!(output[#option_pos]);
*option_pos += 1;
return if let Some(ty) = arg.optional.as_ref() {
let default = if let Some(d) = spec.default_value(name).filter(|d| d.to_string() != "None")
{
quote! { Some(#d) }
} else {
quote! { None }
};
if let syn::Type::Reference(tref) = ty {
let (tref, mut_) = tref_preprocess(tref);
let tmp_as_deref = if mut_.is_some() {
quote! { _tmp.as_mut().map(std::ops::DerefMut::deref_mut) }
} else {
quote! { _tmp.as_ref().map(std::ops::Deref::deref) }
};
quote! {
let #mut_ _tmp = match #arg_value {
Some(_obj) => {
_obj.extract::<Option<<#tref as pyo3::derive_utils::ExtractExt>::Target>>()?
},
None => #default,
};
let #arg_name = #tmp_as_deref;
}
} else {
quote! {
let #arg_name = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
}
}
} else if let Some(default) = spec.default_value(name) {
quote! {
let #arg_name = match #arg_value {
Some(_obj) => _obj.extract()?,
None => #default,
};
}
} else if let syn::Type::Reference(tref) = arg.ty {
let (tref, mut_) = tref_preprocess(tref);
quote! {
let #mut_ _tmp: <#tref as pyo3::derive_utils::ExtractExt>::Target
= #arg_value.unwrap().extract()?;
let #arg_name = &#mut_ *_tmp;
}
} else {
quote! {
let #arg_name = #arg_value.unwrap().extract()?;
}
};
fn tref_preprocess(tref: &syn::TypeReference) -> (syn::TypeReference, Option<syn::token::Mut>) {
let mut tref = tref.to_owned();
tref.lifetime = None;
let mut_ = tref.mutability;
(tref, mut_)
}
}
pub fn impl_py_method_def(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
let doc = &spec.doc;
if spec.args.is_empty() {
quote! {
pyo3::class::PyMethodDefType::Method({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunction(__wrap),
ml_flags: pyo3::ffi::METH_NOARGS,
ml_doc: #doc,
}
})
}
} else {
quote! {
pyo3::class::PyMethodDefType::Method({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
}
})
}
}
}
pub fn impl_py_method_def_new(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
let doc = &spec.doc;
quote! {
pyo3::class::PyMethodDefType::New({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyNewFunc(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
}
})
}
}
pub fn impl_py_method_def_class(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
let doc = &spec.doc;
quote! {
pyo3::class::PyMethodDefType::Class({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS |
pyo3::ffi::METH_CLASS,
ml_doc: #doc,
}
})
}
}
pub fn impl_py_method_def_static(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
let doc = &spec.doc;
quote! {
pyo3::class::PyMethodDefType::Static({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS | pyo3::ffi::METH_STATIC,
ml_doc: #doc,
}
})
}
}
pub fn impl_py_method_class_attribute(spec: &FnSpec<'_>, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
quote! {
pyo3::class::PyMethodDefType::ClassAttribute({
#wrapper
pyo3::class::PyClassAttributeDef {
name: stringify!(#python_name),
meth: __wrap,
}
})
}
}
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
quote! {
pyo3::class::PyMethodDefType::ClassAttribute({
#wrapper
pyo3::class::PyClassAttributeDef {
name: stringify!(#python_name),
meth: __wrap,
}
})
}
}
pub fn impl_py_method_def_call(spec: &FnSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
let doc = &spec.doc;
quote! {
pyo3::class::PyMethodDefType::Call({
#wrapper
pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
}
})
}
}
pub(crate) fn impl_py_setter_def(
python_name: &syn::Ident,
doc: &syn::LitStr,
wrapper: &TokenStream,
) -> TokenStream {
quote! {
pyo3::class::PyMethodDefType::Setter({
#wrapper
pyo3::class::PySetterDef {
name: stringify!(#python_name),
meth: __wrap,
doc: #doc,
}
})
}
}
pub(crate) fn impl_py_getter_def(
python_name: &syn::Ident,
doc: &syn::LitStr,
wrapper: &TokenStream,
) -> TokenStream {
quote! {
pyo3::class::PyMethodDefType::Getter({
#wrapper
pyo3::class::PyGetterDef {
name: stringify!(#python_name),
meth: __wrap,
doc: #doc,
}
})
}
}
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
if args.get(0).map(|py| utils::if_type_is_python(&py.ty)) == Some(true) {
(Some(&args[0]), &args[1..])
} else {
(None, args)
}
}