diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index c390fe5413e..fd43d4f28be 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -252,10 +252,6 @@ pub enum FnType { Setter(SelfType), /// Represents a regular pymethod Fn(SelfType), - /// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder. - FnNew, - /// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order) - FnNewClass(Span), /// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod` FnClass(Span), /// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod` @@ -273,20 +269,14 @@ impl FnType { | FnType::Setter(_) | FnType::Fn(_) | FnType::FnClass(_) - | FnType::FnNewClass(_) | FnType::FnModule(_) => true, - FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, + FnType::FnStatic | FnType::ClassAttribute => false, } } pub fn signature_attribute_allowed(&self) -> bool { match self { - FnType::Fn(_) - | FnType::FnNew - | FnType::FnStatic - | FnType::FnClass(_) - | FnType::FnNewClass(_) - | FnType::FnModule(_) => true, + FnType::Fn(_) | FnType::FnStatic | FnType::FnClass(_) | FnType::FnModule(_) => true, // Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1 // arguments) so cannot have a `signature = (...)` attribute. FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false, @@ -312,7 +302,7 @@ impl FnType { syn::Token![,](Span::call_site()).to_tokens(&mut receiver); Some(receiver) } - FnType::FnClass(span) | FnType::FnNewClass(span) => { + FnType::FnClass(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); @@ -338,7 +328,7 @@ impl FnType { }; Some(quote! { unsafe { #ret }, }) } - FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None, + FnType::FnStatic | FnType::ClassAttribute => None, } } } @@ -424,12 +414,11 @@ impl SelfType { } /// Determines which CPython calling convention a given FnSpec uses. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum CallingConvention { Noargs, // METH_NOARGS Varargs, // METH_VARARGS | METH_KEYWORDS Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature before 3.10) - TpNew, // special convention for tp_new } impl CallingConvention { @@ -461,7 +450,6 @@ pub struct FnSpec<'a> { // r# can be removed by syn::ext::IdentExt::unraw() pub python_name: syn::Ident, pub signature: FunctionSignature<'a>, - pub convention: CallingConvention, pub text_signature: Option, pub asyncness: Option, pub unsafety: Option, @@ -533,16 +521,9 @@ impl<'a> FnSpec<'a> { FunctionSignature::from_arguments(arguments) }; - let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) { - CallingConvention::TpNew - } else { - CallingConvention::from_signature(&signature) - }; - Ok(FnSpec { tp: fn_type, name, - convention, python_name, signature, text_signature, @@ -600,12 +581,12 @@ impl<'a> FnSpec<'a> { [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, [MethodTypeAttribute::New(_)] => { set_name_to_new()?; - FnType::FnNew + FnType::FnStatic } [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { set_name_to_new()?; - FnType::FnNewClass(*span) + FnType::FnClass(*span) } [MethodTypeAttribute::ClassMethod(_)] => { // Add a helpful hint if the classmethod doesn't look like a classmethod @@ -677,6 +658,7 @@ impl<'a> FnSpec<'a> { &self, ident: &proc_macro2::Ident, cls: Option<&syn::Type>, + convention: CallingConvention, ctx: &Ctx, ) -> Result { let Ctx { @@ -799,7 +781,7 @@ impl<'a> FnSpec<'a> { let warnings = self.warnings.build_py_warning(ctx); - Ok(match self.convention { + Ok(match convention { CallingConvention::Noargs => { let mut holders = Holders::new(); let args = self @@ -872,38 +854,18 @@ impl<'a> FnSpec<'a> { } } } - CallingConvention::TpNew => { - let mut holders = Holders::new(); - let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); - let self_arg = self - .tp - .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx); - let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) }; - let init_holders = holders.init_holders(ctx); - quote! { - unsafe fn #ident( - py: #pyo3_path::Python<'_>, - _slf: *mut #pyo3_path::ffi::PyTypeObject, - _args: *mut #pyo3_path::ffi::PyObject, - _kwargs: *mut #pyo3_path::ffi::PyObject - ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { - use #pyo3_path::impl_::callback::IntoPyCallbackOutput; - let function = #rust_name; // Shadow the function name to avoid #3017 - #arg_convert - #init_holders - #warnings - let result = #call; - let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?; - #pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf) - } - } - } }) } /// Return a `PyMethodDef` constructor for this function, matching the selected /// calling convention. - pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream { + pub fn get_methoddef( + &self, + wrapper: impl ToTokens, + doc: &PythonDoc, + convention: CallingConvention, + ctx: &Ctx, + ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let python_name = self.null_terminated_python_name(); let flags = match self.tp { @@ -911,13 +873,12 @@ impl<'a> FnSpec<'a> { FnType::FnStatic => quote! { .flags(#pyo3_path::ffi::METH_STATIC) }, _ => quote! {}, }; - let trampoline = match self.convention { + let trampoline = match convention { CallingConvention::Noargs => Ident::new("noargs", Span::call_site()), CallingConvention::Fastcall => { Ident::new("fastcall_cfunction_with_keywords", Span::call_site()) } CallingConvention::Varargs => Ident::new("cfunction_with_keywords", Span::call_site()), - CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef"), }; quote! { #pyo3_path::impl_::pymethods::PyMethodDef::#trampoline( @@ -944,8 +905,8 @@ impl<'a> FnSpec<'a> { FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, FnType::Fn(_) => Some("self"), FnType::FnModule(_) => Some("module"), - FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"), - FnType::FnStatic | FnType::FnNew => None, + FnType::FnClass(_) => Some("cls"), + FnType::FnStatic => None, }; match self.text_signature.as_ref().map(|attr| &attr.value) { diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index cb35a654763..8e6f1cebf77 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -28,8 +28,8 @@ use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType}; use crate::pymethod::field_python_name; use crate::pymethod::{ impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, - MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, - __RICHCMP__, __STR__, + MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __NEW__, + __REPR__, __RICHCMP__, __STR__, }; use crate::pyversions::{is_abi3_before, is_py_before}; use crate::utils::{self, apply_renaming_rule, Ctx, PythonDoc}; @@ -1746,11 +1746,10 @@ fn complex_enum_struct_variant_new<'a>( }; let spec = FnSpec { - tp: crate::method::FnType::FnNew, + tp: crate::method::FnType::FnStatic, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, - convention: crate::method::CallingConvention::TpNew, text_signature: None, asyncness: None, unsafety: None, @@ -1759,7 +1758,7 @@ fn complex_enum_struct_variant_new<'a>( output: syn::ReturnType::Default, }; - crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) + __NEW__.generate_type_slot(&variant_cls_type, &spec, "__default___new____", ctx) } fn complex_enum_tuple_variant_new<'a>( @@ -1805,11 +1804,10 @@ fn complex_enum_tuple_variant_new<'a>( }; let spec = FnSpec { - tp: crate::method::FnType::FnNew, + tp: crate::method::FnType::FnStatic, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, - convention: crate::method::CallingConvention::TpNew, text_signature: None, asyncness: None, unsafety: None, @@ -1818,7 +1816,7 @@ fn complex_enum_tuple_variant_new<'a>( output: syn::ReturnType::Default, }; - crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) + __NEW__.generate_type_slot(&variant_cls_type, &spec, "__default___new____", ctx) } fn complex_enum_variant_field_getter<'a>( @@ -1838,7 +1836,6 @@ fn complex_enum_variant_field_getter<'a>( name: field_name, python_name: field_name.unraw(), signature, - convention: crate::method::CallingConvention::Noargs, text_signature: None, asyncness: None, unsafety: None, diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index ec2195a4e3c..ac0dc93cb45 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -408,7 +408,6 @@ pub fn impl_wrap_pyfunction( let spec = method::FnSpec { tp, name: &func.sig.ident, - convention: CallingConvention::from_signature(&signature), python_name, signature, text_signature, @@ -426,8 +425,14 @@ pub fn impl_wrap_pyfunction( spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } - let wrapper = spec.get_wrapper_function(&wrapper_ident, None, ctx)?; - let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs, ctx)?, ctx); + let calling_convention = CallingConvention::from_signature(&spec.signature); + let wrapper = spec.get_wrapper_function(&wrapper_ident, None, calling_convention, ctx)?; + let methoddef = spec.get_methoddef( + wrapper_ident, + &spec.get_doc(&func.attrs, ctx)?, + calling_convention, + ctx, + ); let wrapped_pyfunction = quote! { // Create a module with the same name as the `#[pyfunction]` - this way `use ` diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 57634e20fc1..a5cf468546f 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -397,7 +397,6 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) - // We introduce self/cls argument and setup decorators let mut first_argument = None; - let mut output = spec.output.clone(); let mut decorators = Vec::new(); match &spec.tp { FnType::Getter(_) => { @@ -411,16 +410,20 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) - FnType::Fn(_) => { first_argument = Some("self"); } - FnType::FnNew | FnType::FnNewClass(_) => { - first_argument = Some("cls"); - output = parse_quote!(-> #pyo3_path::PyRef); // Hack to return Self while implementing IntoPyObject - } FnType::FnClass(_) => { first_argument = Some("cls"); - decorators.push(PythonIdentifier::builtins("classmethod")); + if spec.python_name != "__new__" { + // special case __new__ - does not get the decorator + decorators.push(PythonIdentifier::builtins("classmethod")); + } } FnType::FnStatic => { - decorators.push(PythonIdentifier::builtins("staticmethod")); + if spec.python_name != "__new__" { + decorators.push(PythonIdentifier::builtins("staticmethod")); + } else { + // special case __new__ - does not get the decorator and gets first argument + first_argument = Some("cls"); + } } FnType::FnModule(_) => (), // TODO: not sure this can happen FnType::ClassAttribute => { @@ -430,13 +433,19 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) - decorators.push(PythonIdentifier::builtins("property")); } } + let return_type = if spec.python_name == "__new__" { + // Hack to return Self while implementing IntoPyObject + parse_quote!(-> #pyo3_path::PyRef) + } else { + spec.output.clone() + }; function_introspection_code( pyo3_path, None, &name, &spec.signature, first_argument, - output, + return_type, decorators, Some(parent), ) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 46d11d1f2a3..4b4b4fb84a6 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -5,10 +5,10 @@ use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule}; #[cfg(feature = "experimental-inspect")] use crate::introspection::unique_element_id; use crate::method::{CallingConvention, ExtractErrorMode, PyArg}; -use crate::params::{impl_regular_arg_param, Holders}; +use crate::params::{impl_arg_params, impl_regular_arg_param, Holders}; use crate::pyfunction::WarningFactory; -use crate::utils::Ctx; use crate::utils::PythonDoc; +use crate::utils::{Ctx, StaticIdent}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, @@ -80,6 +80,7 @@ impl PyMethodKind { fn from_name(name: &str) -> Self { match name { // Protocol implemented through slots + "__new__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEW__)), "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)), "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)), "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)), @@ -244,7 +245,7 @@ pub fn gen_py_method( GeneratedPyMethod::Proto(slot) } PyMethodProtoKind::Call => { - GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec, ctx)?) + GeneratedPyMethod::Proto(impl_call_slot(cls, spec, ctx)?) } PyMethodProtoKind::Traverse => { GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?) @@ -259,19 +260,9 @@ pub fn gen_py_method( } } // ordinary functions (with some specialties) - (_, FnType::Fn(_)) | (_, FnType::FnClass(_)) | (_, FnType::FnStatic) => { - GeneratedPyMethod::Method(impl_py_method_def( - cls, - spec, - &spec.get_doc(meth_attrs, ctx)?, - ctx, - )?) - } - // special prototypes - (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => { - GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec, ctx)?) - } - + (_, FnType::Fn(_) | FnType::FnClass(_) | FnType::FnStatic) => GeneratedPyMethod::Method( + impl_py_method_def(cls, spec, &spec.get_doc(meth_attrs, ctx)?, ctx)?, + ), (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def( cls, PropertyType::Function { @@ -321,18 +312,33 @@ fn ensure_no_forbidden_protocol_attributes( method_name: &str, ) -> syn::Result<()> { if let Some(signature) = &spec.signature.attribute { - // __call__ is allowed to have a signature, but nothing else is. - if !matches!(proto_kind, PyMethodProtoKind::Call) { + // __new__ and __call__ are allowed to have a signature, but nothing else is. + if !matches!( + proto_kind, + PyMethodProtoKind::Slot(SlotDef { + calling_convention: SlotCallingConvention::TpNew, + .. + }) + ) && !matches!(proto_kind, PyMethodProtoKind::Call) + { bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name)); } } if let Some(text_signature) = &spec.text_signature { - bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name)); + // __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(()) } -/// Also used by pyfunction. pub fn impl_py_method_def( cls: &syn::Type, spec: &FnSpec<'_>, @@ -341,8 +347,15 @@ pub fn impl_py_method_def( ) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); - let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; - let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx); + let calling_convention = CallingConvention::from_signature(&spec.signature); + let associated_method = + spec.get_wrapper_function(&wrapper_ident, Some(cls), calling_convention, 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) }; @@ -352,50 +365,12 @@ pub fn impl_py_method_def( }) } -/// Also used by pyclass. -pub fn impl_py_method_def_new( - cls: &syn::Type, - spec: &FnSpec<'_>, - ctx: &Ctx, -) -> Result { +fn impl_call_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; - let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site()); - let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; - // 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 slot_def = quote! { - #pyo3_path::ffi::PyType_Slot { - slot: #pyo3_path::ffi::Py_tp_new, - pfunc: { - #text_signature_impl - #pyo3_path::impl_::trampoline::get_trampoline_function!(newfunc, #cls::#wrapper_ident) - } as #pyo3_path::ffi::newfunc as _ - } - }; - Ok(MethodAndSlotDef { - associated_method, - slot_def, - }) -} - -fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result { - let Ctx { pyo3_path, .. } = ctx; - - // HACK: __call__ proto slot must always use varargs calling convention, so change the spec. - // Probably indicates there's a refactoring opportunity somewhere. - spec.convention = CallingConvention::Varargs; let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site()); - let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; + let associated_method = + spec.get_wrapper_function(&wrapper_ident, Some(cls), CallingConvention::Varargs, ctx)?; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_call, @@ -928,18 +903,16 @@ impl PropertyType<'_> { } } +pub const __NEW__: SlotDef = SlotDef::new("Py_tp_new", "newfunc"); 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") - .ret_ty(Ty::PyHashT) - .return_conversion(TokenGenerator( +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) - .arguments(&[Ty::Object, Ty::CompareOp]); -const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc") - .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]); + .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( @@ -954,18 +927,13 @@ const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_speci ), TokenGenerator(|_| quote! { async_iter_tag }), ); -pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT); -const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc") - .arguments(&[Ty::Object]) - .ret_ty(Ty::Int); -const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]); -const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]); -const __INPLACE_CONCAT__: SlotDef = - SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]); -const __INPLACE_REPEAT__: SlotDef = - SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]); -pub const __GETITEM__: SlotDef = - SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]); +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_concat", "binaryfunc"); +const __INPLACE_REPEAT__: SlotDef = SlotDef::new("Py_sq_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"); @@ -974,71 +942,29 @@ 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").ret_ty(Ty::Int); +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 __IADD__: SlotDef = SlotDef::new("Py_nb_inplace_add", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __ISUB__: SlotDef = SlotDef::new("Py_nb_inplace_subtract", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IMUL__: SlotDef = SlotDef::new("Py_nb_inplace_multiply", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IMATMUL__: SlotDef = SlotDef::new("Py_nb_inplace_matrix_multiply", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __ITRUEDIV__: SlotDef = SlotDef::new("Py_nb_inplace_true_divide", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IFLOORDIV__: SlotDef = SlotDef::new("Py_nb_inplace_floor_divide", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IMOD__: SlotDef = SlotDef::new("Py_nb_inplace_remainder", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc") - .arguments(&[Ty::Object, Ty::IPowModulo]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); -const __ILSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_lshift", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IRSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_rshift", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IAND__: SlotDef = SlotDef::new("Py_nb_inplace_and", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IXOR__: SlotDef = SlotDef::new("Py_nb_inplace_xor", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __IOR__: SlotDef = SlotDef::new("Py_nb_inplace_or", "binaryfunc") - .arguments(&[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .return_self(); -const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc") - .arguments(&[Ty::PyBuffer, Ty::Int]) - .ret_ty(Ty::Int) - .require_unsafe(); -const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc") - .arguments(&[Ty::PyBuffer]) - .ret_ty(Ty::Void) - .require_unsafe(); -const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry") - .arguments(&[]) - .ret_ty(Ty::Int); + +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 { @@ -1216,8 +1142,8 @@ impl ReturnMode { ReturnMode::ReturnSelf => quote! { let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call); _result?; - #pyo3_path::ffi::Py_XINCREF(_raw_slf); - ::std::result::Result::Ok(_raw_slf) + #pyo3_path::ffi::Py_XINCREF(_slf); + ::std::result::Result::Ok(_slf) }, } } @@ -1226,36 +1152,83 @@ impl ReturnMode { pub struct SlotDef { slot: StaticIdent, func_ty: StaticIdent, - arguments: &'static [Ty], + calling_convention: SlotCallingConvention, ret_ty: Ty, extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, } -const NO_ARGUMENTS: &[Ty] = &[]; +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, +} impl SlotDef { 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"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"ipowfunc" => ( + SlotCallingConvention::FixedArguments(&[Ty::Object, Ty::IPowModulo]), + Ty::Object, + ), + _ => panic!("don't know calling convention for func_ty"), + }; + SlotDef { - slot: StaticIdent(slot), - func_ty: StaticIdent(func_ty), - arguments: NO_ARGUMENTS, - ret_ty: Ty::Object, + 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, } } - const fn arguments(mut self, arguments: &'static [Ty]) -> Self { - self.arguments = arguments; - self - } - - const fn ret_ty(mut self, ret_ty: Ty) -> Self { - self.ret_ty = ret_ty; - self + /// 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 { @@ -1298,7 +1271,7 @@ impl SlotDef { let SlotDef { slot, func_ty, - arguments, + calling_convention, extract_error_mode, ret_ty, return_mode, @@ -1310,17 +1283,17 @@ impl SlotDef { spec.name.span() => format!("`{}` must be `unsafe fn`", method_name) ); } - let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect(); - let arg_idents: &Vec<_> = &(0..arguments.len()) - .map(|i| format_ident!("arg{}", i)) - .collect(); let wrapper_ident = format_ident!("__pymethod_{}__", method_name); let ret_ty = ret_ty.ffi_type(ctx); let mut holders = Holders::new(); - let body = generate_method_body( + let MethodBody { + arg_idents, + arg_types, + body, + } = generate_method_body( cls, spec, - arguments, + calling_convention, *extract_error_mode, &mut holders, return_mode.as_ref(), @@ -1332,21 +1305,19 @@ impl SlotDef { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, - _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { let function = #cls::#name; // Shadow the method name to avoid #3017 - let _slf = _raw_slf; #holders #body } }; - let slot_def = quote! {{ + 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, @@ -1357,32 +1328,92 @@ impl SlotDef { fn generate_method_body( cls: &syn::Type, spec: &FnSpec<'_>, - arguments: &[Ty], + calling_convention: &SlotCallingConvention, extract_error_mode: ExtractErrorMode, 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, .. } = ctx; +) -> Result { + let Ctx { + pyo3_path, + output_span, + } = ctx; let self_arg = spec .tp .self_arg(Some(cls), extract_error_mode, holders, ctx); let rust_name = spec.name; - let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?; - let call = quote! { #cls::#rust_name(#self_arg #(#args),*) }; - let body = 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 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 call = quote_spanned! {*output_span=> #cls::#rust_name(#self_arg #(#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 body = quote! { + #text_signature_impl + + use #pyo3_path::impl_::callback::IntoPyCallbackOutput; + #warnings + #arg_convert + let result = #call; + let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?; + #pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf) + }; + (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 call = quote! { #cls::#rust_name(#self_arg #(#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) } }; - let warnings = spec.warnings.build_py_warning(ctx); - Ok(quote! { - #warnings - #body + Ok(MethodBody { + arg_idents, + arg_types, + body, }) } @@ -1403,6 +1434,16 @@ impl SlotFragmentDef { } } + /// Specialized constructor for binary operators (which are a common pattern) + const fn binary_operator(fragment: &'static str) -> Self { + SlotFragmentDef { + fragment, + arguments: &[Ty::Object], + extract_error_mode: ExtractErrorMode::NotImplemented, + ret_ty: Ty::Object, + } + } + const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self @@ -1429,15 +1470,16 @@ impl SlotFragmentDef { 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 arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect(); - let arg_idents: &Vec<_> = &(0..arguments.len()) - .map(|i| format_ident!("arg{}", i)) - .collect(); + let mut holders = Holders::new(); - let body = generate_method_body( + let MethodBody { + arg_idents, + arg_types, + body, + } = generate_method_body( cls, spec, - arguments, + &SlotCallingConvention::FixedArguments(arguments), *extract_error_mode, &mut holders, None, @@ -1450,10 +1492,8 @@ impl SlotFragmentDef { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python, - _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { - let _slf = _raw_slf; #holders #body } @@ -1465,16 +1505,22 @@ impl SlotFragmentDef { unsafe fn #method( self, py: #pyo3_path::Python, - _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { - #cls::#wrapper_ident(py, _raw_slf, #(#arg_idents),*) + #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); const __GETATTR__: SlotFragmentDef = @@ -1488,40 +1534,32 @@ const __SETITEM__: SlotFragmentDef = SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]); const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]); -macro_rules! binary_num_slot_fragment_def { - ($ident:ident, $name:literal) => { - const $ident: SlotFragmentDef = SlotFragmentDef::new($name, &[Ty::Object]) - .extract_error_mode(ExtractErrorMode::NotImplemented) - .ret_ty(Ty::Object); - }; -} - -binary_num_slot_fragment_def!(__ADD__, "__add__"); -binary_num_slot_fragment_def!(__RADD__, "__radd__"); -binary_num_slot_fragment_def!(__SUB__, "__sub__"); -binary_num_slot_fragment_def!(__RSUB__, "__rsub__"); -binary_num_slot_fragment_def!(__MUL__, "__mul__"); -binary_num_slot_fragment_def!(__RMUL__, "__rmul__"); -binary_num_slot_fragment_def!(__MATMUL__, "__matmul__"); -binary_num_slot_fragment_def!(__RMATMUL__, "__rmatmul__"); -binary_num_slot_fragment_def!(__FLOORDIV__, "__floordiv__"); -binary_num_slot_fragment_def!(__RFLOORDIV__, "__rfloordiv__"); -binary_num_slot_fragment_def!(__TRUEDIV__, "__truediv__"); -binary_num_slot_fragment_def!(__RTRUEDIV__, "__rtruediv__"); -binary_num_slot_fragment_def!(__DIVMOD__, "__divmod__"); -binary_num_slot_fragment_def!(__RDIVMOD__, "__rdivmod__"); -binary_num_slot_fragment_def!(__MOD__, "__mod__"); -binary_num_slot_fragment_def!(__RMOD__, "__rmod__"); -binary_num_slot_fragment_def!(__LSHIFT__, "__lshift__"); -binary_num_slot_fragment_def!(__RLSHIFT__, "__rlshift__"); -binary_num_slot_fragment_def!(__RSHIFT__, "__rshift__"); -binary_num_slot_fragment_def!(__RRSHIFT__, "__rrshift__"); -binary_num_slot_fragment_def!(__AND__, "__and__"); -binary_num_slot_fragment_def!(__RAND__, "__rand__"); -binary_num_slot_fragment_def!(__XOR__, "__xor__"); -binary_num_slot_fragment_def!(__RXOR__, "__rxor__"); -binary_num_slot_fragment_def!(__OR__, "__or__"); -binary_num_slot_fragment_def!(__ROR__, "__ror__"); +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) @@ -1578,14 +1616,6 @@ fn extract_proto_arguments( Ok(args) } -struct StaticIdent(&'static str); - -impl ToTokens for StaticIdent { - fn to_tokens(&self, tokens: &mut TokenStream) { - syn::Ident::new(self.0, Span::call_site()).to_tokens(tokens) - } -} - #[derive(Clone, Copy)] struct TokenGenerator(fn(&Ctx) -> TokenStream); diff --git a/pyo3-macros-backend/src/utils.rs b/pyo3-macros-backend/src/utils.rs index 81228cd8b69..20f682fa042 100644 --- a/pyo3-macros-backend/src/utils.rs +++ b/pyo3-macros-backend/src/utils.rs @@ -1,6 +1,6 @@ use crate::attributes::{CrateAttribute, RenamingRule}; use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use std::ffi::CString; use syn::spanned::Spanned; use syn::LitCStr; @@ -328,3 +328,19 @@ pub fn expr_to_python(expr: &syn::Expr) -> String { _ => "...".to_string(), } } + +/// Helper struct for hard-coded identifiers used in the macro code. +#[derive(Clone, Copy)] +pub struct StaticIdent(&'static str); + +impl StaticIdent { + pub const fn new(name: &'static str) -> Self { + Self(name) + } +} + +impl ToTokens for StaticIdent { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(syn::Ident::new(self.0, Span::call_site())); + } +}