feat: Add 'ord' option for PyClass and corresponding tests (#4202)
* feat: Add 'ord' option for PyClass and corresponding tests Updated the macros back-end to include 'ord' as an option for PyClass allowing for Python-style ordering comparison of enum variants. Additionally, test cases to verify the proper functioning of this new feature have been introduced. * update: fix formatting with cargo fmt * update: documented added feature in newsfragments * update: updated saved errors for comparison test for invalid pyclass args * update: removed nested match arms and extended cases for ordering instead * update: alphabetically ordered entries * update: added section to class documentation with example for using ord argument. * refactor: reduced duplication of code using closure to process tokens. * update: used ensure_spanned macro to emit compile time errors for uses of ord on complex enums or structs, updated test errors for bad compile cases * fix: remove errant character * update: added note about PartialOrd being required. * feat: implemented ordering for structs and complex enums. Retained the equality logic for simple enums until PartialEq is deprecated. * update: adjusted compile time error checks for missing PartialOrd implementations. Refactored growing set of comparison tests for simple and complex enums and structs into separate test file. * fix: updated with clippy findings * update: added not to pyclass parameters on ord (assumes that eq will be implemented and merged first) * update: rebased on main after merging of `eq` feature * update: format update * update: update all test output and doc tests * Update guide/src/class.md Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * Update pyo3-macros-backend/src/pyclass.rs Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * Update newsfragments/4202.added.md Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * Update guide/pyclass-parameters.md Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * update: added note about `ord` implementation with example. * fix doc formatting --------- Co-authored-by: Michael Gilbert <git.3mc1o@aleeas.com> Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
This commit is contained in:
parent
fbb6f20c2b
commit
b8fb367582
|
@ -15,6 +15,7 @@
|
|||
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
||||
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
||||
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
||||
| `ord` | Implements `__lt__`, `__gt__`, `__le__`, & `__ge__` using the `PartialOrd` implementation of the underlying Rust datatype. *Requires `eq`* |
|
||||
| `rename_all = "renaming_rule"` | Applies renaming rules to every getters and setters of a struct, or every variants of an enum. Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". |
|
||||
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
||||
| `set_all` | Generates setters for all fields of the pyclass. |
|
||||
|
|
|
@ -1160,6 +1160,32 @@ Python::with_gil(|py| {
|
|||
})
|
||||
```
|
||||
|
||||
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
|
||||
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum MyEnum{
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let cls = py.get_type_bound::<MyEnum>();
|
||||
let a = Py::new(py, MyEnum::A).unwrap();
|
||||
let b = Py::new(py, MyEnum::B).unwrap();
|
||||
let c = Py::new(py, MyEnum::C).unwrap();
|
||||
pyo3::py_run!(py, cls a b c, r#"
|
||||
assert (a < b) == True
|
||||
assert (c <= b) == False
|
||||
assert (c > a) == True
|
||||
"#)
|
||||
})
|
||||
```
|
||||
|
||||
You may not use enums as a base class or let enums inherit from other classes.
|
||||
|
||||
```rust,compile_fail
|
||||
|
|
|
@ -249,6 +249,16 @@ To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq
|
|||
struct Number(i32);
|
||||
```
|
||||
|
||||
To implement `__lt__`, `__le__`, `__gt__`, & `__ge__` using the Rust `PartialOrd` trait implementation, the `ord` option can be used. *Note: Requires `eq`.*
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
#
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
struct Number(i32);
|
||||
```
|
||||
|
||||
### Truthyness
|
||||
|
||||
We'll consider `Number` to be `True` if it is nonzero:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`.
|
|
@ -29,6 +29,7 @@ pub mod kw {
|
|||
syn::custom_keyword!(mapping);
|
||||
syn::custom_keyword!(module);
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(ord);
|
||||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(rename_all);
|
||||
syn::custom_keyword!(sequence);
|
||||
|
|
|
@ -70,6 +70,7 @@ pub struct PyClassPyO3Options {
|
|||
pub mapping: Option<kw::mapping>,
|
||||
pub module: Option<ModuleAttribute>,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub ord: Option<kw::ord>,
|
||||
pub rename_all: Option<RenameAllAttribute>,
|
||||
pub sequence: Option<kw::sequence>,
|
||||
pub set_all: Option<kw::set_all>,
|
||||
|
@ -91,6 +92,7 @@ pub enum PyClassPyO3Option {
|
|||
Mapping(kw::mapping),
|
||||
Module(ModuleAttribute),
|
||||
Name(NameAttribute),
|
||||
Ord(kw::ord),
|
||||
RenameAll(RenameAllAttribute),
|
||||
Sequence(kw::sequence),
|
||||
SetAll(kw::set_all),
|
||||
|
@ -126,6 +128,8 @@ impl Parse for PyClassPyO3Option {
|
|||
input.parse().map(PyClassPyO3Option::Module)
|
||||
} else if lookahead.peek(kw::name) {
|
||||
input.parse().map(PyClassPyO3Option::Name)
|
||||
} else if lookahead.peek(attributes::kw::ord) {
|
||||
input.parse().map(PyClassPyO3Option::Ord)
|
||||
} else if lookahead.peek(kw::rename_all) {
|
||||
input.parse().map(PyClassPyO3Option::RenameAll)
|
||||
} else if lookahead.peek(attributes::kw::sequence) {
|
||||
|
@ -189,6 +193,7 @@ impl PyClassPyO3Options {
|
|||
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
||||
PyClassPyO3Option::Module(module) => set_option!(module),
|
||||
PyClassPyO3Option::Name(name) => set_option!(name),
|
||||
PyClassPyO3Option::Ord(ord) => set_option!(ord),
|
||||
PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all),
|
||||
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
||||
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
||||
|
@ -1665,7 +1670,10 @@ fn impl_pytypeinfo(
|
|||
}
|
||||
}
|
||||
|
||||
fn pyclass_richcmp_arms(options: &PyClassPyO3Options, ctx: &Ctx) -> TokenStream {
|
||||
fn pyclass_richcmp_arms(
|
||||
options: &PyClassPyO3Options,
|
||||
ctx: &Ctx,
|
||||
) -> std::result::Result<TokenStream, syn::Error> {
|
||||
let Ctx { pyo3_path } = ctx;
|
||||
|
||||
let eq_arms = options
|
||||
|
@ -1684,9 +1692,34 @@ fn pyclass_richcmp_arms(options: &PyClassPyO3Options, ctx: &Ctx) -> TokenStream
|
|||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// TODO: `ord` can be integrated here (#4202)
|
||||
#[allow(clippy::let_and_return)]
|
||||
eq_arms
|
||||
if let Some(ord) = options.ord {
|
||||
ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option.");
|
||||
}
|
||||
|
||||
let ord_arms = options
|
||||
.ord
|
||||
.map(|ord| {
|
||||
quote_spanned! { ord.span() =>
|
||||
#pyo3_path::pyclass::CompareOp::Gt => {
|
||||
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val > other, py))
|
||||
},
|
||||
#pyo3_path::pyclass::CompareOp::Lt => {
|
||||
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val < other, py))
|
||||
},
|
||||
#pyo3_path::pyclass::CompareOp::Le => {
|
||||
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val <= other, py))
|
||||
},
|
||||
#pyo3_path::pyclass::CompareOp::Ge => {
|
||||
::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val >= other, py))
|
||||
},
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) });
|
||||
|
||||
Ok(quote! {
|
||||
#eq_arms
|
||||
#ord_arms
|
||||
})
|
||||
}
|
||||
|
||||
fn pyclass_richcmp_simple_enum(
|
||||
|
@ -1723,7 +1756,7 @@ fn pyclass_richcmp_simple_enum(
|
|||
return Ok((None, None));
|
||||
}
|
||||
|
||||
let arms = pyclass_richcmp_arms(&options, ctx);
|
||||
let arms = pyclass_richcmp_arms(&options, ctx)?;
|
||||
|
||||
let eq = options.eq.map(|eq| {
|
||||
quote_spanned! { eq.span() =>
|
||||
|
@ -1732,7 +1765,6 @@ fn pyclass_richcmp_simple_enum(
|
|||
let other = &*other.borrow();
|
||||
return match op {
|
||||
#arms
|
||||
_ => ::std::result::Result::Ok(py.NotImplemented())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1746,7 +1778,6 @@ fn pyclass_richcmp_simple_enum(
|
|||
}) {
|
||||
return match op {
|
||||
#arms
|
||||
_ => ::std::result::Result::Ok(py.NotImplemented())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1786,7 +1817,7 @@ fn pyclass_richcmp(
|
|||
bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.")
|
||||
}
|
||||
|
||||
let arms = pyclass_richcmp_arms(options, ctx);
|
||||
let arms = pyclass_richcmp_arms(options, ctx)?;
|
||||
if options.eq.is_some() {
|
||||
let mut richcmp_impl = parse_quote! {
|
||||
fn __pyo3__generated____richcmp__(
|
||||
|
@ -1799,7 +1830,6 @@ fn pyclass_richcmp(
|
|||
let other = &*#pyo3_path::types::PyAnyMethods::downcast::<Self>(other)?.borrow();
|
||||
match op {
|
||||
#arms
|
||||
_ => ::std::result::Result::Ok(py.NotImplemented())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
#![cfg(feature = "macros")]
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[path = "../src/tests/common.rs"]
|
||||
mod common;
|
||||
|
||||
#[pyclass(eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum MyEnum {
|
||||
Variant,
|
||||
OtherVariant,
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub enum MyEnumOrd {
|
||||
Variant,
|
||||
OtherVariant,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_eq_enum() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let var2 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let other_var = Py::new(py, MyEnum::OtherVariant).unwrap();
|
||||
py_assert!(py, var1 var2, "var1 == var2");
|
||||
py_assert!(py, var1 other_var, "var1 != other_var");
|
||||
py_assert!(py, var1 var2, "(var1 != var2) == False");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_eq_incomparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
py_assert!(py, var1, "(var1 == 'foo') == False");
|
||||
py_assert!(py, var1, "(var1 != 'foo') == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_ord_comparable_opt_in_only() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let var2 = Py::new(py, MyEnum::OtherVariant).unwrap();
|
||||
// ordering on simple enums if opt in only, thus raising an error below
|
||||
py_expect_exception!(py, var1 var2, "(var1 > var2) == False", PyTypeError);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_enum_ord_comparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnumOrd::Variant).unwrap();
|
||||
let var2 = Py::new(py, MyEnumOrd::OtherVariant).unwrap();
|
||||
let var3 = Py::new(py, MyEnumOrd::OtherVariant).unwrap();
|
||||
py_assert!(py, var1 var2, "(var1 > var2) == False");
|
||||
py_assert!(py, var1 var2, "(var1 < var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 >= var2) == False");
|
||||
py_assert!(py, var2 var3, "(var3 >= var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 <= var2) == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub enum MyComplexEnumOrd {
|
||||
Variant(i32),
|
||||
OtherVariant(String),
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub enum MyComplexEnumOrd2 {
|
||||
Variant { msg: String, idx: u32 },
|
||||
OtherVariant { name: String, idx: u32 },
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_enum_ord_comparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyComplexEnumOrd::Variant(-2)).unwrap();
|
||||
let var2 = Py::new(py, MyComplexEnumOrd::Variant(5)).unwrap();
|
||||
let var3 = Py::new(py, MyComplexEnumOrd::OtherVariant("a".to_string())).unwrap();
|
||||
let var4 = Py::new(py, MyComplexEnumOrd::OtherVariant("b".to_string())).unwrap();
|
||||
py_assert!(py, var1 var2, "(var1 > var2) == False");
|
||||
py_assert!(py, var1 var2, "(var1 < var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 >= var2) == False");
|
||||
py_assert!(py, var1 var2, "(var1 <= var2) == True");
|
||||
|
||||
py_assert!(py, var1 var3, "(var1 >= var3) == False");
|
||||
py_assert!(py, var1 var3, "(var1 <= var3) == True");
|
||||
|
||||
py_assert!(py, var3 var4, "(var3 >= var4) == False");
|
||||
py_assert!(py, var3 var4, "(var3 <= var4) == True");
|
||||
|
||||
let var5 = Py::new(
|
||||
py,
|
||||
MyComplexEnumOrd2::Variant {
|
||||
msg: "hello".to_string(),
|
||||
idx: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var6 = Py::new(
|
||||
py,
|
||||
MyComplexEnumOrd2::Variant {
|
||||
msg: "hello".to_string(),
|
||||
idx: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var7 = Py::new(
|
||||
py,
|
||||
MyComplexEnumOrd2::Variant {
|
||||
msg: "goodbye".to_string(),
|
||||
idx: 7,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var8 = Py::new(
|
||||
py,
|
||||
MyComplexEnumOrd2::Variant {
|
||||
msg: "about".to_string(),
|
||||
idx: 0,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var9 = Py::new(
|
||||
py,
|
||||
MyComplexEnumOrd2::OtherVariant {
|
||||
name: "albert".to_string(),
|
||||
idx: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
py_assert!(py, var5 var6, "(var5 == var6) == True");
|
||||
py_assert!(py, var5 var6, "(var5 <= var6) == True");
|
||||
py_assert!(py, var6 var7, "(var6 <= var7) == False");
|
||||
py_assert!(py, var6 var7, "(var6 >= var7) == True");
|
||||
py_assert!(py, var5 var8, "(var5 > var8) == True");
|
||||
py_assert!(py, var8 var9, "(var9 > var8) == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_numeric_ord_comparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, Point { x: 10, y: 2, z: 3 }).unwrap();
|
||||
let var2 = Py::new(py, Point { x: 2, y: 2, z: 3 }).unwrap();
|
||||
let var3 = Py::new(py, Point { x: 1, y: 22, z: 4 }).unwrap();
|
||||
let var4 = Py::new(py, Point { x: 1, y: 3, z: 4 }).unwrap();
|
||||
let var5 = Py::new(py, Point { x: 1, y: 3, z: 4 }).unwrap();
|
||||
py_assert!(py, var1 var2, "(var1 > var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 <= var2) == False");
|
||||
py_assert!(py, var2 var3, "(var3 < var2) == True");
|
||||
py_assert!(py, var3 var4, "(var3 > var4) == True");
|
||||
py_assert!(py, var4 var5, "(var4 == var5) == True");
|
||||
py_assert!(py, var3 var5, "(var3 != var5) == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub struct Person {
|
||||
surname: String,
|
||||
given_name: String,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_string_ord_comparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(
|
||||
py,
|
||||
Person {
|
||||
surname: "zzz".to_string(),
|
||||
given_name: "bob".to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var2 = Py::new(
|
||||
py,
|
||||
Person {
|
||||
surname: "aaa".to_string(),
|
||||
given_name: "sally".to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var3 = Py::new(
|
||||
py,
|
||||
Person {
|
||||
surname: "eee".to_string(),
|
||||
given_name: "qqq".to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var4 = Py::new(
|
||||
py,
|
||||
Person {
|
||||
surname: "ddd".to_string(),
|
||||
given_name: "aaa".to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
py_assert!(py, var1 var2, "(var1 > var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 <= var2) == False");
|
||||
py_assert!(py, var1 var3, "(var1 >= var3) == True");
|
||||
py_assert!(py, var2 var3, "(var2 >= var3) == False");
|
||||
py_assert!(py, var3 var4, "(var3 >= var4) == True");
|
||||
py_assert!(py, var3 var4, "(var3 != var4) == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclass(eq, ord)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Record {
|
||||
name: String,
|
||||
title: String,
|
||||
idx: u32,
|
||||
}
|
||||
|
||||
impl PartialOrd for Record {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.idx.partial_cmp(&other.idx).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_custom_ord_comparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(
|
||||
py,
|
||||
Record {
|
||||
name: "zzz".to_string(),
|
||||
title: "bbb".to_string(),
|
||||
idx: 9,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var2 = Py::new(
|
||||
py,
|
||||
Record {
|
||||
name: "ddd".to_string(),
|
||||
title: "aaa".to_string(),
|
||||
idx: 1,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var3 = Py::new(
|
||||
py,
|
||||
Record {
|
||||
name: "vvv".to_string(),
|
||||
title: "ggg".to_string(),
|
||||
idx: 19,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let var4 = Py::new(
|
||||
py,
|
||||
Record {
|
||||
name: "vvv".to_string(),
|
||||
title: "ggg".to_string(),
|
||||
idx: 19,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
py_assert!(py, var1 var2, "(var1 > var2) == True");
|
||||
py_assert!(py, var1 var2, "(var1 <= var2) == False");
|
||||
py_assert!(py, var1 var3, "(var1 >= var3) == False");
|
||||
py_assert!(py, var2 var3, "(var2 >= var3) == False");
|
||||
py_assert!(py, var3 var4, "(var3 == var4) == True");
|
||||
py_assert!(py, var2 var4, "(var2 != var4) == True");
|
||||
})
|
||||
}
|
|
@ -52,27 +52,6 @@ fn test_enum_arg() {
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_eq_enum() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let var2 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
let other_var = Py::new(py, MyEnum::OtherVariant).unwrap();
|
||||
py_assert!(py, var1 var2, "var1 == var2");
|
||||
py_assert!(py, var1 other_var, "var1 != other_var");
|
||||
py_assert!(py, var1 var2, "(var1 != var2) == False");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_eq_incomparable() {
|
||||
Python::with_gil(|py| {
|
||||
let var1 = Py::new(py, MyEnum::Variant).unwrap();
|
||||
py_assert!(py, var1, "(var1 == 'foo') == False");
|
||||
py_assert!(py, var1, "(var1 != 'foo') == True");
|
||||
})
|
||||
}
|
||||
|
||||
#[pyclass(eq, eq_int)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum CustomDiscriminant {
|
||||
|
|
|
@ -71,4 +71,9 @@ impl HashOptAndManualHash {
|
|||
}
|
||||
}
|
||||
|
||||
#[pyclass(ord)]
|
||||
struct InvalidOrderedStruct {
|
||||
inner: i32
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: expected one of: `crate`, `dict`, `eq`, `eq_int`, `extends`, `freelist`, `frozen`, `get_all`, `hash`, `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`, `hash`, `mapping`, `module`, `name`, `ord`, `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`, `eq`, `eq_int`, `extends`, `freelist`, `frozen`, `get_all`, `hash`, `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`, `hash`, `mapping`, `module`, `name`, `ord`, `rename_all`, `sequence`, `set_all`, `subclass`, `unsendable`, `weakref`
|
||||
--> tests/ui/invalid_pyclass_args.rs:27:11
|
||||
|
|
||||
27 | #[pyclass(weakrev)]
|
||||
|
@ -76,6 +76,12 @@ error: The `hash` option requires the `eq` option.
|
|||
59 | #[pyclass(hash)]
|
||||
| ^^^^
|
||||
|
||||
error: The `ord` option requires the `eq` option.
|
||||
--> tests/ui/invalid_pyclass_args.rs:74:11
|
||||
|
|
||||
74 | #[pyclass(ord)]
|
||||
| ^^^
|
||||
|
||||
error[E0592]: duplicate definitions with name `__pymethod___richcmp____`
|
||||
--> tests/ui/invalid_pyclass_args.rs:36:1
|
||||
|
|
||||
|
|
|
@ -80,4 +80,17 @@ enum ComplexHashOptRequiresEq {
|
|||
B { msg: String },
|
||||
}
|
||||
|
||||
#[pyclass(ord)]
|
||||
enum InvalidOrderedComplexEnum {
|
||||
VariantA (i32),
|
||||
VariantB { msg: String }
|
||||
}
|
||||
|
||||
#[pyclass(eq,ord)]
|
||||
#[derive(PartialEq)]
|
||||
enum InvalidOrderedComplexEnum2 {
|
||||
VariantA (i32),
|
||||
VariantB { msg: String }
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -60,6 +60,12 @@ error: The `hash` option requires the `eq` option.
|
|||
76 | #[pyclass(hash)]
|
||||
| ^^^^
|
||||
|
||||
error: The `ord` option requires the `eq` option.
|
||||
--> tests/ui/invalid_pyclass_enum.rs:83:11
|
||||
|
|
||||
83 | #[pyclass(ord)]
|
||||
| ^^^
|
||||
|
||||
error[E0369]: binary operation `==` cannot be applied to type `&SimpleEqOptRequiresPartialEq`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:31:11
|
||||
|
|
||||
|
@ -151,3 +157,71 @@ help: consider annotating `ComplexHashOptRequiresHash` with `#[derive(Hash)]`
|
|||
64 + #[derive(Hash)]
|
||||
65 | enum ComplexHashOptRequiresHash {
|
||||
|
|
||||
|
||||
error[E0369]: binary operation `>` cannot be applied to type `&InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:89:14
|
||||
|
|
||||
89 | #[pyclass(eq,ord)]
|
||||
| ^^^
|
||||
|
|
||||
note: an implementation of `PartialOrd` might be missing for `InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:91:1
|
||||
|
|
||||
91 | enum InvalidOrderedComplexEnum2 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialOrd`
|
||||
help: consider annotating `InvalidOrderedComplexEnum2` with `#[derive(PartialEq, PartialOrd)]`
|
||||
|
|
||||
91 + #[derive(PartialEq, PartialOrd)]
|
||||
92 | enum InvalidOrderedComplexEnum2 {
|
||||
|
|
||||
|
||||
error[E0369]: binary operation `<` cannot be applied to type `&InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:89:14
|
||||
|
|
||||
89 | #[pyclass(eq,ord)]
|
||||
| ^^^
|
||||
|
|
||||
note: an implementation of `PartialOrd` might be missing for `InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:91:1
|
||||
|
|
||||
91 | enum InvalidOrderedComplexEnum2 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialOrd`
|
||||
help: consider annotating `InvalidOrderedComplexEnum2` with `#[derive(PartialEq, PartialOrd)]`
|
||||
|
|
||||
91 + #[derive(PartialEq, PartialOrd)]
|
||||
92 | enum InvalidOrderedComplexEnum2 {
|
||||
|
|
||||
|
||||
error[E0369]: binary operation `<=` cannot be applied to type `&InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:89:14
|
||||
|
|
||||
89 | #[pyclass(eq,ord)]
|
||||
| ^^^
|
||||
|
|
||||
note: an implementation of `PartialOrd` might be missing for `InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:91:1
|
||||
|
|
||||
91 | enum InvalidOrderedComplexEnum2 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialOrd`
|
||||
help: consider annotating `InvalidOrderedComplexEnum2` with `#[derive(PartialEq, PartialOrd)]`
|
||||
|
|
||||
91 + #[derive(PartialEq, PartialOrd)]
|
||||
92 | enum InvalidOrderedComplexEnum2 {
|
||||
|
|
||||
|
||||
error[E0369]: binary operation `>=` cannot be applied to type `&InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:89:14
|
||||
|
|
||||
89 | #[pyclass(eq,ord)]
|
||||
| ^^^
|
||||
|
|
||||
note: an implementation of `PartialOrd` might be missing for `InvalidOrderedComplexEnum2`
|
||||
--> tests/ui/invalid_pyclass_enum.rs:91:1
|
||||
|
|
||||
91 | enum InvalidOrderedComplexEnum2 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ must implement `PartialOrd`
|
||||
help: consider annotating `InvalidOrderedComplexEnum2` with `#[derive(PartialEq, PartialOrd)]`
|
||||
|
|
||||
91 + #[derive(PartialEq, PartialOrd)]
|
||||
92 | enum InvalidOrderedComplexEnum2 {
|
||||
|
|
||||
|
|
Loading…
Reference in New Issue