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:
parent
cb347370ff
commit
d1a7cf400a
|
@ -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. |
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
Deprecate implicit integer comparision for simple enums in favor of `#[pyclass(eq_int)]`.
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -18,7 +18,8 @@ pub fn enums(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum SimpleEnum {
|
||||
Sunday,
|
||||
Monday,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -89,7 +89,8 @@ mod declarative_module {
|
|||
}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(PartialEq)]
|
||||
enum Enum {
|
||||
A,
|
||||
B,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
||||
|
|
Loading…
Reference in New Issue