add pyclass `hash` option (#4206)
* add pyclass `hash` option * add newsfragment * require `frozen` option for `hash` * simplify `hash` without `frozen` error message Co-authored-by: David Hewitt <mail@davidhewitt.dev> * require `eq` for `hash` * prevent manual `__hash__` with `#pyo3(hash)` * combine error messages --------- Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
parent
25c1db4767
commit
a7a5c10b8a
|
@ -11,6 +11,7 @@
|
||||||
| <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">`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. |
|
| <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. |
|
||||||
| `get_all` | Generates getters for all fields of the pyclass. |
|
| `get_all` | Generates getters for all fields of the pyclass. |
|
||||||
|
| `hash` | Implements `__hash__` using the `Hash` implementation of the underlying Rust datatype. |
|
||||||
| `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. |
|
||||||
|
|
|
@ -121,6 +121,19 @@ impl Number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
To implement `__hash__` using the Rust [`Hash`] trait implementation, the `hash` option can be used.
|
||||||
|
This option is only available for `frozen` classes to prevent accidental hash changes from mutating the object. If you need
|
||||||
|
an `__hash__` implementation for a mutable class, use the manual method from above. This option also requires `eq`: According to the
|
||||||
|
[Python docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) "If a class does not define an `__eq__()`
|
||||||
|
method it should not define a `__hash__()` operation either"
|
||||||
|
```rust
|
||||||
|
# use pyo3::prelude::*;
|
||||||
|
#
|
||||||
|
#[pyclass(frozen, eq, hash)]
|
||||||
|
#[derive(PartialEq, Hash)]
|
||||||
|
struct Number(i32);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
|
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Added `#[pyclass(hash)]` option to implement `__hash__` in terms of the `Hash` implementation
|
|
@ -22,6 +22,7 @@ pub mod kw {
|
||||||
syn::custom_keyword!(frozen);
|
syn::custom_keyword!(frozen);
|
||||||
syn::custom_keyword!(get);
|
syn::custom_keyword!(get);
|
||||||
syn::custom_keyword!(get_all);
|
syn::custom_keyword!(get_all);
|
||||||
|
syn::custom_keyword!(hash);
|
||||||
syn::custom_keyword!(item);
|
syn::custom_keyword!(item);
|
||||||
syn::custom_keyword!(from_item_all);
|
syn::custom_keyword!(from_item_all);
|
||||||
syn::custom_keyword!(mapping);
|
syn::custom_keyword!(mapping);
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::pyfunction::ConstructorAttribute;
|
||||||
use crate::pyimpl::{gen_py_const, PyClassMethodsType};
|
use crate::pyimpl::{gen_py_const, PyClassMethodsType};
|
||||||
use crate::pymethod::{
|
use crate::pymethod::{
|
||||||
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
|
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
|
||||||
SlotDef, __GETITEM__, __INT__, __LEN__, __REPR__, __RICHCMP__,
|
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__,
|
||||||
};
|
};
|
||||||
use crate::utils::Ctx;
|
use crate::utils::Ctx;
|
||||||
use crate::utils::{self, apply_renaming_rule, PythonDoc};
|
use crate::utils::{self, apply_renaming_rule, PythonDoc};
|
||||||
|
@ -21,6 +21,7 @@ use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use quote::{format_ident, quote, quote_spanned};
|
use quote::{format_ident, quote, quote_spanned};
|
||||||
use syn::ext::IdentExt;
|
use syn::ext::IdentExt;
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::parse_quote_spanned;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::{parse_quote, spanned::Spanned, Result, Token};
|
use syn::{parse_quote, spanned::Spanned, Result, Token};
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ pub struct PyClassPyO3Options {
|
||||||
pub get_all: Option<kw::get_all>,
|
pub get_all: Option<kw::get_all>,
|
||||||
pub freelist: Option<FreelistAttribute>,
|
pub freelist: Option<FreelistAttribute>,
|
||||||
pub frozen: Option<kw::frozen>,
|
pub frozen: Option<kw::frozen>,
|
||||||
|
pub hash: Option<kw::hash>,
|
||||||
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>,
|
||||||
|
@ -85,6 +87,7 @@ enum PyClassPyO3Option {
|
||||||
Freelist(FreelistAttribute),
|
Freelist(FreelistAttribute),
|
||||||
Frozen(kw::frozen),
|
Frozen(kw::frozen),
|
||||||
GetAll(kw::get_all),
|
GetAll(kw::get_all),
|
||||||
|
Hash(kw::hash),
|
||||||
Mapping(kw::mapping),
|
Mapping(kw::mapping),
|
||||||
Module(ModuleAttribute),
|
Module(ModuleAttribute),
|
||||||
Name(NameAttribute),
|
Name(NameAttribute),
|
||||||
|
@ -115,6 +118,8 @@ impl Parse for PyClassPyO3Option {
|
||||||
input.parse().map(PyClassPyO3Option::Frozen)
|
input.parse().map(PyClassPyO3Option::Frozen)
|
||||||
} else if lookahead.peek(attributes::kw::get_all) {
|
} else if lookahead.peek(attributes::kw::get_all) {
|
||||||
input.parse().map(PyClassPyO3Option::GetAll)
|
input.parse().map(PyClassPyO3Option::GetAll)
|
||||||
|
} else if lookahead.peek(attributes::kw::hash) {
|
||||||
|
input.parse().map(PyClassPyO3Option::Hash)
|
||||||
} else if lookahead.peek(attributes::kw::mapping) {
|
} else if lookahead.peek(attributes::kw::mapping) {
|
||||||
input.parse().map(PyClassPyO3Option::Mapping)
|
input.parse().map(PyClassPyO3Option::Mapping)
|
||||||
} else if lookahead.peek(attributes::kw::module) {
|
} else if lookahead.peek(attributes::kw::module) {
|
||||||
|
@ -180,6 +185,7 @@ impl PyClassPyO3Options {
|
||||||
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
|
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
|
||||||
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
|
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
|
||||||
PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
|
PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
|
||||||
|
PyClassPyO3Option::Hash(hash) => set_option!(hash),
|
||||||
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),
|
||||||
|
@ -363,8 +369,12 @@ fn impl_class(
|
||||||
let (default_richcmp, default_richcmp_slot) =
|
let (default_richcmp, default_richcmp_slot) =
|
||||||
pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?;
|
pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?;
|
||||||
|
|
||||||
|
let (default_hash, default_hash_slot) =
|
||||||
|
pyclass_hash(&args.options, &syn::parse_quote!(#cls), ctx)?;
|
||||||
|
|
||||||
let mut slots = Vec::new();
|
let mut slots = Vec::new();
|
||||||
slots.extend(default_richcmp_slot);
|
slots.extend(default_richcmp_slot);
|
||||||
|
slots.extend(default_hash_slot);
|
||||||
|
|
||||||
let py_class_impl = PyClassImplsBuilder::new(
|
let py_class_impl = PyClassImplsBuilder::new(
|
||||||
cls,
|
cls,
|
||||||
|
@ -393,6 +403,7 @@ fn impl_class(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl #cls {
|
impl #cls {
|
||||||
#default_richcmp
|
#default_richcmp
|
||||||
|
#default_hash
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -798,9 +809,11 @@ fn impl_simple_enum(
|
||||||
|
|
||||||
let (default_richcmp, default_richcmp_slot) =
|
let (default_richcmp, default_richcmp_slot) =
|
||||||
pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx);
|
pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx);
|
||||||
|
let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?;
|
||||||
|
|
||||||
let mut default_slots = vec![default_repr_slot, default_int_slot];
|
let mut default_slots = vec![default_repr_slot, default_int_slot];
|
||||||
default_slots.extend(default_richcmp_slot);
|
default_slots.extend(default_richcmp_slot);
|
||||||
|
default_slots.extend(default_hash_slot);
|
||||||
|
|
||||||
let pyclass_impls = PyClassImplsBuilder::new(
|
let pyclass_impls = PyClassImplsBuilder::new(
|
||||||
cls,
|
cls,
|
||||||
|
@ -827,6 +840,7 @@ fn impl_simple_enum(
|
||||||
#default_repr
|
#default_repr
|
||||||
#default_int
|
#default_int
|
||||||
#default_richcmp
|
#default_richcmp
|
||||||
|
#default_hash
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -858,9 +872,11 @@ fn impl_complex_enum(
|
||||||
let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx);
|
let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx);
|
||||||
|
|
||||||
let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?;
|
let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?;
|
||||||
|
let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?;
|
||||||
|
|
||||||
let mut default_slots = vec![];
|
let mut default_slots = vec![];
|
||||||
default_slots.extend(default_richcmp_slot);
|
default_slots.extend(default_richcmp_slot);
|
||||||
|
default_slots.extend(default_hash_slot);
|
||||||
|
|
||||||
let impl_builder = PyClassImplsBuilder::new(
|
let impl_builder = PyClassImplsBuilder::new(
|
||||||
cls,
|
cls,
|
||||||
|
@ -967,6 +983,7 @@ fn impl_complex_enum(
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl #cls {
|
impl #cls {
|
||||||
#default_richcmp
|
#default_richcmp
|
||||||
|
#default_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
#(#variant_cls_zsts)*
|
#(#variant_cls_zsts)*
|
||||||
|
@ -1783,6 +1800,35 @@ fn pyclass_richcmp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pyclass_hash(
|
||||||
|
options: &PyClassPyO3Options,
|
||||||
|
cls: &syn::Type,
|
||||||
|
ctx: &Ctx,
|
||||||
|
) -> Result<(Option<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
|
||||||
|
if options.hash.is_some() {
|
||||||
|
ensure_spanned!(
|
||||||
|
options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option.";
|
||||||
|
options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option.";
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// FIXME: Use hash.map(...).unzip() on MSRV >= 1.66
|
||||||
|
match options.hash {
|
||||||
|
Some(opt) => {
|
||||||
|
let mut hash_impl = parse_quote_spanned! { opt.span() =>
|
||||||
|
fn __pyo3__generated____hash__(&self) -> u64 {
|
||||||
|
let mut s = ::std::collections::hash_map::DefaultHasher::new();
|
||||||
|
::std::hash::Hash::hash(self, &mut s);
|
||||||
|
::std::hash::Hasher::finish(&s)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash_slot =
|
||||||
|
generate_protocol_slot(cls, &mut hash_impl, &__HASH__, "__hash__", ctx).unwrap();
|
||||||
|
Ok((Some(hash_impl), Some(hash_slot)))
|
||||||
|
}
|
||||||
|
None => Ok((None, None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Implements most traits used by `#[pyclass]`.
|
/// Implements most traits used by `#[pyclass]`.
|
||||||
///
|
///
|
||||||
/// Specifically, it implements traits that only depend on class name,
|
/// Specifically, it implements traits that only depend on class name,
|
||||||
|
|
|
@ -910,7 +910,7 @@ impl PropertyType<'_> {
|
||||||
|
|
||||||
const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
|
const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc");
|
||||||
pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
|
pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc");
|
||||||
const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
|
pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc")
|
||||||
.ret_ty(Ty::PyHashT)
|
.ret_ty(Ty::PyHashT)
|
||||||
.return_conversion(TokenGenerator(
|
.return_conversion(TokenGenerator(
|
||||||
|Ctx { pyo3_path }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
|
|Ctx { pyo3_path }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput },
|
||||||
|
|
|
@ -25,7 +25,20 @@ macro_rules! ensure_spanned {
|
||||||
if !($condition) {
|
if !($condition) {
|
||||||
bail_spanned!($span => $msg);
|
bail_spanned!($span => $msg);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
($($condition:expr, $span:expr => $msg:expr;)*) => {
|
||||||
|
if let Some(e) = [$(
|
||||||
|
(!($condition)).then(|| err_spanned!($span => $msg)),
|
||||||
|
)*]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.reduce(|mut acc, e| {
|
||||||
|
acc.combine(e);
|
||||||
|
acc
|
||||||
|
}) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the given type `ty` is `pyo3::Python`.
|
/// Check if the given type `ty` is `pyo3::Python`.
|
||||||
|
|
|
@ -200,6 +200,34 @@ fn class_with_object_field() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, hash)]
|
||||||
|
#[derive(PartialEq, Hash)]
|
||||||
|
struct ClassWithHash {
|
||||||
|
value: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn class_with_hash() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
use pyo3::types::IntoPyDict;
|
||||||
|
let class = ClassWithHash { value: 42 };
|
||||||
|
let hash = {
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
class.hash(&mut hasher);
|
||||||
|
hasher.finish() as isize
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = [
|
||||||
|
("obj", Py::new(py, class).unwrap().into_any()),
|
||||||
|
("hsh", hash.into_py(py)),
|
||||||
|
]
|
||||||
|
.into_py_dict_bound(py);
|
||||||
|
|
||||||
|
py_assert!(py, *env, "hash(obj) == hsh");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass(unsendable, subclass)]
|
#[pyclass(unsendable, subclass)]
|
||||||
struct UnsendableBase {
|
struct UnsendableBase {
|
||||||
value: std::rc::Rc<usize>,
|
value: std::rc::Rc<usize>,
|
||||||
|
|
|
@ -220,3 +220,63 @@ fn test_renaming_all_enum_variants() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, eq_int, hash)]
|
||||||
|
#[derive(PartialEq, Hash)]
|
||||||
|
enum SimpleEnumWithHash {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_enum_with_hash() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
use pyo3::types::IntoPyDict;
|
||||||
|
let class = SimpleEnumWithHash::A;
|
||||||
|
let hash = {
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
class.hash(&mut hasher);
|
||||||
|
hasher.finish() as isize
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = [
|
||||||
|
("obj", Py::new(py, class).unwrap().into_any()),
|
||||||
|
("hsh", hash.into_py(py)),
|
||||||
|
]
|
||||||
|
.into_py_dict_bound(py);
|
||||||
|
|
||||||
|
py_assert!(py, *env, "hash(obj) == hsh");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(eq, hash)]
|
||||||
|
#[derive(PartialEq, Hash)]
|
||||||
|
enum ComplexEnumWithHash {
|
||||||
|
A(u32),
|
||||||
|
B { msg: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complex_enum_with_hash() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
use pyo3::types::IntoPyDict;
|
||||||
|
let class = ComplexEnumWithHash::B {
|
||||||
|
msg: String::from("Hello"),
|
||||||
|
};
|
||||||
|
let hash = {
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
class.hash(&mut hasher);
|
||||||
|
hasher.finish() as isize
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = [
|
||||||
|
("obj", Py::new(py, class).unwrap().into_any()),
|
||||||
|
("hsh", hash.into_py(py)),
|
||||||
|
]
|
||||||
|
.into_py_dict_bound(py);
|
||||||
|
|
||||||
|
py_assert!(py, *env, "hash(obj) == hsh");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -52,4 +52,23 @@ impl EqOptAndManualRichCmp {
|
||||||
#[pyclass(eq_int)]
|
#[pyclass(eq_int)]
|
||||||
struct NoEqInt {}
|
struct NoEqInt {}
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, hash)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct HashOptRequiresHash;
|
||||||
|
|
||||||
|
#[pyclass(hash)]
|
||||||
|
#[derive(Hash)]
|
||||||
|
struct HashWithoutFrozenAndEq;
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, hash)]
|
||||||
|
#[derive(PartialEq, Hash)]
|
||||||
|
struct HashOptAndManualHash {}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl HashOptAndManualHash {
|
||||||
|
fn __hash__(&self) -> u64 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
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`
|
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`
|
||||||
--> 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`, `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`, `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)]
|
||||||
|
@ -64,6 +64,18 @@ error: `eq_int` can only be used on simple enums.
|
||||||
52 | #[pyclass(eq_int)]
|
52 | #[pyclass(eq_int)]
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
||||||
|
error: The `hash` option requires the `frozen` option.
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:59:11
|
||||||
|
|
|
||||||
|
59 | #[pyclass(hash)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: The `hash` option requires the `eq` option.
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:59:11
|
||||||
|
|
|
||||||
|
59 | #[pyclass(hash)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
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
|
||||||
|
|
|
|
||||||
|
@ -75,6 +87,17 @@ error[E0592]: duplicate definitions with name `__pymethod___richcmp____`
|
||||||
|
|
|
|
||||||
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0592]: duplicate definitions with name `__pymethod___hash____`
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:63:1
|
||||||
|
|
|
||||||
|
63 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `__pymethod___hash____`
|
||||||
|
...
|
||||||
|
67 | #[pymethods]
|
||||||
|
| ------------ other definition for `__pymethod___hash____`
|
||||||
|
|
|
||||||
|
= 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`
|
error[E0369]: binary operation `==` cannot be applied to type `&EqOptRequiresEq`
|
||||||
--> tests/ui/invalid_pyclass_args.rs:33:11
|
--> tests/ui/invalid_pyclass_args.rs:33:11
|
||||||
|
|
|
|
||||||
|
@ -144,3 +167,51 @@ note: candidate #2 is defined in an impl for the type `EqOptAndManualRichCmp`
|
||||||
40 | #[pymethods]
|
40 | #[pymethods]
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `HashOptRequiresHash: Hash` is not satisfied
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:55:23
|
||||||
|
|
|
||||||
|
55 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^ the trait `Hash` is not implemented for `HashOptRequiresHash`
|
||||||
|
|
|
||||||
|
help: consider annotating `HashOptRequiresHash` with `#[derive(Hash)]`
|
||||||
|
|
|
||||||
|
57 + #[derive(Hash)]
|
||||||
|
58 | struct HashOptRequiresHash;
|
||||||
|
|
|
||||||
|
|
||||||
|
error[E0034]: multiple applicable items in scope
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:63:1
|
||||||
|
|
|
||||||
|
63 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ multiple `__pymethod___hash____` found
|
||||||
|
|
|
||||||
|
note: candidate #1 is defined in an impl for the type `HashOptAndManualHash`
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:63:1
|
||||||
|
|
|
||||||
|
63 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
note: candidate #2 is defined in an impl for the type `HashOptAndManualHash`
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:67:1
|
||||||
|
|
|
||||||
|
67 | #[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:67:1
|
||||||
|
|
|
||||||
|
67 | #[pymethods]
|
||||||
|
| ^^^^^^^^^^^^ multiple `__pymethod___hash____` found
|
||||||
|
|
|
||||||
|
note: candidate #1 is defined in an impl for the type `HashOptAndManualHash`
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:63:1
|
||||||
|
|
|
||||||
|
63 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
note: candidate #2 is defined in an impl for the type `HashOptAndManualHash`
|
||||||
|
--> tests/ui/invalid_pyclass_args.rs:67:1
|
||||||
|
|
|
||||||
|
67 | #[pymethods]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
|
@ -46,4 +46,32 @@ enum NoEqInt {
|
||||||
B { msg: String },
|
B { msg: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, eq_int, hash)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum SimpleHashOptRequiresHash {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(frozen, eq, hash)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ComplexHashOptRequiresHash {
|
||||||
|
A(i32),
|
||||||
|
B { msg: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(hash)]
|
||||||
|
#[derive(Hash)]
|
||||||
|
enum SimpleHashOptRequiresFrozenAndEq {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(hash)]
|
||||||
|
#[derive(Hash)]
|
||||||
|
enum ComplexHashOptRequiresEq {
|
||||||
|
A(i32),
|
||||||
|
B { msg: String },
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -36,6 +36,24 @@ error: `eq_int` can only be used on simple enums.
|
||||||
43 | #[pyclass(eq_int)]
|
43 | #[pyclass(eq_int)]
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
||||||
|
error: The `hash` option requires the `frozen` option.
|
||||||
|
--> tests/ui/invalid_pyclass_enum.rs:63:11
|
||||||
|
|
|
||||||
|
63 | #[pyclass(hash)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: The `hash` option requires the `eq` option.
|
||||||
|
--> tests/ui/invalid_pyclass_enum.rs:63:11
|
||||||
|
|
|
||||||
|
63 | #[pyclass(hash)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: The `hash` option requires the `eq` option.
|
||||||
|
--> tests/ui/invalid_pyclass_enum.rs:70:11
|
||||||
|
|
|
||||||
|
70 | #[pyclass(hash)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
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
|
||||||
|
|
|
|
||||||
|
@ -103,3 +121,27 @@ help: consider annotating `ComplexEqOptRequiresPartialEq` with `#[derive(Partial
|
||||||
38 + #[derive(PartialEq)]
|
38 + #[derive(PartialEq)]
|
||||||
39 | enum ComplexEqOptRequiresPartialEq {
|
39 | enum ComplexEqOptRequiresPartialEq {
|
||||||
|
|
|
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `SimpleHashOptRequiresHash: Hash` is not satisfied
|
||||||
|
--> tests/ui/invalid_pyclass_enum.rs:49:31
|
||||||
|
|
|
||||||
|
49 | #[pyclass(frozen, eq, eq_int, hash)]
|
||||||
|
| ^^^^ the trait `Hash` is not implemented for `SimpleHashOptRequiresHash`
|
||||||
|
|
|
||||||
|
help: consider annotating `SimpleHashOptRequiresHash` with `#[derive(Hash)]`
|
||||||
|
|
|
||||||
|
51 + #[derive(Hash)]
|
||||||
|
52 | enum SimpleHashOptRequiresHash {
|
||||||
|
|
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `ComplexHashOptRequiresHash: Hash` is not satisfied
|
||||||
|
--> tests/ui/invalid_pyclass_enum.rs:56:23
|
||||||
|
|
|
||||||
|
56 | #[pyclass(frozen, eq, hash)]
|
||||||
|
| ^^^^ the trait `Hash` is not implemented for `ComplexHashOptRequiresHash`
|
||||||
|
|
|
||||||
|
help: consider annotating `ComplexHashOptRequiresHash` with `#[derive(Hash)]`
|
||||||
|
|
|
||||||
|
58 + #[derive(Hash)]
|
||||||
|
59 | enum ComplexHashOptRequiresHash {
|
||||||
|
|
|
||||||
|
|
Loading…
Reference in New Issue