The Wayback Machine - https://web.archive.org/web/20230307172416/https://github.com/RustPython/RustPython/pull/4640
Skip to content
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

Update to pass test for unhashable collections #4640

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 !

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

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.

vm/src/class.rs Show resolved Hide resolved
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
.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

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

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.

vm/src/builtins/slice.rs Outdated Show resolved Hide resolved
@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
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.

None yet

2 participants