1use std::borrow::Cow;
2use std::ffi::CString;
3
4use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule};
5#[cfg(feature = "experimental-inspect")]
6use crate::introspection::unique_element_id;
7use crate::method::{CallingConvention, ExtractErrorMode, PyArg};
8use crate::params::{impl_regular_arg_param, Holders};
9use crate::pyfunction::WarningFactory;
10use crate::utils::PythonDoc;
11use crate::utils::{Ctx, LitCStr};
12use crate::{
13 method::{FnArg, FnSpec, FnType, SelfType},
14 pyfunction::PyFunctionOptions,
15};
16use crate::{quotes, utils};
17use proc_macro2::{Span, TokenStream};
18use quote::{format_ident, quote, quote_spanned, ToTokens};
19use syn::{ext::IdentExt, spanned::Spanned, Field, Ident, Result};
20
21pub struct MethodAndMethodDef {
23 pub associated_method: TokenStream,
25 pub method_def: TokenStream,
27}
28
29#[cfg(feature = "experimental-inspect")]
30impl MethodAndMethodDef {
31 pub fn add_introspection(&mut self, data: TokenStream) {
32 let const_name = format_ident!("_{}", unique_element_id()); self.associated_method.extend(quote! {
34 const #const_name: () = {
35 #data
36 };
37 });
38 }
39}
40
41pub struct MethodAndSlotDef {
43 pub associated_method: TokenStream,
45 pub slot_def: TokenStream,
47}
48
49#[cfg(feature = "experimental-inspect")]
50impl MethodAndSlotDef {
51 pub fn add_introspection(&mut self, data: TokenStream) {
52 let const_name = format_ident!("_{}", unique_element_id()); self.associated_method.extend(quote! {
54 const #const_name: () = {
55 #data
56 };
57 });
58 }
59}
60
61pub enum GeneratedPyMethod {
62 Method(MethodAndMethodDef),
63 Proto(MethodAndSlotDef),
64 SlotTraitImpl(String, TokenStream),
65}
66
67pub struct PyMethod<'a> {
68 kind: PyMethodKind,
69 method_name: String,
70 pub spec: FnSpec<'a>,
71}
72
73enum PyMethodKind {
74 Fn,
75 Proto(PyMethodProtoKind),
76}
77
78impl PyMethodKind {
79 fn from_name(name: &str) -> Self {
80 match name {
81 "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)),
83 "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)),
84 "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)),
85 "__richcmp__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RICHCMP__)),
86 "__get__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GET__)),
87 "__iter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITER__)),
88 "__next__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEXT__)),
89 "__await__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AWAIT__)),
90 "__aiter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AITER__)),
91 "__anext__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ANEXT__)),
92 "__len__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__LEN__)),
93 "__contains__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONTAINS__)),
94 "__concat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONCAT__)),
95 "__repeat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPEAT__)),
96 "__inplace_concat__" => {
97 PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_CONCAT__))
98 }
99 "__inplace_repeat__" => {
100 PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_REPEAT__))
101 }
102 "__getitem__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETITEM__)),
103 "__pos__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__POS__)),
104 "__neg__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEG__)),
105 "__abs__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ABS__)),
106 "__invert__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INVERT__)),
107 "__index__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INDEX__)),
108 "__int__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INT__)),
109 "__float__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__FLOAT__)),
110 "__bool__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__BOOL__)),
111 "__iadd__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IADD__)),
112 "__isub__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ISUB__)),
113 "__imul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMUL__)),
114 "__imatmul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMATMUL__)),
115 "__itruediv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITRUEDIV__)),
116 "__ifloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IFLOORDIV__)),
117 "__imod__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMOD__)),
118 "__ipow__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IPOW__)),
119 "__ilshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ILSHIFT__)),
120 "__irshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IRSHIFT__)),
121 "__iand__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IAND__)),
122 "__ixor__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IXOR__)),
123 "__ior__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IOR__)),
124 "__getbuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETBUFFER__)),
125 "__releasebuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RELEASEBUFFER__)),
126 "__getattribute__" => {
128 PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTRIBUTE__))
129 }
130 "__getattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTR__)),
131 "__setattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETATTR__)),
132 "__delattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELATTR__)),
133 "__set__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SET__)),
134 "__delete__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELETE__)),
135 "__setitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETITEM__)),
136 "__delitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELITEM__)),
137 "__add__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ADD__)),
138 "__radd__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RADD__)),
139 "__sub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SUB__)),
140 "__rsub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSUB__)),
141 "__mul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MUL__)),
142 "__rmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMUL__)),
143 "__matmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MATMUL__)),
144 "__rmatmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMATMUL__)),
145 "__floordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__FLOORDIV__)),
146 "__rfloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RFLOORDIV__)),
147 "__truediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__TRUEDIV__)),
148 "__rtruediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RTRUEDIV__)),
149 "__divmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DIVMOD__)),
150 "__rdivmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RDIVMOD__)),
151 "__mod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MOD__)),
152 "__rmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMOD__)),
153 "__lshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LSHIFT__)),
154 "__rlshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RLSHIFT__)),
155 "__rshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSHIFT__)),
156 "__rrshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RRSHIFT__)),
157 "__and__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__AND__)),
158 "__rand__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RAND__)),
159 "__xor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__XOR__)),
160 "__rxor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RXOR__)),
161 "__or__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__OR__)),
162 "__ror__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ROR__)),
163 "__pow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__POW__)),
164 "__rpow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RPOW__)),
165 "__lt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LT__)),
166 "__le__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LE__)),
167 "__eq__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__EQ__)),
168 "__ne__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__NE__)),
169 "__gt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GT__)),
170 "__ge__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GE__)),
171 "__call__" => PyMethodKind::Proto(PyMethodProtoKind::Call),
173 "__traverse__" => PyMethodKind::Proto(PyMethodProtoKind::Traverse),
174 "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Clear),
175 _ => PyMethodKind::Fn,
177 }
178 }
179}
180
181enum PyMethodProtoKind {
182 Slot(&'static SlotDef),
183 Call,
184 Traverse,
185 Clear,
186 SlotFragment(&'static SlotFragmentDef),
187}
188
189impl<'a> PyMethod<'a> {
190 pub fn parse(
191 sig: &'a mut syn::Signature,
192 meth_attrs: &mut Vec<syn::Attribute>,
193 options: PyFunctionOptions,
194 ) -> Result<Self> {
195 check_generic(sig)?;
196 ensure_function_options_valid(&options)?;
197 let spec = FnSpec::parse(sig, meth_attrs, options)?;
198
199 let method_name = spec.python_name.to_string();
200 let kind = PyMethodKind::from_name(&method_name);
201
202 Ok(Self {
203 kind,
204 method_name,
205 spec,
206 })
207 }
208}
209
210pub fn is_proto_method(name: &str) -> bool {
211 match PyMethodKind::from_name(name) {
212 PyMethodKind::Fn => false,
213 PyMethodKind::Proto(_) => true,
214 }
215}
216
217pub fn gen_py_method(
218 cls: &syn::Type,
219 method: PyMethod<'_>,
220 meth_attrs: &[syn::Attribute],
221 ctx: &Ctx,
222) -> Result<GeneratedPyMethod> {
223 let spec = &method.spec;
224 let Ctx { pyo3_path, .. } = ctx;
225
226 if spec.asyncness.is_some() {
227 ensure_spanned!(
228 cfg!(feature = "experimental-async"),
229 spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature"
230 );
231 }
232
233 Ok(match (method.kind, &spec.tp) {
234 (_, FnType::ClassAttribute) => {
237 GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec, ctx)?)
238 }
239 (PyMethodKind::Proto(proto_kind), _) => {
240 ensure_no_forbidden_protocol_attributes(&proto_kind, spec, &method.method_name)?;
241 match proto_kind {
242 PyMethodProtoKind::Slot(slot_def) => {
243 let slot = slot_def.generate_type_slot(cls, spec, &method.method_name, ctx)?;
244 GeneratedPyMethod::Proto(slot)
245 }
246 PyMethodProtoKind::Call => {
247 GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec, ctx)?)
248 }
249 PyMethodProtoKind::Traverse => {
250 GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?)
251 }
252 PyMethodProtoKind::Clear => {
253 GeneratedPyMethod::Proto(impl_clear_slot(cls, spec, ctx)?)
254 }
255 PyMethodProtoKind::SlotFragment(slot_fragment_def) => {
256 let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec, ctx)?;
257 GeneratedPyMethod::SlotTraitImpl(method.method_name, proto)
258 }
259 }
260 }
261 (_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
263 cls,
264 spec,
265 &spec.get_doc(meth_attrs, ctx)?,
266 None,
267 ctx,
268 )?),
269 (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
270 cls,
271 spec,
272 &spec.get_doc(meth_attrs, ctx)?,
273 Some(quote!(#pyo3_path::ffi::METH_CLASS)),
274 ctx,
275 )?),
276 (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
277 cls,
278 spec,
279 &spec.get_doc(meth_attrs, ctx)?,
280 Some(quote!(#pyo3_path::ffi::METH_STATIC)),
281 ctx,
282 )?),
283 (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => {
285 GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec, ctx)?)
286 }
287
288 (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def(
289 cls,
290 PropertyType::Function {
291 self_type,
292 spec,
293 doc: spec.get_doc(meth_attrs, ctx)?,
294 },
295 ctx,
296 )?),
297 (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def(
298 cls,
299 PropertyType::Function {
300 self_type,
301 spec,
302 doc: spec.get_doc(meth_attrs, ctx)?,
303 },
304 ctx,
305 )?),
306 (_, FnType::FnModule(_)) => {
307 unreachable!("methods cannot be FnModule")
308 }
309 })
310}
311
312pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
313 let err_msg = |typ| format!("Python functions cannot have generic {typ} parameters");
314 for param in &sig.generics.params {
315 match param {
316 syn::GenericParam::Lifetime(_) => {}
317 syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")),
318 syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")),
319 }
320 }
321 Ok(())
322}
323
324fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> {
325 if let Some(pass_module) = &options.pass_module {
326 bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods");
327 }
328 Ok(())
329}
330
331fn ensure_no_forbidden_protocol_attributes(
332 proto_kind: &PyMethodProtoKind,
333 spec: &FnSpec<'_>,
334 method_name: &str,
335) -> syn::Result<()> {
336 if let Some(signature) = &spec.signature.attribute {
337 if !matches!(proto_kind, PyMethodProtoKind::Call) {
339 bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name));
340 }
341 }
342 if let Some(text_signature) = &spec.text_signature {
343 bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name));
344 }
345 Ok(())
346}
347
348pub fn impl_py_method_def(
350 cls: &syn::Type,
351 spec: &FnSpec<'_>,
352 doc: &PythonDoc,
353 flags: Option<TokenStream>,
354 ctx: &Ctx,
355) -> Result<MethodAndMethodDef> {
356 let Ctx { pyo3_path, .. } = ctx;
357 let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name);
358 let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
359 let add_flags = flags.map(|flags| quote!(.flags(#flags)));
360 let methoddef_type = match spec.tp {
361 FnType::FnStatic => quote!(Static),
362 FnType::FnClass(_) => quote!(Class),
363 _ => quote!(Method),
364 };
365 let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc, ctx);
366 let method_def = quote! {
367 #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
368 #pyo3_path::impl_::pymethods::PyMethodDefType::#methoddef_type(#methoddef #add_flags)
369 )
370 };
371 Ok(MethodAndMethodDef {
372 associated_method,
373 method_def,
374 })
375}
376
377pub fn impl_py_method_def_new(
379 cls: &syn::Type,
380 spec: &FnSpec<'_>,
381 ctx: &Ctx,
382) -> Result<MethodAndSlotDef> {
383 let Ctx { pyo3_path, .. } = ctx;
384 let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site());
385 let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
386 let text_signature_impl = spec.text_signature_call_signature().map(|text_signature| {
390 quote! {
391 #[allow(unknown_lints, non_local_definitions)]
392 impl #pyo3_path::impl_::pyclass::doc::PyClassNewTextSignature for #cls {
393 const TEXT_SIGNATURE: &'static str = #text_signature;
394 }
395 }
396 });
397 let slot_def = quote! {
398 #pyo3_path::ffi::PyType_Slot {
399 slot: #pyo3_path::ffi::Py_tp_new,
400 pfunc: {
401 unsafe extern "C" fn trampoline(
402 subtype: *mut #pyo3_path::ffi::PyTypeObject,
403 args: *mut #pyo3_path::ffi::PyObject,
404 kwargs: *mut #pyo3_path::ffi::PyObject,
405 ) -> *mut #pyo3_path::ffi::PyObject {
406
407 #text_signature_impl
408
409 #pyo3_path::impl_::trampoline::newfunc(
410 subtype,
411 args,
412 kwargs,
413 #cls::#wrapper_ident
414 )
415 }
416 trampoline
417 } as #pyo3_path::ffi::newfunc as _
418 }
419 };
420 Ok(MethodAndSlotDef {
421 associated_method,
422 slot_def,
423 })
424}
425
426fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result<MethodAndSlotDef> {
427 let Ctx { pyo3_path, .. } = ctx;
428
429 spec.convention = CallingConvention::Varargs;
432
433 let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site());
434 let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?;
435 let slot_def = quote! {
436 #pyo3_path::ffi::PyType_Slot {
437 slot: #pyo3_path::ffi::Py_tp_call,
438 pfunc: {
439 unsafe extern "C" fn trampoline(
440 slf: *mut #pyo3_path::ffi::PyObject,
441 args: *mut #pyo3_path::ffi::PyObject,
442 kwargs: *mut #pyo3_path::ffi::PyObject,
443 ) -> *mut #pyo3_path::ffi::PyObject
444 {
445 #pyo3_path::impl_::trampoline::ternaryfunc(
446 slf,
447 args,
448 kwargs,
449 #cls::#wrapper_ident
450 )
451 }
452 trampoline
453 } as #pyo3_path::ffi::ternaryfunc as _
454 }
455 };
456 Ok(MethodAndSlotDef {
457 associated_method,
458 slot_def,
459 })
460}
461
462fn impl_traverse_slot(
463 cls: &syn::Type,
464 spec: &FnSpec<'_>,
465 ctx: &Ctx,
466) -> syn::Result<MethodAndSlotDef> {
467 let Ctx { pyo3_path, .. } = ctx;
468 if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
469 return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
470 Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
471 should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \
472 prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic."));
473 }
474
475 if let FnType::Fn(SelfType::TryFromBoundRef(span))
477 | FnType::Fn(SelfType::Receiver {
478 mutable: true,
479 span,
480 }) = spec.tp
481 {
482 bail_spanned! { span =>
483 "__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
484 `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
485 should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \
486 prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic."
487 }
488 }
489
490 ensure_spanned!(
491 spec.warnings.is_empty(),
492 spec.warnings.span() => "__traverse__ cannot be used with #[pyo3(warn)]"
493 );
494
495 let rust_fn_ident = spec.name;
496
497 let associated_method = quote! {
498 pub unsafe extern "C" fn __pymethod_traverse__(
499 slf: *mut #pyo3_path::ffi::PyObject,
500 visit: #pyo3_path::ffi::visitproc,
501 arg: *mut ::std::ffi::c_void,
502 ) -> ::std::ffi::c_int {
503 #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg, #cls::__pymethod_traverse__)
504 }
505 };
506 let slot_def = quote! {
507 #pyo3_path::ffi::PyType_Slot {
508 slot: #pyo3_path::ffi::Py_tp_traverse,
509 pfunc: #cls::__pymethod_traverse__ as #pyo3_path::ffi::traverseproc as _
510 }
511 };
512 Ok(MethodAndSlotDef {
513 associated_method,
514 slot_def,
515 })
516}
517
518fn impl_clear_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> syn::Result<MethodAndSlotDef> {
519 let Ctx { pyo3_path, .. } = ctx;
520 let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
521 let self_type = match &spec.tp {
522 FnType::Fn(self_type) => self_type,
523 _ => bail_spanned!(spec.name.span() => "expected instance method for `__clear__` function"),
524 };
525 let mut holders = Holders::new();
526 let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
527
528 if let [arg, ..] = args {
529 bail_spanned!(arg.ty().span() => "`__clear__` function expected to have no arguments");
530 }
531
532 let name = &spec.name;
533 let holders = holders.init_holders(ctx);
534 let fncall = if py_arg.is_some() {
535 quote!(#cls::#name(#slf, py))
536 } else {
537 quote!(#cls::#name(#slf))
538 };
539
540 let associated_method = quote! {
541 pub unsafe extern "C" fn __pymethod___clear____(
542 _slf: *mut #pyo3_path::ffi::PyObject,
543 ) -> ::std::ffi::c_int {
544 #pyo3_path::impl_::pymethods::_call_clear(_slf, |py, _slf| {
545 #holders
546 let result = #fncall;
547 let result = #pyo3_path::impl_::wrap::converter(&result).wrap(result)?;
548 ::std::result::Result::Ok(result)
549 }, #cls::__pymethod___clear____)
550 }
551 };
552 let slot_def = quote! {
553 #pyo3_path::ffi::PyType_Slot {
554 slot: #pyo3_path::ffi::Py_tp_clear,
555 pfunc: #cls::__pymethod___clear____ as #pyo3_path::ffi::inquiry as _
556 }
557 };
558 Ok(MethodAndSlotDef {
559 associated_method,
560 slot_def,
561 })
562}
563
564pub(crate) fn impl_py_class_attribute(
565 cls: &syn::Type,
566 spec: &FnSpec<'_>,
567 ctx: &Ctx,
568) -> syn::Result<MethodAndMethodDef> {
569 let Ctx { pyo3_path, .. } = ctx;
570 let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
571 ensure_spanned!(
572 args.is_empty(),
573 args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)"
574 );
575
576 ensure_spanned!(
577 spec.warnings.is_empty(),
578 spec.warnings.span()
579 => "#[classattr] cannot be used with #[pyo3(warn)]"
580 );
581
582 let name = &spec.name;
583 let fncall = if py_arg.is_some() {
584 quote!(function(py))
585 } else {
586 quote!(function())
587 };
588
589 let wrapper_ident = format_ident!("__pymethod_{}__", name);
590 let python_name = spec.null_terminated_python_name(ctx);
591 let body = quotes::ok_wrap(fncall, ctx);
592
593 let associated_method = quote! {
594 fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> {
595 let function = #cls::#name; let result = #body;
597 #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result)
598 }
599 };
600
601 let method_def = quote! {
602 #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
603 #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({
604 #pyo3_path::impl_::pymethods::PyClassAttributeDef::new(
605 #python_name,
606 #cls::#wrapper_ident
607 )
608 })
609 )
610 };
611
612 Ok(MethodAndMethodDef {
613 associated_method,
614 method_def,
615 })
616}
617
618fn impl_call_setter(
619 cls: &syn::Type,
620 spec: &FnSpec<'_>,
621 self_type: &SelfType,
622 holders: &mut Holders,
623 ctx: &Ctx,
624) -> syn::Result<TokenStream> {
625 let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
626 let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
627
628 if args.is_empty() {
629 bail_spanned!(spec.name.span() => "setter function expected to have one argument");
630 } else if args.len() > 1 {
631 bail_spanned!(
632 args[1].ty().span() =>
633 "setter function can have at most two arguments ([pyo3::Python,] and value)"
634 );
635 }
636
637 let name = &spec.name;
638 let fncall = if py_arg.is_some() {
639 quote!(#cls::#name(#slf, py, _val))
640 } else {
641 quote!(#cls::#name(#slf, _val))
642 };
643
644 Ok(fncall)
645}
646
647pub fn impl_py_setter_def(
649 cls: &syn::Type,
650 property_type: PropertyType<'_>,
651 ctx: &Ctx,
652) -> Result<MethodAndMethodDef> {
653 let Ctx { pyo3_path, .. } = ctx;
654 let python_name = property_type.null_terminated_python_name(ctx)?;
655 let doc = property_type.doc(ctx)?;
656 let mut holders = Holders::new();
657 let setter_impl = match property_type {
658 PropertyType::Descriptor {
659 field_index, field, ..
660 } => {
661 let slf = SelfType::Receiver {
662 mutable: true,
663 span: Span::call_site(),
664 }
665 .receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx);
666 if let Some(ident) = &field.ident {
667 quote!({ #slf.#ident = _val; })
669 } else {
670 let index = syn::Index::from(field_index);
672 quote!({ #slf.#index = _val; })
673 }
674 }
675 PropertyType::Function {
676 spec, self_type, ..
677 } => impl_call_setter(cls, spec, self_type, &mut holders, ctx)?,
678 };
679
680 let wrapper_ident = match property_type {
681 PropertyType::Descriptor {
682 field: syn::Field {
683 ident: Some(ident), ..
684 },
685 ..
686 } => {
687 format_ident!("__pymethod_set_{}__", ident)
688 }
689 PropertyType::Descriptor { field_index, .. } => {
690 format_ident!("__pymethod_set_field_{}__", field_index)
691 }
692 PropertyType::Function { spec, .. } => {
693 format_ident!("__pymethod_set_{}__", spec.name)
694 }
695 };
696
697 let extract = match &property_type {
698 PropertyType::Function { spec, .. } => {
699 let (_, args) = split_off_python_arg(&spec.signature.arguments);
700 let value_arg = &args[0];
701 let (from_py_with, ident) =
702 if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) {
703 let ident = syn::Ident::new("from_py_with", from_py_with.span());
704 (
705 quote_spanned! { from_py_with.span() =>
706 let #ident = #from_py_with;
707 },
708 ident,
709 )
710 } else {
711 (quote!(), syn::Ident::new("dummy", Span::call_site()))
712 };
713
714 let arg = if let FnArg::Regular(arg) = &value_arg {
715 arg
716 } else {
717 bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`.");
718 };
719
720 let extract = impl_regular_arg_param(
721 arg,
722 ident,
723 quote!(::std::option::Option::Some(_value.into())),
724 &mut holders,
725 ctx,
726 );
727
728 quote! {
729 #from_py_with
730 let _val = #extract;
731 }
732 }
733 PropertyType::Descriptor { field, .. } => {
734 let span = field.ty.span();
735 let name = field
736 .ident
737 .as_ref()
738 .map(|i| i.to_string())
739 .unwrap_or_default();
740
741 let holder = holders.push_holder(span);
742 quote! {
743 #[allow(unused_imports)]
744 use #pyo3_path::impl_::pyclass::Probe as _;
745 let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?;
746 }
747 }
748 };
749
750 let mut cfg_attrs = TokenStream::new();
751 if let PropertyType::Descriptor { field, .. } = &property_type {
752 for attr in field
753 .attrs
754 .iter()
755 .filter(|attr| attr.path().is_ident("cfg"))
756 {
757 attr.to_tokens(&mut cfg_attrs);
758 }
759 }
760
761 let warnings = if let PropertyType::Function { spec, .. } = &property_type {
762 spec.warnings.build_py_warning(ctx)
763 } else {
764 quote!()
765 };
766
767 let init_holders = holders.init_holders(ctx);
768 let associated_method = quote! {
769 #cfg_attrs
770 unsafe fn #wrapper_ident(
771 py: #pyo3_path::Python<'_>,
772 _slf: *mut #pyo3_path::ffi::PyObject,
773 _value: *mut #pyo3_path::ffi::PyObject,
774 ) -> #pyo3_path::PyResult<::std::ffi::c_int> {
775 use ::std::convert::Into;
776 let _value = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_value)
777 .ok_or_else(|| {
778 #pyo3_path::exceptions::PyAttributeError::new_err("can't delete attribute")
779 })?;
780 #init_holders
781 #extract
782 #warnings
783 let result = #setter_impl;
784 #pyo3_path::impl_::callback::convert(py, result)
785 }
786 };
787
788 let method_def = quote! {
789 #cfg_attrs
790 #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
791 #pyo3_path::impl_::pymethods::PyMethodDefType::Setter(
792 #pyo3_path::impl_::pymethods::PySetterDef::new(
793 #python_name,
794 #cls::#wrapper_ident,
795 #doc
796 )
797 )
798 )
799 };
800
801 Ok(MethodAndMethodDef {
802 associated_method,
803 method_def,
804 })
805}
806
807fn impl_call_getter(
808 cls: &syn::Type,
809 spec: &FnSpec<'_>,
810 self_type: &SelfType,
811 holders: &mut Holders,
812 ctx: &Ctx,
813) -> syn::Result<TokenStream> {
814 let (py_arg, args) = split_off_python_arg(&spec.signature.arguments);
815 let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx);
816 ensure_spanned!(
817 args.is_empty(),
818 args[0].ty().span() => "getter function can only have one argument (of type pyo3::Python)"
819 );
820
821 let name = &spec.name;
822 let fncall = if py_arg.is_some() {
823 quote!(#cls::#name(#slf, py))
824 } else {
825 quote!(#cls::#name(#slf))
826 };
827
828 Ok(fncall)
829}
830
831pub fn impl_py_getter_def(
833 cls: &syn::Type,
834 property_type: PropertyType<'_>,
835 ctx: &Ctx,
836) -> Result<MethodAndMethodDef> {
837 let Ctx { pyo3_path, .. } = ctx;
838 let python_name = property_type.null_terminated_python_name(ctx)?;
839 let doc = property_type.doc(ctx)?;
840
841 let mut cfg_attrs = TokenStream::new();
842 if let PropertyType::Descriptor { field, .. } = &property_type {
843 for attr in field
844 .attrs
845 .iter()
846 .filter(|attr| attr.path().is_ident("cfg"))
847 {
848 attr.to_tokens(&mut cfg_attrs);
849 }
850 }
851
852 let mut holders = Holders::new();
853 match property_type {
854 PropertyType::Descriptor {
855 field_index, field, ..
856 } => {
857 let ty = &field.ty;
858 let field = if let Some(ident) = &field.ident {
859 ident.to_token_stream()
860 } else {
861 syn::Index::from(field_index).to_token_stream()
862 };
863
864 let generator = quote_spanned! { ty.span() =>
867 #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime(
868 || GENERATOR.generate(#python_name, #doc)
869 )
870 };
871 let method_def = quote! {
874 #cfg_attrs
875 {
876 #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _;
878
879 struct Offset;
880 unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset {
881 fn offset() -> usize {
882 #pyo3_path::impl_::pyclass::class_offset::<#cls>() +
883 #pyo3_path::impl_::pyclass::offset_of!(#cls, #field)
884 }
885 }
886
887 const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::<
888 #cls,
889 #ty,
890 Offset,
891 { #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE },
892 { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#ty>::VALUE },
893 { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#ty>::VALUE },
894 > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() };
895 #generator
896 }
897 };
898
899 Ok(MethodAndMethodDef {
900 associated_method: quote! {},
901 method_def,
902 })
903 }
904 PropertyType::Function {
906 spec, self_type, ..
907 } => {
908 let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name);
909 let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?;
910 let body = quote! {
911 #pyo3_path::impl_::callback::convert(py, #call)
912 };
913
914 let init_holders = holders.init_holders(ctx);
915 let warnings = spec.warnings.build_py_warning(ctx);
916
917 let associated_method = quote! {
918 #cfg_attrs
919 unsafe fn #wrapper_ident(
920 py: #pyo3_path::Python<'_>,
921 _slf: *mut #pyo3_path::ffi::PyObject
922 ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
923 #init_holders
924 #warnings
925 let result = #body;
926 result
927 }
928 };
929
930 let method_def = quote! {
931 #cfg_attrs
932 #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static(
933 #pyo3_path::impl_::pymethods::PyMethodDefType::Getter(
934 #pyo3_path::impl_::pymethods::PyGetterDef::new(
935 #python_name,
936 #cls::#wrapper_ident,
937 #doc
938 )
939 )
940 )
941 };
942
943 Ok(MethodAndMethodDef {
944 associated_method,
945 method_def,
946 })
947 }
948 }
949}
950
951fn split_off_python_arg<'a, 'b>(args: &'a [FnArg<'b>]) -> (Option<&'a PyArg<'b>>, &'a [FnArg<'b>]) {
953 match args {
954 [FnArg::Py(py), args @ ..] => (Some(py), args),
955 args => (None, args),
956 }
957}
958
959pub enum PropertyType<'a> {
960 Descriptor {
961 field_index: usize,
962 field: &'a syn::Field,
963 python_name: Option<&'a NameAttribute>,
964 renaming_rule: Option<RenamingRule>,
965 },
966 Function {
967 self_type: &'a SelfType,
968 spec: &'a FnSpec<'a>,
969 doc: PythonDoc,
970 },
971}
972
973impl PropertyType<'_> {
974 fn null_terminated_python_name(&self, ctx: &Ctx) -> Result<LitCStr> {
975 match self {
976 PropertyType::Descriptor {
977 field,
978 python_name,
979 renaming_rule,
980 ..
981 } => {
982 let name = field_python_name(field, *python_name, *renaming_rule)?;
983 let name = CString::new(name).unwrap();
984 Ok(LitCStr::new(name, field.span(), ctx))
985 }
986 PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)),
987 }
988 }
989
990 fn doc(&self, ctx: &Ctx) -> Result<Cow<'_, PythonDoc>> {
991 match self {
992 PropertyType::Descriptor { field, .. } => {
993 utils::get_doc(&field.attrs, None, ctx).map(Cow::Owned)
994 }
995 PropertyType::Function { doc, .. } => Ok(Cow::Borrowed(doc)),
996 }
997 }
998}
999
1000pub const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
1001pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
1002pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
1003 .ret_ty(Ty::PyHashT)
1004 .return_conversion(TokenGenerator(
1005 |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::impl_::callback::HashCallbackOutput },
1006 ));
1007pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc")
1008 .extract_error_mode(ExtractErrorMode::NotImplemented)
1009 .arguments(&[Ty::Object, Ty::CompareOp]);
1010const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc")
1011 .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]);
1012const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc");
1013const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc")
1014 .return_specialized_conversion(
1015 TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }),
1016 TokenGenerator(|_| quote! { iter_tag }),
1017 );
1018const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc");
1019const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc");
1020const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_specialized_conversion(
1021 TokenGenerator(
1022 |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind },
1023 ),
1024 TokenGenerator(|_| quote! { async_iter_tag }),
1025);
1026pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT);
1027const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc")
1028 .arguments(&[Ty::Object])
1029 .ret_ty(Ty::Int);
1030const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
1031const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
1032const __INPLACE_CONCAT__: SlotDef =
1033 SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]);
1034const __INPLACE_REPEAT__: SlotDef =
1035 SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]);
1036pub const __GETITEM__: SlotDef =
1037 SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]);
1038
1039const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc");
1040const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc");
1041const __ABS__: SlotDef = SlotDef::new("Py_nb_absolute", "unaryfunc");
1042const __INVERT__: SlotDef = SlotDef::new("Py_nb_invert", "unaryfunc");
1043const __INDEX__: SlotDef = SlotDef::new("Py_nb_index", "unaryfunc");
1044pub const __INT__: SlotDef = SlotDef::new("Py_nb_int", "unaryfunc");
1045const __FLOAT__: SlotDef = SlotDef::new("Py_nb_float", "unaryfunc");
1046const __BOOL__: SlotDef = SlotDef::new("Py_nb_bool", "inquiry").ret_ty(Ty::Int);
1047
1048const __IADD__: SlotDef = SlotDef::new("Py_nb_inplace_add", "binaryfunc")
1049 .arguments(&[Ty::Object])
1050 .extract_error_mode(ExtractErrorMode::NotImplemented)
1051 .return_self();
1052const __ISUB__: SlotDef = SlotDef::new("Py_nb_inplace_subtract", "binaryfunc")
1053 .arguments(&[Ty::Object])
1054 .extract_error_mode(ExtractErrorMode::NotImplemented)
1055 .return_self();
1056const __IMUL__: SlotDef = SlotDef::new("Py_nb_inplace_multiply", "binaryfunc")
1057 .arguments(&[Ty::Object])
1058 .extract_error_mode(ExtractErrorMode::NotImplemented)
1059 .return_self();
1060const __IMATMUL__: SlotDef = SlotDef::new("Py_nb_inplace_matrix_multiply", "binaryfunc")
1061 .arguments(&[Ty::Object])
1062 .extract_error_mode(ExtractErrorMode::NotImplemented)
1063 .return_self();
1064const __ITRUEDIV__: SlotDef = SlotDef::new("Py_nb_inplace_true_divide", "binaryfunc")
1065 .arguments(&[Ty::Object])
1066 .extract_error_mode(ExtractErrorMode::NotImplemented)
1067 .return_self();
1068const __IFLOORDIV__: SlotDef = SlotDef::new("Py_nb_inplace_floor_divide", "binaryfunc")
1069 .arguments(&[Ty::Object])
1070 .extract_error_mode(ExtractErrorMode::NotImplemented)
1071 .return_self();
1072const __IMOD__: SlotDef = SlotDef::new("Py_nb_inplace_remainder", "binaryfunc")
1073 .arguments(&[Ty::Object])
1074 .extract_error_mode(ExtractErrorMode::NotImplemented)
1075 .return_self();
1076const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc")
1077 .arguments(&[Ty::Object, Ty::IPowModulo])
1078 .extract_error_mode(ExtractErrorMode::NotImplemented)
1079 .return_self();
1080const __ILSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_lshift", "binaryfunc")
1081 .arguments(&[Ty::Object])
1082 .extract_error_mode(ExtractErrorMode::NotImplemented)
1083 .return_self();
1084const __IRSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_rshift", "binaryfunc")
1085 .arguments(&[Ty::Object])
1086 .extract_error_mode(ExtractErrorMode::NotImplemented)
1087 .return_self();
1088const __IAND__: SlotDef = SlotDef::new("Py_nb_inplace_and", "binaryfunc")
1089 .arguments(&[Ty::Object])
1090 .extract_error_mode(ExtractErrorMode::NotImplemented)
1091 .return_self();
1092const __IXOR__: SlotDef = SlotDef::new("Py_nb_inplace_xor", "binaryfunc")
1093 .arguments(&[Ty::Object])
1094 .extract_error_mode(ExtractErrorMode::NotImplemented)
1095 .return_self();
1096const __IOR__: SlotDef = SlotDef::new("Py_nb_inplace_or", "binaryfunc")
1097 .arguments(&[Ty::Object])
1098 .extract_error_mode(ExtractErrorMode::NotImplemented)
1099 .return_self();
1100const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc")
1101 .arguments(&[Ty::PyBuffer, Ty::Int])
1102 .ret_ty(Ty::Int)
1103 .require_unsafe();
1104const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc")
1105 .arguments(&[Ty::PyBuffer])
1106 .ret_ty(Ty::Void)
1107 .require_unsafe();
1108const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry")
1109 .arguments(&[])
1110 .ret_ty(Ty::Int);
1111
1112#[derive(Clone, Copy)]
1113enum Ty {
1114 Object,
1115 MaybeNullObject,
1116 NonNullObject,
1117 IPowModulo,
1118 CompareOp,
1119 Int,
1120 PyHashT,
1121 PySsizeT,
1122 Void,
1123 PyBuffer,
1124}
1125
1126impl Ty {
1127 fn ffi_type(self, ctx: &Ctx) -> TokenStream {
1128 let Ctx {
1129 pyo3_path,
1130 output_span,
1131 } = ctx;
1132 let pyo3_path = pyo3_path.to_tokens_spanned(*output_span);
1133 match self {
1134 Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject },
1135 Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> },
1136 Ty::IPowModulo => quote! { #pyo3_path::impl_::pymethods::IPowModulo },
1137 Ty::Int | Ty::CompareOp => quote! { ::std::ffi::c_int },
1138 Ty::PyHashT => quote! { #pyo3_path::ffi::Py_hash_t },
1139 Ty::PySsizeT => quote! { #pyo3_path::ffi::Py_ssize_t },
1140 Ty::Void => quote! { () },
1141 Ty::PyBuffer => quote! { *mut #pyo3_path::ffi::Py_buffer },
1142 }
1143 }
1144
1145 fn extract(
1146 self,
1147 ident: &syn::Ident,
1148 arg: &FnArg<'_>,
1149 extract_error_mode: ExtractErrorMode,
1150 holders: &mut Holders,
1151 ctx: &Ctx,
1152 ) -> TokenStream {
1153 let Ctx { pyo3_path, .. } = ctx;
1154 match self {
1155 Ty::Object => extract_object(
1156 extract_error_mode,
1157 holders,
1158 arg,
1159 format_ident!("ref_from_ptr"),
1160 quote! { #ident },
1161 ctx
1162 ),
1163 Ty::MaybeNullObject => extract_object(
1164 extract_error_mode,
1165 holders,
1166 arg,
1167 format_ident!("ref_from_ptr"),
1168 quote! {
1169 if #ident.is_null() {
1170 #pyo3_path::ffi::Py_None()
1171 } else {
1172 #ident
1173 }
1174 },
1175 ctx
1176 ),
1177 Ty::NonNullObject => extract_object(
1178 extract_error_mode,
1179 holders,
1180 arg,
1181 format_ident!("ref_from_non_null"),
1182 quote! { #ident },
1183 ctx
1184 ),
1185 Ty::IPowModulo => extract_object(
1186 extract_error_mode,
1187 holders,
1188 arg,
1189 format_ident!("ref_from_ptr"),
1190 quote! { #ident.as_ptr() },
1191 ctx
1192 ),
1193 Ty::CompareOp => extract_error_mode.handle_error(
1194 quote! {
1195 #pyo3_path::class::basic::CompareOp::from_raw(#ident)
1196 .ok_or_else(|| #pyo3_path::exceptions::PyValueError::new_err("invalid comparison operator"))
1197 },
1198 ctx
1199 ),
1200 Ty::PySsizeT => {
1201 let ty = arg.ty();
1202 extract_error_mode.handle_error(
1203 quote! {
1204 ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string()))
1205 },
1206 ctx
1207 )
1208 }
1209 Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident },
1211 }
1212 }
1213}
1214
1215fn extract_object(
1216 extract_error_mode: ExtractErrorMode,
1217 holders: &mut Holders,
1218 arg: &FnArg<'_>,
1219 ref_from_method: Ident,
1220 source_ptr: TokenStream,
1221 ctx: &Ctx,
1222) -> TokenStream {
1223 let Ctx { pyo3_path, .. } = ctx;
1224 let name = arg.name().unraw().to_string();
1225
1226 let extract = if let Some(FromPyWithAttribute {
1227 kw,
1228 value: extractor,
1229 }) = arg.from_py_with()
1230 {
1231 let extractor = quote_spanned! { kw.span =>
1232 { let from_py_with: fn(_) -> _ = #extractor; from_py_with }
1233 };
1234
1235 quote! {
1236 #pyo3_path::impl_::extract_argument::from_py_with(
1237 unsafe { #pyo3_path::impl_::pymethods::BoundRef::#ref_from_method(py, &#source_ptr).0 },
1238 #name,
1239 #extractor,
1240 )
1241 }
1242 } else {
1243 let holder = holders.push_holder(Span::call_site());
1244 quote! {{
1245 #[allow(unused_imports)]
1246 use #pyo3_path::impl_::pyclass::Probe as _;
1247 #pyo3_path::impl_::extract_argument::extract_argument(
1248 unsafe { #pyo3_path::impl_::pymethods::BoundRef::#ref_from_method(py, &#source_ptr).0 },
1249 &mut #holder,
1250 #name
1251 )
1252 }}
1253 };
1254
1255 let extracted = extract_error_mode.handle_error(extract, ctx);
1256 quote!(#extracted)
1257}
1258
1259enum ReturnMode {
1260 ReturnSelf,
1261 Conversion(TokenGenerator),
1262 SpecializedConversion(TokenGenerator, TokenGenerator),
1263}
1264
1265impl ReturnMode {
1266 fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream {
1267 let Ctx { pyo3_path, .. } = ctx;
1268 match self {
1269 ReturnMode::Conversion(conversion) => {
1270 let conversion = TokenGeneratorCtx(*conversion, ctx);
1271 quote! {
1272 let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::impl_::callback::convert(py, #call);
1273 #pyo3_path::impl_::callback::convert(py, _result)
1274 }
1275 }
1276 ReturnMode::SpecializedConversion(traits, tag) => {
1277 let traits = TokenGeneratorCtx(*traits, ctx);
1278 let tag = TokenGeneratorCtx(*tag, ctx);
1279 quote! {
1280 let _result = #call;
1281 use #pyo3_path::impl_::pymethods::{#traits};
1282 (&_result).#tag().convert(py, _result)
1283 }
1284 }
1285 ReturnMode::ReturnSelf => quote! {
1286 let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call);
1287 _result?;
1288 #pyo3_path::ffi::Py_XINCREF(_raw_slf);
1289 ::std::result::Result::Ok(_raw_slf)
1290 },
1291 }
1292 }
1293}
1294
1295pub struct SlotDef {
1296 slot: StaticIdent,
1297 func_ty: StaticIdent,
1298 arguments: &'static [Ty],
1299 ret_ty: Ty,
1300 extract_error_mode: ExtractErrorMode,
1301 return_mode: Option<ReturnMode>,
1302 require_unsafe: bool,
1303}
1304
1305const NO_ARGUMENTS: &[Ty] = &[];
1306
1307impl SlotDef {
1308 const fn new(slot: &'static str, func_ty: &'static str) -> Self {
1309 SlotDef {
1310 slot: StaticIdent(slot),
1311 func_ty: StaticIdent(func_ty),
1312 arguments: NO_ARGUMENTS,
1313 ret_ty: Ty::Object,
1314 extract_error_mode: ExtractErrorMode::Raise,
1315 return_mode: None,
1316 require_unsafe: false,
1317 }
1318 }
1319
1320 const fn arguments(mut self, arguments: &'static [Ty]) -> Self {
1321 self.arguments = arguments;
1322 self
1323 }
1324
1325 const fn ret_ty(mut self, ret_ty: Ty) -> Self {
1326 self.ret_ty = ret_ty;
1327 self
1328 }
1329
1330 const fn return_conversion(mut self, return_conversion: TokenGenerator) -> Self {
1331 self.return_mode = Some(ReturnMode::Conversion(return_conversion));
1332 self
1333 }
1334
1335 const fn return_specialized_conversion(
1336 mut self,
1337 traits: TokenGenerator,
1338 tag: TokenGenerator,
1339 ) -> Self {
1340 self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag));
1341 self
1342 }
1343
1344 const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self {
1345 self.extract_error_mode = extract_error_mode;
1346 self
1347 }
1348
1349 const fn return_self(mut self) -> Self {
1350 self.return_mode = Some(ReturnMode::ReturnSelf);
1351 self
1352 }
1353
1354 const fn require_unsafe(mut self) -> Self {
1355 self.require_unsafe = true;
1356 self
1357 }
1358
1359 pub fn generate_type_slot(
1360 &self,
1361 cls: &syn::Type,
1362 spec: &FnSpec<'_>,
1363 method_name: &str,
1364 ctx: &Ctx,
1365 ) -> Result<MethodAndSlotDef> {
1366 let Ctx { pyo3_path, .. } = ctx;
1367 let SlotDef {
1368 slot,
1369 func_ty,
1370 arguments,
1371 extract_error_mode,
1372 ret_ty,
1373 return_mode,
1374 require_unsafe,
1375 } = self;
1376 if *require_unsafe {
1377 ensure_spanned!(
1378 spec.unsafety.is_some(),
1379 spec.name.span() => format!("`{}` must be `unsafe fn`", method_name)
1380 );
1381 }
1382 let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect();
1383 let arg_idents: &Vec<_> = &(0..arguments.len())
1384 .map(|i| format_ident!("arg{}", i))
1385 .collect();
1386 let wrapper_ident = format_ident!("__pymethod_{}__", method_name);
1387 let ret_ty = ret_ty.ffi_type(ctx);
1388 let mut holders = Holders::new();
1389 let body = generate_method_body(
1390 cls,
1391 spec,
1392 arguments,
1393 *extract_error_mode,
1394 &mut holders,
1395 return_mode.as_ref(),
1396 ctx,
1397 )?;
1398 let name = spec.name;
1399 let holders = holders.init_holders(ctx);
1400 let associated_method = quote! {
1401 #[allow(non_snake_case)]
1402 unsafe fn #wrapper_ident(
1403 py: #pyo3_path::Python<'_>,
1404 _raw_slf: *mut #pyo3_path::ffi::PyObject,
1405 #(#arg_idents: #arg_types),*
1406 ) -> #pyo3_path::PyResult<#ret_ty> {
1407 let function = #cls::#name; let _slf = _raw_slf;
1409 #holders
1410 #body
1411 }
1412 };
1413 let slot_def = quote! {{
1414 unsafe extern "C" fn trampoline(
1415 _slf: *mut #pyo3_path::ffi::PyObject,
1416 #(#arg_idents: #arg_types),*
1417 ) -> #ret_ty
1418 {
1419 #pyo3_path::impl_::trampoline:: #func_ty (
1420 _slf,
1421 #(#arg_idents,)*
1422 #cls::#wrapper_ident
1423 )
1424 }
1425
1426 #pyo3_path::ffi::PyType_Slot {
1427 slot: #pyo3_path::ffi::#slot,
1428 pfunc: trampoline as #pyo3_path::ffi::#func_ty as _
1429 }
1430 }};
1431 Ok(MethodAndSlotDef {
1432 associated_method,
1433 slot_def,
1434 })
1435 }
1436}
1437
1438fn generate_method_body(
1439 cls: &syn::Type,
1440 spec: &FnSpec<'_>,
1441 arguments: &[Ty],
1442 extract_error_mode: ExtractErrorMode,
1443 holders: &mut Holders,
1444 return_mode: Option<&ReturnMode>,
1445 ctx: &Ctx,
1446) -> Result<TokenStream> {
1447 let Ctx { pyo3_path, .. } = ctx;
1448 let self_arg = spec
1449 .tp
1450 .self_arg(Some(cls), extract_error_mode, holders, ctx);
1451 let rust_name = spec.name;
1452 let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?;
1453 let call = quote! { #cls::#rust_name(#self_arg #(#args),*) };
1454 let body = if let Some(return_mode) = return_mode {
1455 return_mode.return_call_output(call, ctx)
1456 } else {
1457 quote! {
1458 let result = #call;
1459 #pyo3_path::impl_::callback::convert(py, result)
1460 }
1461 };
1462 let warnings = spec.warnings.build_py_warning(ctx);
1463
1464 Ok(quote! {
1465 #warnings
1466 #body
1467 })
1468}
1469
1470struct SlotFragmentDef {
1471 fragment: &'static str,
1472 arguments: &'static [Ty],
1473 extract_error_mode: ExtractErrorMode,
1474 ret_ty: Ty,
1475}
1476
1477impl SlotFragmentDef {
1478 const fn new(fragment: &'static str, arguments: &'static [Ty]) -> Self {
1479 SlotFragmentDef {
1480 fragment,
1481 arguments,
1482 extract_error_mode: ExtractErrorMode::Raise,
1483 ret_ty: Ty::Void,
1484 }
1485 }
1486
1487 const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self {
1488 self.extract_error_mode = extract_error_mode;
1489 self
1490 }
1491
1492 const fn ret_ty(mut self, ret_ty: Ty) -> Self {
1493 self.ret_ty = ret_ty;
1494 self
1495 }
1496
1497 fn generate_pyproto_fragment(
1498 &self,
1499 cls: &syn::Type,
1500 spec: &FnSpec<'_>,
1501 ctx: &Ctx,
1502 ) -> Result<TokenStream> {
1503 let Ctx { pyo3_path, .. } = ctx;
1504 let SlotFragmentDef {
1505 fragment,
1506 arguments,
1507 extract_error_mode,
1508 ret_ty,
1509 } = self;
1510 let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment);
1511 let method = syn::Ident::new(fragment, Span::call_site());
1512 let wrapper_ident = format_ident!("__pymethod_{}__", fragment);
1513 let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type(ctx)).collect();
1514 let arg_idents: &Vec<_> = &(0..arguments.len())
1515 .map(|i| format_ident!("arg{}", i))
1516 .collect();
1517 let mut holders = Holders::new();
1518 let body = generate_method_body(
1519 cls,
1520 spec,
1521 arguments,
1522 *extract_error_mode,
1523 &mut holders,
1524 None,
1525 ctx,
1526 )?;
1527 let ret_ty = ret_ty.ffi_type(ctx);
1528 let holders = holders.init_holders(ctx);
1529 Ok(quote! {
1530 impl #cls {
1531 #[allow(non_snake_case)]
1532 unsafe fn #wrapper_ident(
1533 py: #pyo3_path::Python,
1534 _raw_slf: *mut #pyo3_path::ffi::PyObject,
1535 #(#arg_idents: #arg_types),*
1536 ) -> #pyo3_path::PyResult<#ret_ty> {
1537 let _slf = _raw_slf;
1538 #holders
1539 #body
1540 }
1541 }
1542
1543 impl #pyo3_path::impl_::pyclass::#fragment_trait<#cls> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#cls> {
1544
1545 #[inline]
1546 unsafe fn #method(
1547 self,
1548 py: #pyo3_path::Python,
1549 _raw_slf: *mut #pyo3_path::ffi::PyObject,
1550 #(#arg_idents: #arg_types),*
1551 ) -> #pyo3_path::PyResult<#ret_ty> {
1552 #cls::#wrapper_ident(py, _raw_slf, #(#arg_idents),*)
1553 }
1554 }
1555 })
1556 }
1557}
1558
1559const __GETATTRIBUTE__: SlotFragmentDef =
1560 SlotFragmentDef::new("__getattribute__", &[Ty::Object]).ret_ty(Ty::Object);
1561const __GETATTR__: SlotFragmentDef =
1562 SlotFragmentDef::new("__getattr__", &[Ty::Object]).ret_ty(Ty::Object);
1563const __SETATTR__: SlotFragmentDef =
1564 SlotFragmentDef::new("__setattr__", &[Ty::Object, Ty::NonNullObject]);
1565const __DELATTR__: SlotFragmentDef = SlotFragmentDef::new("__delattr__", &[Ty::Object]);
1566const __SET__: SlotFragmentDef = SlotFragmentDef::new("__set__", &[Ty::Object, Ty::NonNullObject]);
1567const __DELETE__: SlotFragmentDef = SlotFragmentDef::new("__delete__", &[Ty::Object]);
1568const __SETITEM__: SlotFragmentDef =
1569 SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]);
1570const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]);
1571
1572macro_rules! binary_num_slot_fragment_def {
1573 ($ident:ident, $name:literal) => {
1574 const $ident: SlotFragmentDef = SlotFragmentDef::new($name, &[Ty::Object])
1575 .extract_error_mode(ExtractErrorMode::NotImplemented)
1576 .ret_ty(Ty::Object);
1577 };
1578}
1579
1580binary_num_slot_fragment_def!(__ADD__, "__add__");
1581binary_num_slot_fragment_def!(__RADD__, "__radd__");
1582binary_num_slot_fragment_def!(__SUB__, "__sub__");
1583binary_num_slot_fragment_def!(__RSUB__, "__rsub__");
1584binary_num_slot_fragment_def!(__MUL__, "__mul__");
1585binary_num_slot_fragment_def!(__RMUL__, "__rmul__");
1586binary_num_slot_fragment_def!(__MATMUL__, "__matmul__");
1587binary_num_slot_fragment_def!(__RMATMUL__, "__rmatmul__");
1588binary_num_slot_fragment_def!(__FLOORDIV__, "__floordiv__");
1589binary_num_slot_fragment_def!(__RFLOORDIV__, "__rfloordiv__");
1590binary_num_slot_fragment_def!(__TRUEDIV__, "__truediv__");
1591binary_num_slot_fragment_def!(__RTRUEDIV__, "__rtruediv__");
1592binary_num_slot_fragment_def!(__DIVMOD__, "__divmod__");
1593binary_num_slot_fragment_def!(__RDIVMOD__, "__rdivmod__");
1594binary_num_slot_fragment_def!(__MOD__, "__mod__");
1595binary_num_slot_fragment_def!(__RMOD__, "__rmod__");
1596binary_num_slot_fragment_def!(__LSHIFT__, "__lshift__");
1597binary_num_slot_fragment_def!(__RLSHIFT__, "__rlshift__");
1598binary_num_slot_fragment_def!(__RSHIFT__, "__rshift__");
1599binary_num_slot_fragment_def!(__RRSHIFT__, "__rrshift__");
1600binary_num_slot_fragment_def!(__AND__, "__and__");
1601binary_num_slot_fragment_def!(__RAND__, "__rand__");
1602binary_num_slot_fragment_def!(__XOR__, "__xor__");
1603binary_num_slot_fragment_def!(__RXOR__, "__rxor__");
1604binary_num_slot_fragment_def!(__OR__, "__or__");
1605binary_num_slot_fragment_def!(__ROR__, "__ror__");
1606
1607const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object])
1608 .extract_error_mode(ExtractErrorMode::NotImplemented)
1609 .ret_ty(Ty::Object);
1610const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object])
1611 .extract_error_mode(ExtractErrorMode::NotImplemented)
1612 .ret_ty(Ty::Object);
1613
1614const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object])
1615 .extract_error_mode(ExtractErrorMode::NotImplemented)
1616 .ret_ty(Ty::Object);
1617const __LE__: SlotFragmentDef = SlotFragmentDef::new("__le__", &[Ty::Object])
1618 .extract_error_mode(ExtractErrorMode::NotImplemented)
1619 .ret_ty(Ty::Object);
1620const __EQ__: SlotFragmentDef = SlotFragmentDef::new("__eq__", &[Ty::Object])
1621 .extract_error_mode(ExtractErrorMode::NotImplemented)
1622 .ret_ty(Ty::Object);
1623const __NE__: SlotFragmentDef = SlotFragmentDef::new("__ne__", &[Ty::Object])
1624 .extract_error_mode(ExtractErrorMode::NotImplemented)
1625 .ret_ty(Ty::Object);
1626const __GT__: SlotFragmentDef = SlotFragmentDef::new("__gt__", &[Ty::Object])
1627 .extract_error_mode(ExtractErrorMode::NotImplemented)
1628 .ret_ty(Ty::Object);
1629const __GE__: SlotFragmentDef = SlotFragmentDef::new("__ge__", &[Ty::Object])
1630 .extract_error_mode(ExtractErrorMode::NotImplemented)
1631 .ret_ty(Ty::Object);
1632
1633fn extract_proto_arguments(
1634 spec: &FnSpec<'_>,
1635 proto_args: &[Ty],
1636 extract_error_mode: ExtractErrorMode,
1637 holders: &mut Holders,
1638 ctx: &Ctx,
1639) -> Result<Vec<TokenStream>> {
1640 let mut args = Vec::with_capacity(spec.signature.arguments.len());
1641 let mut non_python_args = 0;
1642
1643 for arg in &spec.signature.arguments {
1644 if let FnArg::Py(..) = arg {
1645 args.push(quote! { py });
1646 } else {
1647 let ident = syn::Ident::new(&format!("arg{non_python_args}"), Span::call_site());
1648 let conversions = proto_args.get(non_python_args)
1649 .ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
1650 .extract(&ident, arg, extract_error_mode, holders, ctx);
1651 non_python_args += 1;
1652 args.push(conversions);
1653 }
1654 }
1655
1656 if non_python_args != proto_args.len() {
1657 bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args));
1658 }
1659 Ok(args)
1660}
1661
1662struct StaticIdent(&'static str);
1663
1664impl ToTokens for StaticIdent {
1665 fn to_tokens(&self, tokens: &mut TokenStream) {
1666 syn::Ident::new(self.0, Span::call_site()).to_tokens(tokens)
1667 }
1668}
1669
1670#[derive(Clone, Copy)]
1671struct TokenGenerator(fn(&Ctx) -> TokenStream);
1672
1673struct TokenGeneratorCtx<'ctx>(TokenGenerator, &'ctx Ctx);
1674
1675impl ToTokens for TokenGeneratorCtx<'_> {
1676 fn to_tokens(&self, tokens: &mut TokenStream) {
1677 let Self(TokenGenerator(gen), ctx) = self;
1678 (gen)(ctx).to_tokens(tokens)
1679 }
1680}
1681
1682pub fn field_python_name(
1683 field: &Field,
1684 name_attr: Option<&NameAttribute>,
1685 renaming_rule: Option<RenamingRule>,
1686) -> Result<String> {
1687 if let Some(name_attr) = name_attr {
1688 return Ok(name_attr.value.0.to_string());
1689 }
1690 let Some(ident) = &field.ident else {
1691 bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
1692 };
1693 let mut name = ident.unraw().to_string();
1694 if let Some(rule) = renaming_rule {
1695 name = utils::apply_renaming_rule(rule, &name);
1696 }
1697 Ok(name)
1698}