Skip to content

Update to pass test for unhashable collections #4640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 7, 2023
2 changes: 0 additions & 2 deletions Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,6 @@ def __await__(self):
self.assertFalse(isinstance(CoroLike(), Coroutine))
self.assertFalse(issubclass(CoroLike, Coroutine))

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_Hashable(self):
# Check some non-hashables
non_samples = [bytearray(), list(), set(), dict()]
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3516,8 +3516,6 @@ def test_top_level_class_var(self):

class CollectionsAbcTests(BaseTestCase):

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_hashable(self):
self.assertIsInstance(42, typing.Hashable)
self.assertNotIsInstance([], typing.Hashable)
Expand Down
10 changes: 10 additions & 0 deletions derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ fn generate_class_def(
module_name: Option<&str>,
base: Option<String>,
metaclass: Option<String>,
unhashable: bool,
attrs: &[Attribute],
) -> Result<TokenStream> {
let doc = attrs.doc().or_else(|| {
Expand All @@ -242,6 +243,11 @@ fn generate_class_def(
Some(v) => quote!(Some(#v) ),
None => quote!(None),
};
let unhashable = if unhashable {
quote!(true)
} else {
quote!(false)
};
let basicsize = quote!(std::mem::size_of::<#ident>());
let is_pystruct = attrs.iter().any(|attr| {
attr.path.is_ident("derive")
Expand Down Expand Up @@ -290,6 +296,7 @@ fn generate_class_def(
const TP_NAME: &'static str = #module_class_name;
const DOC: Option<&'static str> = #doc;
const BASICSIZE: usize = #basicsize;
const UNHASHABLE: bool = #unhashable;
}

impl ::rustpython_vm::class::StaticType for #ident {
Expand Down Expand Up @@ -319,12 +326,15 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result<TokenStrea
let module_name = class_meta.module()?;
let base = class_meta.base()?;
let metaclass = class_meta.metaclass()?;
let unhashable = class_meta.unhashable()?;

let class_def = generate_class_def(
ident,
&class_name,
module_name.as_deref(),
base,
metaclass,
unhashable,
attrs,
)?;

Expand Down
7 changes: 6 additions & 1 deletion derive-impl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ impl ItemMeta for AttrItemMeta {
pub(crate) struct ClassItemMeta(ItemMetaInner);

impl ItemMeta for ClassItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["module", "name", "base", "metaclass"];
const ALLOWED_NAMES: &'static [&'static str] =
&["module", "name", "base", "metaclass", "unhashable"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand Down Expand Up @@ -299,6 +300,10 @@ impl ClassItemMeta {
self.inner()._optional_str("base")
}

pub fn unhashable(&self) -> Result<bool> {
self.inner()._bool("unhashable")
}

pub fn metaclass(&self) -> Result<Option<String>> {
self.inner()._optional_str("metaclass")
}
Expand Down
10 changes: 3 additions & 7 deletions vm/src/builtins/bytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ use crate::{
},
sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp},
types::{
AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable,
Initializer, IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
Unhashable,
AsBuffer, AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Initializer,
IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
},
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
VirtualMachine,
Expand All @@ -41,7 +40,7 @@ use bstr::ByteSlice;
use once_cell::sync::Lazy;
use std::mem::size_of;

#[pyclass(module = false, name = "bytearray")]
#[pyclass(module = false, name = "bytearray", unhashable = true)]
#[derive(Debug, Default)]
pub struct PyByteArray {
inner: PyRwLock<PyBytesInner>,
Expand Down Expand Up @@ -100,7 +99,6 @@ pub(crate) fn init(context: &Context) {
with(
Constructor,
Initializer,
Hashable,
Comparable,
AsBuffer,
AsMapping,
Expand Down Expand Up @@ -873,8 +871,6 @@ impl AsNumber for PyByteArray {
}
}

impl Unhashable for PyByteArray {}

impl Iterable for PyByteArray {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyByteArrayIterator {
Expand Down
9 changes: 3 additions & 6 deletions vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::{
protocol::{PyIterIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
recursion::ReprGuard,
types::{
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Hashable, Initializer,
IterNext, IterNextIterable, Iterable, PyComparisonOp, Unconstructible, Unhashable,
AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, Initializer, IterNext,
IterNextIterable, Iterable, PyComparisonOp, Unconstructible,
},
vm::VirtualMachine,
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
Expand All @@ -32,7 +32,7 @@ use std::fmt;

pub type DictContentType = dictdatatype::Dict;

#[pyclass(module = false, name = "dict")]
#[pyclass(module = false, name = "dict", unhashable = true)]
#[derive(Default)]
pub struct PyDict {
entries: DictContentType,
Expand Down Expand Up @@ -206,7 +206,6 @@ impl PyDict {
Constructor,
Initializer,
AsMapping,
Hashable,
Comparable,
Iterable,
AsSequence,
Expand Down Expand Up @@ -512,8 +511,6 @@ impl Comparable for PyDict {
}
}

impl Unhashable for PyDict {}

impl Iterable for PyDict {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyDictKeyIterator::new(zelf).into_pyobject(vm))
Expand Down
18 changes: 4 additions & 14 deletions vm/src/builtins/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ use crate::{
sequence::{MutObjectSequenceOp, OptionalRangeArgs, SequenceExt, SequenceMutExt},
sliceable::{SequenceIndex, SliceableSequenceMutOp, SliceableSequenceOp},
types::{
AsMapping, AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext,
IterNextIterable, Iterable, PyComparisonOp, Unconstructible, Unhashable,
AsMapping, AsSequence, Comparable, Constructor, Initializer, IterNext, IterNextIterable,
Iterable, PyComparisonOp, Unconstructible,
},
utils::collection_repr,
vm::VirtualMachine,
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
};
use std::{fmt, ops::DerefMut};

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

#[pyclass(
with(
Constructor,
Initializer,
AsMapping,
Iterable,
Hashable,
Comparable,
AsSequence
),
with(Constructor, Initializer, AsMapping, Iterable, Comparable, AsSequence),
flags(BASETYPE)
)]
impl PyList {
Expand Down Expand Up @@ -492,8 +484,6 @@ impl Comparable for PyList {
}
}

impl Unhashable for PyList {}

fn do_sort(
vm: &VirtualMachine,
values: &mut Vec<PyObjectRef>,
Expand Down
18 changes: 4 additions & 14 deletions vm/src/builtins/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::{
types::AsNumber,
types::{
AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext, IterNextIterable,
Iterable, PyComparisonOp, Unconstructible, Unhashable,
Iterable, PyComparisonOp, Unconstructible,
},
utils::collection_repr,
vm::VirtualMachine,
Expand All @@ -28,7 +28,7 @@ use std::{fmt, ops::Deref};

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

#[pyclass(module = false, name = "set")]
#[pyclass(module = false, name = "set", unhashable = true)]
#[derive(Default)]
pub struct PySet {
pub(super) inner: PySetInner,
Expand Down Expand Up @@ -70,7 +70,7 @@ impl PySet {
}
}

#[pyclass(module = false, name = "frozenset")]
#[pyclass(module = false, name = "frozenset", unhashable = true)]
#[derive(Default)]
pub struct PyFrozenSet {
inner: PySetInner,
Expand Down Expand Up @@ -489,15 +489,7 @@ fn reduce_set(
}

#[pyclass(
with(
Constructor,
Initializer,
AsSequence,
Hashable,
Comparable,
Iterable,
AsNumber
),
with(Constructor, Initializer, AsSequence, Comparable, Iterable, AsNumber),
flags(BASETYPE)
)]
impl PySet {
Expand Down Expand Up @@ -805,8 +797,6 @@ impl Comparable for PySet {
}
}

impl Unhashable for PySet {}

impl Iterable for PySet {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(zelf.inner.iter().into_pyobject(vm))
Expand Down
8 changes: 3 additions & 5 deletions vm/src/builtins/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use crate::{
convert::ToPyObject,
function::{FuncArgs, OptionalArg, PyComparisonValue},
sliceable::SaturatedSlice,
types::{Comparable, Constructor, Hashable, PyComparisonOp, Unhashable},
types::{Comparable, Constructor, PyComparisonOp},
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
};
use num_bigint::{BigInt, ToBigInt};
use num_traits::{One, Signed, Zero};

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

#[pyclass(with(Hashable, Comparable))]
#[pyclass(with(Comparable))]
impl PySlice {
#[pygetset]
fn start(&self, vm: &VirtualMachine) -> PyObjectRef {
Expand Down Expand Up @@ -258,8 +258,6 @@ impl Comparable for PySlice {
}
}

impl Unhashable for PySlice {}

#[pyclass(module = false, name = "EllipsisType")]
#[derive(Debug)]
pub struct PyEllipsis;
Expand Down
5 changes: 2 additions & 3 deletions vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,11 +1019,10 @@ impl SetAttr for PyType {
}
let assign = value.is_assign();

let mut attributes = zelf.attributes.write();
if let PySetterValue::Assign(value) = value {
attributes.insert(attr_name, value);
zelf.attributes.write().insert(attr_name, value);
} else {
let prev_value = attributes.remove(attr_name);
let prev_value = zelf.attributes.write().remove(attr_name);
if prev_value.is_none() {
return Err(vm.new_exception(
vm.ctx.exceptions.attribute_error.to_owned(),
Expand Down
13 changes: 12 additions & 1 deletion vm/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
builtins::{PyBaseObject, PyBoundMethod, PyType, PyTypeRef},
identifier,
object::{Py, PyObjectPayload, PyObjectRef, PyRef},
types::{PyTypeFlags, PyTypeSlots},
types::{hash_not_implemented, PyTypeFlags, PyTypeSlots},
vm::Context,
};
use rustpython_common::{lock::PyRwLock, static_cell};
Expand Down Expand Up @@ -60,6 +60,7 @@ pub trait PyClassDef {
const TP_NAME: &'static str;
const DOC: Option<&'static str> = None;
const BASICSIZE: usize;
const UNHASHABLE: bool = false;
}

impl<T> PyClassDef for PyRef<T>
Expand All @@ -71,6 +72,7 @@ where
const TP_NAME: &'static str = T::TP_NAME;
const DOC: Option<&'static str> = T::DOC;
const BASICSIZE: usize = T::BASICSIZE;
const UNHASHABLE: bool = T::UNHASHABLE;
}

pub trait PyClassImpl: PyClassDef {
Expand Down Expand Up @@ -112,6 +114,10 @@ pub trait PyClassImpl: PyClassDef {
.into();
class.set_attr(identifier!(ctx, __new__), bound);
}

if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
}
}

fn make_class(ctx: &Context) -> PyTypeRef
Expand Down Expand Up @@ -140,6 +146,11 @@ pub trait PyClassImpl: PyClassDef {
doc: Self::DOC,
..Default::default()
};

if Self::UNHASHABLE {
slots.hash.store(Some(hash_not_implemented));
}

Self::extend_slots(&mut slots);
slots
}
Expand Down
10 changes: 4 additions & 6 deletions vm/src/stdlib/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ mod _collections {
sequence::{MutObjectSequenceOp, OptionalRangeArgs},
sliceable::SequenceIndexOp,
types::{
AsSequence, Comparable, Constructor, Hashable, Initializer, IterNext, IterNextIterable,
Iterable, PyComparisonOp, Unhashable,
AsSequence, Comparable, Constructor, Initializer, IterNext, IterNextIterable, Iterable,
PyComparisonOp,
},
utils::collection_repr,
AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
Expand All @@ -27,7 +27,7 @@ mod _collections {
use std::collections::VecDeque;

#[pyattr]
#[pyclass(name = "deque")]
#[pyclass(name = "deque", unhashable = true)]
#[derive(Debug, Default, PyPayload)]
struct PyDeque {
deque: PyRwLock<VecDeque<PyObjectRef>>,
Expand Down Expand Up @@ -57,7 +57,7 @@ mod _collections {

#[pyclass(
flags(BASETYPE),
with(Constructor, Initializer, AsSequence, Comparable, Hashable, Iterable)
with(Constructor, Initializer, AsSequence, Comparable, Iterable)
)]
impl PyDeque {
#[pymethod]
Expand Down Expand Up @@ -574,8 +574,6 @@ mod _collections {
}
}

impl Unhashable for PyDeque {}

impl Iterable for PyDeque {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyDequeIterator::new(zelf).into_pyobject(vm))
Expand Down
Loading