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:
Michael Gilbert 2024-06-07 12:08:53 -07:00 committed by GitHub
parent fbb6f20c2b
commit b8fb367582
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 465 additions and 32 deletions

View File

@ -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. |

View File

@ -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

View File

@ -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:

View File

@ -0,0 +1 @@
Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`.

View File

@ -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);

View File

@ -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())
} }
} }
}; };

View File

@ -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");
})
}

View File

@ -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 {

View File

@ -71,4 +71,9 @@ impl HashOptAndManualHash {
} }
} }
#[pyclass(ord)]
struct InvalidOrderedStruct {
inner: i32
}
fn main() {} fn main() {}

View File

@ -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
| |

View File

@ -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() {}

View File

@ -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 {
|