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

Conversation

seungha-kim
Copy link
Contributor

In CPython:

  • If a type is marked as unhashable on the native side (i.e. PyObject_HashNotImplemented is assigned to hash slot), then __hash__ attribute of the type dict is set to None so that the type is considered as unhashable on Python side too.
  • If a type is created at runtime and "__hash__": None is given as the type dict (like type('C', (object,), {'__hash__': None})), then the hash slot is filled with PyObject_HashNotImplemented.

In RustPython, Unhashable trait exists to mark a type as unhashble. But there is no way to check if a type implements Unhashable trait at runtime, so it is hard to follow the CPython's mechanism.

So I've added unhashable_wrapper function similar to PyObject_HashNotImplemented, and used that as a marker for unhashable on the native side.


Thanks for the advice from @youknowone !

Comment on lines 605 to 610
context
.types
.list_type
.slots
.hash
.store(Some(unhashable_wrapper));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a more elegant way to do this...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I missed adding unhashable_wrapper thing for deque, so test_deque fails. Deque implementation resides in collections.rs, but there is no type initialization function similar to this init function in collections.rs. Where is a good place to put this code?

@youknowone
Copy link
Member

I will look in the details.
The single failing test is this one:

======================================================================
FAIL: test_hash (test.test_deque.TestBasic.test_hash)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/RustPython/RustPython/pylib/Lib/test/test_deque.py", line 537, in test_hash
    self.assertRaises(TypeError, hash, deque('abc'))
AssertionError: TypeError not raised by hash

Comment on lines 94 to 99
context
.types
.bytearray_type
.slots
.hash
.store(Some(unhashable_wrapper));
Copy link
Member

@youknowone youknowone Mar 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a suggestion about the location of this initialization.

We put these information to PyClassDef for builtin types.
adding HASHABLE const with default value false will be good.

Then we use the information during making class. that's PyClassImpl::make_slots.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll try.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe default value is false, not true. hashable is common in builtin type world, but not outside.

Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a few more style suggestions.

vm/src/class.rs Outdated
Comment on lines 120 to 121
.map(|h| h as usize == unhashable_wrapper as usize)
.unwrap_or(false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map(|h| h as usize == unhashable_wrapper as usize)
.unwrap_or(false)
.map_or(0, |h| h as usize) == unhashable_wrapper as usize

the cost of map_or(0, |h| h as usize) is zero here due to its layout.

vm/src/class.rs Outdated
@@ -112,6 +112,16 @@ pub trait PyClassImpl: PyClassDef {
.into();
class.set_attr(identifier!(ctx, __new__), bound);
}

if class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to check if this is related to #3460 later

Comment on lines 426 to 427
.map(|a| a.is(&ctx.none()))
.unwrap_or(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map(|a| a.is(&ctx.none()))
.unwrap_or(false);
.map_or(false, |a| a.is(&ctx.none));

ctx.none() increase reference count of None. We don't need to do.

Comment on lines 428 to 432
if is_unhashable {
toggle_slot!(hash, unhashable_wrapper);
} else {
toggle_slot!(hash, hash_wrapper);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if is_unhashable {
toggle_slot!(hash, unhashable_wrapper);
} else {
toggle_slot!(hash, hash_wrapper);
}
let wrapper = if is_unhashable {
unhashable_wrapper
} else {
hash_wrapper
};
toggle_slot!(wrapper)

@@ -243,6 +243,11 @@ fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
}
}

/// Marks a type as unhashable. Similar to PyObject_HashNotImplemented in CPython
pub fn unhashable_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn unhashable_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
pub fn hash_unhashable(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {

Other functions are called wrapper because they are wrapping a python call.
This function is not a wrapper of python call, so giving a different name would be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, probably hash_not_implemented by following cpython naming

Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! it now passes deque too.

vm/src/class.rs Outdated
@@ -60,6 +60,7 @@ pub trait PyClassDef {
const TP_NAME: &'static str;
const DOC: Option<&'static str> = None;
const BASICSIZE: usize;
const UNHASHABLE: bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const UNHASHABLE: bool;
const UNHASHABLE: bool = false;

default value can be placed here

vm/src/class.rs Outdated
@@ -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 = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyRef<T> must follows T

Suggested change
const UNHASHABLE: bool = false;
const UNHASHABLE: bool = T::UNHASHABLE;

@youknowone youknowone added the z-ls-2023 Tag to track Line OSS Sprint 2023 label Mar 7, 2023
Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thank you for digging in this tricky issue.

@youknowone youknowone merged commit 223fe14 into RustPython:main Mar 7, 2023
@seungha-kim seungha-kim deleted the feature/unhashable branch March 7, 2023 14:53
minhrongcon2000 pushed a commit to minhrongcon2000/RustPython that referenced this pull request Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
z-ls-2023 Tag to track Line OSS Sprint 2023
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants