Skip to content

Commit 223fe14

Browse files
authored
Update to pass test for unhashable collections (#4640)
1 parent 10eb20e commit 223fe14

File tree

13 files changed

+67
-78
lines changed

13 files changed

+67
-78
lines changed

Lib/test/test_collections.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,8 +892,6 @@ def __await__(self):
892892
self.assertFalse(isinstance(CoroLike(), Coroutine))
893893
self.assertFalse(issubclass(CoroLike, Coroutine))
894894

895-
# TODO: RUSTPYTHON
896-
@unittest.expectedFailure
897895
def test_Hashable(self):
898896
# Check some non-hashables
899897
non_samples = [bytearray(), list(), set(), dict()]

Lib/test/test_typing.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3516,8 +3516,6 @@ def test_top_level_class_var(self):
35163516

35173517
class CollectionsAbcTests(BaseTestCase):
35183518

3519-
# TODO: RUSTPYTHON
3520-
@unittest.expectedFailure
35213519
def test_hashable(self):
35223520
self.assertIsInstance(42, typing.Hashable)
35233521
self.assertNotIsInstance([], typing.Hashable)

derive-impl/src/pyclass.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ fn generate_class_def(
218218
module_name: Option<&str>,
219219
base: Option<String>,
220220
metaclass: Option<String>,
221+
unhashable: bool,
221222
attrs: &[Attribute],
222223
) -> Result<TokenStream> {
223224
let doc = attrs.doc().or_else(|| {
@@ -242,6 +243,11 @@ fn generate_class_def(
242243
Some(v) => quote!(Some(#v) ),
243244
None => quote!(None),
244245
};
246+
let unhashable = if unhashable {
247+
quote!(true)
248+
} else {
249+
quote!(false)
250+
};
245251
let basicsize = quote!(std::mem::size_of::<#ident>());
246252
let is_pystruct = attrs.iter().any(|attr| {
247253
attr.path.is_ident("derive")
@@ -290,6 +296,7 @@ fn generate_class_def(
290296
const TP_NAME: &'static str = #module_class_name;
291297
const DOC: Option<&'static str> = #doc;
292298
const BASICSIZE: usize = #basicsize;
299+
const UNHASHABLE: bool = #unhashable;
293300
}
294301

295302
impl ::rustpython_vm::class::StaticType for #ident {
@@ -319,12 +326,15 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
319326
let module_name = class_meta.module()?;
320327
let base = class_meta.base()?;
321328
let metaclass = class_meta.metaclass()?;
329+
let unhashable = class_meta.unhashable()?;
330+
322331
let class_def = generate_class_def(
323332
ident,
324333
&class_name,
325334
module_name.as_deref(),
326335
base,
327336
metaclass,
337+
unhashable,
328338
attrs,
329339
)?;
330340

derive-impl/src/util.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ impl ItemMeta for AttrItemMeta {
263263
pub(crate) struct ClassItemMeta(ItemMetaInner);
264264

265265
impl ItemMeta for ClassItemMeta {
266-
const ALLOWED_NAMES: &'static [&'static str] = &["module", "name", "base", "metaclass"];
266+
const ALLOWED_NAMES: &'static [&'static str] =
267+
&["module", "name", "base", "metaclass", "unhashable"];
267268

268269
fn from_inner(inner: ItemMetaInner) -> Self {
269270
Self(inner)
@@ -299,6 +300,10 @@ impl ClassItemMeta {
299300
self.inner()._optional_str("base")
300301
}
301302

303+
pub fn unhashable(&self) -> Result<bool> {
304+
self.inner()._bool("unhashable")
305+
}
306+
302307
pub fn metaclass(&self) -> Result<Option<String>> {
303308
self.inner()._optional_str("metaclass")
304309
}

vm/src/builtins/bytearray.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ use crate::{
3030
},
3131
sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp},
3232
types::{
33-
AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable,
34-
Initializer, IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
35-
Unhashable,
33+
AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Initializer,
34+
IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
3635
},
3736
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
3837
VirtualMachine,
@@ -41,7 +40,7 @@ use bstr::ByteSlice;
4140
use once_cell::sync::Lazy;
4241
use std::mem::size_of;
4342

44-
#[pyclass(module = false, name = "bytearray")]
43+
#[pyclass(module = false, name = "bytearray", unhashable = true)]
4544
#[derive(Debug, Default)]
4645
pub struct PyByteArray {
4746
inner: PyRwLock<PyBytesInner>,
@@ -100,7 +99,6 @@ pub(crate) fn init(context: &Context) {
10099
with(
101100
Constructor,
102101
Initializer,
103-
Hashable,
104102
Comparable,
105103
AsBuffer,
106104
AsMapping,
@@ -873,8 +871,6 @@ impl AsNumber for PyByteArray {
873871
}
874872
}
875873

876-
impl Unhashable for PyByteArray {}
877-
878874
impl Iterable for PyByteArray {
879875
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
880876
Ok(PyByteArrayIterator {

vm/src/builtins/dict.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use crate::{
1919
protocol::{PyIterIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
2020
recursion::ReprGuard,
2121
types::{
22-
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, Initializer,
23-
IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible, Unhashable,
22+
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Initializer, IterNext,
23+
IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
2424
},
2525
vm::VirtualMachine,
2626
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
@@ -32,7 +32,7 @@ use std::fmt;
3232

3333
pub type DictContentType = dictdatatype::Dict;
3434

35-
#[pyclass(module = false, name = "dict")]
35+
#[pyclass(module = false, name = "dict", unhashable = true)]
3636
#[derive(Default)]
3737
pub struct PyDict {
3838
entries: DictContentType,
@@ -206,7 +206,6 @@ impl PyDict {
206206
Constructor,
207207
Initializer,
208208
AsMapping,
209-
Hashable,
210209
Comparable,
211210
Iterable,
212211
AsSequence,
@@ -512,8 +511,6 @@ impl Comparable for PyDict {
512511
}
513512
}
514513

515-
impl Unhashable for PyDict {}
516-
517514
impl Iterable for PyDict {
518515
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
519516
Ok(PyDictKeyIterator::new(zelf).into_pyobject(vm))

vm/src/builtins/list.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ use crate::{
1313
sequence::{MutObjectSequenceOp, OptionalRangeArgs, SequenceExt, SequenceMutExt},
1414
sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp},
1515
types::{
16-
AsMapping, AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext,
17-
IterNextIterable, Iterable, PyComparisonOp, Unconstructible, Unhashable,
16+
AsMapping, AsSequence, Comparable, Constructor, Initializer, IterNext, IterNextIterable,
17+
Iterable, PyComparisonOp, Unconstructible,
1818
},
1919
utils::collection_repr,
2020
vm::VirtualMachine,
2121
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
2222
};
2323
use std::{fmt, ops::DerefMut};
2424

25-
#[pyclass(module = false, name = "list")]
25+
#[pyclass(module = false, name = "list", unhashable = true)]
2626
#[derive(Default)]
2727
pub struct PyList {
2828
elements: PyRwLock<Vec<PyObjectRef>>,
@@ -86,15 +86,7 @@ pub(crate) struct SortOptions {
8686
pub type PyListRef = PyRef<PyList>;
8787

8888
#[pyclass(
89-
with(
90-
Constructor,
91-
Initializer,
92-
AsMapping,
93-
Iterable,
94-
Hashable,
95-
Comparable,
96-
AsSequence
97-
),
89+
with(Constructor, Initializer, AsMapping, Iterable, Comparable, AsSequence),
9890
flags(BASETYPE)
9991
)]
10092
impl PyList {
@@ -492,8 +484,6 @@ impl Comparable for PyList {
492484
}
493485
}
494486

495-
impl Unhashable for PyList {}
496-
497487
fn do_sort(
498488
vm: &VirtualMachine,
499489
values: &mut Vec<PyObjectRef>,

vm/src/builtins/set.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
types::AsNumber,
1818
types::{
1919
AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext, IterNextIterable,
20-
Iterable, PyComparisonOp, Unconstructible, Unhashable,
20+
Iterable, PyComparisonOp, Unconstructible,
2121
},
2222
utils::collection_repr,
2323
vm::VirtualMachine,
@@ -28,7 +28,7 @@ use std::{fmt, ops::Deref};
2828

2929
pub type SetContentType = dictdatatype::Dict<()>;
3030

31-
#[pyclass(module = false, name = "set")]
31+
#[pyclass(module = false, name = "set", unhashable = true)]
3232
#[derive(Default)]
3333
pub struct PySet {
3434
pub(super) inner: PySetInner,
@@ -70,7 +70,7 @@ impl PySet {
7070
}
7171
}
7272

73-
#[pyclass(module = false, name = "frozenset")]
73+
#[pyclass(module = false, name = "frozenset", unhashable = true)]
7474
#[derive(Default)]
7575
pub struct PyFrozenSet {
7676
inner: PySetInner,
@@ -489,15 +489,7 @@ fn reduce_set(
489489
}
490490

491491
#[pyclass(
492-
with(
493-
Constructor,
494-
Initializer,
495-
AsSequence,
496-
Hashable,
497-
Comparable,
498-
Iterable,
499-
AsNumber
500-
),
492+
with(Constructor, Initializer, AsSequence, Comparable, Iterable, AsNumber),
501493
flags(BASETYPE)
502494
)]
503495
impl PySet {
@@ -805,8 +797,6 @@ impl Comparable for PySet {
805797
}
806798
}
807799

808-
impl Unhashable for PySet {}
809-
810800
impl Iterable for PySet {
811801
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
812802
Ok(zelf.inner.iter().into_pyobject(vm))

vm/src/builtins/slice.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use crate::{
55
convert::ToPyObject,
66
function::{FuncArgs, OptionalArg, PyComparisonValue},
77
sliceable::SaturatedSlice,
8-
types::{Comparable, Constructor, Hashable, PyComparisonOp, Unhashable},
8+
types::{Comparable, Constructor, PyComparisonOp},
99
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
1010
};
1111
use num_bigint::{BigInt, ToBigInt};
1212
use num_traits::{One, Signed, Zero};
1313

14-
#[pyclass(module = false, name = "slice")]
14+
#[pyclass(module = false, name = "slice", unhashable = true)]
1515
#[derive(Debug)]
1616
pub struct PySlice {
1717
pub start: Option<PyObjectRef>,
@@ -25,7 +25,7 @@ impl PyPayload for PySlice {
2525
}
2626
}
2727

28-
#[pyclass(with(Hashable, Comparable))]
28+
#[pyclass(with(Comparable))]
2929
impl PySlice {
3030
#[pygetset]
3131
fn start(&self, vm: &VirtualMachine) -> PyObjectRef {
@@ -258,8 +258,6 @@ impl Comparable for PySlice {
258258
}
259259
}
260260

261-
impl Unhashable for PySlice {}
262-
263261
#[pyclass(module = false, name = "EllipsisType")]
264262
#[derive(Debug)]
265263
pub struct PyEllipsis;

vm/src/builtins/type.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,11 +1019,10 @@ impl SetAttr for PyType {
10191019
}
10201020
let assign = value.is_assign();
10211021

1022-
let mut attributes = zelf.attributes.write();
10231022
if let PySetterValue::Assign(value) = value {
1024-
attributes.insert(attr_name, value);
1023+
zelf.attributes.write().insert(attr_name, value);
10251024
} else {
1026-
let prev_value = attributes.remove(attr_name);
1025+
let prev_value = zelf.attributes.write().remove(attr_name);
10271026
if prev_value.is_none() {
10281027
return Err(vm.new_exception(
10291028
vm.ctx.exceptions.attribute_error.to_owned(),

vm/src/class.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
builtins::{PyBaseObject, PyBoundMethod, PyType, PyTypeRef},
55
identifier,
66
object::{Py, PyObjectPayload, PyObjectRef, PyRef},
7-
types::{PyTypeFlags, PyTypeSlots},
7+
types::{hash_not_implemented, PyTypeFlags, PyTypeSlots},
88
vm::Context,
99
};
1010
use rustpython_common::{lock::PyRwLock, static_cell};
@@ -60,6 +60,7 @@ pub trait PyClassDef {
6060
const TP_NAME: &'static str;
6161
const DOC: Option<&'static str> = None;
6262
const BASICSIZE: usize;
63+
const UNHASHABLE: bool = false;
6364
}
6465

6566
impl<T> PyClassDef for PyRef<T>
@@ -71,6 +72,7 @@ where
7172
const TP_NAME: &'static str = T::TP_NAME;
7273
const DOC: Option<&'static str> = T::DOC;
7374
const BASICSIZE: usize = T::BASICSIZE;
75+
const UNHASHABLE: bool = T::UNHASHABLE;
7476
}
7577

7678
pub trait PyClassImpl: PyClassDef {
@@ -112,6 +114,10 @@ pub trait PyClassImpl: PyClassDef {
112114
.into();
113115
class.set_attr(identifier!(ctx, __new__), bound);
114116
}
117+
118+
if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
119+
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
120+
}
115121
}
116122

117123
fn make_class(ctx: &Context) -> PyTypeRef
@@ -140,6 +146,11 @@ pub trait PyClassImpl: PyClassDef {
140146
doc: Self::DOC,
141147
..Default::default()
142148
};
149+
150+
if Self::UNHASHABLE {
151+
slots.hash.store(Some(hash_not_implemented));
152+
}
153+
143154
Self::extend_slots(&mut slots);
144155
slots
145156
}

vm/src/stdlib/collections.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ mod _collections {
1616
sequence::{MutObjectSequenceOp, OptionalRangeArgs},
1717
sliceable::SequenceIndexOp,
1818
types::{
19-
AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext, IterNextIterable,
20-
Iterable, PyComparisonOp, Unhashable,
19+
AsSequence, Comparable, Constructor, Initializer, IterNext, IterNextIterable, Iterable,
20+
PyComparisonOp,
2121
},
2222
utils::collection_repr,
2323
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
@@ -27,7 +27,7 @@ mod _collections {
2727
use std::collections::VecDeque;
2828

2929
#[pyattr]
30-
#[pyclass(name = "deque")]
30+
#[pyclass(name = "deque", unhashable = true)]
3131
#[derive(Debug, Default, PyPayload)]
3232
struct PyDeque {
3333
deque: PyRwLock<VecDeque<PyObjectRef>>,
@@ -57,7 +57,7 @@ mod _collections {
5757

5858
#[pyclass(
5959
flags(BASETYPE),
60-
with(Constructor, Initializer, AsSequence, Comparable, Hashable, Iterable)
60+
with(Constructor, Initializer, AsSequence, Comparable, Iterable)
6161
)]
6262
impl PyDeque {
6363
#[pymethod]
@@ -574,8 +574,6 @@ mod _collections {
574574
}
575575
}
576576

577-
impl Unhashable for PyDeque {}
578-
579577
impl Iterable for PyDeque {
580578
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
581579
Ok(PyDequeIterator::new(zelf).into_pyobject(vm))

0 commit comments

Comments
 (0)