add pyclass `eq` option (#4210)

* add pyclass `eq` option

* prevent manual impl of `__richcmp__` or `__eq__` with `#[pyclass(eq)]`

* add simple enum `eq_int` option

* rearrange names to fix deprecation warning

* add newsfragment and migration

* update docs

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
Icxolu 2024-05-31 16:13:30 +02:00 committed by GitHub
parent cb347370ff
commit d1a7cf400a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 501 additions and 72 deletions

View File

@ -5,6 +5,8 @@
| `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. |
| <span style="white-space: pre">`crate = "some::path"`</span> | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. |
| `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. |
| `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. |
| `eq_int` | Implements `__eq__` using `__int__` for simple enums. |
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |

View File

@ -37,14 +37,16 @@ struct Number(i32);
// PyO3 supports unit-only enums (which contain only unit variants)
// These simple enums behave similarly to Python's enumerations (enum.Enum)
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 30, // PyO3 supports custom discriminants.
}
// PyO3 supports custom discriminants in unit-only enums
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum HttpResponse {
Ok = 200,
NotFound = 404,
@ -1053,7 +1055,8 @@ PyO3 adds a class attribute for each variant, so you can access them in Python w
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant,
@ -1075,7 +1078,8 @@ You can also convert your simple enums into `int`:
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Variant,
OtherVariant = 10,
@ -1087,8 +1091,6 @@ Python::with_gil(|py| {
pyo3::py_run!(py, cls x, r#"
assert int(cls.Variant) == x
assert int(cls.OtherVariant) == 10
assert cls.OtherVariant == 10 # You can also compare against int.
assert 10 == cls.OtherVariant
"#)
})
```
@ -1097,7 +1099,8 @@ PyO3 also provides `__repr__` for enums:
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum{
Variant,
OtherVariant,
@ -1117,7 +1120,8 @@ All methods defined by PyO3 can be overridden. For example here's how you overri
```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum MyEnum {
Answer = 42,
}
@ -1139,7 +1143,8 @@ Enums and their variants can also be renamed using `#[pyo3(name)]`.
```rust
# use pyo3::prelude::*;
#[pyclass(name = "RenamedEnum")]
#[pyclass(eq, eq_int, name = "RenamedEnum")]
#[derive(PartialEq)]
enum MyEnum {
#[pyo3(name = "UPPERCASE")]
Variant,

View File

@ -226,6 +226,16 @@ impl Number {
# }
```
To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used.
```rust
# use pyo3::prelude::*;
#
#[pyclass(eq)]
#[derive(PartialEq)]
struct Number(i32);
```
### Truthyness
We'll consider `Number` to be `True` if it is nonzero:
@ -305,3 +315,4 @@ fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
[SipHash]: https://en.wikipedia.org/wiki/SipHash
[`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html

View File

@ -47,6 +47,40 @@ However, take care to note that the behaviour is different from previous version
Related to this, we also added a `pyo3_disable_reference_pool` conditional compilation flag which removes the infrastructure necessary to apply delayed reference count decrements implied by `impl<T> Drop for Py<T>`. They do not appear to be a soundness hazard as they should lead to memory leaks in the worst case. However, the global synchronization adds significant overhead to cross the Python-Rust boundary. Enabling this feature will remove these costs and make the `Drop` implementation abort the process if called without the GIL being held instead.
</details>
### Require explicit opt-in for comparison for simple enums
<details open>
<summary><small>Click to expand</small></summary>
With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute.
To migrate, place a `#[pyo3(eq, eq_int)]` attribute on simple enum classes.
Before:
```rust
# #![allow(deprecated, dead_code)]
# use pyo3::prelude::*;
#[pyclass]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```
After:
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum SimpleEnum {
VariantA,
VariantB = 42,
}
```
</details>
## from 0.20.* to 0.21
<details open>
<summary><small>Click to expand</small></summary>

View File

@ -0,0 +1,2 @@
Added `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`.
Added `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants.

View File

@ -0,0 +1 @@
Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`.

View File

@ -14,6 +14,8 @@ pub mod kw {
syn::custom_keyword!(cancel_handle);
syn::custom_keyword!(constructor);
syn::custom_keyword!(dict);
syn::custom_keyword!(eq);
syn::custom_keyword!(eq_int);
syn::custom_keyword!(extends);
syn::custom_keyword!(freelist);
syn::custom_keyword!(from_py_with);

View File

@ -18,7 +18,7 @@ use crate::utils::Ctx;
use crate::utils::{self, apply_renaming_rule, PythonDoc};
use crate::PyFunctionOptions;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use quote::{format_ident, quote, quote_spanned};
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
@ -59,6 +59,8 @@ impl PyClassArgs {
pub struct PyClassPyO3Options {
pub krate: Option<CrateAttribute>,
pub dict: Option<kw::dict>,
pub eq: Option<kw::eq>,
pub eq_int: Option<kw::eq_int>,
pub extends: Option<ExtendsAttribute>,
pub get_all: Option<kw::get_all>,
pub freelist: Option<FreelistAttribute>,
@ -77,6 +79,8 @@ pub struct PyClassPyO3Options {
enum PyClassPyO3Option {
Crate(CrateAttribute),
Dict(kw::dict),
Eq(kw::eq),
EqInt(kw::eq_int),
Extends(ExtendsAttribute),
Freelist(FreelistAttribute),
Frozen(kw::frozen),
@ -99,6 +103,10 @@ impl Parse for PyClassPyO3Option {
input.parse().map(PyClassPyO3Option::Crate)
} else if lookahead.peek(kw::dict) {
input.parse().map(PyClassPyO3Option::Dict)
} else if lookahead.peek(kw::eq) {
input.parse().map(PyClassPyO3Option::Eq)
} else if lookahead.peek(kw::eq_int) {
input.parse().map(PyClassPyO3Option::EqInt)
} else if lookahead.peek(kw::extends) {
input.parse().map(PyClassPyO3Option::Extends)
} else if lookahead.peek(attributes::kw::freelist) {
@ -166,6 +174,8 @@ impl PyClassPyO3Options {
match option {
PyClassPyO3Option::Crate(krate) => set_option!(krate),
PyClassPyO3Option::Dict(dict) => set_option!(dict),
PyClassPyO3Option::Eq(eq) => set_option!(eq),
PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int),
PyClassPyO3Option::Extends(extends) => set_option!(extends),
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
@ -350,6 +360,12 @@ fn impl_class(
let Ctx { pyo3_path } = ctx;
let pytypeinfo_impl = impl_pytypeinfo(cls, args, None, ctx);
let (default_richcmp, default_richcmp_slot) =
pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?;
let mut slots = Vec::new();
slots.extend(default_richcmp_slot);
let py_class_impl = PyClassImplsBuilder::new(
cls,
args,
@ -361,7 +377,7 @@ fn impl_class(
field_options,
ctx,
)?,
vec![],
slots,
)
.doc(doc)
.impl_all(ctx)?;
@ -372,6 +388,12 @@ fn impl_class(
#pytypeinfo_impl
#py_class_impl
#[doc(hidden)]
#[allow(non_snake_case)]
impl #cls {
#default_richcmp
}
})
}
@ -723,7 +745,6 @@ fn impl_simple_enum(
methods_type: PyClassMethodsType,
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
let cls = simple_enum.ident;
let ty: syn::Type = syn::parse_quote!(#cls);
let variants = simple_enum.variants;
@ -775,50 +796,11 @@ fn impl_simple_enum(
(int_impl, int_slot)
};
let (default_richcmp, default_richcmp_slot) = {
let mut richcmp_impl: syn::ImplItemFn = syn::parse_quote! {
fn __pyo3__richcmp__(
&self,
py: #pyo3_path::Python,
other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
op: #pyo3_path::basic::CompareOp
) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
use #pyo3_path::conversion::ToPyObject;
use #pyo3_path::types::PyAnyMethods;
use ::core::result::Result::*;
match op {
#pyo3_path::basic::CompareOp::Eq => {
let self_val = self.__pyo3__int__();
if let Ok(i) = other.extract::<#repr_type>() {
return Ok((self_val == i).to_object(py));
}
if let Ok(other) = other.extract::<#pyo3_path::PyRef<Self>>() {
return Ok((self_val == other.__pyo3__int__()).to_object(py));
}
let (default_richcmp, default_richcmp_slot) =
pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx);
return Ok(py.NotImplemented());
}
#pyo3_path::basic::CompareOp::Ne => {
let self_val = self.__pyo3__int__();
if let Ok(i) = other.extract::<#repr_type>() {
return Ok((self_val != i).to_object(py));
}
if let Ok(other) = other.extract::<#pyo3_path::PyRef<Self>>() {
return Ok((self_val != other.__pyo3__int__()).to_object(py));
}
return Ok(py.NotImplemented());
}
_ => Ok(py.NotImplemented()),
}
}
};
let richcmp_slot =
generate_default_protocol_slot(&ty, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap();
(richcmp_impl, richcmp_slot)
};
let default_slots = vec![default_repr_slot, default_int_slot, default_richcmp_slot];
let mut default_slots = vec![default_repr_slot, default_int_slot];
default_slots.extend(default_richcmp_slot);
let pyclass_impls = PyClassImplsBuilder::new(
cls,
@ -857,6 +839,8 @@ fn impl_complex_enum(
ctx: &Ctx,
) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
let cls = complex_enum.ident;
let ty: syn::Type = syn::parse_quote!(#cls);
// Need to rig the enum PyClass options
let args = {
@ -873,7 +857,10 @@ fn impl_complex_enum(
let variants = complex_enum.variants;
let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx);
let default_slots = vec![];
let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?;
let mut default_slots = vec![];
default_slots.extend(default_richcmp_slot);
let impl_builder = PyClassImplsBuilder::new(
cls,
@ -978,7 +965,9 @@ fn impl_complex_enum(
#[doc(hidden)]
#[allow(non_snake_case)]
impl #cls {}
impl #cls {
#default_richcmp
}
#(#variant_cls_zsts)*
@ -1276,6 +1265,23 @@ fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident
format_ident!("{}_{}", enum_, variant)
}
fn generate_protocol_slot(
cls: &syn::Type,
method: &mut syn::ImplItemFn,
slot: &SlotDef,
name: &str,
ctx: &Ctx,
) -> syn::Result<MethodAndSlotDef> {
let spec = FnSpec::parse(
&mut method.sig,
&mut Vec::new(),
PyFunctionOptions::default(),
ctx,
)
.unwrap();
slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)
}
fn generate_default_protocol_slot(
cls: &syn::Type,
method: &mut syn::ImplItemFn,
@ -1637,6 +1643,146 @@ fn impl_pytypeinfo(
}
}
fn pyclass_richcmp_arms(options: &PyClassPyO3Options, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let eq_arms = options
.eq
.map(|eq| {
quote_spanned! { eq.span() =>
#pyo3_path::pyclass::CompareOp::Eq => {
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val == other, py))
},
#pyo3_path::pyclass::CompareOp::Ne => {
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val != other, py))
},
}
})
.unwrap_or_default();
// TODO: `ord` can be integrated here (#4202)
#[allow(clippy::let_and_return)]
eq_arms
}
fn pyclass_richcmp_simple_enum(
options: &PyClassPyO3Options,
cls: &syn::Type,
repr_type: &syn::Ident,
ctx: &Ctx,
) -> (Option<syn::ImplItemFn>, Option<MethodAndSlotDef>) {
let Ctx { pyo3_path } = ctx;
let arms = pyclass_richcmp_arms(options, ctx);
let deprecation = options
.eq_int
.map(|_| TokenStream::new())
.unwrap_or_else(|| {
quote! {
#[deprecated(
since = "0.22.0",
note = "Implicit equality for simple enums is deprecated. Use `#[pyclass(eq, eq_int)` to keep the current behavior."
)]
const DEPRECATION: () = ();
const _: () = DEPRECATION;
}
});
let mut options = options.clone();
options.eq_int = Some(parse_quote!(eq_int));
if options.eq.is_none() && options.eq_int.is_none() {
return (None, None);
}
let eq = options.eq.map(|eq| {
quote_spanned! { eq.span() =>
let self_val = self;
if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::<Self>(other) {
let other = &*other.borrow();
return match op {
#arms
_ => ::std::result::Result::Ok(py.NotImplemented())
}
}
}
});
let eq_int = options.eq_int.map(|eq_int| {
quote_spanned! { eq_int.span() =>
let self_val = self.__pyo3__int__();
if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| {
#pyo3_path::types::PyAnyMethods::downcast::<Self>(other).map(|o| o.borrow().__pyo3__int__())
}) {
return match op {
#arms
_ => ::std::result::Result::Ok(py.NotImplemented())
}
}
}
});
let mut richcmp_impl = parse_quote! {
fn __pyo3__generated____richcmp__(
&self,
py: #pyo3_path::Python,
other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
op: #pyo3_path::pyclass::CompareOp
) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
#deprecation
#eq
#eq_int
::std::result::Result::Ok(py.NotImplemented())
}
};
let richcmp_slot = if options.eq.is_some() {
generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx).unwrap()
} else {
generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap()
};
(Some(richcmp_impl), Some(richcmp_slot))
}
fn pyclass_richcmp(
options: &PyClassPyO3Options,
cls: &syn::Type,
ctx: &Ctx,
) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
let Ctx { pyo3_path } = ctx;
if let Some(eq_int) = options.eq_int {
bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.")
}
let arms = pyclass_richcmp_arms(options, ctx);
if options.eq.is_some() {
let mut richcmp_impl = parse_quote! {
fn __pyo3__generated____richcmp__(
&self,
py: #pyo3_path::Python,
other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>,
op: #pyo3_path::pyclass::CompareOp
) -> #pyo3_path::PyResult<#pyo3_path::PyObject> {
let self_val = self;
let other = &*#pyo3_path::types::PyAnyMethods::downcast::<Self>(other)?.borrow();
match op {
#arms
_ => ::std::result::Result::Ok(py.NotImplemented())
}
}
};
let richcmp_slot =
generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx)
.unwrap();
Ok((Some(richcmp_impl), Some(richcmp_slot)))
} else {
Ok((None, None))
}
}
/// Implements most traits used by `#[pyclass]`.
///
/// Specifically, it implements traits that only depend on class name,

