use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule}; #[cfg(feature = "experimental-inspect")] use crate::introspection::unique_element_id; use crate::method::{ CallingConvention, ClassMethodReceiver, ExtractErrorMode, PyArg, SelfConversionPolicy, }; use crate::params::{impl_arg_params, impl_regular_arg_param, Holders}; use crate::pyfunction::WarningFactory; use crate::utils::PythonDoc; use crate::utils::{Ctx, StaticIdent}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, }; use crate::{quotes, utils}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::LitCStr; use syn::{ext::IdentExt, spanned::Spanned, Field, Ident, Result}; /// Generated code for a single pymethod item. pub struct MethodAndMethodDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The method def which will be used to register this pymethod pub method_def: TokenStream, } #[cfg(feature = "experimental-inspect")] impl MethodAndMethodDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } /// Generated code for a single pymethod item which is registered by a slot. pub struct MethodAndSlotDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The slot def which will be used to register this pymethod pub slot_def: TokenStream, } #[cfg(feature = "experimental-inspect")] impl MethodAndSlotDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } pub enum GeneratedPyMethod { Method(MethodAndMethodDef), Proto(MethodAndSlotDef), SlotTraitImpl(String, TokenStream), } pub struct PyMethod<'a> { kind: PyMethodKind, method_name: String, pub spec: FnSpec<'a>, } enum PyMethodKind { Fn, Proto(PyMethodProtoKind), } impl PyMethodKind { fn from_name(name: &str) -> Self { match name { // Protocol implemented through slots "__new__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEW__)), "__init__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INIT__)), "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)), "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)), "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)), "__richcmp__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RICHCMP__)), "__get__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GET__)), "__iter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITER__)), "__next__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEXT__)), "__await__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AWAIT__)), "__aiter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AITER__)), "__anext__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ANEXT__)), "__len__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__LEN__)), "__contains__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONTAINS__)), "__concat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONCAT__)), "__repeat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPEAT__)), "__inplace_concat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_CONCAT__)) } "__inplace_repeat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_REPEAT__)) } "__getitem__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETITEM__)), "__pos__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__POS__)), "__neg__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEG__)), "__abs__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ABS__)), "__invert__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INVERT__)), "__index__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INDEX__)), "__int__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INT__)), "__float__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__FLOAT__)), "__bool__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__BOOL__)), "__iadd__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IADD__)), "__isub__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ISUB__)), "__imul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMUL__)), "__imatmul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMATMUL__)), "__itruediv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITRUEDIV__)), "__ifloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IFLOORDIV__)), "__imod__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMOD__)), "__ipow__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IPOW__)), "__ilshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ILSHIFT__)), "__irshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IRSHIFT__)), "__iand__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IAND__)), "__ixor__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IXOR__)), "__ior__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IOR__)), "__getbuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETBUFFER__)), "__releasebuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RELEASEBUFFER__)), // Protocols implemented through traits "__getattribute__" => { PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTRIBUTE__)) } "__getattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTR__)), "__setattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETATTR__)), "__delattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELATTR__)), "__set__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SET__)), "__delete__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELETE__)), "__setitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETITEM__)), "__delitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELITEM__)), "__add__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ADD__)), "__radd__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RADD__)), "__sub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SUB__)), "__rsub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSUB__)), "__mul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MUL__)), "__rmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMUL__)), "__matmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MATMUL__)), "__rmatmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMATMUL__)), "__floordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__FLOORDIV__)), "__rfloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RFLOORDIV__)), "__truediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__TRUEDIV__)), "__rtruediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RTRUEDIV__)), "__divmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DIVMOD__)), "__rdivmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RDIVMOD__)), "__mod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MOD__)), "__rmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMOD__)), "__lshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LSHIFT__)), "__rlshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RLSHIFT__)), "__rshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSHIFT__)), "__rrshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RRSHIFT__)), "__and__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__AND__)), "__rand__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RAND__)), "__xor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__XOR__)), "__rxor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RXOR__)), "__or__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__OR__)), "__ror__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ROR__)), "__pow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__POW__)), "__rpow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RPOW__)), "__lt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LT__)), "__le__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LE__)), "__eq__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__EQ__)), "__ne__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__NE__)), "__gt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GT__)), "__ge__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GE__)), // Some tricky protocols which don't fit the pattern of the rest "__call__" => PyMethodKind::Proto(PyMethodProtoKind::Call), "__traverse__" => PyMethodKind::Proto(PyMethodProtoKind::Traverse), "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Clear), // Not a proto _ => PyMethodKind::Fn, } } } enum PyMethodProtoKind { Slot(&'static SlotDef), Call, Traverse, Clear, SlotFragment(&'static SlotFragmentDef), } impl PyMethodProtoKind { /// Whether Python hands this protocol an `args`/`kwargs` pair. /// /// `tp_new`, `tp_init` and `tp_call` do, so they accept keyword arguments and a /// `#[pyo3(signature = ...)]` has something to act on. Every other protocol is reached /// through a slot wrapper with a fixed C-level signature: its parameters are positional-only /// and a signature attribute would have nothing to unpack. /// /// Should `signature` ever be accepted on protocol methods just to supply type hints, only /// the `ensure_no_forbidden_protocol_attributes` use goes away: which parameters Python sees /// as positional-only is dictated by CPython, not by what the user declares. fn takes_args_and_kwargs(&self) -> bool { match self { PyMethodProtoKind::Slot(slot) => slot.takes_args_and_kwargs(), PyMethodProtoKind::Call => true, PyMethodProtoKind::SlotFragment(_) | PyMethodProtoKind::Traverse | PyMethodProtoKind::Clear => false, } } } impl<'a> PyMethod<'a> { pub fn parse( sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result { check_generic(sig)?; ensure_function_options_valid(&options)?; let mut spec = FnSpec::parse(sig, meth_attrs, options)?; let method_name = spec.python_name.to_string(); let kind = PyMethodKind::from_name(&method_name); // The parameters of a method backed by a type slot are positional-only, record that in // the signature. Nothing else can: `#[pyo3(signature = ...)]` is rejected for exactly // these methods, see `ensure_no_forbidden_protocol_attributes`. if let PyMethodKind::Proto(proto) = &kind { if !proto.takes_args_and_kwargs() { spec.signature .python_signature .make_all_parameters_positional_only(); } } Ok(Self { kind, method_name, spec, }) } #[cfg(feature = "experimental-inspect")] pub fn is_returning_not_implemented_on_extraction_error(&self) -> bool { match &self.kind { PyMethodKind::Fn => false, PyMethodKind::Proto(proto) => match proto { PyMethodProtoKind::Slot(slot) => { matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented) } PyMethodProtoKind::SlotFragment(slot) => { matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented) } PyMethodProtoKind::Call | PyMethodProtoKind::Traverse | PyMethodProtoKind::Clear => false, }, } } } pub fn is_proto_method(name: &str) -> bool { match PyMethodKind::from_name(name) { PyMethodKind::Fn => false, PyMethodKind::Proto(_) => true, } } pub fn gen_py_method( cls: &syn::Type, method: PyMethod<'_>, meth_attrs: &[syn::Attribute], ctx: &Ctx, ) -> Result { let spec = &method.spec; if spec.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } Ok(match (method.kind, &spec.tp) { // Class attributes go before protos so that class attributes can be used to set proto // method to None. (_, FnType::ClassAttribute) => { GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec, ctx)?) } (PyMethodKind::Proto(proto_kind), _) => { ensure_no_forbidden_protocol_attributes(&proto_kind, spec, &method.method_name)?; match proto_kind { PyMethodProtoKind::Slot(slot_def) => { let slot = slot_def.generate_type_slot(cls, spec, &method.method_name, ctx)?; GeneratedPyMethod::Proto(slot) } PyMethodProtoKind::Call => { GeneratedPyMethod::Proto(impl_call_slot(cls, spec, ctx)?) } PyMethodProtoKind::Traverse => { GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?) } PyMethodProtoKind::Clear => { GeneratedPyMethod::Proto(impl_clear_slot(cls, spec, ctx)?) } PyMethodProtoKind::SlotFragment(slot_fragment_def) => { let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec, ctx)?; GeneratedPyMethod::SlotTraitImpl(method.method_name, proto) } } } // ordinary functions (with some specialties) (_, FnType::Fn(_) | FnType::FnClass(_) | FnType::FnStatic) => GeneratedPyMethod::Method( impl_py_method_def(cls, spec, spec.get_doc(meth_attrs).as_ref(), ctx)?, ), (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, ctx, )?), (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, ctx, )?), (_, FnType::Deleter(self_type)) => GeneratedPyMethod::Method(impl_py_deleter_def( cls, self_type, spec, spec.get_doc(meth_attrs), ctx, )?), (_, FnType::FnModule(_)) => { unreachable!("methods cannot be FnModule") } }) } pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> { let err_msg = |typ| format!("Python functions cannot have generic {typ} parameters"); for param in &sig.generics.params { match param { syn::GenericParam::Lifetime(_) => {} syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")), syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")), } } Ok(()) } fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> { if let Some(pass_module) = &options.pass_module { bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods"); } Ok(()) } fn ensure_no_forbidden_protocol_attributes( proto_kind: &PyMethodProtoKind, spec: &FnSpec<'_>, method_name: &str, ) -> syn::Result<()> { if let Some(signature) = &spec.signature.attribute { // __new__, __init__ and __call__ are allowed to have a signature, but nothing else is. if !proto_kind.takes_args_and_kwargs() { bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name)); } } if let Some(text_signature) = &spec.text_signature { // __new__ is also allowed a text_signature (no other proto method is) if !matches!( proto_kind, PyMethodProtoKind::Slot(SlotDef { calling_convention: SlotCallingConvention::TpNew, .. }) ) { bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name)); } } Ok(()) } pub fn impl_py_method_def( cls: &syn::Type, spec: &FnSpec<'_>, doc: Option<&PythonDoc>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); let calling_convention = CallingConvention::from_signature(&spec.signature); let associated_method = spec.get_wrapper_function( &wrapper_ident, Some(cls), calling_convention, // SAFETY: Methods in `tp_methods` are dispatched through CPython's // method-wrapper descriptor, which enforces that the receiver is an // instance of the owning type before reaching the C function. The // trusted path is therefore valid. unsafe { SelfConversionPolicy::trusted() }, ClassMethodReceiver::Class, ctx, )?; let methoddef = spec.get_methoddef( quote! { #cls::#wrapper_ident }, doc, calling_convention, ctx, )?; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::Method(#methoddef) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site()); let associated_method = spec.get_wrapper_function( &wrapper_ident, Some(cls), CallingConvention::Varargs, // SAFETY: The `tp_call` slot is dispatched by CPython, which guarantees // the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, ClassMethodReceiver::Instance, ctx, )?; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_call, pfunc: #pyo3_path::impl_::trampoline::get_trampoline_function!(ternaryfunc, #cls::#wrapper_ident) as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_traverse_slot( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) { return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \ Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic.")); } // check that the receiver does not try to smuggle an (implicit) `Python` token into here if let FnType::Fn(SelfType::TryFromBoundRef { span, .. }) | FnType::Fn(SelfType::Receiver { mutable: true, span, .. }) = spec.tp { bail_spanned! { span => "__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \ `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic." } } ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "__traverse__ cannot be used with #[pyo3(warn)]" ); let rust_fn_ident = spec.name; let associated_method = quote! { pub unsafe extern "C" fn __pymethod_traverse__( slf: *mut #pyo3_path::ffi::PyObject, visit: #pyo3_path::ffi::visitproc, arg: *mut ::std::ffi::c_void, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg, #cls::__pymethod_traverse__) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_traverse, pfunc: #cls::__pymethod_traverse__ as #pyo3_path::ffi::traverseproc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_clear_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let self_type = match &spec.tp { FnType::Fn(self_type) => self_type, _ => bail_spanned!(spec.name.span() => "expected instance method for `__clear__` function"), }; let mut holders = Holders::new(); let slf = self_type.receiver( cls, ExtractErrorMode::Raise, // SAFETY: The `tp_clear` slot is dispatched by CPython, which guarantees the // receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, &mut holders, ctx, ); if let [arg, ..] = args { bail_spanned!(arg.ty().span() => "`__clear__` function expected to have no arguments"); } let name = &spec.name; let holders = holders.init_holders(ctx); let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; let associated_method = quote! { pub unsafe extern "C" fn __pymethod___clear____( _slf: *mut #pyo3_path::ffi::PyObject, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_clear::<#cls>(_slf, |py, _slf| { #holders let result = #fncall; let result = #pyo3_path::impl_::wrap::converter(&result).wrap(result)?; ::std::result::Result::Ok(result) }, #cls::__pymethod___clear____) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_clear, pfunc: #cls::__pymethod___clear____ as #pyo3_path::ffi::inquiry as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } pub(crate) fn impl_py_class_attribute( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); ensure_spanned!( args.is_empty(), args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)" ); ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "#[classattr] cannot be used with #[pyo3(warn)]" ); let name = &spec.name; let fncall = if py_arg.is_some() { quote!(function(py)) } else { quote!(function()) }; let wrapper_ident = format_ident!("__pymethod_{}__", name); let python_name = spec.null_terminated_python_name(); let body = quotes::ok_wrap(fncall, ctx); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let function = #cls::#name; // Shadow the method name to avoid #3017 let result = #body; #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::PyClassAttributeDef::new( #python_name, #cls::#wrapper_ident ) }) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_setter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver( cls, ExtractErrorMode::Raise, // SAFETY: The setter function is dispatched by CPython's method-wrapper // descriptor, which enforces the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, holders, ctx, ); if args.is_empty() { bail_spanned!(spec.name.span() => "setter function expected to have one argument"); } else if args.len() > 1 { bail_spanned!( args[1].ty().span() => "setter function can have at most two arguments ([pyo3::Python,] and value)" ); } 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) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_setter_def( cls: &syn::Type, property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let mut holders = Holders::new(); let setter_impl = match property_type { PropertyType::Descriptor { field_index, field, .. } => { let slf = SelfType::Receiver { mutable: true, span: Span::call_site(), non_null: true, } .receiver( cls, ExtractErrorMode::Raise, // SAFETY: The setter function is dispatched by CPython, which // guarantees the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, &mut holders, ctx, ); if let Some(ident) = &field.ident { // named struct field quote!({ #slf.#ident = _val; }) } else { // tuple struct field let index = syn::Index::from(field_index); quote!({ #slf.#index = _val; }) } } PropertyType::Function { spec, self_type, .. } => impl_call_setter(cls, spec, self_type, &mut holders, ctx)?, }; let wrapper_ident = match property_type { PropertyType::Descriptor { field: syn::Field { ident: Some(ident), .. }, .. } => { format_ident!("__pymethod_set_{}__", ident) } PropertyType::Descriptor { field_index, .. } => { format_ident!("__pymethod_set_field_{}__", field_index) } PropertyType::Function { spec, .. } => { format_ident!("__pymethod_set_{}__", spec.name) } }; let extract = match &property_type { PropertyType::Function { spec, .. } => { let (_, args) = split_off_python_arg(&spec.signature.arguments); let value_arg = &args[0]; let (from_py_with, ident) = if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) { let ident = syn::Ident::new("from_py_with", from_py_with.span()); ( quote_spanned! { from_py_with.span() => let #ident = #from_py_with; }, ident, ) } else { (quote!(), syn::Ident::new("dummy", Span::call_site())) }; let arg = if let FnArg::Regular(arg) = &value_arg { arg } else { bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`."); }; let extract = impl_regular_arg_param( arg, ident, quote!(::std::option::Option::Some(_value)), &mut holders, ctx, ); quote! { #from_py_with let _val = #extract; } } PropertyType::Descriptor { field, .. } => { let span = field.ty.span(); let name = field .ident .as_ref() .map(|i| i.to_string()) .unwrap_or_default(); let holder = holders.push_holder(span); quote! { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value, &mut #holder, #name)?; } } }; let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let warnings = if let PropertyType::Function { spec, .. } = &property_type { spec.warnings.build_py_warning(ctx) } else { quote!() }; let init_holders = holders.init_holders(ctx); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, _value: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, ) -> #pyo3_path::PyResult<::std::ffi::c_int> { use ::std::convert::Into; let _value = #pyo3_path::impl_::extract_argument::cast_non_null_function_argument(py, _value); #init_holders #extract #warnings let result = #setter_impl; #pyo3_path::impl_::callback::convert(py, result) } }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pymethods::PyMethodDefType::Setter( #pyo3_path::impl_::pymethods::PySetterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_getter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver( cls, ExtractErrorMode::Raise, // SAFETY: The getter function is dispatched by CPython's method-wrapper // descriptor, which enforces the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, holders, ctx, ); ensure_spanned!( args.is_empty(), args[0].ty().span() => "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) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let mut holders = Holders::new(); match property_type { PropertyType::Descriptor { field_index, field, .. } => { let ty = &field.ty; let field = if let Some(ident) = &field.ident { ident.to_token_stream() } else { syn::Index::from(field_index).to_token_stream() }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let generator = quote_spanned! { ty.span() => GENERATOR.generate(#python_name, #doc) }; // This is separate from `generator` so that the unsafe below does not inherit the span and thus does not // trigger the `unsafe_code` lint let method_def = quote! { #cfg_attrs { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::< #cls, #ty, { ::std::mem::offset_of!(#cls, #field) }, { #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#ty>::VALUE }, > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() }; #generator } }; Ok(MethodAndMethodDef { associated_method: quote! {}, method_def, }) } // Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results. PropertyType::Function { spec, self_type, .. } => { let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name); let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?; let body = quote! { #pyo3_path::impl_::callback::convert(py, #call) }; let init_holders = holders.init_holders(ctx); let warnings = spec.warnings.build_py_warning(ctx); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #init_holders #warnings let result = #body; result } }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pymethods::PyMethodDefType::Getter( #pyo3_path::impl_::pymethods::PyGetterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } } } pub fn impl_py_deleter_def( cls: &syn::Type, self_type: &SelfType, spec: &FnSpec<'_>, doc: Option, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = spec.null_terminated_python_name(); let mut holders = Holders::new(); let deleter_impl = impl_call_deleter(cls, spec, self_type, &mut holders, ctx)?; let wrapper_ident = format_ident!("__pymethod_delete_{}__", spec.name); let warnings = spec.warnings.build_py_warning(ctx); let init_holders = holders.init_holders(ctx); let doc = doc_to_optional_cstr(doc.as_ref(), ctx)?; let associated_method = quote! { unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, ) -> #pyo3_path::PyResult<::std::ffi::c_int> { #init_holders #warnings let result = #deleter_impl; #pyo3_path::impl_::callback::convert(py, result) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::Deleter( #pyo3_path::impl_::pymethods::PyDeleterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_deleter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver( cls, ExtractErrorMode::Raise, // SAFETY: The deleter function is dispatched by CPython's method-wrapper // descriptor, which enforces the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, holders, ctx, ); if !args.is_empty() { bail_spanned!(spec.name.span() => "deleter function can have at most one argument ([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) } /// Split an argument of pyo3::Python from the front of the arg list, if present fn split_off_python_arg<'a, 'b>(args: &'a [FnArg<'b>]) -> (Option<&'a PyArg<'b>>, &'a [FnArg<'b>]) { match args { [FnArg::Py(py), args @ ..] => (Some(py), args), args => (None, args), } } pub enum PropertyType<'a> { Descriptor { field_index: usize, field: &'a Field, python_name: Option<&'a NameAttribute>, renaming_rule: Option, }, Function { self_type: &'a SelfType, spec: &'a FnSpec<'a>, doc: Option, }, } impl PropertyType<'_> { fn null_terminated_python_name(&self) -> Result { match self { PropertyType::Descriptor { field, python_name, renaming_rule, .. } => { let name = field_python_name(field, *python_name, *renaming_rule)?; let name = CString::new(name).unwrap(); Ok(LitCStr::new(&name, field.span())) } PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()), } } fn doc(&self) -> Option> { Some(match self { PropertyType::Descriptor { field, .. } => { Cow::Owned(utils::get_doc(&field.attrs, None)?) } PropertyType::Function { doc, .. } => Cow::Borrowed(doc.as_ref()?), }) } } pub const __NEW__: SlotDef = SlotDef::new("Py_tp_new", "newfunc"); pub const __INIT__: SlotDef = SlotDef::new("Py_tp_init", "initproc"); pub const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc"); pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc"); pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc").return_conversion(TokenGenerator( |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::impl_::callback::HashCallbackOutput }, )); pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") .extract_error_mode(ExtractErrorMode::NotImplemented); const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc"); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc") .return_specialized_conversion( TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }), TokenGenerator(|_| quote! { iter_tag }), ); const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc"); const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc"); const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_specialized_conversion( TokenGenerator( |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind }, ), TokenGenerator(|_| quote! { async_iter_tag }), ); pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc"); const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc"); const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc"); const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc"); const __INPLACE_CONCAT__: SlotDef = SlotDef::new("Py_sq_inplace_concat", "binaryfunc"); const __INPLACE_REPEAT__: SlotDef = SlotDef::new("Py_sq_inplace_repeat", "ssizeargfunc"); pub const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc"); const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc"); const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc"); const __ABS__: SlotDef = SlotDef::new("Py_nb_absolute", "unaryfunc"); const __INVERT__: SlotDef = SlotDef::new("Py_nb_invert", "unaryfunc"); const __INDEX__: SlotDef = SlotDef::new("Py_nb_index", "unaryfunc"); pub const __INT__: SlotDef = SlotDef::new("Py_nb_int", "unaryfunc"); const __FLOAT__: SlotDef = SlotDef::new("Py_nb_float", "unaryfunc"); const __BOOL__: SlotDef = SlotDef::new("Py_nb_bool", "inquiry"); const __IADD__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_add"); const __ISUB__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_subtract"); const __IMUL__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_multiply"); const __IMATMUL__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_matrix_multiply"); const __ITRUEDIV__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_true_divide"); const __IFLOORDIV__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_floor_divide"); const __IMOD__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_remainder"); const __ILSHIFT__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_lshift"); const __IRSHIFT__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_rshift"); const __IAND__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_and"); const __IXOR__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_xor"); const __IOR__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_or"); const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ternaryfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc").require_unsafe(); const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc").require_unsafe(); const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry"); #[derive(Clone, Copy)] enum Ty { Object, MaybeNullObject, NonNullObject, CompareOp, Int, PyHashT, PySsizeT, Void, PyBuffer, } impl Ty { fn ffi_type(self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); match self { Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> }, Ty::Int | Ty::CompareOp => quote! { ::std::ffi::c_int }, Ty::PyHashT => quote! { #pyo3_path::ffi::Py_hash_t }, Ty::PySsizeT => quote! { #pyo3_path::ffi::Py_ssize_t }, Ty::Void => quote! { () }, Ty::PyBuffer => quote! { *mut #pyo3_path::ffi::Py_buffer }, } } fn extract( self, ident: &syn::Ident, arg: &FnArg<'_>, extract_error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { Ty::Object => extract_object( extract_error_mode, holders, arg, REF_FROM_PTR, CAST_FUNCTION_ARGUMENT, quote! { #ident }, ctx ), Ty::MaybeNullObject => extract_object( extract_error_mode, holders, arg, REF_FROM_PTR, CAST_FUNCTION_ARGUMENT, quote! { if #ident.is_null() { #pyo3_path::ffi::Py_None() } else { #ident } }, ctx ), Ty::NonNullObject => extract_object( extract_error_mode, holders, arg, REF_FROM_NON_NULL, CAST_NON_NULL_FUNCTION_ARGUMENT, quote! { #ident }, ctx ), Ty::CompareOp => extract_error_mode.handle_error( quote! { #pyo3_path::class::basic::CompareOp::from_raw(#ident) .ok_or_else(|| #pyo3_path::exceptions::PyValueError::new_err("invalid comparison operator")) }, ctx ), Ty::PySsizeT => { let ty = arg.ty(); extract_error_mode.handle_error( quote! { ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string())) }, ctx ) } // Just pass other types through unmodified Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident }, } } } const REF_FROM_PTR: StaticIdent = StaticIdent::new("ref_from_ptr"); const REF_FROM_NON_NULL: StaticIdent = StaticIdent::new("ref_from_non_null"); const CAST_FUNCTION_ARGUMENT: StaticIdent = StaticIdent::new("cast_function_argument"); const CAST_NON_NULL_FUNCTION_ARGUMENT: StaticIdent = StaticIdent::new("cast_non_null_function_argument"); fn extract_object( extract_error_mode: ExtractErrorMode, holders: &mut Holders, arg: &FnArg<'_>, ref_from_method: StaticIdent, cast_method: StaticIdent, source_ptr: TokenStream, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = arg.name().unraw().to_string(); let extract = if let Some(FromPyWithAttribute { kw, value: extractor, }) = arg.from_py_with() { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #extractor; from_py_with } }; quote! { #pyo3_path::impl_::extract_argument::from_py_with( unsafe { #pyo3_path::Bound::#ref_from_method(py, &#source_ptr) }, #name, #extractor, ) } } else { let holder = holders.push_holder(Span::call_site()); quote! {{ #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; #pyo3_path::impl_::extract_argument::extract_argument( unsafe { #pyo3_path::impl_::extract_argument::#cast_method(py, #source_ptr) }, &mut #holder, #name ) }} }; let extracted = extract_error_mode.handle_error(extract, ctx); quote!(#extracted) } enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), SpecializedConversion(TokenGenerator, TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { ReturnMode::Conversion(conversion) => { let conversion = TokenGeneratorCtx(*conversion, ctx); quote! { let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::impl_::callback::convert(py, #call); #pyo3_path::impl_::callback::convert(py, _result) } } ReturnMode::SpecializedConversion(traits, tag) => { let traits = TokenGeneratorCtx(*traits, ctx); let tag = TokenGeneratorCtx(*tag, ctx); quote! { let _result = #call; use #pyo3_path::impl_::pymethods::{#traits}; (&_result).#tag().convert(py, _result) } } ReturnMode::ReturnSelf => quote! { let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call); _result?; #pyo3_path::ffi::Py_XINCREF(_slf); ::std::result::Result::Ok(_slf) }, } } } pub struct SlotDef { slot: StaticIdent, func_ty: StaticIdent, calling_convention: SlotCallingConvention, ret_ty: Ty, extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, } enum SlotCallingConvention { /// Specific set of arguments for the slot function FixedArguments(&'static [Ty]), /// Arbitrary arguments for `__new__` from the signature (extracted from args / kwargs) TpNew, TpInit, } impl SlotDef { /// Whether this slot receives Python's `args`/`kwargs` pair rather than a fixed set of /// positional arguments. /// /// Only `tp_new` and `tp_init` do; every other slot is exposed to Python through a slot /// wrapper which rejects keyword arguments, e.g. `__eq__` has the signature /// `($self, value, /)`. pub const fn takes_args_and_kwargs(&self) -> bool { matches!( self.calling_convention, SlotCallingConvention::TpNew | SlotCallingConvention::TpInit ) } const fn new(slot: &'static str, func_ty: &'static str) -> Self { // The FFI function pointer type determines the arguments and return type let (calling_convention, ret_ty) = match func_ty.as_bytes() { b"newfunc" => (SlotCallingConvention::TpNew, Ty::Object), b"initproc" => (SlotCallingConvention::TpInit, Ty::Int), b"reprfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"hashfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::PyHashT), b"richcmpfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object, Ty::CompareOp]), Ty::Object, ), b"descrgetfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]), Ty::Object, ), b"getiterfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"iternextfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"unaryfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"lenfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::PySsizeT), b"objobjproc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object]), Ty::Int, ), b"binaryfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object]), Ty::Object, ), b"inquiry" => (SlotCallingConvention::FixedArguments(&[]), Ty::Int), b"ssizeargfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::PySsizeT]), Ty::Object, ), b"getbufferproc" => ( SlotCallingConvention::FixedArguments(&[Ty::PyBuffer, Ty::Int]), Ty::Int, ), b"releasebufferproc" => ( SlotCallingConvention::FixedArguments(&[Ty::PyBuffer]), Ty::Void, ), b"ternaryfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object, Ty::Object]), Ty::Object, ), _ => panic!("don't know calling convention for func_ty"), }; SlotDef { slot: StaticIdent::new(slot), func_ty: StaticIdent::new(func_ty), calling_convention, ret_ty, extract_error_mode: ExtractErrorMode::Raise, return_mode: None, require_unsafe: false, } } /// Specialized constructor for binary inplace operators const fn binary_inplace_operator(slot: &'static str) -> Self { SlotDef::new(slot, "binaryfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self() } const fn return_conversion(mut self, return_conversion: TokenGenerator) -> Self { self.return_mode = Some(ReturnMode::Conversion(return_conversion)); self } const fn return_specialized_conversion( mut self, traits: TokenGenerator, tag: TokenGenerator, ) -> Self { self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag)); self } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn return_self(mut self) -> Self { self.return_mode = Some(ReturnMode::ReturnSelf); self } const fn require_unsafe(mut self) -> Self { self.require_unsafe = true; self } pub fn generate_type_slot( &self, cls: &syn::Type, spec: &FnSpec<'_>, method_name: &str, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let SlotDef { slot, func_ty, calling_convention, extract_error_mode, ret_ty, return_mode, require_unsafe, } = self; if *require_unsafe { ensure_spanned!( spec.unsafety.is_some(), spec.name.span() => format!("`{}` must be `unsafe fn`", method_name) ); } let wrapper_ident = format_ident!("__pymethod_{}__", method_name); let ret_ty = ret_ty.ffi_type(ctx); let mut holders = Holders::new(); let MethodBody { arg_idents, arg_types, body, } = generate_method_body( cls, spec, calling_convention, *extract_error_mode, // SAFETY: All extension-type slots use trusted self: CPython's slot dispatch // contract guarantees the receiver is of the correct type. unsafe { SelfConversionPolicy::trusted() }, &mut holders, return_mode.as_ref(), ctx, )?; let name = spec.name; let holders = holders.init_holders(ctx); let associated_method = quote! { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { let function = #cls::#name; // Shadow the method name to avoid #3017 #holders #body } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::#slot, pfunc: #pyo3_path::impl_::trampoline::get_trampoline_function!(#func_ty, #cls::#wrapper_ident) as #pyo3_path::ffi::#func_ty as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } } #[allow(clippy::too_many_arguments)] fn generate_method_body( cls: &syn::Type, spec: &FnSpec<'_>, calling_convention: &SlotCallingConvention, extract_error_mode: ExtractErrorMode, self_conversion: SelfConversionPolicy, holders: &mut Holders, // NB ignored if calling_convention is SlotCallingConvention::TpNew, possibly should merge into that enum return_mode: Option<&ReturnMode>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, output_span, } = ctx; let self_arg = spec.tp.self_arg( Some(cls), extract_error_mode, self_conversion, match calling_convention { SlotCallingConvention::TpNew => ClassMethodReceiver::Class, SlotCallingConvention::TpInit | SlotCallingConvention::FixedArguments(_) => { ClassMethodReceiver::Instance } }, holders, ctx, ); let rust_name = spec.name; let warnings = spec.warnings.build_py_warning(ctx); let (arg_idents, arg_types, body) = match calling_convention { SlotCallingConvention::TpNew => { let arg_idents = vec![ format_ident!("_slf"), format_ident!("_args"), format_ident!("_kwargs"), ]; let arg_types = vec![ quote! { *mut #pyo3_path::ffi::PyTypeObject }, quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, ]; let (arg_convert, args) = impl_arg_params(spec, Some(cls), false, holders, ctx); let args = self_arg.into_iter().chain(args); let call = quote_spanned! {*output_span=> #cls::#rust_name(#(#args),*) }; // Use just the text_signature_call_signature() because the class' Python name // isn't known to `#[pymethods]` - that has to be attached at runtime from the PyClassImpl // trait implementation created by `#[pyclass]`. let text_signature_impl = spec.text_signature_call_signature().map(|text_signature| { quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::doc::PyClassNewTextSignature for #cls { const TEXT_SIGNATURE: &'static str = #text_signature; } } }); let py = syn::Ident::new("py", Span::call_site()); let initializer = syn::Ident::new("initializer", Span::call_site()); let slf = syn::Ident::new("_slf", Span::call_site()); // Having just this call emitted at the span of the return value helps surface errors // if the user passed an invalid return type. let conversion = quote_spanned! { *output_span => #pyo3_path::impl_::pymethods::tp_new_impl::<_, #cls>(#py, #initializer, #slf) }; let value = syn::Ident::new("value", Span::call_site()); let resolver = quote_spanned! { *output_span => #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value); }; let body = quote! { #text_signature_impl #warnings #arg_convert let result = #call; let #value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?; let #initializer = #resolver; unsafe { #conversion } }; (arg_idents, arg_types, body) } SlotCallingConvention::TpInit => { let arg_idents = vec![ format_ident!("_slf"), format_ident!("_args"), format_ident!("_kwargs"), ]; let arg_types = vec![ quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, ]; let (arg_convert, args) = impl_arg_params(spec, Some(cls), false, holders, ctx); let args = self_arg.into_iter().chain(args); let call = quote! {{ let r = #cls::#rust_name(#(#args),*); #pyo3_path::impl_::wrap::converter(&r) .wrap(r) .map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)? }}; let output = quote_spanned! { *output_span => result.convert(py) }; let body = quote! { use #pyo3_path::impl_::callback::IntoPyCallbackOutput; #warnings #arg_convert let result = #call; #output }; (arg_idents, arg_types, body) } SlotCallingConvention::FixedArguments(arguments) => { let arg_idents: Vec<_> = std::iter::once(format_ident!("_slf")) .chain((0..arguments.len()).map(|i| format_ident!("arg{}", i))) .collect(); let arg_types: Vec<_> = std::iter::once(quote! { *mut #pyo3_path::ffi::PyObject }) .chain(arguments.iter().map(|arg| arg.ffi_type(ctx))) .collect(); let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?; let args = self_arg.into_iter().chain(args); let call = quote! { #cls::#rust_name(#(#args),*) }; let result = if let Some(return_mode) = return_mode { return_mode.return_call_output(call, ctx) } else { quote! { let result = #call; #pyo3_path::impl_::callback::convert(py, result) } }; let body = quote! { #warnings #result }; (arg_idents, arg_types, body) } }; Ok(MethodBody { arg_idents, arg_types, body, }) } struct SlotFragmentDef { fragment: &'static str, arguments: &'static [Ty], extract_error_mode: ExtractErrorMode, ret_ty: Ty, /// Self-conversion policy for this slot fragment. /// /// Most slot fragments are called by CPython with a receiver that is /// guaranteed to be of the correct type (`Trusted`). However, binary /// operator fragments are combined into a single slot (e.g. `nb_add`) /// where the runtime helper may swap operands and call the reflected /// fragment with a receiver of an unknown (potentially wrong) type. /// Those fragments must use `Checked` so that a type mismatch returns /// `NotImplemented` instead of causing undefined behaviour. self_conversion: SelfConversionPolicy, } impl SlotFragmentDef { const fn new(fragment: &'static str, arguments: &'static [Ty]) -> Self { SlotFragmentDef { fragment, arguments, extract_error_mode: ExtractErrorMode::Raise, ret_ty: Ty::Void, self_conversion: SelfConversionPolicy::checked(), } } const fn binary_operator(fragment: &'static str) -> Self { // Binary operator fragments (`__add__`, `__radd__`, etc.) are combined // into a shared slot (e.g. `nb_add`) that may call the forward fragment // with a non-class receiver (e.g. `1 + MyClass()` → `nb_add(1, c)`). // The runtime helper then tries the reflected fragment with the operands // swapped, which can also produce a non-class `_slf`. Both cases require // a checked type conversion so that a mismatch gracefully returns // `NotImplemented` rather than causing undefined behaviour. // TODO: addressing #6024 could allow to simplify this by using type // checks to directly dispatch to the right fragment. SlotFragmentDef { fragment, arguments: &[Ty::Object], extract_error_mode: ExtractErrorMode::NotImplemented, ret_ty: Ty::Object, self_conversion: SelfConversionPolicy::checked(), } } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn ret_ty(mut self, ret_ty: Ty) -> Self { self.ret_ty = ret_ty; self } const fn self_conversion_policy(mut self, policy: SelfConversionPolicy) -> Self { self.self_conversion = policy; self } fn generate_pyproto_fragment( &self, cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let SlotFragmentDef { fragment, arguments, extract_error_mode, ret_ty, self_conversion, } = self; let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment); let method = syn::Ident::new(fragment, Span::call_site()); let wrapper_ident = format_ident!("__pymethod_{}__", fragment); let mut holders = Holders::new(); let MethodBody { arg_idents, arg_types, body, } = generate_method_body( cls, spec, &SlotCallingConvention::FixedArguments(arguments), *extract_error_mode, *self_conversion, &mut holders, None, ctx, )?; let ret_ty = ret_ty.ffi_type(ctx); let holders = holders.init_holders(ctx); Ok(quote! { impl #cls { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { #holders #body } } impl #pyo3_path::impl_::pyclass::#fragment_trait<#cls> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#cls> { #[inline] unsafe fn #method( self, py: #pyo3_path::Python, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { #cls::#wrapper_ident(py, #(#arg_idents),*) } } }) } } /// The reusable components of a method body. pub struct MethodBody { pub arg_idents: Vec, pub arg_types: Vec, pub body: TokenStream, } const __GETATTRIBUTE__: SlotFragmentDef = SlotFragmentDef::new("__getattribute__", &[Ty::Object]) .ret_ty(Ty::Object) // SAFETY: `tp_getattro` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __GETATTR__: SlotFragmentDef = SlotFragmentDef::new("__getattr__", &[Ty::Object]) .ret_ty(Ty::Object) // SAFETY: `tp_getattro` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __SETATTR__: SlotFragmentDef = SlotFragmentDef::new("__setattr__", &[Ty::Object, Ty::NonNullObject]) // SAFETY: `tp_setattro` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __DELATTR__: SlotFragmentDef = SlotFragmentDef::new("__delattr__", &[Ty::Object]) // SAFETY: `tp_setattro` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __SET__: SlotFragmentDef = SlotFragmentDef::new("__set__", &[Ty::Object, Ty::NonNullObject]) // SAFETY: `tp_descr_set` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __DELETE__: SlotFragmentDef = SlotFragmentDef::new("__delete__", &[Ty::Object]) // SAFETY: `tp_descr_set` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __SETITEM__: SlotFragmentDef = SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]) // SAFETY: `mp_ass_subscript` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]) // SAFETY: `mp_ass_subscript` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __ADD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__add__"); const __RADD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__radd__"); const __SUB__: SlotFragmentDef = SlotFragmentDef::binary_operator("__sub__"); const __RSUB__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rsub__"); const __MUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__mul__"); const __RMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmul__"); const __MATMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__matmul__"); const __RMATMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmatmul__"); const __FLOORDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__floordiv__"); const __RFLOORDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rfloordiv__"); const __TRUEDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__truediv__"); const __RTRUEDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rtruediv__"); const __DIVMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__divmod__"); const __RDIVMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rdivmod__"); const __MOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__mod__"); const __RMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmod__"); const __LSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__lshift__"); const __RLSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rlshift__"); const __RSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rshift__"); const __RRSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rrshift__"); const __AND__: SlotFragmentDef = SlotFragmentDef::binary_operator("__and__"); const __RAND__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rand__"); const __XOR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__xor__"); const __RXOR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rxor__"); const __OR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__or__"); const __ROR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__ror__"); const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __LE__: SlotFragmentDef = SlotFragmentDef::new("__le__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __EQ__: SlotFragmentDef = SlotFragmentDef::new("__eq__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __NE__: SlotFragmentDef = SlotFragmentDef::new("__ne__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __GT__: SlotFragmentDef = SlotFragmentDef::new("__gt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); const __GE__: SlotFragmentDef = SlotFragmentDef::new("__ge__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object) // SAFETY: `tp_richcompare` is wrapped by CPython to guarantee correct receiver type .self_conversion_policy(unsafe { SelfConversionPolicy::trusted() }); fn extract_proto_arguments( spec: &FnSpec<'_>, proto_args: &[Ty], extract_error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> Result> { let mut args = Vec::with_capacity(spec.signature.arguments.len()); let mut non_python_args = 0; for arg in &spec.signature.arguments { if let FnArg::Py(..) = arg { args.push(quote! { py }); } else { let ident = syn::Ident::new(&format!("arg{non_python_args}"), Span::call_site()); let conversions = proto_args.get(non_python_args) .ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))? .extract(&ident, arg, extract_error_mode, holders, ctx); non_python_args += 1; args.push(conversions); } } if non_python_args != proto_args.len() { bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args)); } Ok(args) } #[derive(Clone, Copy)] struct TokenGenerator(fn(&Ctx) -> TokenStream); struct TokenGeneratorCtx<'ctx>(TokenGenerator, &'ctx Ctx); impl ToTokens for TokenGeneratorCtx<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let Self(TokenGenerator(gen), ctx) = self; (gen)(ctx).to_tokens(tokens) } } pub fn field_python_name( field: &Field, name_attr: Option<&NameAttribute>, renaming_rule: Option, ) -> Result { if let Some(name_attr) = name_attr { return Ok(name_attr.value.0.to_string()); } let Some(ident) = &field.ident else { bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); }; let mut name = ident.unraw().to_string(); if let Some(rule) = renaming_rule { name = utils::apply_renaming_rule(rule, &name); } Ok(name) } fn doc_to_optional_cstr(doc: Option<&PythonDoc>, ctx: &Ctx) -> Result { Ok(if let Some(doc) = doc { let doc = doc.to_cstr_stream(ctx)?; quote!(::std::option::Option::Some(#doc)) } else { quote!(::std::option::Option::None) }) }