Allow inline struct, enum, fn and mod inside of declarative modules (#3902)
* Inline struct, enum, fn and mod inside of declarative modules * remove news fragment --------- Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
parent
b08ee4b7e1
commit
fe84fed966
|
@ -115,19 +115,10 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
|
||||||
for item in &mut *items {
|
for item in &mut *items {
|
||||||
match item {
|
match item {
|
||||||
Item::Use(item_use) => {
|
Item::Use(item_use) => {
|
||||||
let mut is_pyo3 = false;
|
let is_pymodule_export =
|
||||||
item_use.attrs.retain(|attr| {
|
find_and_remove_attribute(&mut item_use.attrs, "pymodule_export");
|
||||||
let found = attr.path().is_ident("pymodule_export");
|
if is_pymodule_export {
|
||||||
is_pyo3 |= found;
|
let cfg_attrs = get_cfg_attributes(&item_use.attrs);
|
||||||
!found
|
|
||||||
});
|
|
||||||
if is_pyo3 {
|
|
||||||
let cfg_attrs = item_use
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.filter(|attr| attr.path().is_ident("cfg"))
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
extract_use_items(
|
extract_use_items(
|
||||||
&item_use.tree,
|
&item_use.tree,
|
||||||
&cfg_attrs,
|
&cfg_attrs,
|
||||||
|
@ -137,23 +128,116 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item::Fn(item_fn) => {
|
Item::Fn(item_fn) => {
|
||||||
let mut is_module_init = false;
|
ensure_spanned!(
|
||||||
item_fn.attrs.retain(|attr| {
|
!has_attribute(&item_fn.attrs, "pymodule_export"),
|
||||||
let found = attr.path().is_ident("pymodule_init");
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
is_module_init |= found;
|
);
|
||||||
!found
|
let is_pymodule_init =
|
||||||
});
|
find_and_remove_attribute(&mut item_fn.attrs, "pymodule_init");
|
||||||
if is_module_init {
|
let ident = &item_fn.sig.ident;
|
||||||
ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one pymodule_init may be specified");
|
if is_pymodule_init {
|
||||||
let ident = &item_fn.sig.ident;
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item_fn.attrs, "pyfunction"),
|
||||||
|
item_fn.span() => "`#[pyfunction]` cannot be used alongside `#[pymodule_init]`"
|
||||||
|
);
|
||||||
|
ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified");
|
||||||
pymodule_init = Some(quote! { #ident(module)?; });
|
pymodule_init = Some(quote! { #ident(module)?; });
|
||||||
} else {
|
} else if has_attribute(&item_fn.attrs, "pyfunction") {
|
||||||
bail_spanned!(item.span() => "only 'use' statements and and pymodule_init functions are allowed in #[pymodule]")
|
module_items.push(ident.clone());
|
||||||
|
module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item => {
|
Item::Struct(item_struct) => {
|
||||||
bail_spanned!(item.span() => "only 'use' statements and and pymodule_init functions are allowed in #[pymodule]")
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item_struct.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
if has_attribute(&item_struct.attrs, "pyclass") {
|
||||||
|
module_items.push(item_struct.ident.clone());
|
||||||
|
module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Item::Enum(item_enum) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item_enum.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
if has_attribute(&item_enum.attrs, "pyclass") {
|
||||||
|
module_items.push(item_enum.ident.clone());
|
||||||
|
module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item::Mod(item_mod) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item_mod.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
if has_attribute(&item_mod.attrs, "pymodule") {
|
||||||
|
module_items.push(item_mod.ident.clone());
|
||||||
|
module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item::ForeignMod(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Trait(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Const(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Static(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Macro(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::ExternCrate(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Impl(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::TraitAlias(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Type(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Item::Union(item) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
!has_attribute(&item.attrs, "pymodule_export"),
|
||||||
|
item.span() => "`#[pymodule_export]` may only be used on `use` statements"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,6 +439,31 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
|
||||||
Ok(pyfn_args)
|
Ok(pyfn_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path().is_ident("cfg"))
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_and_remove_attribute(attrs: &mut Vec<syn::Attribute>, ident: &str) -> bool {
|
||||||
|
let mut found = false;
|
||||||
|
attrs.retain(|attr| {
|
||||||
|
if attr.path().is_ident(ident) {
|
||||||
|
found = true;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool {
|
||||||
|
attrs.iter().any(|attr| attr.path().is_ident(ident))
|
||||||
|
}
|
||||||
|
|
||||||
enum PyModulePyO3Option {
|
enum PyModulePyO3Option {
|
||||||
Crate(CrateAttribute),
|
Crate(CrateAttribute),
|
||||||
Name(NameAttribute),
|
Name(NameAttribute),
|
||||||
|
|
|
@ -15,8 +15,8 @@ struct ValueClass {
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl ValueClass {
|
impl ValueClass {
|
||||||
#[new]
|
#[new]
|
||||||
fn new(value: usize) -> ValueClass {
|
fn new(value: usize) -> Self {
|
||||||
ValueClass { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,33 @@ mod declarative_module {
|
||||||
#[pymodule_export]
|
#[pymodule_export]
|
||||||
use super::{declarative_module2, double, MyError, ValueClass as Value};
|
use super::{declarative_module2, double, MyError, ValueClass as Value};
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
mod inner {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
fn triple(x: usize) -> usize {
|
||||||
|
x * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct Struct;
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Struct {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
enum Enum {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pymodule_init]
|
#[pymodule_init]
|
||||||
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add("double2", m.getattr("double")?)
|
m.add("double2", m.getattr("double")?)
|
||||||
|
@ -65,7 +92,6 @@ mod declarative_submodule {
|
||||||
use super::{double, double_value};
|
use super::{double, double_value};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A module written using declarative syntax.
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
#[pyo3(name = "declarative_module_renamed")]
|
#[pyo3(name = "declarative_module_renamed")]
|
||||||
mod declarative_module2 {
|
mod declarative_module2 {
|
||||||
|
@ -84,7 +110,7 @@ fn test_declarative_module() {
|
||||||
);
|
);
|
||||||
|
|
||||||
py_assert!(py, m, "m.double(2) == 4");
|
py_assert!(py, m, "m.double(2) == 4");
|
||||||
py_assert!(py, m, "m.double2(3) == 6");
|
py_assert!(py, m, "m.inner.triple(3) == 9");
|
||||||
py_assert!(py, m, "m.declarative_submodule.double(4) == 8");
|
py_assert!(py, m, "m.declarative_submodule.double(4) == 8");
|
||||||
py_assert!(
|
py_assert!(
|
||||||
py,
|
py,
|
||||||
|
@ -97,5 +123,7 @@ fn test_declarative_module() {
|
||||||
py_assert!(py, m, "not hasattr(m, 'LocatedClass')");
|
py_assert!(py, m, "not hasattr(m, 'LocatedClass')");
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
py_assert!(py, m, "hasattr(m, 'LocatedClass')");
|
py_assert!(py, m, "hasattr(m, 'LocatedClass')");
|
||||||
|
py_assert!(py, m, "isinstance(m.inner.Struct(), m.inner.Struct)");
|
||||||
|
py_assert!(py, m, "isinstance(m.inner.Enum.A, m.inner.Enum)");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: only 'use' statements and and pymodule_init functions are allowed in #[pymodule]
|
error: `#[pymodule_export]` may only be used on `use` statements
|
||||||
--> tests/ui/invalid_pymodule_trait.rs:5:5
|
--> tests/ui/invalid_pymodule_trait.rs:5:5
|
||||||
|
|
|
|
||||||
5 | #[pymodule_export]
|
5 | #[pymodule_export]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
error: only one pymodule_init may be specified
|
error: only one `#[pymodule_init]` may be specified
|
||||||
--> tests/ui/invalid_pymodule_two_pymodule_init.rs:11:5
|
--> tests/ui/invalid_pymodule_two_pymodule_init.rs:11:5
|
||||||
|
|
|
|
||||||
11 | fn init2(m: &PyModule) -> PyResult<()> {
|
11 | fn init2(m: &PyModule) -> PyResult<()> {
|
||||||
|
|
Loading…
Reference in New Issue