View File

@ -18,7 +18,8 @@ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> {
Ok(())
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
pub enum SimpleEnum {
Sunday,
Monday,

View File

@ -29,8 +29,9 @@ pub struct Bar {
c: ::std::option::Option<crate::Py<Foo2>>,
}
#[crate::pyclass]
#[crate::pyclass(eq, eq_int)]
#[pyo3(crate = "crate")]
#[derive(PartialEq)]
pub enum Enum {
Var0,
}

View File

@ -89,7 +89,8 @@ mod declarative_module {
}
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum Enum {
A,
B,

View File

@ -6,7 +6,8 @@ use pyo3::prelude::*;
mod common;
// Test default generated __repr__.
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum TestDefaultRepr {
Var,
}
@ -23,7 +24,8 @@ fn test_default_slot_exists() {
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(PartialEq)]
enum OverrideSlot {
Var,
}

View File

@ -6,7 +6,7 @@ use pyo3::py_run;
#[path = "../src/tests/common.rs"]
mod common;
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum MyEnum {
Variant,
@ -73,7 +73,8 @@ fn test_enum_eq_incomparable() {
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
enum CustomDiscriminant {
One = 1,
Two = 2,
@ -121,7 +122,8 @@ fn test_enum_compare_int() {
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u8)]
enum SmallEnum {
V = 1,
@ -135,7 +137,8 @@ fn test_enum_compare_int_no_throw_when_overflow() {
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(usize)]
#[allow(clippy::enum_clike_unportable_variant)]
enum BigEnum {
@ -147,12 +150,14 @@ fn test_big_enum_no_overflow() {
Python::with_gil(|py| {
let usize_max = usize::MAX;
let v = Py::new(py, BigEnum::V).unwrap();
py_assert!(py, usize_max v, "v == usize_max");
py_assert!(py, usize_max v, "int(v) == usize_max");
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(u16, align(8))]
enum TestReprParse {
V,
@ -163,7 +168,7 @@ fn test_repr_parse() {
assert_eq!(std::mem::align_of::<TestReprParse>(), 8);
}
#[pyclass(name = "MyEnum")]
#[pyclass(eq, eq_int, name = "MyEnum")]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameEnum {
Variant,
@ -177,7 +182,7 @@ fn test_rename_enum_repr_correct() {
})
}
#[pyclass]
#[pyclass(eq, eq_int)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RenameVariantEnum {
#[pyo3(name = "VARIANT")]
@ -192,7 +197,8 @@ fn test_rename_variant_repr_correct() {
})
}
#[pyclass(rename_all = "SCREAMING_SNAKE_CASE")]
#[pyclass(eq, eq_int, rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(clippy::enum_variant_names)]
enum RenameAllVariantsEnum {
VariantOne,

View File

@ -196,3 +196,9 @@ fn test_wrap_pyfunction(py: Python<'_>, m: &Bound<'_, PyModule>) {
let _ = wrap_pyfunction_bound!(double, py);
let _ = wrap_pyfunction_bound!(double)(py);
}
#[pyclass]
pub enum SimpleEnumWithoutEq {
VariamtA,
VariantB,
}

View File

@ -34,6 +34,14 @@ error: use of deprecated constant `__pyfunction_pyfunction_option_4::SIGNATURE`:
138 | fn pyfunction_option_4(
| ^^^^^^^^^^^^^^^^^^^
error: use of deprecated constant `SimpleEnumWithoutEq::__pyo3__generated____richcmp__::DEPRECATION`: Implicit equality for simple enums is deprecated. Use `#[pyclass(eq, eq_int)` to keep the current behavior.
--> tests/ui/deprecations.rs:200:1
|
200 | #[pyclass]
| ^^^^^^^^^^
|
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
error: use of deprecated struct `pyo3::PyCell`: `PyCell` was merged into `Bound`, use that instead; see the migration guide for more info
--> tests/ui/deprecations.rs:23:30
|

View File

@ -30,4 +30,26 @@ struct InvalidArg {}
#[pyclass(mapping, sequence)]
struct CannotBeMappingAndSequence {}
#[pyclass(eq)]
struct EqOptRequiresEq {}
#[pyclass(eq)]
#[derive(PartialEq)]
struct EqOptAndManualRichCmp {}
#[pymethods]
impl EqOptAndManualRichCmp {
fn __richcmp__(
&self,
_py: Python,
_other: Bound<'_, PyAny>,
_op: pyo3::pyclass::CompareOp,
) -> PyResult<PyObject> {
todo!()
}
}
#[pyclass(eq_int)]
struct NoEqInt {}
fn main() {}

View File

@ -1,4 +1,4 @@
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `rename_all`, `sequence`, `set_all`, `subclass`, `unsendable`, `weakref`
error: expected one of: `crate`, `dict`, `eq`, `eq_int`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `rename_all`, `sequence`, `set_all`, `subclass`, `unsendable`, `weakref`
--> tests/ui/invalid_pyclass_args.rs:3:11
|
3 | #[pyclass(extend=pyo3::types::PyDict)]
@ -46,7 +46,7 @@ error: expected string literal
24 | #[pyclass(module = my_module)]
| ^^^^^^^^^
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `rename_all`, `sequence`, `set_all`, `subclass`, `unsendable`, `weakref`
error: expected one of: `crate`, `dict`, `eq`, `eq_int`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `rename_all`, `sequence`, `set_all`, `subclass`, `unsendable`, `weakref`
--> tests/ui/invalid_pyclass_args.rs:27:11
|
27 | #[pyclass(weakrev)]
@ -57,3 +57,90 @@ error: a `#[pyclass]` cannot be both a `mapping` and a `sequence`
|
31 | struct CannotBeMappingAndSequence {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `eq_int` can only be used on simple enums.
--> tests/ui/invalid_pyclass_args.rs:52:11
|
52 | #[pyclass(eq_int)]
| ^^^^^^
error[E0592]: duplicate definitions with name `__pymethod___richcmp____`
--> tests/ui/invalid_pyclass_args.rs:36:1
|
36 | #[pyclass(eq)]
| ^^^^^^^^^^^^^^ duplicate definitions for `__pymethod___richcmp____`
...
40 | #[pymethods]
| ------------ other definition for `__pymethod___richcmp____`
|
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0369]: binary operation `==` cannot be applied to type `&EqOptRequiresEq`
--> tests/ui/invalid_pyclass_args.rs:33:11
|
33 | #[pyclass(eq)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `EqOptRequiresEq`
--> tests/ui/invalid_pyclass_args.rs:34:1
|
34 | struct EqOptRequiresEq {}
| ^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `EqOptRequiresEq` with `#[derive(PartialEq)]`
|
34 + #[derive(PartialEq)]
35 | struct EqOptRequiresEq {}
|
error[E0369]: binary operation `!=` cannot be applied to type `&EqOptRequiresEq`
--> tests/ui/invalid_pyclass_args.rs:33:11
|
33 | #[pyclass(eq)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `EqOptRequiresEq`
--> tests/ui/invalid_pyclass_args.rs:34:1
|
34 | struct EqOptRequiresEq {}
| ^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `EqOptRequiresEq` with `#[derive(PartialEq)]`
|
34 + #[derive(PartialEq)]
35 | struct EqOptRequiresEq {}
|
error[E0034]: multiple applicable items in scope
--> tests/ui/invalid_pyclass_args.rs:36:1
|
36 | #[pyclass(eq)]
| ^^^^^^^^^^^^^^ multiple `__pymethod___richcmp____` found
|
note: candidate #1 is defined in an impl for the type `EqOptAndManualRichCmp`
--> tests/ui/invalid_pyclass_args.rs:36:1
|
36 | #[pyclass(eq)]
| ^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `EqOptAndManualRichCmp`
--> tests/ui/invalid_pyclass_args.rs:40:1
|
40 | #[pymethods]
| ^^^^^^^^^^^^
= note: this error originates in the attribute macro `pyclass` which comes from the expansion of the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0034]: multiple applicable items in scope
--> tests/ui/invalid_pyclass_args.rs:40:1
|
40 | #[pymethods]
| ^^^^^^^^^^^^ multiple `__pymethod___richcmp____` found
|
note: candidate #1 is defined in an impl for the type `EqOptAndManualRichCmp`
--> tests/ui/invalid_pyclass_args.rs:36:1
|
36 | #[pyclass(eq)]
| ^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `EqOptAndManualRichCmp`
--> tests/ui/invalid_pyclass_args.rs:40:1
|
40 | #[pymethods]
| ^^^^^^^^^^^^
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -28,4 +28,22 @@ enum SimpleNoSignature {
B,
}
#[pyclass(eq, eq_int)]
enum SimpleEqOptRequiresPartialEq {
A,
B,
}
#[pyclass(eq)]
enum ComplexEqOptRequiresPartialEq {
A(i32),
B { msg: String },
}
#[pyclass(eq_int)]
enum NoEqInt {
A(i32),
B { msg: String },
}
fn main() {}

View File

@ -29,3 +29,77 @@ error: `constructor` can't be used on a simple enum variant
|
26 | #[pyo3(constructor = (a, b))]
| ^^^^^^^^^^^
error: `eq_int` can only be used on simple enums.
--> tests/ui/invalid_pyclass_enum.rs:43:11
|
43 | #[pyclass(eq_int)]
| ^^^^^^
error[E0369]: binary operation `==` cannot be applied to type `&SimpleEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:31:11
|
31 | #[pyclass(eq, eq_int)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `SimpleEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:32:1
|
32 | enum SimpleEqOptRequiresPartialEq {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `SimpleEqOptRequiresPartialEq` with `#[derive(PartialEq)]`
|
32 + #[derive(PartialEq)]
33 | enum SimpleEqOptRequiresPartialEq {
|
error[E0369]: binary operation `!=` cannot be applied to type `&SimpleEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:31:11
|
31 | #[pyclass(eq, eq_int)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `SimpleEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:32:1
|
32 | enum SimpleEqOptRequiresPartialEq {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `SimpleEqOptRequiresPartialEq` with `#[derive(PartialEq)]`
|
32 + #[derive(PartialEq)]
33 | enum SimpleEqOptRequiresPartialEq {
|
error[E0369]: binary operation `==` cannot be applied to type `&ComplexEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:37:11
|
37 | #[pyclass(eq)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `ComplexEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:38:1
|
38 | enum ComplexEqOptRequiresPartialEq {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `ComplexEqOptRequiresPartialEq` with `#[derive(PartialEq)]`
|
38 + #[derive(PartialEq)]
39 | enum ComplexEqOptRequiresPartialEq {
|
error[E0369]: binary operation `!=` cannot be applied to type `&ComplexEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:37:11
|
37 | #[pyclass(eq)]
| ^^
|
note: an implementation of `PartialEq` might be missing for `ComplexEqOptRequiresPartialEq`
--> tests/ui/invalid_pyclass_enum.rs:38:1
|
38 | enum ComplexEqOptRequiresPartialEq {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `ComplexEqOptRequiresPartialEq` with `#[derive(PartialEq)]`
|
38 + #[derive(PartialEq)]
39 | enum ComplexEqOptRequiresPartialEq {
|