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