Skip to content
77 changes: 19 additions & 58 deletions pyo3-macros-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -338,7 +328,7 @@ impl FnType {
};
Some(quote! { unsafe { #ret }, })
}
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None,
FnType::FnStatic | FnType::ClassAttribute => None,
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<TextSignatureAttribute>,
pub asyncness: Option<syn::Token![async]>,
pub unsafety: Option<syn::Token![unsafe]>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -677,6 +658,7 @@ impl<'a> FnSpec<'a> {
&self,
ident: &proc_macro2::Ident,
cls: Option<&syn::Type>,
convention: CallingConvention,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -872,52 +854,31 @@ 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 {
FnType::FnClass(_) => quote! { .flags(#pyo3_path::ffi::METH_CLASS) },
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(
Expand All @@ -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) {
Expand Down
15 changes: 6 additions & 9 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -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>(
Expand Down Expand Up @@ -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,
Expand All @@ -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>(
Expand All @@ -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,
Expand Down
11 changes: 8 additions & 3 deletions pyo3-macros-backend/src/pyfunction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,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,
Expand All @@ -424,8 +423,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 <the function>`
Expand Down
25 changes: 17 additions & 8 deletions pyo3-macros-backend/src/pyimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,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(_) => {
Expand All @@ -409,16 +408,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<Self>); // Hack to return Self while implementing IntoPyObject
}
FnType::FnClass(_) => {
first_argument = Some("cls");
decorators.push("classmethod".into());
if spec.python_name != "__new__" {
// special case __new__ - does not get the decorator
decorators.push("classmethod".into());
}
}
FnType::FnStatic => {
decorators.push("staticmethod".into());
if spec.python_name != "__new__" {
decorators.push("staticmethod".into());
} 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 => {
Expand All @@ -428,13 +431,19 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) -
decorators.push("property".into());
}
}
let return_type = if spec.python_name == "__new__" {
// Hack to return Self while implementing IntoPyObject
parse_quote!(-> #pyo3_path::PyRef<Self>)
} else {
spec.output.clone()
};
function_introspection_code(
pyo3_path,
None,
&name,
&spec.signature,
first_argument,
output,
return_type,
decorators,
Some(parent),
)
Expand Down
Loading
Loading