From 88e40251aa2b867ad6ea5350d49a324a2400e62d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 23 Nov 2021 21:21:54 +0100 Subject: [PATCH] pyo3_path, part 2: add pyo3_path options and use them. --- pyo3-macros-backend/src/attributes.rs | 16 ++++++- pyo3-macros-backend/src/from_pyobject.rs | 20 ++++++--- pyo3-macros-backend/src/method.rs | 46 ++++++++++++------- pyo3-macros-backend/src/module.rs | 30 +++++++++++-- pyo3-macros-backend/src/pyclass.rs | 56 ++++++++++++++++++++---- pyo3-macros-backend/src/pyfunction.rs | 35 ++++++++++----- pyo3-macros-backend/src/pyimpl.rs | 26 ++++++++--- pyo3-macros-backend/src/pyproto.rs | 17 +++++-- 8 files changed, 192 insertions(+), 54 deletions(-) diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index a0569848525..3971b05f1d2 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -2,7 +2,7 @@ use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, token::Comma, - Attribute, ExprPath, Ident, LitStr, Result, Token, + Attribute, ExprPath, Ident, LitStr, Path, Result, Token, }; pub mod kw { @@ -17,6 +17,7 @@ pub mod kw { syn::custom_keyword!(signature); syn::custom_keyword!(text_signature); syn::custom_keyword!(transparent); + syn::custom_keyword!(pyo3_path); } #[derive(Clone, Debug, PartialEq)] @@ -43,6 +44,19 @@ impl Parse for NameAttribute { } } +/// For specifying the path to the pyo3 crate. +#[derive(Clone, Debug, PartialEq)] +pub struct PyO3PathAttribute(pub Path); + +impl Parse for PyO3PathAttribute { + fn parse(input: ParseStream) -> Result { + let _: kw::pyo3_path = input.parse()?; + let _: Token![=] = input.parse()?; + let string_literal: LitStr = input.parse()?; + string_literal.parse().map(PyO3PathAttribute) + } +} + #[derive(Clone, Debug, PartialEq)] pub struct TextSignatureAttribute { pub kw: kw::text_signature, diff --git a/pyo3-macros-backend/src/from_pyobject.rs b/pyo3-macros-backend/src/from_pyobject.rs index ef9d971a58c..1ae652a0d95 100644 --- a/pyo3-macros-backend/src/from_pyobject.rs +++ b/pyo3-macros-backend/src/from_pyobject.rs @@ -1,5 +1,5 @@ use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parenthesized, @@ -519,12 +519,20 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { }; let ident = &tokens.ident; + let const_name = syn::Ident::new( + &format!("_PYO3__FROMPYOBJECT_FOR_{}", ident), + Span::call_site(), + ); Ok(quote!( - #[automatically_derived] - impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause { - fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult { - #derives + const #const_name: () = { + use ::pyo3 as _pyo3; // TODO + + #[automatically_derived] + impl#trait_generics _pyo3::FromPyObject<#lt_param> for #ident#generics #where_clause { + fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult { + #derives + } } - } + }; )) } diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 885c59e0525..0bdfcaa8f6a 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -228,6 +228,7 @@ pub struct FnSpec<'a> { pub deprecations: Deprecations, pub convention: CallingConvention, pub text_signature: Option, + pub pyo3_path: syn::Path, } pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { @@ -254,12 +255,14 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> Result { impl<'a> FnSpec<'a> { /// Parser function signature and function attributes pub fn parse( + // Signature is mutable to remove the `Python` argument. sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result> { let PyFunctionOptions { text_signature, + pyo3_path, name, mut deprecations, .. @@ -278,6 +281,9 @@ impl<'a> FnSpec<'a> { let name = &sig.ident; let ty = get_return_info(&sig.output); let python_name = python_name.as_ref().unwrap_or(name).unraw(); + let pyo3_path = pyo3_path + .map(|p| p.0) + .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()); let doc = utils::get_doc( meth_attrs, @@ -311,6 +317,7 @@ impl<'a> FnSpec<'a> { doc, deprecations, text_signature, + pyo3_path, }) } @@ -472,14 +479,16 @@ impl<'a> FnSpec<'a> { }; let rust_call = quote! { _pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) }; + let pyo3_path = &self.pyo3_path; Ok(match self.convention { CallingConvention::Noargs => { quote! { - unsafe extern "C" fn #ident ( - _slf: *mut _pyo3::ffi::PyObject, - _args: *mut _pyo3::ffi::PyObject, - ) -> *mut _pyo3::ffi::PyObject + pub(crate) unsafe extern "C" fn #ident ( + _slf: *mut #pyo3_path::ffi::PyObject, + _args: *mut #pyo3_path::ffi::PyObject, + ) -> *mut #pyo3_path::ffi::PyObject { + use #pyo3_path as _pyo3; #deprecations _pyo3::callback::handle_panic(|#py| { #self_conversion @@ -491,12 +500,13 @@ impl<'a> FnSpec<'a> { CallingConvention::Fastcall => { let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?; quote! { - unsafe extern "C" fn #ident ( - _slf: *mut _pyo3::ffi::PyObject, - _args: *const *mut _pyo3::ffi::PyObject, - _nargs: _pyo3::ffi::Py_ssize_t, - _kwnames: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + pub(crate) unsafe extern "C" fn #ident ( + _slf: *mut #pyo3_path::ffi::PyObject, + _args: *const *mut #pyo3_path::ffi::PyObject, + _nargs: #pyo3_path::ffi::Py_ssize_t, + _kwnames: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject { + use #pyo3_path as _pyo3; #deprecations _pyo3::callback::handle_panic(|#py| { #self_conversion @@ -518,11 +528,12 @@ impl<'a> FnSpec<'a> { CallingConvention::Varargs => { let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?; quote! { - unsafe extern "C" fn #ident ( - _slf: *mut _pyo3::ffi::PyObject, - _args: *mut _pyo3::ffi::PyObject, - _kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + pub(crate) unsafe extern "C" fn #ident ( + _slf: *mut #pyo3_path::ffi::PyObject, + _args: *mut #pyo3_path::ffi::PyObject, + _kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject { + use #pyo3_path as _pyo3; #deprecations _pyo3::callback::handle_panic(|#py| { #self_conversion @@ -538,11 +549,12 @@ impl<'a> FnSpec<'a> { let rust_call = quote! { #rust_name(#(#arg_names),*) }; let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?; quote! { - unsafe extern "C" fn #ident ( - subtype: *mut _pyo3::ffi::PyTypeObject, - _args: *mut _pyo3::ffi::PyObject, - _kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + pub(crate) unsafe extern "C" fn #ident ( + subtype: *mut #pyo3_path::ffi::PyTypeObject, + _args: *mut #pyo3_path::ffi::PyObject, + _kwargs: *mut #pyo3_path::ffi::PyObject) -> *mut #pyo3_path::ffi::PyObject { + use #pyo3_path as _pyo3; #deprecations use _pyo3::callback::IntoPyCallbackOutput; _pyo3::callback::handle_panic(|#py| { diff --git a/pyo3-macros-backend/src/module.rs b/pyo3-macros-backend/src/module.rs index b6013003ba0..b8d2fd7abf5 100644 --- a/pyo3-macros-backend/src/module.rs +++ b/pyo3-macros-backend/src/module.rs @@ -2,7 +2,10 @@ //! Code generation for the function that initializes a python module and adds classes and function. use crate::{ - attributes::{self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute}, + attributes::{ + self, is_attribute_ident, take_attributes, take_pyo3_options, NameAttribute, + PyO3PathAttribute, + }, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, utils::PythonDoc, }; @@ -16,17 +19,20 @@ use syn::{ Ident, Path, Result, }; +#[derive(Default)] pub struct PyModuleOptions { + pyo3_path: Option, name: Option, } impl PyModuleOptions { pub fn from_attrs(attrs: &mut Vec) -> Result { - let mut options: PyModuleOptions = PyModuleOptions { name: None }; + let mut options: PyModuleOptions = Default::default(); for option in take_pyo3_options(attrs)? { match option { PyModulePyO3Option::Name(name) => options.set_name(name.0)?, + PyModulePyO3Option::PyO3Path(path) => options.set_pyo3_path(path)?, } } @@ -42,12 +48,26 @@ impl PyModuleOptions { self.name = Some(name); Ok(()) } + + fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> Result<()> { + ensure_spanned!( + self.pyo3_path.is_none(), + path.0.span() => "`pyo3_path` may only be specified once" + ); + + self.pyo3_path = Some(path); + Ok(()) + } } /// Generates the function that is called by the python interpreter to initialize the native /// module pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> TokenStream { let name = options.name.unwrap_or_else(|| fnname.unraw()); + let pyo3_path = options + .pyo3_path + .map(|p| p.0) + .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()); let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site()); quote! { @@ -55,7 +75,8 @@ pub fn py_init(fnname: &Ident, options: PyModuleOptions, doc: PythonDoc) -> Toke #[allow(non_snake_case)] /// This autogenerated function is called by the python interpreter when importing /// the module. - pub unsafe extern "C" fn #cb_name() -> *mut _pyo3::ffi::PyObject { + pub unsafe extern "C" fn #cb_name() -> *mut #pyo3_path::ffi::PyObject { + use #pyo3_path as _pyo3; use _pyo3::derive_utils::ModuleDef; static NAME: &str = concat!(stringify!(#name), "\0"); static DOC: &str = #doc; @@ -143,6 +164,7 @@ fn get_pyfn_attr(attrs: &mut Vec) -> syn::Result, pub deprecations: Deprecations, + pub pyo3_path: Option, } enum PyClassPyO3Option { TextSignature(TextSignatureAttribute), + PyO3Path(PyO3PathAttribute), } impl Parse for PyClassPyO3Option { @@ -197,6 +201,8 @@ impl Parse for PyClassPyO3Option { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::text_signature) { input.parse().map(PyClassPyO3Option::TextSignature) + } else if lookahead.peek(attributes::kw::pyo3_path) { + input.parse().map(PyClassPyO3Option::PyO3Path) } else { Err(lookahead.error()) } @@ -211,6 +217,9 @@ impl PyClassPyO3Options { PyClassPyO3Option::TextSignature(text_signature) => { options.set_text_signature(text_signature)?; } + PyClassPyO3Option::PyO3Path(path) => { + options.set_pyo3_path(path)?; + } } } Ok(options) @@ -227,6 +236,15 @@ impl PyClassPyO3Options { self.text_signature = Some(text_signature); Ok(()) } + + pub fn set_pyo3_path(&mut self, path: PyO3PathAttribute) -> syn::Result<()> { + ensure_spanned!( + self.pyo3_path.is_none(), + path.0.span() => "`text_signature` may only be specified once" + ); + self.pyo3_path = Some(path); + Ok(()) + } } pub fn build_py_class( @@ -242,6 +260,10 @@ pub fn build_py_class( .as_ref() .map(|attr| (get_class_python_name(&class.ident, args), attr)), ); + let pyo3_path = options + .pyo3_path + .map(|p| p.0) + .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()); ensure_spanned!( class.generics.params.is_empty(), @@ -278,6 +300,7 @@ pub fn build_py_class( field_options, methods_type, options.deprecations, + pyo3_path, ) } @@ -358,6 +381,7 @@ fn impl_class( field_options: Vec<(&syn::Field, FieldPyO3Options)>, methods_type: PyClassMethodsType, deprecations: Deprecations, + pyo3_path: syn::Path, ) -> syn::Result { let pytypeinfo_impl = impl_pytypeinfo(cls, attr, Some(&deprecations)); @@ -367,12 +391,21 @@ fn impl_class( let descriptors = impl_descriptors(cls, field_options)?; + let const_name = syn::Ident::new( + &format!("_PYO3__PYCLASS_FOR_{}", cls.unraw()), + Span::call_site(), + ); + Ok(quote! { - #pytypeinfo_impl + const #const_name: () = { + use #pyo3_path as _pyo3; + + #pytypeinfo_impl - #py_class_impl + #py_class_impl - #descriptors + #descriptors + }; }) } @@ -425,14 +458,21 @@ fn impl_enum_class( .impl_all(); let descriptors = unit_variants_as_descriptors(cls, variants.iter().map(|v| v.ident)); - Ok(quote! { + let const_name = syn::Ident::new( + &format!("_PYO3__PYCLASS_FOR_{}", cls.unraw()), + Span::call_site(), + ); - #pytypeinfo + Ok(quote! { + const #const_name: () = { + use ::pyo3 as _pyo3; // TODO - #pyclass_impls + #pytypeinfo - #descriptors + #pyclass_impls + #descriptors + }; }) } diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index b6e2c0faa1d..50b44afc4c8 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -3,7 +3,7 @@ use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, FromPyWithAttribute, - NameAttribute, TextSignatureAttribute, + NameAttribute, PyO3PathAttribute, TextSignatureAttribute, }, deprecations::Deprecations, method::{self, CallingConvention, FnArg}, @@ -239,17 +239,12 @@ pub struct PyFunctionOptions { pub signature: Option, pub text_signature: Option, pub deprecations: Deprecations, + pub pyo3_path: Option, } impl Parse for PyFunctionOptions { fn parse(input: ParseStream) -> Result { - let mut options = PyFunctionOptions { - pass_module: None, - name: None, - signature: None, - text_signature: None, - deprecations: Deprecations::new(), - }; + let mut options = PyFunctionOptions::default(); while !input.is_empty() { let lookahead = input.lookahead1(); @@ -262,6 +257,9 @@ impl Parse for PyFunctionOptions { if !input.is_empty() { let _: Comma = input.parse()?; } + } else if lookahead.peek(attributes::kw::pyo3_path) { + // TODO needs duplicate check? + options.pyo3_path = Some(input.parse()?); } else { // If not recognised attribute, this is "legacy" pyfunction syntax #[pyfunction(a, b)] // @@ -280,6 +278,7 @@ pub enum PyFunctionOption { PassModule(attributes::kw::pass_module), Signature(PyFunctionSignature), TextSignature(TextSignatureAttribute), + PyO3Path(PyO3PathAttribute), } impl Parse for PyFunctionOption { @@ -293,6 +292,8 @@ impl Parse for PyFunctionOption { input.parse().map(PyFunctionOption::Signature) } else if lookahead.peek(attributes::kw::text_signature) { input.parse().map(PyFunctionOption::TextSignature) + } else if lookahead.peek(attributes::kw::pyo3_path) { + input.parse().map(PyFunctionOption::PyO3Path) } else { Err(lookahead.error()) } @@ -335,6 +336,13 @@ impl PyFunctionOptions { ); self.text_signature = Some(text_signature); } + PyFunctionOption::PyO3Path(path) => { + ensure_spanned!( + self.pyo3_path.is_none(), + path.0.span() => "`pyo3_path` may only be specified once" + ); + self.pyo3_path = Some(path); + } } } Ok(()) @@ -410,6 +418,10 @@ pub fn impl_wrap_pyfunction( ); let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); + let pyo3_path = options + .pyo3_path + .map(|p| p.0) + .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()); let spec = method::FnSpec { tp: if options.pass_module.is_some() { @@ -426,6 +438,7 @@ pub fn impl_wrap_pyfunction( doc, deprecations: options.deprecations, text_signature: options.text_signature, + pyo3_path: pyo3_path.clone(), }; let wrapper_ident = format_ident!("__pyo3_raw_{}", spec.name); @@ -434,9 +447,11 @@ pub fn impl_wrap_pyfunction( let wrapped_pyfunction = quote! { #wrapper + pub(crate) fn #function_wrapper_ident<'a>( - args: impl ::std::convert::Into<_pyo3::derive_utils::PyFunctionArguments<'a>> - ) -> _pyo3::PyResult<&'a _pyo3::types::PyCFunction> { + args: impl ::std::convert::Into<#pyo3_path::derive_utils::PyFunctionArguments<'a>> + ) -> #pyo3_path::PyResult<&'a #pyo3_path::types::PyCFunction> { + use #pyo3_path as _pyo3; _pyo3::types::PyCFunction::internal_new(#methoddef, args.into()) } }; diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 94add037f69..aa2618763c4 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -7,7 +7,7 @@ use crate::{ pyfunction::PyFunctionOptions, pymethod::{self, is_proto_method}, }; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use pymethod::GeneratedPyMethod; use quote::quote; use syn::spanned::Spanned; @@ -95,25 +95,39 @@ pub fn impl_methods( add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments); + let ty_name = quote!(#ty).to_string().replace('#', "__"); + let const_name = syn::Ident::new( + &format!("_PYO3__METHODS_FOR_{}", ty_name), + Span::call_site(), + ); + Ok(match methods_type { PyClassMethodsType::Specialization => { let methods_registration = impl_py_methods(ty, methods); let protos_registration = impl_protos(ty, proto_impls); quote! { - #(#trait_impls)* + const #const_name: () = { + use ::pyo3 as _pyo3; // TODO - #protos_registration + #(#trait_impls)* - #methods_registration + #protos_registration + + #methods_registration + }; } } PyClassMethodsType::Inventory => { let inventory = submit_methods_inventory(ty, methods, proto_impls); quote! { - #(#trait_impls)* + const #const_name: () = { // TODO needs to be more unique + use ::pyo3 as _pyo3; // TODO + + #(#trait_impls)* - #inventory + #inventory + }; } } }) diff --git a/pyo3-macros-backend/src/pyproto.rs b/pyo3-macros-backend/src/pyproto.rs index eda675bfce9..920c7168c26 100644 --- a/pyo3-macros-backend/src/pyproto.rs +++ b/pyo3-macros-backend/src/pyproto.rs @@ -50,6 +50,7 @@ fn impl_proto_impl( let mut trait_impls = TokenStream::new(); let mut py_methods = Vec::new(); let mut method_names = HashSet::new(); + // TODO needs pyo3_path let module = proto.module(); for iimpl in impls.iter_mut() { @@ -86,10 +87,20 @@ fn impl_proto_impl( } let normal_methods = impl_normal_methods(py_methods, ty, proto); let protocol_methods = impl_proto_methods(method_names, ty, proto); + + let ty_name = quote!(#ty).to_string().replace('#', "__"); + let const_name = syn::Ident::new( + &format!("_PYO3__PYPROTO_{}_FOR_{}", proto.name, ty_name), + Span::call_site(), + ); + Ok(quote! { - #trait_impls - #normal_methods - #protocol_methods + const #const_name: () = { + use ::pyo3 as _pyo3; // TODO + #trait_impls + #normal_methods + #protocol_methods + }; }) }