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..868bd42a8f0 100644 --- a/pyo3-macros-backend/src/from_pyobject.rs +++ b/pyo3-macros-backend/src/from_pyobject.rs @@ -1,7 +1,11 @@ -use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute}; -use proc_macro2::TokenStream; +use crate::{ + attributes::{self, get_pyo3_options, FromPyWithAttribute, PyO3PathAttribute}, + utils::get_pyo3_path, +}; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ + ext::IdentExt, parenthesized, parse::{Parse, ParseStream}, parse_quote, @@ -300,20 +304,25 @@ impl<'a> Container<'a> { } } +#[derive(Default)] struct ContainerOptions { /// Treat the Container as a Wrapper, directly extract its fields from the input object. transparent: bool, /// Change the name of an enum variant in the generated error message. annotation: Option, + /// Change the path for the pyo3 crate + pyo3_path: Option, } /// Attributes for deriving FromPyObject scoped on containers. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug)] enum ContainerPyO3Attribute { /// Treat the Container as a Wrapper, directly extract its fields from the input object. Transparent(attributes::kw::transparent), /// Change the name of an enum variant in the generated error message. ErrorAnnotation(LitStr), + /// Change the path for the pyo3 crate + PyO3Path(PyO3PathAttribute), } impl Parse for ContainerPyO3Attribute { @@ -326,6 +335,8 @@ impl Parse for ContainerPyO3Attribute { let _: attributes::kw::annotation = input.parse()?; let _: Token![=] = input.parse()?; input.parse().map(ContainerPyO3Attribute::ErrorAnnotation) + } else if lookahead.peek(attributes::kw::pyo3_path) { + input.parse().map(ContainerPyO3Attribute::PyO3Path) } else { Err(lookahead.error()) } @@ -334,10 +345,8 @@ impl Parse for ContainerPyO3Attribute { impl ContainerOptions { fn from_attrs(attrs: &[Attribute]) -> Result { - let mut options = ContainerOptions { - transparent: false, - annotation: None, - }; + let mut options = ContainerOptions::default(); + for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { for pyo3_attr in pyo3_attrs { @@ -356,6 +365,13 @@ impl ContainerOptions { ); options.annotation = Some(lit_str); } + ContainerPyO3Attribute::PyO3Path(path) => { + ensure_spanned!( + options.pyo3_path.is_none(), + path.0.span() => "`pyo3_path` may only be provided once" + ); + options.pyo3_path = Some(path); + } } } } @@ -499,13 +515,18 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { .predicates .push(parse_quote!(#gen_ident: FromPyObject<#lt_param>)) } + let options = ContainerOptions::from_attrs(&tokens.attrs)?; + let pyo3_path = get_pyo3_path(&options.pyo3_path); let derives = match &tokens.data { syn::Data::Enum(en) => { + if options.transparent || options.annotation.is_some() { + bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \ + at top level for enums"); + } let en = Enum::new(en, &tokens.ident)?; en.build() } syn::Data::Struct(st) => { - let options = ContainerOptions::from_attrs(&tokens.attrs)?; if let Some(lit_str) = &options.annotation { bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs"); } @@ -518,13 +539,23 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { ), }; + let const_name = syn::Ident::new( + &format!("_PYO3__FROMPYOBJECT_FOR_{}", tokens.ident.unraw()), + Span::call_site(), + ); + let ident = &tokens.ident; + 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_path as _pyo3; + + #[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..70669d78fb8 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -5,7 +5,7 @@ use crate::deprecations::Deprecation; use crate::params::{accept_args_kwargs, impl_arg_params}; use crate::pyfunction::PyFunctionOptions; use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature}; -use crate::utils::{self, PythonDoc}; +use crate::utils::{self, get_pyo3_path, PythonDoc}; use crate::{deprecations::Deprecations, pyfunction::Argument}; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; @@ -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,7 @@ 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 = get_pyo3_path(&pyo3_path); let doc = utils::get_doc( meth_attrs, @@ -311,6 +315,7 @@ impl<'a> FnSpec<'a> { doc, deprecations, text_signature, + pyo3_path, }) } @@ -472,14 +477,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 + _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 @@ -492,11 +499,12 @@ impl<'a> FnSpec<'a> { 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 + _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 @@ -519,10 +527,11 @@ impl<'a> FnSpec<'a> { 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 + _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 @@ -539,10 +548,11 @@ impl<'a> FnSpec<'a> { 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 + 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..2475f8d644b 100644 --- a/pyo3-macros-backend/src/module.rs +++ b/pyo3-macros-backend/src/module.rs @@ -2,9 +2,12 @@ //! 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, + utils::{get_pyo3_path, PythonDoc}, }; use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -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,23 @@ 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 = get_pyo3_path(&options.pyo3_path); let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site()); quote! { @@ -55,7 +72,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 +161,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,7 @@ pub fn build_py_class( .as_ref() .map(|attr| (get_class_python_name(&class.ident, args), attr)), ); + let pyo3_path = get_pyo3_path(&options.pyo3_path); ensure_spanned!( class.generics.params.is_empty(), @@ -278,6 +297,7 @@ pub fn build_py_class( field_options, methods_type, options.deprecations, + pyo3_path, ) } @@ -358,6 +378,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 +388,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 + }; }) } @@ -382,10 +412,12 @@ struct PyClassEnumVariant<'a> { } pub fn build_py_enum( - enum_: &syn::ItemEnum, - args: PyClassArgs, + enum_: &mut syn::ItemEnum, + args: &PyClassArgs, method_type: PyClassMethodsType, ) -> syn::Result { + let options = PyClassPyO3Options::take_pyo3_options(&mut enum_.attrs)?; + if enum_.variants.is_empty() { bail_spanned!(enum_.brace_token.span => "Empty enums can't be #[pyclass]."); } @@ -394,45 +426,57 @@ pub fn build_py_enum( .iter() .map(|v| extract_variant_data(v)) .collect::>()?; - impl_enum(enum_, args, variants, method_type) + impl_enum(enum_, args, variants, method_type, options) } fn impl_enum( enum_: &syn::ItemEnum, - attrs: PyClassArgs, + args: &PyClassArgs, variants: Vec, methods_type: PyClassMethodsType, + options: PyClassPyO3Options, ) -> syn::Result { let enum_name = &enum_.ident; - let doc = utils::get_doc(&enum_.attrs, None); - let enum_cls = impl_enum_class(enum_name, &attrs, variants, doc, methods_type)?; - - Ok(quote! { - #enum_cls - }) + let doc = utils::get_doc( + &enum_.attrs, + options + .text_signature + .as_ref() + .map(|attr| (get_class_python_name(&enum_.ident, args), attr)), + ); + let pyo3_path = get_pyo3_path(&options.pyo3_path); + impl_enum_class(enum_name, args, variants, doc, methods_type, pyo3_path) } fn impl_enum_class( cls: &syn::Ident, - attr: &PyClassArgs, + args: &PyClassArgs, variants: Vec, doc: PythonDoc, methods_type: PyClassMethodsType, + pyo3_path: syn::Path, ) -> syn::Result { - let pytypeinfo = impl_pytypeinfo(cls, attr, None); - let pyclass_impls = PyClassImplsBuilder::new(cls, attr, methods_type) + let pytypeinfo = impl_pytypeinfo(cls, args, None); + let pyclass_impls = PyClassImplsBuilder::new(cls, args, methods_type) .doc(doc) .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_path as _pyo3; - #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..9f6f5bf3ada 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -3,12 +3,12 @@ use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, FromPyWithAttribute, - NameAttribute, TextSignatureAttribute, + NameAttribute, PyO3PathAttribute, TextSignatureAttribute, }, deprecations::Deprecations, method::{self, CallingConvention, FnArg}, pymethod::check_generic, - utils::{self, ensure_not_async_fn}, + utils::{self, ensure_not_async_fn, get_pyo3_path}, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; @@ -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,7 @@ pub fn impl_wrap_pyfunction( ); let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); + let pyo3_path = get_pyo3_path(&options.pyo3_path); let spec = method::FnSpec { tp: if options.pass_module.is_some() { @@ -426,6 +435,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 +444,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..032133df3f3 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -3,14 +3,20 @@ use std::collections::HashSet; use crate::{ + attributes::{self, take_pyo3_options, PyO3PathAttribute}, konst::{ConstAttributes, ConstSpec}, pyfunction::PyFunctionOptions, pymethod::{self, is_proto_method}, + utils::get_pyo3_path, }; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use pymethod::GeneratedPyMethod; use quote::quote; -use syn::spanned::Spanned; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Result, +}; /// The mechanism used to collect `#[pymethods]` into the type object #[derive(Copy, Clone)] @@ -19,6 +25,50 @@ pub enum PyClassMethodsType { Inventory, } +enum PyImplPyO3Option { + PyO3Path(PyO3PathAttribute), +} + +impl Parse for PyImplPyO3Option { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(attributes::kw::pyo3_path) { + input.parse().map(PyImplPyO3Option::PyO3Path) + } else { + Err(lookahead.error()) + } + } +} + +#[derive(Default)] +pub struct PyImplOptions { + pyo3_path: Option, +} + +impl PyImplOptions { + pub fn from_attrs(attrs: &mut Vec) -> Result { + let mut options: PyImplOptions = Default::default(); + + for option in take_pyo3_options(attrs)? { + match option { + PyImplPyO3Option::PyO3Path(path) => options.set_pyo3_path(path)?, + } + } + + Ok(options) + } + + 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(()) + } +} + pub fn build_py_methods( ast: &mut syn::ItemImpl, methods_type: PyClassMethodsType, @@ -31,7 +81,8 @@ pub fn build_py_methods( "#[pymethods] cannot be used with lifetime parameters or generics" ); } else { - impl_methods(&ast.self_ty, &mut ast.items, methods_type) + let options = PyImplOptions::from_attrs(&mut ast.attrs)?; + impl_methods(&ast.self_ty, &mut ast.items, methods_type, options) } } @@ -39,6 +90,7 @@ pub fn impl_methods( ty: &syn::Type, impls: &mut Vec, methods_type: PyClassMethodsType, + options: PyImplOptions, ) -> syn::Result { let mut trait_impls = Vec::new(); let mut proto_impls = Vec::new(); @@ -95,25 +147,41 @@ pub fn impl_methods( add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments); + // no easy way to get the Ident and call unraw()... + let ty_name = quote!(#ty).to_string().replace('#', "__"); + let const_name = syn::Ident::new( + &format!("_PYO3__PYMETHODS_FOR_{}", ty_name), + Span::call_site(), + ); + let pyo3_path = get_pyo3_path(&options.pyo3_path); + 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_path as _pyo3; + + #(#trait_impls)* - #protos_registration + #protos_registration - #methods_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_path as _pyo3; + + #(#trait_impls)* - #inventory + #inventory + }; } } }) diff --git a/pyo3-macros-backend/src/pyproto.rs b/pyo3-macros-backend/src/pyproto.rs index eda675bfce9..00d4227adff 100644 --- a/pyo3-macros-backend/src/pyproto.rs +++ b/pyo3-macros-backend/src/pyproto.rs @@ -86,10 +86,21 @@ fn impl_proto_impl( } let normal_methods = impl_normal_methods(py_methods, ty, proto); let protocol_methods = impl_proto_methods(method_names, ty, proto); + + // no easy way to get the Ident and call unraw()... + 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; // pyproto doesn't support specifying #[pyo3(pyo3_path)] + #trait_impls + #normal_methods + #protocol_methods + }; }) } diff --git a/pyo3-macros-backend/src/utils.rs b/pyo3-macros-backend/src/utils.rs index 3c3e510248a..30de0f2b46b 100644 --- a/pyo3-macros-backend/src/utils.rs +++ b/pyo3-macros-backend/src/utils.rs @@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::spanned::Spanned; -use crate::attributes::TextSignatureAttribute; +use crate::attributes::{PyO3PathAttribute, TextSignatureAttribute}; /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. macro_rules! err_spanned { @@ -193,3 +193,10 @@ pub(crate) fn replace_self(ty: &mut syn::Type, cls: &syn::Type) { _ => {} } } + +/// Extract the path to the pyo3 crate, or use the default (`::pyo3`). +pub(crate) fn get_pyo3_path(attr: &Option) -> syn::Path { + attr.as_ref() + .map(|p| p.0.clone()) + .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()) +} diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index f08317d159a..a6f6725c6e5 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -216,16 +216,16 @@ fn pyclass_impl( } fn pyclass_enum_impl( - attr: TokenStream, - enum_: syn::ItemEnum, + attrs: TokenStream, + mut ast: syn::ItemEnum, methods_type: PyClassMethodsType, ) -> TokenStream { - let args = parse_macro_input!(attr with PyClassArgs::parse_enum_args); + let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args); let expanded = - build_py_enum(&enum_, args, methods_type).unwrap_or_else(|e| e.into_compile_error()); + build_py_enum(&mut ast, &args, methods_type).unwrap_or_else(|e| e.into_compile_error()); quote!( - #enum_ + #ast #expanded ) .into() diff --git a/tests/ui/invalid_frompy_derive.stderr b/tests/ui/invalid_frompy_derive.stderr index b5cb180dcee..50bd6c73caa 100644 --- a/tests/ui/invalid_frompy_derive.stderr +++ b/tests/ui/invalid_frompy_derive.stderr @@ -132,7 +132,7 @@ error: only one of `attribute` or `item` can be provided 118 | #[pyo3(item, attribute)] | ^ -error: expected `transparent` or `annotation` +error: expected one of: `transparent`, `annotation`, `pyo3_path` --> tests/ui/invalid_frompy_derive.rs:123:8 | 123 | #[pyo3(unknown = "should not work")]