Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,6 @@ def test_constructor(self):
self.assertRaises(TypeError, memoryview, argument=ob)
self.assertRaises(TypeError, memoryview, ob, argument=True)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ecb1920>
def test_gc(self):
return super().test_gc()

class ArrayMemoryviewTest(unittest.TestCase,
BaseMemoryviewTests, BaseArrayMemoryTests):

Expand Down Expand Up @@ -606,10 +602,6 @@ class BytesMemorySliceTest(unittest.TestCase,
BaseMemorySliceTests, BaseBytesMemoryTests):
pass

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ecb13e0>
def test_gc(self):
return super().test_gc()

class ArrayMemorySliceTest(unittest.TestCase,
BaseMemorySliceTests, BaseArrayMemoryTests):
pass
Expand All @@ -618,10 +610,6 @@ class BytesMemorySliceSliceTest(unittest.TestCase,
BaseMemorySliceSliceTests, BaseBytesMemoryTests):
pass

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: False is not true : <test.test_memoryview.MyObject object at 0x84ddca1c0>
def test_gc(self):
return super().test_gc()

class ArrayMemorySliceSliceTest(unittest.TestCase,
BaseMemorySliceSliceTests, BaseArrayMemoryTests):
pass
Expand Down
13 changes: 10 additions & 3 deletions crates/vm/src/builtins/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,31 @@ struct PyMemoryViewFromFlagsArgs {
flags: ArgIndex,
}

#[pyclass(module = false, name = "memoryview")]
#[pyclass(module = false, name = "memoryview", traverse)]
#[derive(Debug)]
pub struct PyMemoryView {
/// One share of the acquisition this view is looking at, given up when the
/// view is released or dropped.
buffer: PyBuffer,
// the released memoryview does not mean the buffer is destroyed
// because the possible another memoryview is viewing from it
#[pytraverse(skip)]
released: AtomicCell<bool>,
/// Forbids handing out anything that outlives this view, for the window
/// passed to `__release_buffer__`.
#[pytraverse(skip)]
restricted: AtomicCell<bool>,
#[pytraverse(skip)]
format_spec: FormatSpec,
// memoryview's options could be different from buffer's options
#[pytraverse(skip)]
desc: BufferDescriptor,
#[pytraverse(skip)]
hash: OnceCell<PyHash>,
/// Buffers handed out of this view that have not been given back yet. The
/// view cannot be released while any of them is outstanding, so that what
/// reads them keeps reading memory that is still there. self->exports
#[pytraverse(skip)]
exports: AtomicCell<usize>,
}

Expand Down Expand Up @@ -1409,7 +1415,7 @@ pub(crate) fn init(ctx: &'static Context) {
PyBufferWindow::extend_class(ctx, PyBufferWindow::init_builtin_type());
}

#[pyclass(module = false, name = "_buffer_wrapper")]
#[pyclass(module = false, name = "_buffer_wrapper", traverse)]
#[derive(Debug)]
struct PyBufferWrapper {
// bw->obj: the object whose `__buffer__` produced the view
Expand All @@ -1420,6 +1426,7 @@ struct PyBufferWrapper {
/// forwards shares of it rather than owning one.
view: PyBuffer,
/// Exports handed out for this wrapper; the wrapper is spent at zero.
#[pytraverse(skip)]
exports: AtomicCell<usize>,
}

Expand Down Expand Up @@ -1465,7 +1472,7 @@ static BUFFER_WRAPPER_METHODS: BufferMethods = BufferMethods {
// Read-only window over an exporter, handed to `__release_buffer__`. It owns no
// export, like a `Py_buffer` whose `obj` is NULL, so releasing it is inert and
// cannot recurse back into the hook.
#[pyclass(module = false, name = "_buffer_window")]
#[pyclass(module = false, name = "_buffer_window", traverse)]
#[derive(Debug)]
struct PyBufferWindow {
source: PyBuffer,
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/protocol/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ impl PyObject {
return vm.with_recursion("while hashing", || hash(self, vm));
}

Err(vm.new_type_error(format!("unhashable type: '{}'", self.class().name())))
Err(vm.new_type_error(format!("unhashable type: '{}'", self.class().slot_name())))
}

// type protocol
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/types/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {

/// Marks a type as unhashable. Similar to PyObject_HashNotImplemented in CPython
pub fn hash_not_implemented(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
Err(vm.new_type_error(format!("unhashable type: '{}'", zelf.class().name())))
Err(vm.new_type_error(format!("unhashable type: '{}'", zelf.class().slot_name())))
}

fn call_wrapper(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
Expand Down
27 changes: 27 additions & 0 deletions extra_tests/snippets/builtin_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,30 @@ def __hash__(self):
{deep_tuple: 1}
with assert_raises(RecursionError):
{deep_tuple}


def test_unhashable_names_the_type_as_written():
# The message names the type the way `tp_name` does, so a type defined in a
# module carries the module with it.
import array
import collections

for value, name in [
(array.array("B", b"a"), "array.array"),
(collections.deque(), "collections.deque"),
([], "list"),
(bytearray(), "bytearray"),
(Unhashable(), "Unhashable"),
]:
try:
hash(value)
raise AssertionError(f"hashed {name}")
except TypeError as e:
assert str(e) == f"unhashable type: '{name}'", e


class Unhashable:
__hash__ = None


test_unhashable_names_the_type_as_written()
50 changes: 50 additions & 0 deletions extra_tests/snippets/stdlib_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,54 @@ def build():
# tee holds its buffer through a second object, which has to be walked too
assert collects(lambda c: itertools.tee(c)[0])


# A view keeps the object it looks at in a field of its own, and the wrapper a
# `__buffer__` produces keeps the exporter the same way.
class Buf(bytearray):
pass


def collects_view(wrap):
"""Report whether the collector breaks a cycle that runs through a view."""

def build():
container = Buf(b"abc")
node = Node()
container.node = node
node.held = wrap(container)
return weakref.ref(node)

gc.collect()
ref = build()
gc.collect()
return ref() is None


assert collects_view(memoryview)
assert collects_view(lambda c: memoryview(c)[1:])
assert collects_view(lambda c: memoryview(c).cast("B"))
assert collects_view(lambda c: memoryview(memoryview(c)))


class Exporter:
def __buffer__(self, flags):
return memoryview(b"abcdef")


def collects_exporter():
def build():
exporter = Exporter()
node = Node()
exporter.node = node
node.held = memoryview(exporter)
return weakref.ref(node)

gc.collect()
ref = build()
gc.collect()
return ref() is None


assert collects_exporter()

print("ok")
Loading