From 83186cce78894c0c391c0b4d1a7b64b8ff31d3aa Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sun, 18 May 2025 15:37:13 +0200 Subject: [PATCH] Reworks bytes into dynamic array bytes continues to be the preferred name for u8[...]. Also, putting bytes values into the VM and taking them out still uses Python bytes values. This also lets used use the len function on them, for whatever that's worth. --- phasm/compiler.py | 27 +++++++- phasm/ourlang.py | 3 + phasm/parser.py | 7 +++ phasm/prelude/__init__.py | 62 +++++++++++-------- phasm/stdlib/types.py | 10 ++- phasm/type3/constraints.py | 38 +++++++++++- phasm/type3/constraintsgenerator.py | 5 +- phasm/type3/routers.py | 12 +++- phasm/type3/types.py | 27 ++++++++ tests/integration/helpers.py | 41 ++++++++++++ tests/integration/test_lang/generator.md | 12 ++-- .../generator_dynamic_array_u64.json | 5 ++ .../test_typeclasses/test_sized.py | 2 +- 13 files changed, 210 insertions(+), 41 deletions(-) create mode 100644 tests/integration/test_lang/generator_dynamic_array_u64.json diff --git a/phasm/compiler.py b/phasm/compiler.py index 0d4293c..f13f41c 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -15,8 +15,10 @@ from .type3.types import ( IntType3, Type3, TypeApplication_Struct, + TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar, + TypeConstructor_DynamicArray, TypeConstructor_Function, TypeConstructor_StaticArray, TypeConstructor_Tuple, @@ -109,12 +111,25 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu args: tuple[Type3, ...] - if isinstance(inp.type3.application, TypeApplication_TypeStar): + alloc_size_header = None + + if isinstance(inp.type3.application, TypeApplication_Type): + # Possibly paranoid assert. If we have a future variadic type, + # does it also do this tuple instantation like this? + assert isinstance(inp.type3.application.constructor, TypeConstructor_DynamicArray) + + sa_type, = inp.type3.application.arguments + + args = tuple(sa_type for _ in inp.elements) + alloc_size = 4 + calculate_alloc_size(sa_type, is_member=False) * len(inp.elements) + alloc_size_header = len(inp.elements) + elif isinstance(inp.type3.application, TypeApplication_TypeStar): # Possibly paranoid assert. If we have a future variadic type, # does it also do this tuple instantation like this? assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple) args = inp.type3.application.arguments + alloc_size = calculate_alloc_size(inp.type3, is_member=False) elif isinstance(inp.type3.application, TypeApplication_TypeInt): # Possibly paranoid assert. If we have a future type of kind * -> Int -> *, # does it also do this tuple instantation like this? @@ -123,6 +138,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu sa_type, sa_len = inp.type3.application.arguments args = tuple(sa_type for _ in range(sa_len.value)) + alloc_size = calculate_alloc_size(inp.type3, is_member=False) else: raise NotImplementedError('tuple_instantiation', inp.type3) @@ -135,12 +151,17 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})') # Allocated the required amounts of bytes in memory - wgn.i32.const(calculate_alloc_size(inp.type3, is_member=False)) + wgn.i32.const(alloc_size) wgn.call(stdlib_alloc.__alloc__) wgn.local.set(tmp_var) + if alloc_size_header is not None: + wgn.local.get(tmp_var) + wgn.i32.const(alloc_size_header) + wgn.i32.store() + # Store each element individually - offset = 0 + offset = 0 if alloc_size_header is None else 4 for element, exp_type3 in zip(inp.elements, args, strict=True): assert element.type3 == exp_type3 diff --git a/phasm/ourlang.py b/phasm/ourlang.py index d25097d..6d828d9 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -301,6 +301,9 @@ class FunctionParam: self.name = name self.type3 = type3 + def __repr__(self) -> str: + return f'FunctionParam({self.name!r}, {self.type3!r})' + class Function: """ A function processes input and produces output diff --git a/phasm/parser.py b/phasm/parser.py index 1fc1139..4ff906c 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -655,8 +655,15 @@ class OurVisitor: if isinstance(node.slice, ast.Slice): _raise_static_error(node, 'Must subscript using an index') + if not isinstance(node.slice, ast.Constant): _raise_static_error(node, 'Must subscript using a constant index') + + if node.slice.value is Ellipsis: + return prelude.dynamic_array( + self.visit_type(module, node.value), + ) + if not isinstance(node.slice.value, int): _raise_static_error(node, 'Must subscript using a constant integer index') if not isinstance(node.ctx, ast.Load): diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 77eb142..3b53e67 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -20,6 +20,7 @@ from ..type3.types import ( Type3, TypeApplication_Nullary, TypeConstructor_Base, + TypeConstructor_DynamicArray, TypeConstructor_Function, TypeConstructor_StaticArray, TypeConstructor_Struct, @@ -158,9 +159,15 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None)) A 32-bits IEEE 754 float, of 64 bits width. """ -bytes_ = Type3('bytes', TypeApplication_Nullary(None, None)) +def da_on_create(args: tuple[Type3], typ: Type3) -> None: + instance_type_class(InternalPassAsPointer, typ) + +dynamic_array = TypeConstructor_DynamicArray('dynamic_array', on_create=da_on_create) """ -This is a runtime-determined length piece of memory that can be indexed at runtime. +This is a dynamic length piece of memory. + +It should be applied with two arguments. It has a runtime +determined length, and each argument is the same. """ def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None: @@ -168,12 +175,10 @@ def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None: static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) """ -A type constructor. +This is a fixed length piece of memory. -Any static array is a fixed length piece of memory that can be indexed at runtime. - -It should be applied with one argument. It has a runtime-dynamic length -of the same type repeated. +It should be applied with two arguments. It has a compile time +determined length, and each argument is the same. """ def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None: @@ -208,20 +213,6 @@ This is like a tuple, but each argument is named, so that developers can get and set fields by name. """ -PRELUDE_TYPES: dict[str, Type3] = { - 'none': none, - 'bool': bool_, - 'u8': u8, - 'u32': u32, - 'u64': u64, - 'i8': i8, - 'i32': i32, - 'i64': i64, - 'f32': f32, - 'f64': f64, - 'bytes': bytes_, -} - a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) @@ -232,7 +223,7 @@ InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, o Internal type class to keep track which types we pass arounds as a pointer. """ -instance_type_class(InternalPassAsPointer, bytes_) +# instance_type_class(InternalPassAsPointer, bytes_) # instance_type_class(InternalPassAsPointer, static_array) # instance_type_class(InternalPassAsPointer, tuple_) # instance_type_class(InternalPassAsPointer, struct) @@ -537,12 +528,15 @@ instance_type_class(Floating, f64, methods={ 'sqrt': stdtypes.f64_floating_sqrt, }) -Sized_ = Type3Class('Sized', (a, ), methods={ - 'len': [a, u32], +Sized_ = Type3Class('Sized', (t, ), methods={ + 'len': [t(a), u32], }, operators={}) # FIXME: Once we get type class families, add [] here -instance_type_class(Sized_, bytes_, methods={ - 'len': stdtypes.bytes_sized_len, +instance_type_class(Sized_, dynamic_array, methods={ + 'len': stdtypes.dynamic_array_sized_len, +}) +instance_type_class(Sized_, static_array, methods={ + 'len': stdtypes.static_array_sized_len, }) Extendable = Type3Class('Extendable', (a, b, ), methods={ @@ -595,6 +589,22 @@ instance_type_class(Foldable, static_array, methods={ 'sum': stdtypes.static_array_sum, }) +bytes_ = dynamic_array(u8) + +PRELUDE_TYPES: dict[str, Type3] = { + 'none': none, + 'bool': bool_, + 'u8': u8, + 'u32': u32, + 'u64': u64, + 'i8': i8, + 'i32': i32, + 'i64': i64, + 'f32': f32, + 'f64': f64, + 'bytes': bytes_, +} + PRELUDE_TYPE_CLASSES = { 'Eq': Eq, 'Ord': Ord, diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 425dfa0..77efc12 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -1006,11 +1006,19 @@ def f64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: ## ### ## Class Sized -def bytes_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None: +def dynamic_array_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None: del tv_map # The length is stored in the first 4 bytes g.i32.load() +def static_array_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None: + assert len(tv_map) == 1 + sa_type, sa_len = next(iter(tv_map.values())) + assert isinstance(sa_type, Type3) + assert isinstance(sa_len, IntType3) + + g.i32.const(sa_len.value) + ## ### ## Extendable diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 0eeb6fb..6046724 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -15,6 +15,7 @@ from .types import ( Type3, TypeApplication_Nullary, TypeApplication_Struct, + TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar, TypeConstructor_Base, @@ -292,6 +293,14 @@ class TupleMatchConstraint(ConstraintBase): self.exp_type = exp_type self.args = list(args) + def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult: + sa_type, = sa_args + + return [ + SameTypeConstraint(arg, sa_type) + for arg in self.args + ] + def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult: sa_type, sa_len = sa_args @@ -313,6 +322,7 @@ class TupleMatchConstraint(ConstraintBase): ] GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]() + GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) @@ -340,7 +350,7 @@ class MustImplementTypeClassConstraint(ConstraintBase): types: list[Type3OrPlaceholder] DATA = { - 'bytes': {'Foldable'}, + 'dynamic_array': {'Foldable'}, } def __init__(self, context: Context, type_class3: Union[str, Type3Class], typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None: @@ -363,7 +373,7 @@ class MustImplementTypeClassConstraint(ConstraintBase): typ_list.append(typ) continue - if isinstance(typ.application, (TypeApplication_TypeInt, TypeApplication_TypeStar)): + if isinstance(typ.application, (TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar)): typ_list.append(typ.application.constructor) continue @@ -420,6 +430,29 @@ class LiteralFitsConstraint(ConstraintBase): self.type3 = type3 self.literal = literal + def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult: + if not isinstance(self.literal, ourlang.ConstantTuple): + return Error('Must be tuple', comment=self.comment) + + da_type, = da_args + + res: list[ConstraintBase] = [] + + res.extend( + LiteralFitsConstraint(da_type, y) + for y in self.literal.value + ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in + res.extend( + SameTypeConstraint(da_type, PlaceholderForType([y])) + for y in self.literal.value + ) + + return res + def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult: if not isinstance(self.literal, ourlang.ConstantTuple): return Error('Must be tuple', comment=self.comment) @@ -501,6 +534,7 @@ class LiteralFitsConstraint(ConstraintBase): return res GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]() + GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) GENERATE_ROUTER.add(prelude.struct, _generate_struct) GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 1a4771a..b0183d3 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -176,7 +176,10 @@ def _expression_function_call( if not isinstance(sig_arg.application, TypeVariableApplication_Unary): raise NotImplementedError(sig_arg.application) - assert sig_arg.application.arguments in type_var_map # When does this happen? + if sig_arg.application.arguments not in type_var_map: + # e.g., len :: t a -> u32 + # i.e. "a" does not matter at all + continue yield SameTypeArgumentConstraint( type_var_map[sig_arg], diff --git a/phasm/type3/routers.py b/phasm/type3/routers.py index c592dac..90b8fa4 100644 --- a/phasm/type3/routers.py +++ b/phasm/type3/routers.py @@ -6,7 +6,13 @@ from .functions import ( TypeVariableApplication_Unary, ) from .typeclasses import Type3ClassArgs -from .types import KindArgument, Type3, TypeApplication_TypeInt, TypeConstructor_Base +from .types import ( + KindArgument, + Type3, + TypeApplication_Type, + TypeApplication_TypeInt, + TypeConstructor_Base, +) class NoRouteForTypeException(Exception): @@ -104,6 +110,10 @@ class TypeClassArgsRouter[S, R]: key.append(typ.application.constructor) if isinstance(tvar.application, TypeVariableApplication_Unary): + if isinstance(typ.application, TypeApplication_Type): + arguments[tvar.application.arguments] = typ.application.arguments + continue + # FIXME: This feels sketchy. Shouldn't the type variable # have the exact same number as arguments? if isinstance(typ.application, TypeApplication_TypeInt): diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 489fd01..cd9b187 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -195,6 +195,26 @@ class TypeConstructor_Base[T]: def __repr__(self) -> str: return f'{self.__class__.__name__}({self.name!r}, ...)' +class TypeConstructor_Type(TypeConstructor_Base[Tuple[Type3]]): + """ + Base class type constructors of kind: * -> * + + Notably, static array. + """ + __slots__ = () + + def make_application(self, key: Tuple[Type3]) -> 'TypeApplication_Type': + return TypeApplication_Type(self, key) + + def make_name(self, key: Tuple[Type3]) -> str: + return f'{self.name} {key[0].name} ' + + def __call__(self, arg0: Type3) -> Type3: + return self.construct((arg0, )) + +class TypeApplication_Type(TypeApplication_Base[TypeConstructor_Type, Tuple[Type3]]): + pass + class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]): """ Base class type constructors of kind: * -> Int -> * @@ -231,6 +251,13 @@ class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]): class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]): pass +class TypeConstructor_DynamicArray(TypeConstructor_Type): + def make_name(self, key: Tuple[Type3]) -> str: + if 'u8' == key[0].name: + return 'bytes' + + return f'{key[0].name}[...]' + class TypeConstructor_StaticArray(TypeConstructor_TypeInt): def make_name(self, key: Tuple[Type3, IntType3]) -> str: return f'{key[0].name}[{key[1].value}]' diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 74cfb19..d444f74 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -152,6 +152,25 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in runner.interpreter_write_memory(adr + 4, val) return adr +def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int: + runner, val = attrs + + da_type, = da_args + + if not isinstance(val, tuple): + raise InvalidArgumentException(f'Expected tuple; got {val!r} instead') + + alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True) + adr = runner.call('stdlib.alloc.__alloc__', alloc_size) + assert isinstance(adr, int) # Type int + sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + + offset = adr + offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val)) + for val_el_val in val: + offset += _write_memory_stored_value(runner, offset, da_type, val_el_val) + return adr + def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int: runner, val = attrs @@ -211,6 +230,7 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]() ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) +ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array) ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array) ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct) ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple) @@ -325,6 +345,26 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator yield all_bytes[offset:offset + size] offset += size +def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any: + runner, adr = attrs + da_type, = da_args + + sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n') + + read_bytes = runner.interpreter_read_memory(adr, 4) + array_len, = struct.unpack(' Any: runner, adr = attrs sub_typ, len_typ = sa_args @@ -379,6 +419,7 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]() LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address) +LOAD_FROM_ADDRESS_ROUTER.add(prelude.dynamic_array, _load_dynamic_array_from_address) LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address) LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address) LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address) diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index 187d05a..fb0244b 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -60,7 +60,7 @@ CONSTANT: (u32, ) = $VAL0 ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'): expect_type_error( 'Tuple element count mismatch', 'The given literal must fit the expected type', @@ -113,7 +113,7 @@ def testEntry() -> i32: ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'): expect_type_error( 'Mismatch between applied types argument count', 'The type of a tuple is a combination of its members', @@ -175,7 +175,7 @@ def testEntry() -> i32: ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'): expect_type_error( TYPE + ' must be (u32, ) instead', 'The type of the value returned from function constant should match its return type', @@ -226,7 +226,7 @@ def select(x: $TYPE) -> (u32, ): ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'): expect_type_error( TYPE + ' must be (u32, ) instead', 'The type of the value returned from function select should match its return type', @@ -273,7 +273,7 @@ def testEntry() -> i32: ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'): expect_type_error( 'Mismatch between applied types argument count', # FIXME: Shouldn't this be the same as for the else statement? @@ -330,7 +330,7 @@ def testEntry() -> i32: ``` ```py -if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'): expect_type_error( TYPE + ' must be (u32, ) instead', 'The type of the value passed to argument 0 of function helper should match the type of that argument', diff --git a/tests/integration/test_lang/generator_dynamic_array_u64.json b/tests/integration/test_lang/generator_dynamic_array_u64.json new file mode 100644 index 0000000..e9b6adf --- /dev/null +++ b/tests/integration/test_lang/generator_dynamic_array_u64.json @@ -0,0 +1,5 @@ +{ + "TYPE_NAME": "dynamic_array_u64", + "TYPE": "u64[...]", + "VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )" +} diff --git a/tests/integration/test_typeclasses/test_sized.py b/tests/integration/test_typeclasses/test_sized.py index ea2e9a8..1b8b6eb 100644 --- a/tests/integration/test_typeclasses/test_sized.py +++ b/tests/integration/test_typeclasses/test_sized.py @@ -6,7 +6,7 @@ from ..helpers import Suite @pytest.mark.integration_test @pytest.mark.parametrize('type_, in_put, exp_result', [ ('bytes', b'Hello, world!', 13), - # ('u8[4]', (1, 2, 3, 4), 4), # FIXME: Implement this + ('u8[4]', (1, 2, 3, 4), 4), ]) def test_len(type_, in_put, exp_result): code_py = f"""