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. |
|
| `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">`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. |
|
| <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". |
|
| `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. |
|
| `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. |
|
| `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.
|
You may not use enums as a base class or let enums inherit from other classes.
|
||||||
|
|
||||||
```rust,compile_fail
|
```rust,compile_fail
|
||||||
|
|
|
@ -249,6 +249,16 @@ To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq
|
||||||
struct Number(i32);
|
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
|
### Truthyness
|
||||||
|
|
||||||
We'll consider `Number` to be `True` if it is nonzero:
|
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!(mapping);
|
||||||
syn::custom_keyword!(module);
|
syn::custom_keyword!(module);
|
||||||
syn::custom_keyword!(name);
|
syn::custom_keyword!(name);
|
||||||
|
syn::custom_keyword!(ord);
|
||||||
syn::custom_keyword!(pass_module);
|
syn::custom_keyword!(pass_module);
|
||||||
syn::custom_keyword!(rename_all);
|
syn::custom_keyword!(rename_all);
|
||||||
syn::custom_keyword!(sequence);
|
syn::custom_keyword!(sequence);
|
||||||
|
|
|
@ -70,6 +70,7 @@ pub struct PyClassPyO3Options {
|
||||||
pub mapping: Option<kw::mapping>,
|
pub mapping: Option<kw::mapping>,
|
||||||
pub module: Option<ModuleAttribute>,
|
pub module: Option<ModuleAttribute>,
|
||||||
pub name: Option<NameAttribute>,
|
pub name: Option<NameAttribute>,
|
||||||
|
pub ord: Option<kw::ord>,
|
||||||
pub rename_all: Option<RenameAllAttribute>,
|
pub rename_all: Option<RenameAllAttribute>,
|
||||||
pub sequence: Option<kw::sequence>,
|
pub sequence: Option<kw::sequence>,
|
||||||
pub set_all: Option<kw::set_all>,
|
pub set_all: Option<kw::set_all>,
|
||||||
|
@ -91,6 +92,7 @@ pub enum PyClassPyO3Option {
|
||||||
Mapping(kw::mapping),
|
Mapping(kw::mapping),
|
||||||
Module(ModuleAttribute),
|
Module(ModuleAttribute),
|
||||||
Name(NameAttribute),
|
Name(NameAttribute),
|
||||||
|
Ord(kw::ord),
|
||||||
RenameAll(RenameAllAttribute),
|
RenameAll(RenameAllAttribute),
|
||||||
Sequence(kw::sequence),
|
Sequence(kw::sequence),
|
||||||
SetAll(kw::set_all),
|
SetAll(kw::set_all),
|
||||||
|
@ -126,6 +128,8 @@ impl Parse for PyClassPyO3Option {
|
||||||
input.parse().map(PyClassPyO3Option::Module)
|
input.parse().map(PyClassPyO3Option::Module)
|
||||||
} else if lookahead.peek(kw::name) {
|
} else if lookahead.peek(kw::name) {
|
||||||
input.parse().map(PyClassPyO3Option::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) {
|
} else if lookahead.peek(kw::rename_all) {
|
||||||
input.parse().map(PyClassPyO3Option::RenameAll)
|
input.parse().map(PyClassPyO3Option::RenameAll)
|
||||||
} else if lookahead.peek(attributes::kw::sequence) {
|
} else if lookahead.peek(attributes::kw::sequence) {
|
||||||
|
@ -189,6 +193,7 @@ impl PyClassPyO3Options {
|
||||||
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
||||||
PyClassPyO3Option::Module(module) => set_option!(module),
|
PyClassPyO3Option::Module(module) => set_option!(module),
|
||||||
PyClassPyO3Option::Name(name) => set_option!(name),
|
PyClassPyO3Option::Name(name) => set_option!(name),
|
||||||
|
PyClassPyO3Option::Ord(ord) => set_option!(ord),
|
||||||
PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all),
|
PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all),
|
||||||
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
||||||
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
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 Ctx { pyo3_path } = ctx;
|
||||||
|
|
||||||
let eq_arms = options
|
let eq_arms = options
|
||||||
|
@ -1684,9 +1692,34 @@ fn pyclass_richcmp_arms(options: &PyClassPyO3Options, ctx: &Ctx) -> TokenStream
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// TODO: `ord` can be integrated here (#4202)
|
if let Some(ord) = options.ord {
|
||||||
#[allow(clippy::let_and_return)]
|
ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option.");
|
||||||
eq_arms
|
}
|
||||||
|
|
||||||
|
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(
|
fn pyclass_richcmp_simple_enum(
|
||||||
|
@ -1723,7 +1756,7 @@ fn pyclass_richcmp_simple_enum(
|
||||||
return Ok((None, None));
|
return Ok((None, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let arms = pyclass_richcmp_arms(&options, ctx);
|
let arms = pyclass_richcmp_arms(&options, ctx)?;
|
||||||
|
|
||||||
let eq = options.eq.map(|eq| {
|
let eq = options.eq.map(|eq| {
|
||||||
quote_spanned! { eq.span() =>
|
quote_spanned! { eq.span() =>
|
||||||
|
@ -1732,7 +1765,6 @@ fn pyclass_richcmp_simple_enum(
|
||||||
let other = &*other.borrow();
|
let other = &*other.borrow();
|
||||||
return match op {
|
return match op {
|
||||||
#arms
|
#arms
|
||||||
_ => ::std::result::Result::Ok(py.NotImplemented())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1746,7 +1778,6 @@ fn pyclass_richcmp_simple_enum(
|
||||||
}) {
|
}) {
|
||||||
return match op {
|
return match op {
|
||||||
#arms
|
#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.")
|
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() {
|
if options.eq.is_some() {
|
||||||
let mut richcmp_impl = parse_quote! {
|
let mut richcmp_impl = parse_quote! {
|
||||||
fn __pyo3__generated____richcmp__(
|
fn __pyo3__generated____richcmp__(
|
||||||
|
@ -1799,7 +1830,6 @@ fn pyclass_richcmp(
|
||||||
let other = &*#pyo3_path::types::PyAnyMethods::downcast::<Self>(other)?.borrow();
|
let other = &*#pyo3_path::types::PyAnyMethods::downcast::<Self>(other)?.borrow();
|
||||||
match op {
|
match op {
|
||||||
#arms
|
#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)]
|
#[pyclass(eq, eq_int)]
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
enum CustomDiscriminant {
|
enum CustomDiscriminant {
|
||||||
|
|
|
@ -71,4 +71,9 @@ impl HashOptAndManualHash {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(ord)]
|
||||||
|
struct InvalidOrderedStruct {
|
||||||
|
inner: i32
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
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
|
--> tests/ui/invalid_pyclass_args.rs:3:11
|
||||||
|
|
|
|
||||||
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
||||||
|
@ -46,7 +46,7 @@ error: expected string literal
|
||||||
24 | #[pyclass(module = my_module)]
|
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
|
--> tests/ui/invalid_pyclass_args.rs:27:11
|
||||||
|
|
|
|
||||||
27 | #[pyclass(weakrev)]
|
27 | #[pyclass(weakrev)]
|
||||||
|
@ -76,6 +76,12 @@ error: The `hash` option requires the `eq` option.
|
||||||
59 | #[pyclass(hash)]
|
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____`
|
error[E0592]: duplicate definitions with name `__pymethod___richcmp____`
|
||||||
--> tests/ui/invalid_pyclass_args.rs:36:1
|
--> tests/ui/invalid_pyclass_args.rs:36:1
|
||||||
|
|
|
|
||||||
|
|
|
@ -80,4 +80,17 @@ enum ComplexHashOptRequiresEq {
|
||||||
B { msg: String },
|
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() {}
|
fn main() {}
|
||||||
|
|
|
@ -60,6 +60,12 @@ error: The `hash` option requires the `eq` option.
|
||||||
76 | #[pyclass(hash)]
|
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`
|
error[E0369]: binary operation `==` cannot be applied to type `&SimpleEqOptRequiresPartialEq`
|
||||||
--> tests/ui/invalid_pyclass_enum.rs:31:11
|
--> tests/ui/invalid_pyclass_enum.rs:31:11
|
||||||
|
|
|
|
||||||
|
@ -151,3 +157,71 @@ help: consider annotating `ComplexHashOptRequiresHash` with `#[derive(Hash)]`
|
||||||
64 + #[derive(Hash)]
|
64 + #[derive(Hash)]
|
||||||
65 | enum ComplexHashOptRequiresHash {
|
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