Add error messages for unsupported macro features on compilation (#4194)
* First implementation * tweak error message wording * Fix boolean logic * Remove redundant parens * Add test for weakref error * Fix test * Reword error message * Add expected error output * Rinse and repeat for `dict` * Add test output file * Ignore Rust Rover config files * cargo fmt * Add newsfragment * Update newsfragments/4194.added.md Co-authored-by: David Hewitt <mail@davidhewitt.dev> * Use ensure_spanned! macro Co-authored-by: David Hewitt <mail@davidhewitt.dev> * Use ensure_spanned! macro for weakref error, too Co-authored-by: David Hewitt <mail@davidhewitt.dev> * Revert "Ignore Rust Rover config files" This reverts commit 6c8a2eec581ed250ec792d8465772d649b0a3199. * Update wording for error message Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * Update weakref error message, too * Refactor constant to a pyversions module * Fix compiler errors * Another wording update Co-authored-by: David Hewitt <mail@davidhewitt.dev> * And make weakref wording the same * Fix compiler error due to using weakref in our own code * Fix after merge * apply conditional pyclass * update conditional compilation in tests --------- Co-authored-by: cojmeister <luqas.c@gmail.com> Co-authored-by: David Hewitt <mail@davidhewitt.dev> Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
This commit is contained in:
parent
9648d595a5
commit
79591f2161
|
@ -0,0 +1 @@
|
||||||
|
Added error messages when using `weakref` or `dict` when compiling for `abi3` for Python older than 3.9
|
|
@ -19,6 +19,7 @@ mod pyclass;
|
||||||
mod pyfunction;
|
mod pyfunction;
|
||||||
mod pyimpl;
|
mod pyimpl;
|
||||||
mod pymethod;
|
mod pymethod;
|
||||||
|
mod pyversions;
|
||||||
mod quotes;
|
mod quotes;
|
||||||
|
|
||||||
pub use frompyobject::build_derive_from_pyobject;
|
pub use frompyobject::build_derive_from_pyobject;
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
|
use quote::{format_ident, quote, quote_spanned};
|
||||||
|
use syn::ext::IdentExt;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{parse_quote, parse_quote_spanned, spanned::Spanned, Result, Token};
|
||||||
|
|
||||||
use crate::attributes::kw::frozen;
|
use crate::attributes::kw::frozen;
|
||||||
use crate::attributes::{
|
use crate::attributes::{
|
||||||
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
||||||
|
@ -14,16 +21,9 @@ use crate::pymethod::{
|
||||||
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
|
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
|
||||||
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__,
|
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__,
|
||||||
};
|
};
|
||||||
use crate::utils::Ctx;
|
|
||||||
use crate::utils::{self, apply_renaming_rule, PythonDoc};
|
use crate::utils::{self, apply_renaming_rule, PythonDoc};
|
||||||
use crate::PyFunctionOptions;
|
use crate::utils::{is_abi3, Ctx};
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use crate::{pyversions, PyFunctionOptions};
|
||||||
use quote::{format_ident, quote, quote_spanned};
|
|
||||||
use syn::ext::IdentExt;
|
|
||||||
use syn::parse::{Parse, ParseStream};
|
|
||||||
use syn::parse_quote_spanned;
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::{parse_quote, spanned::Spanned, Result, Token};
|
|
||||||
|
|
||||||
/// If the class is derived from a Rust `struct` or `enum`.
|
/// If the class is derived from a Rust `struct` or `enum`.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -180,9 +180,17 @@ impl PyClassPyO3Options {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let python_version = pyo3_build_config::get().version;
|
||||||
|
|
||||||
match option {
|
match option {
|
||||||
PyClassPyO3Option::Crate(krate) => set_option!(krate),
|
PyClassPyO3Option::Crate(krate) => set_option!(krate),
|
||||||
PyClassPyO3Option::Dict(dict) => set_option!(dict),
|
PyClassPyO3Option::Dict(dict) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
python_version >= pyversions::PY_3_9 || !is_abi3(),
|
||||||
|
dict.span() => "`dict` requires Python >= 3.9 when using the `abi3` feature"
|
||||||
|
);
|
||||||
|
set_option!(dict);
|
||||||
|
}
|
||||||
PyClassPyO3Option::Eq(eq) => set_option!(eq),
|
PyClassPyO3Option::Eq(eq) => set_option!(eq),
|
||||||
PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int),
|
PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int),
|
||||||
PyClassPyO3Option::Extends(extends) => set_option!(extends),
|
PyClassPyO3Option::Extends(extends) => set_option!(extends),
|
||||||
|
@ -199,7 +207,13 @@ impl PyClassPyO3Options {
|
||||||
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
||||||
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
||||||
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
||||||
PyClassPyO3Option::Weakref(weakref) => set_option!(weakref),
|
PyClassPyO3Option::Weakref(weakref) => {
|
||||||
|
ensure_spanned!(
|
||||||
|
python_version >= pyversions::PY_3_9 || !is_abi3(),
|
||||||
|
weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature"
|
||||||
|
);
|
||||||
|
set_option!(weakref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
use pyo3_build_config::PythonVersion;
|
||||||
|
|
||||||
|
pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 };
|
|
@ -10,15 +10,24 @@ pub struct Foo;
|
||||||
#[pyo3(crate = "crate")]
|
#[pyo3(crate = "crate")]
|
||||||
pub struct Foo2;
|
pub struct Foo2;
|
||||||
|
|
||||||
#[crate::pyclass(
|
#[cfg_attr(any(Py_3_9, not(Py_LIMITED_API)), crate::pyclass(
|
||||||
name = "ActuallyBar",
|
name = "ActuallyBar",
|
||||||
freelist = 8,
|
freelist = 8,
|
||||||
|
unsendable,
|
||||||
|
subclass,
|
||||||
|
extends = crate::types::PyAny,
|
||||||
|
module = "Spam",
|
||||||
weakref,
|
weakref,
|
||||||
|
dict
|
||||||
|
))]
|
||||||
|
#[cfg_attr(not(any(Py_3_9, not(Py_LIMITED_API))), crate::pyclass(
|
||||||
|
name = "ActuallyBar",
|
||||||
|
freelist = 8,
|
||||||
unsendable,
|
unsendable,
|
||||||
subclass,
|
subclass,
|
||||||
extends = crate::types::PyAny,
|
extends = crate::types::PyAny,
|
||||||
module = "Spam"
|
module = "Spam"
|
||||||
)]
|
))]
|
||||||
#[pyo3(crate = "crate")]
|
#[pyo3(crate = "crate")]
|
||||||
pub struct Bar {
|
pub struct Bar {
|
||||||
#[pyo3(get, set)]
|
#[pyo3(get, set)]
|
||||||
|
|
|
@ -428,6 +428,7 @@ fn test_tuple_struct_class() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
#[pyclass(dict, subclass)]
|
#[pyclass(dict, subclass)]
|
||||||
struct DunderDictSupport {
|
struct DunderDictSupport {
|
||||||
// Make sure that dict_offset runs with non-zero sized Self
|
// Make sure that dict_offset runs with non-zero sized Self
|
||||||
|
@ -479,6 +480,7 @@ fn access_dunder_dict() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the base class has dict support, child class also has dict
|
// If the base class has dict support, child class also has dict
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
#[pyclass(extends=DunderDictSupport)]
|
#[pyclass(extends=DunderDictSupport)]
|
||||||
struct InheritDict {
|
struct InheritDict {
|
||||||
_value: usize,
|
_value: usize,
|
||||||
|
@ -509,6 +511,7 @@ fn inherited_dict() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
#[pyclass(weakref, dict)]
|
#[pyclass(weakref, dict)]
|
||||||
struct WeakRefDunderDictSupport {
|
struct WeakRefDunderDictSupport {
|
||||||
// Make sure that weaklist_offset runs with non-zero sized Self
|
// Make sure that weaklist_offset runs with non-zero sized Self
|
||||||
|
@ -534,6 +537,7 @@ fn weakref_dunder_dict_support() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
#[pyclass(weakref, subclass)]
|
#[pyclass(weakref, subclass)]
|
||||||
struct WeakRefSupport {
|
struct WeakRefSupport {
|
||||||
_pad: [u8; 32],
|
_pad: [u8; 32],
|
||||||
|
@ -559,6 +563,7 @@ fn weakref_support() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the base class has weakref support, child class also has weakref.
|
// If the base class has weakref support, child class also has weakref.
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
#[pyclass(extends=WeakRefSupport)]
|
#[pyclass(extends=WeakRefSupport)]
|
||||||
struct InheritWeakRef {
|
struct InheritWeakRef {
|
||||||
_value: usize,
|
_value: usize,
|
||||||
|
@ -666,3 +671,48 @@ fn drop_unsendable_elsewhere() {
|
||||||
capture.borrow_mut(py).uninstall(py);
|
capture.borrow_mut(py).uninstall(py);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
|
fn test_unsendable_dict() {
|
||||||
|
#[pyclass(dict, unsendable)]
|
||||||
|
struct UnsendableDictClass {}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl UnsendableDictClass {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
UnsendableDictClass {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inst = Py::new(py, UnsendableDictClass {}).unwrap();
|
||||||
|
py_run!(py, inst, "assert inst.__dict__ == {}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
|
fn test_unsendable_dict_with_weakref() {
|
||||||
|
#[pyclass(dict, unsendable, weakref)]
|
||||||
|
struct UnsendableDictClassWithWeakRef {}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl UnsendableDictClassWithWeakRef {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inst = Py::new(py, UnsendableDictClassWithWeakRef {}).unwrap();
|
||||||
|
py_run!(py, inst, "assert inst.__dict__ == {}");
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
inst,
|
||||||
|
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -67,4 +67,8 @@ fn test_compile_errors() {
|
||||||
#[cfg(all(Py_LIMITED_API, not(feature = "experimental-async")))]
|
#[cfg(all(Py_LIMITED_API, not(feature = "experimental-async")))]
|
||||||
// output changes with async feature
|
// output changes with async feature
|
||||||
t.compile_fail("tests/ui/abi3_inheritance.rs");
|
t.compile_fail("tests/ui/abi3_inheritance.rs");
|
||||||
|
#[cfg(all(Py_LIMITED_API, not(Py_3_9)))]
|
||||||
|
t.compile_fail("tests/ui/abi3_weakref.rs");
|
||||||
|
#[cfg(all(Py_LIMITED_API, not(Py_3_9)))]
|
||||||
|
t.compile_fail("tests/ui/abi3_dict.rs");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
#![cfg(feature = "macros")]
|
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
|
||||||
use pyo3::py_run;
|
|
||||||
|
|
||||||
#[pyclass(dict, unsendable)]
|
|
||||||
struct UnsendableDictClass {}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl UnsendableDictClass {
|
|
||||||
#[new]
|
|
||||||
fn new() -> Self {
|
|
||||||
UnsendableDictClass {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
|
||||||
fn test_unsendable_dict() {
|
|
||||||
Python::with_gil(|py| {
|
|
||||||
let inst = Py::new(py, UnsendableDictClass {}).unwrap();
|
|
||||||
py_run!(py, inst, "assert inst.__dict__ == {}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pyclass(dict, unsendable, weakref)]
|
|
||||||
struct UnsendableDictClassWithWeakRef {}
|
|
||||||
|
|
||||||
#[pymethods]
|
|
||||||
impl UnsendableDictClassWithWeakRef {
|
|
||||||
#[new]
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
|
||||||
fn test_unsendable_dict_with_weakref() {
|
|
||||||
Python::with_gil(|py| {
|
|
||||||
let inst = Py::new(py, UnsendableDictClassWithWeakRef {}).unwrap();
|
|
||||||
py_run!(py, inst, "assert inst.__dict__ == {}");
|
|
||||||
py_run!(
|
|
||||||
py,
|
|
||||||
inst,
|
|
||||||
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::py_run;
|
use pyo3::py_run;
|
||||||
use pyo3::types::{PyDict, PyTuple};
|
use pyo3::types::PyTuple;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -113,11 +113,16 @@ fn pytuple_pyclass_iter() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(dict, module = "test_module")]
|
#[test]
|
||||||
struct PickleSupport {}
|
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
|
||||||
|
fn test_pickle() {
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
|
||||||
#[pymethods]
|
#[pyclass(dict, module = "test_module")]
|
||||||
impl PickleSupport {
|
struct PickleSupport {}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PickleSupport {
|
||||||
#[new]
|
#[new]
|
||||||
fn new() -> PickleSupport {
|
fn new() -> PickleSupport {
|
||||||
PickleSupport {}
|
PickleSupport {}
|
||||||
|
@ -131,9 +136,9 @@ impl PickleSupport {
|
||||||
let dict = slf.to_object(py).getattr(py, "__dict__")?;
|
let dict = slf.to_object(py).getattr(py, "__dict__")?;
|
||||||
Ok((cls, PyTuple::empty_bound(py), dict))
|
Ok((cls, PyTuple::empty_bound(py), dict))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_module(module: Bound<'_, PyModule>) -> PyResult<()> {
|
fn add_module(module: Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
PyModule::import_bound(module.py(), "sys")?
|
PyModule::import_bound(module.py(), "sys")?
|
||||||
.dict()
|
.dict()
|
||||||
.get_item("modules")
|
.get_item("modules")
|
||||||
|
@ -141,11 +146,8 @@ fn add_module(module: Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<PyDict>()?
|
.downcast::<PyDict>()?
|
||||||
.set_item(module.name()?, module)
|
.set_item(module.name()?, module)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)]
|
|
||||||
fn test_pickle() {
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let module = PyModule::new_bound(py, "test_module").unwrap();
|
let module = PyModule::new_bound(py, "test_module").unwrap();
|
||||||
module.add_class::<PickleSupport>().unwrap();
|
module.add_class::<PickleSupport>().unwrap();
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
//! With abi3, dict not supported until python 3.9 or greater
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(dict)]
|
||||||
|
struct TestClass {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: `dict` requires Python >= 3.9 when using the `abi3` feature
|
||||||
|
--> tests/ui/abi3_dict.rs:4:11
|
||||||
|
|
|
||||||
|
4 | #[pyclass(dict)]
|
||||||
|
| ^^^^
|
|
@ -0,0 +1,7 @@
|
||||||
|
//! With abi3, weakref not supported until python 3.9 or greater
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(weakref)]
|
||||||
|
struct TestClass {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: `weakref` requires Python >= 3.9 when using the `abi3` feature
|
||||||
|
--> tests/ui/abi3_weakref.rs:4:11
|
||||||
|
|
|
||||||
|
4 | #[pyclass(weakref)]
|
||||||
|
| ^^^^^^^
|
Loading…
Reference in New Issue