Skip to content
Open
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
17 changes: 16 additions & 1 deletion spy/tests/compiler/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from spy.vm.modules.unsafe import UNSAFE
from spy.vm.object import W_Type
from spy.vm.registry import ModuleRegistry
from spy.vm.struct import UnwrappedStruct
from spy.vm.struct import SPyTuple, UnwrappedStruct


def test_UnwrappedStruct():
Expand All @@ -22,6 +22,21 @@ def test_UnwrappedStruct():
assert us1 == (1, 2)


def test_SPyTuple():
tup = SPyTuple(FQN("_tuple::tuple[i32, i32]::_tup"), {"_item0": 4, "_item1": 8})
assert tup == (4, 8)
assert len(tup) == 2
assert tup[0] == 4
assert tup[1] == 8
assert list(tup) == [4, 8]
a, b = tup
assert (a, b) == (4, 8)
assert repr(tup) == "SPyTuple([4, 8])"
# attribute access is still available, like any other UnwrappedStruct
assert tup._item0 == 4
assert tup._item1 == 8


class TestStructOnStack(CompilerTest):
"""
Test for structs allocated on the stack, passed around by value as
Expand Down
29 changes: 29 additions & 0 deletions spy/tests/compiler/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ def foo(a: i32, b: i32) -> tuple[i32, i32]:
tup = mod.foo(3, 4)
assert tup == (3, 4)

def test_testing_return_tuple(self):
# tuples are returned to the *Python* test
mod = self.compile("""
def foo() -> tuple[i32, i32, i32]:
return 1, 2, 3

def nested() -> tuple[i32, tuple[i32, i32]]:
return 1, (2, 3)

def mixed_type() -> tuple[i32, f64, bool, str]:
return 1, 2.5, True, "hello"
""")
a, b, c = mod.foo()
assert a == 1
assert b == 2

tup = mod.foo()
assert len(tup) == 3
assert tup[0] == 1
assert tup[2] == 3

tup = mod.nested()
assert tup == (1, (2, 3))
a, (b, c) = tup
assert (a, b, c) == (1, 2, 3)

tup = mod.mixed_type()
assert tup == (1, 2.5, True, "hello")

def test_unpacking_wrong_number(self):
src = """
def make_tuple() -> tuple[int, int]:
Expand Down
15 changes: 10 additions & 5 deletions spy/tests/wasm_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from spy.vm.modules.unsafe.ptr import W_PtrType
from spy.vm.object import W_Type
from spy.vm.str import ll_str_new
from spy.vm.struct import UnwrappedStruct, W_StructType
from spy.vm.struct import SPyTuple, UnwrappedStruct, W_StructType
from spy.vm.vm import SPyVM


Expand Down Expand Up @@ -189,9 +189,12 @@ def to_py_result(self, w_T: W_Type, res: Any) -> Any:
return WasmPtr(addr, length)
elif isinstance(w_T, W_StructType):
# when you return struct-by-val from C, wasmtime automatically
# converts them into a list, flattening nested structs
assert isinstance(res, list)
pyres = unflatten_struct(self.ll, w_T, res)
# converts them into a list, flattening nested structs. However,
# for a struct with a single flat field, wasmtime returns a bare
# scalar instead of a one-element list.
if not isinstance(res, list):
res = [res]
pyres = unflatten_struct(self.vm, self.ll, w_T, res)
if w_T.fqn == FQN(
"_list::list[i32]::_ListImpl"
): # reading list[i32] for tests
Expand Down Expand Up @@ -308,7 +311,7 @@ def __call__(self, *py_args: Any, unwrap: bool = True) -> Any:


def unflatten_struct(
ll: LLSPyInstance, w_T: W_StructType, flat_values: list[Any]
vm: SPyVM, ll: LLSPyInstance, w_T: W_StructType, flat_values: list[Any]
) -> UnwrappedStruct:
"""
Unflatten a struct from a flat list of values.
Expand Down Expand Up @@ -354,6 +357,8 @@ def unflatten(w_T: W_StructType, start_idx: int) -> tuple[UnwrappedStruct, int]:
content[w_field.name] = flat_values[idx]
idx += 1

if vm.is_tuple_type(w_T):
return SPyTuple(w_T.fqn, content), idx
return UnwrappedStruct(w_T.fqn, content), idx

result, consumed = unflatten(w_T, 0)
Expand Down
22 changes: 22 additions & 0 deletions spy/vm/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ def spy_unwrap(self, vm: "SPyVM") -> Any:
fqn = self.w_structtype.fqn

fields = {key: w_obj.spy_unwrap(vm) for key, w_obj in self.values_w.items()}
if vm.is_tuple_type(self.w_structtype):
return SPyTuple(fqn, fields)
return UnwrappedStruct(fqn, fields)

def __repr__(self) -> str:
Expand Down Expand Up @@ -431,6 +433,26 @@ def __repr__(self) -> str:
return f"<UnwrappedStruct {self.fqn}: {self._content}>"


class SPyTuple(UnwrappedStruct):
"""
Return value of vm.unwrap(w_some_tuple), where w_some_tuple is an
instance of the stdlib `tuple[T1, T2, ...]` (which is implemented as a
struct with fields `_item0`, `_item1`, ...). Purely a testing convenience.
"""

def __len__(self) -> int:
return len(self._content)

def __iter__(self) -> Any:
return iter(self._content.values())

def __getitem__(self, i: Any) -> Any:
return tuple(self._content.values())[i]

def __repr__(self) -> str:
return f"SPyTuple({list(self._content.values())!r})"


def unwrap_list(vm: "SPyVM", w_list: W_Object) -> list[Any]:
"""
Only useful in tests
Expand Down
Loading