Changes AppliedType to TypeConstructor
First to be more in line with how the literature treats these types. But also to make them workable with type classes.
This commit is contained in:
parent
87866cff55
commit
234bfaa8df
13
TODO.md
13
TODO.md
@ -14,21 +14,19 @@
|
||||
- Functions don't seem to be a thing on typing level yet?
|
||||
- static_array and tuple should probably not be PrimitiveType3, but instead subclass AppliedType3?
|
||||
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
|
||||
- Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type?
|
||||
- Have a set of rules or guidelines for the constraint comments, they're messy.
|
||||
|
||||
- test_bitwise_or_inv_type
|
||||
- test_bytes_index_out_of_bounds vs static trap(?)
|
||||
- test_num.py is probably best as part of the generator?
|
||||
- Find pytest.mark.skip
|
||||
- There's a weird resolve_as reference in calculate_alloc_size
|
||||
- Either there should be more of them or less
|
||||
- At first glance, looks like failure in the typing system
|
||||
- Related to the FIXME in phasm_type3?
|
||||
- Related: Parser is putting stuff in ModuleDataBlock
|
||||
- WEBASSEMBLY_BUILTIN_BYTES_OPS is special cased
|
||||
- Should be part of a prelude (?)
|
||||
- In Haskell this is not a type class
|
||||
- Casting is not implemented except u32 which is special cased
|
||||
- Parser is putting stuff in ModuleDataBlock
|
||||
- Compiler should probably do that
|
||||
- Make prelude more an actual thing
|
||||
- Merge in type3types.LOOKUP_TABLE
|
||||
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
|
||||
@ -36,3 +34,8 @@
|
||||
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed
|
||||
- Move UnaryOp.operator into type class methods
|
||||
- Move foldr into type class methods
|
||||
- PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something.
|
||||
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
|
||||
- Type constuctor should also be able to constuct placeholders - somehow.
|
||||
- Make struct a type constructor?
|
||||
- Add InternalPassAsPointer to Struct?
|
||||
|
||||
@ -27,21 +27,6 @@ def type3(inp: Type3OrPlaceholder) -> str:
|
||||
if inp is type3types.none:
|
||||
return 'None'
|
||||
|
||||
if isinstance(inp, type3types.AppliedType3):
|
||||
if inp.base == type3types.tuple:
|
||||
return '(' + ', '.join(
|
||||
type3(x)
|
||||
for x in inp.args
|
||||
if isinstance(x, Type3) # Skip ints, not allowed here anyhow
|
||||
) + ', )'
|
||||
|
||||
if inp.base == type3types.static_array:
|
||||
assert 2 == len(inp.args)
|
||||
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
|
||||
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
|
||||
|
||||
return type3(inp.args[0]) + '[' + inp.args[1].name + ']'
|
||||
|
||||
return inp.name
|
||||
|
||||
def struct_definition(inp: ourlang.StructDefinition) -> str:
|
||||
|
||||
@ -286,7 +286,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
||||
if inp == type3types.f64:
|
||||
return wasm.WasmTypeFloat64()
|
||||
|
||||
if inp == type3types.bytes:
|
||||
if inp == type3types.bytes_:
|
||||
# bytes are passed as pointer
|
||||
# And pointers are i32
|
||||
return wasm.WasmTypeInt32()
|
||||
@ -295,13 +295,7 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
||||
# Structs are passed as pointer, which are i32
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if isinstance(inp, type3types.AppliedType3):
|
||||
if inp.base == type3types.static_array:
|
||||
# Static Arrays are passed as pointer, which are i32
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if inp.base == type3types.tuple:
|
||||
# Tuples are passed as pointer, which are i32
|
||||
if type3classes.InternalPassAsPointer in inp.classes:
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
raise NotImplementedError(type3, inp)
|
||||
@ -310,9 +304,21 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
|
||||
"""
|
||||
Compile: Instantiation (allocation) of a tuple
|
||||
"""
|
||||
assert isinstance(inp.type3, type3types.AppliedType3)
|
||||
assert inp.type3.base is type3types.tuple
|
||||
assert len(inp.elements) == len(inp.type3.args)
|
||||
assert isinstance(inp.type3, type3types.PrimitiveType3)
|
||||
|
||||
args: list[type3types.PrimitiveType3] = []
|
||||
|
||||
sa_args = type3types.static_array.did_construct(inp.type3)
|
||||
if sa_args is not None:
|
||||
sa_type, sa_len = sa_args
|
||||
args = [sa_type for _ in range(sa_len.value)]
|
||||
|
||||
if not args:
|
||||
tp_args = type3types.tuple_.did_construct(inp.type3)
|
||||
if tp_args is None:
|
||||
raise NotImplementedError
|
||||
|
||||
args = list(tp_args)
|
||||
|
||||
comment_elements = ''
|
||||
for element in inp.elements:
|
||||
@ -329,14 +335,17 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
|
||||
|
||||
# Store each element individually
|
||||
offset = 0
|
||||
for element, exp_type3 in zip(inp.elements, inp.type3.args):
|
||||
for element, exp_type3 in zip(inp.elements, args):
|
||||
if isinstance(exp_type3, type3types.PlaceholderForType):
|
||||
assert exp_type3.resolve_as is not None
|
||||
assert isinstance(exp_type3.resolve_as, type3types.PrimitiveType3)
|
||||
exp_type3 = exp_type3.resolve_as
|
||||
|
||||
assert element.type3 == exp_type3
|
||||
|
||||
if isinstance(exp_type3, type3types.StructType3) or isinstance(exp_type3, type3types.AppliedType3):
|
||||
if type3classes.InternalPassAsPointer in exp_type3.classes:
|
||||
mtyp = 'i32'
|
||||
elif isinstance(exp_type3, type3types.StructType3):
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||
@ -357,6 +366,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
"""
|
||||
Compile: Any expression
|
||||
"""
|
||||
if isinstance(inp, (ourlang.ConstantStruct, ourlang.ConstantTuple, )):
|
||||
# These are implemented elsewhere
|
||||
raise Exception
|
||||
|
||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
@ -401,11 +414,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
if isinstance(inp.variable, ourlang.ModuleConstantDef):
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
if isinstance(inp.type3, type3types.PrimitiveType3):
|
||||
expression(wgn, inp.variable.constant)
|
||||
if type3classes.InternalPassAsPointer in inp.type3.classes:
|
||||
# FIXME: Artifact from older version
|
||||
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
assert address is not None, 'Value not allocated'
|
||||
wgn.i32.const(address)
|
||||
return
|
||||
|
||||
if inp.type3 is type3types.bytes:
|
||||
if inp.type3 is type3types.bytes_:
|
||||
assert isinstance(inp.variable.constant, ourlang.ConstantBytes)
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
@ -421,25 +439,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
wgn.i32.const(address)
|
||||
return
|
||||
|
||||
if isinstance(inp.type3, type3types.AppliedType3):
|
||||
if inp.type3.base == type3types.static_array:
|
||||
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
assert address is not None, 'Value not allocated'
|
||||
wgn.i32.const(address)
|
||||
if isinstance(inp.type3, type3types.PrimitiveType3):
|
||||
expression(wgn, inp.variable.constant)
|
||||
return
|
||||
|
||||
if inp.type3.base == type3types.tuple:
|
||||
assert isinstance(inp.variable.constant, ourlang.ConstantTuple)
|
||||
|
||||
address = inp.variable.constant.data_block.address
|
||||
assert address is not None, 'Value not allocated'
|
||||
wgn.i32.const(address)
|
||||
return
|
||||
|
||||
raise NotImplementedError(expression, inp.variable, inp.type3.base)
|
||||
|
||||
raise NotImplementedError(expression, inp)
|
||||
|
||||
raise NotImplementedError(expression, inp.variable)
|
||||
@ -479,7 +482,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
|
||||
if inp.type3 == type3types.u32:
|
||||
if inp.operator == 'len':
|
||||
if inp.right.type3 == type3types.bytes:
|
||||
if inp.right.type3 == type3types.bytes_:
|
||||
wgn.i32.load()
|
||||
return
|
||||
|
||||
@ -528,19 +531,17 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
if isinstance(inp, ourlang.Subscript):
|
||||
assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
if inp.varref.type3 is type3types.bytes:
|
||||
if inp.varref.type3 is type3types.bytes_:
|
||||
expression(wgn, inp.varref)
|
||||
expression(wgn, inp.index)
|
||||
wgn.call(stdlib_types.__subscript_bytes__)
|
||||
return
|
||||
|
||||
if isinstance(inp.varref.type3, type3types.AppliedType3):
|
||||
if inp.varref.type3.base == type3types.static_array:
|
||||
assert 2 == len(inp.varref.type3.args)
|
||||
el_type = inp.varref.type3.args[0]
|
||||
assert isinstance(el_type, type3types.Type3)
|
||||
el_len = inp.varref.type3.args[1]
|
||||
assert isinstance(el_len, type3types.IntType3)
|
||||
assert isinstance(inp.varref.type3, type3types.PrimitiveType3)
|
||||
|
||||
sa_args = type3types.static_array.did_construct(inp.varref.type3)
|
||||
if sa_args is not None:
|
||||
el_type, el_len = sa_args
|
||||
|
||||
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
|
||||
# and we don't need to do the out of bounds check
|
||||
@ -568,24 +569,24 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
wgn.add_statement(f'{mtyp}.load')
|
||||
return
|
||||
|
||||
if inp.varref.type3.base == type3types.tuple:
|
||||
tp_args = type3types.tuple_.did_construct(inp.varref.type3)
|
||||
if tp_args is not None:
|
||||
assert isinstance(inp.index, ourlang.ConstantPrimitive)
|
||||
assert isinstance(inp.index.value, int)
|
||||
|
||||
offset = 0
|
||||
for el_type in inp.varref.type3.args[0:inp.index.value]:
|
||||
for el_type in tp_args[0:inp.index.value]:
|
||||
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
offset += calculate_alloc_size(el_type)
|
||||
|
||||
# This doubles as the out of bounds check
|
||||
el_type = inp.varref.type3.args[inp.index.value]
|
||||
el_type = tp_args[inp.index.value]
|
||||
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
expression(wgn, inp.varref)
|
||||
|
||||
if isinstance(el_type, type3types.StructType3):
|
||||
mtyp = 'i32'
|
||||
elif isinstance(el_type, type3types.AppliedType3) and el_type.base is type3types.tuple:
|
||||
elif type3classes.InternalPassAsPointer in el_type.classes:
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||
@ -618,7 +619,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
||||
"""
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
if inp.iter.type3 is not type3types.bytes:
|
||||
if inp.iter.type3 is not type3types.bytes_:
|
||||
raise NotImplementedError(expression_fold, inp, inp.iter.type3)
|
||||
|
||||
wgn.add_statement('nop', comment='acu :: u8')
|
||||
@ -906,7 +907,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
|
||||
data_list.append(module_data_f64(constant.value))
|
||||
continue
|
||||
|
||||
if constant.type3 == type3types.bytes:
|
||||
if constant.type3 == type3types.bytes_:
|
||||
assert isinstance(constant, ourlang.ConstantBytes)
|
||||
assert isinstance(constant.value, bytes)
|
||||
data_list.append(module_data_u32(len(constant.value)))
|
||||
@ -987,7 +988,9 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
||||
# Store each member individually
|
||||
for memname, mtyp3 in inp.struct_type3.members.items():
|
||||
mtyp: Optional[str]
|
||||
if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3):
|
||||
if type3classes.InternalPassAsPointer in mtyp3.classes:
|
||||
mtyp = 'i32'
|
||||
elif isinstance(mtyp3, type3types.StructType3):
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||
|
||||
@ -275,6 +275,9 @@ class StatementReturn(Statement):
|
||||
def __init__(self, value: Expression) -> None:
|
||||
self.value = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'StatementReturn({repr(self.value)})'
|
||||
|
||||
class StatementIf(Statement):
|
||||
"""
|
||||
An if statement within a function
|
||||
|
||||
@ -664,7 +664,7 @@ class OurVisitor:
|
||||
|
||||
raise NotImplementedError(f'{node.value} as constant')
|
||||
|
||||
def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
|
||||
def visit_type(self, module: Module, node: ast.expr) -> type3types.PrimitiveType3:
|
||||
if isinstance(node, ast.Constant):
|
||||
if node.value is None:
|
||||
return type3types.none
|
||||
@ -693,18 +693,17 @@ class OurVisitor:
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
return type3types.AppliedType3(
|
||||
type3types.static_array,
|
||||
[self.visit_type(module, node.value), type3types.IntType3(node.slice.value)],
|
||||
return type3types.static_array(
|
||||
self.visit_type(module, node.value),
|
||||
type3types.IntType3(node.slice.value),
|
||||
)
|
||||
|
||||
if isinstance(node, ast.Tuple):
|
||||
if not isinstance(node.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
return type3types.AppliedType3(
|
||||
type3types.tuple,
|
||||
(self.visit_type(module, elt) for elt in node.elts)
|
||||
return type3types.tuple_(
|
||||
*(self.visit_type(module, elt) for elt in node.elts)
|
||||
)
|
||||
|
||||
raise NotImplementedError(f'{node} as type')
|
||||
|
||||
@ -11,7 +11,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
|
||||
if typ in (type3types.u64, type3types.i64, type3types.f64, ):
|
||||
return 8
|
||||
|
||||
if typ == type3types.bytes:
|
||||
if typ == type3types.bytes_:
|
||||
if is_member:
|
||||
return 4
|
||||
|
||||
@ -27,28 +27,30 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
|
||||
for x in typ.members.values()
|
||||
)
|
||||
|
||||
if isinstance(typ, type3types.AppliedType3):
|
||||
if typ.base is type3types.static_array:
|
||||
assert isinstance(typ, type3types.PrimitiveType3)
|
||||
|
||||
sa_args = type3types.static_array.did_construct(typ)
|
||||
if sa_args is not None:
|
||||
if is_member:
|
||||
# tuples referred to by other structs or tuples are pointers
|
||||
return 4
|
||||
|
||||
assert isinstance(typ.args[0], type3types.Type3)
|
||||
assert isinstance(typ.args[1], type3types.IntType3)
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
return typ.args[1].value * calculate_alloc_size(typ.args[0], is_member=True)
|
||||
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
|
||||
|
||||
if typ.base is type3types.tuple:
|
||||
tp_args = type3types.tuple_.did_construct(typ)
|
||||
if tp_args is not None:
|
||||
if is_member:
|
||||
# tuples referred to by other structs or tuples are pointers
|
||||
return 4
|
||||
|
||||
size = 0
|
||||
for arg in typ.args:
|
||||
for arg in tp_args:
|
||||
assert not isinstance(arg, type3types.IntType3)
|
||||
|
||||
if isinstance(arg, type3types.PlaceholderForType):
|
||||
assert arg.resolve_as is not None
|
||||
assert isinstance(arg.resolve_as, type3types.PrimitiveType3)
|
||||
arg = arg.resolve_as
|
||||
|
||||
size += calculate_alloc_size(arg, is_member=True)
|
||||
|
||||
@ -48,9 +48,12 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
|
||||
g.local.get(ofs)
|
||||
g.local.get(adr)
|
||||
g.i32.load()
|
||||
g.i32.lt_u()
|
||||
g.i32.ge_u()
|
||||
|
||||
with g.if_():
|
||||
# The offset is outside the allocated bytes
|
||||
g.unreachable(comment='Out of bounds')
|
||||
|
||||
# The offset is less than the length
|
||||
|
||||
g.local.get(adr)
|
||||
@ -61,10 +64,6 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
|
||||
g.i32.load8_u()
|
||||
g.return_()
|
||||
|
||||
# The offset is outside the allocated bytes
|
||||
g.i32.const(0)
|
||||
g.return_()
|
||||
|
||||
return i32('return') # To satisfy mypy
|
||||
|
||||
@func_wrapper()
|
||||
|
||||
@ -112,10 +112,6 @@ class SameTypeConstraint(ConstraintBase):
|
||||
known_types.append(typ)
|
||||
continue
|
||||
|
||||
if isinstance(typ, types.AppliedType3):
|
||||
known_types.append(typ)
|
||||
continue
|
||||
|
||||
if isinstance(typ, types.PlaceholderForType):
|
||||
if typ.resolve_as is not None:
|
||||
known_types.append(typ.resolve_as)
|
||||
@ -132,38 +128,6 @@ class SameTypeConstraint(ConstraintBase):
|
||||
|
||||
first_type = known_types[0]
|
||||
for typ in known_types[1:]:
|
||||
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
|
||||
if first_type.base is types.tuple and typ.base is types.static_array:
|
||||
# Swap so we can reuse the code below
|
||||
# Hope that it still gives proper type errors
|
||||
first_type, typ = typ, first_type
|
||||
|
||||
if first_type.base is types.static_array and typ.base is types.tuple:
|
||||
assert isinstance(first_type.args[1], types.IntType3)
|
||||
length = first_type.args[1].value
|
||||
|
||||
if len(typ.args) != length:
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
for typ_arg in typ.args:
|
||||
new_constraint_list.append(SameTypeConstraint(
|
||||
first_type.args[0], typ_arg
|
||||
))
|
||||
continue
|
||||
|
||||
if first_type.base != typ.base:
|
||||
return Error('Mismatch between applied types base', comment=self.comment)
|
||||
|
||||
if len(first_type.args) != len(typ.args):
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
for first_type_arg, typ_arg in zip(first_type.args, typ.args):
|
||||
new_constraint_list.append(SameTypeConstraint(
|
||||
first_type_arg, typ_arg
|
||||
))
|
||||
|
||||
continue
|
||||
|
||||
if typ != first_type:
|
||||
return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment)
|
||||
|
||||
@ -198,45 +162,46 @@ class SameTypeConstraint(ConstraintBase):
|
||||
|
||||
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
|
||||
|
||||
class IntegerCompareConstraint(ConstraintBase):
|
||||
"""
|
||||
Verifies that the given IntType3 are in order (<=)
|
||||
"""
|
||||
__slots__ = ('int_type3_list', )
|
||||
|
||||
int_type3_list: List[types.IntType3]
|
||||
|
||||
def __init__(self, *int_type3: types.IntType3, comment: Optional[str] = None) -> None:
|
||||
class TupleMatchConstraint(ConstraintBase):
|
||||
def __init__(self, exp_type: types.Type3OrPlaceholder, args: List[types.Type3OrPlaceholder], comment: str):
|
||||
super().__init__(comment=comment)
|
||||
|
||||
assert len(int_type3) > 1
|
||||
self.int_type3_list = [*int_type3]
|
||||
self.exp_type = exp_type
|
||||
self.args = list(args)
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
val_list = [x.value for x in self.int_type3_list]
|
||||
exp_type = self.exp_type
|
||||
if isinstance(exp_type, types.PlaceholderForType):
|
||||
if exp_type.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
prev_val = val_list.pop(0)
|
||||
for next_val in val_list:
|
||||
if prev_val > next_val:
|
||||
return Error(f'{prev_val} must be less or equal than {next_val}')
|
||||
exp_type = exp_type.resolve_as
|
||||
|
||||
prev_val = next_val
|
||||
assert isinstance(exp_type, types.PrimitiveType3)
|
||||
|
||||
return None
|
||||
sa_args = types.static_array.did_construct(exp_type)
|
||||
if sa_args is not None:
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
' <= '.join('{t' + str(idx) + '}' for idx in range(len(self.int_type3_list))),
|
||||
{
|
||||
't' + str(idx): typ
|
||||
for idx, typ in enumerate(self.int_type3_list)
|
||||
},
|
||||
)
|
||||
if sa_len.value != len(self.args):
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args = ', '.join(repr(x) for x in self.int_type3_list)
|
||||
return [
|
||||
SameTypeConstraint(arg, sa_type)
|
||||
for arg in self.args
|
||||
]
|
||||
|
||||
return f'IntegerCompareConstraint({args}, comment={repr(self.comment)})'
|
||||
tp_args = types.tuple_.did_construct(exp_type)
|
||||
if tp_args is not None:
|
||||
if len(tp_args) != len(self.args):
|
||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||
|
||||
return [
|
||||
SameTypeConstraint(arg, oth_arg)
|
||||
for arg, oth_arg in zip(self.args, tp_args)
|
||||
]
|
||||
|
||||
raise NotImplementedError(exp_type)
|
||||
|
||||
class CastableConstraint(ConstraintBase):
|
||||
"""
|
||||
@ -292,14 +257,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
||||
type3: types.Type3OrPlaceholder
|
||||
|
||||
DATA = {
|
||||
'u8': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||
'u32': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||
'u64': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||
'i32': {'EqualComparison', 'StrictPartialOrder'},
|
||||
'i64': {'EqualComparison', 'StrictPartialOrder'},
|
||||
'bytes': {'Foldable', 'Sized'},
|
||||
'f32': {'Fractional', 'FloatingPoint'},
|
||||
'f64': {'Fractional', 'FloatingPoint'},
|
||||
}
|
||||
|
||||
def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
||||
@ -401,7 +359,7 @@ class LiteralFitsConstraint(ConstraintBase):
|
||||
|
||||
return Error('Must be real', comment=self.comment) # FIXME: Add line information
|
||||
|
||||
if self.type3 is types.bytes:
|
||||
if self.type3 is types.bytes_:
|
||||
if isinstance(self.literal.value, bytes):
|
||||
return None
|
||||
|
||||
@ -409,45 +367,47 @@ class LiteralFitsConstraint(ConstraintBase):
|
||||
|
||||
res: NewConstraintList
|
||||
|
||||
if isinstance(self.type3, types.AppliedType3):
|
||||
if self.type3.base == types.tuple:
|
||||
assert isinstance(self.type3, types.PrimitiveType3)
|
||||
|
||||
tp_args = types.tuple_.did_construct(self.type3)
|
||||
if tp_args is not None:
|
||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||
return Error('Must be tuple', comment=self.comment)
|
||||
|
||||
if len(self.type3.args) != len(self.literal.value):
|
||||
if len(tp_args) != len(self.literal.value):
|
||||
return Error('Tuple element count mismatch', comment=self.comment)
|
||||
|
||||
res = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(x, y)
|
||||
for x, y in zip(self.type3.args, self.literal.value)
|
||||
for x, y in zip(tp_args, self.literal.value)
|
||||
)
|
||||
res.extend(
|
||||
SameTypeConstraint(x, y.type3)
|
||||
for x, y in zip(self.type3.args, self.literal.value)
|
||||
for x, y in zip(tp_args, self.literal.value)
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
if self.type3.base == types.static_array:
|
||||
sa_args = types.static_array.did_construct(self.type3)
|
||||
if sa_args is not None:
|
||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||
return Error('Must be tuple', comment=self.comment)
|
||||
|
||||
assert 2 == len(self.type3.args)
|
||||
assert isinstance(self.type3.args[1], types.IntType3)
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if self.type3.args[1].value != len(self.literal.value):
|
||||
if sa_len.value != len(self.literal.value):
|
||||
return Error('Member count mismatch', comment=self.comment)
|
||||
|
||||
res = []
|
||||
|
||||
res.extend(
|
||||
LiteralFitsConstraint(self.type3.args[0], y)
|
||||
LiteralFitsConstraint(sa_type, y)
|
||||
for y in self.literal.value
|
||||
)
|
||||
res.extend(
|
||||
SameTypeConstraint(self.type3.args[0], y.type3)
|
||||
SameTypeConstraint(sa_type, y.type3)
|
||||
for y in self.literal.value
|
||||
)
|
||||
|
||||
@ -511,57 +471,58 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
||||
self.index_type3 = index.type3
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
if isinstance(self.type3, types.PlaceholderForType):
|
||||
if self.type3.resolve_as is None:
|
||||
exp_type = self.type3
|
||||
if isinstance(exp_type, types.PlaceholderForType):
|
||||
if exp_type.resolve_as is None:
|
||||
return RequireTypeSubstitutes()
|
||||
|
||||
self.type3 = self.type3.resolve_as
|
||||
exp_type = exp_type.resolve_as
|
||||
|
||||
assert isinstance(exp_type, types.PrimitiveType3)
|
||||
|
||||
sa_args = types.static_array.did_construct(exp_type)
|
||||
if sa_args is not None:
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
if isinstance(self.type3, types.AppliedType3):
|
||||
if self.type3.base == types.static_array:
|
||||
result: List[ConstraintBase] = [
|
||||
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
|
||||
]
|
||||
|
||||
if isinstance(self.index, ourlang.ConstantPrimitive):
|
||||
assert isinstance(self.index.value, int)
|
||||
assert isinstance(self.type3.args[1], types.IntType3)
|
||||
|
||||
result.append(
|
||||
IntegerCompareConstraint(
|
||||
types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1),
|
||||
comment='Subscript static array must fit the size of the array'
|
||||
)
|
||||
)
|
||||
if self.index.value < 0 or sa_len.value <= self.index.value:
|
||||
return Error('Tuple index out of range')
|
||||
|
||||
return result
|
||||
|
||||
if self.type3.base == types.tuple:
|
||||
# We special case tuples to allow for ease of use to the programmer
|
||||
# e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples
|
||||
# we use a[0] and a[1] and allow for a[2] and on.
|
||||
tp_args = types.tuple_.did_construct(exp_type)
|
||||
if tp_args is not None:
|
||||
if not isinstance(self.index, ourlang.ConstantPrimitive):
|
||||
return Error('Must index with literal')
|
||||
|
||||
if not isinstance(self.index.value, int):
|
||||
return Error('Must index with integer literal')
|
||||
|
||||
if self.index.value < 0 or len(self.type3.args) <= self.index.value:
|
||||
if self.index.value < 0 or len(tp_args) <= self.index.value:
|
||||
return Error('Tuple index out of range')
|
||||
|
||||
return [
|
||||
SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'),
|
||||
SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
|
||||
SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
|
||||
]
|
||||
|
||||
if self.type3 is types.bytes:
|
||||
if exp_type is types.bytes_:
|
||||
return [
|
||||
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
|
||||
SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
|
||||
]
|
||||
|
||||
if self.type3.name in types.LOOKUP_TABLE:
|
||||
return Error(f'{self.type3.name} cannot be subscripted')
|
||||
|
||||
raise NotImplementedError(self.type3)
|
||||
return Error(f'{exp_type.name} cannot be subscripted')
|
||||
|
||||
def human_readable(self) -> HumanReadableRet:
|
||||
return (
|
||||
|
||||
@ -16,6 +16,7 @@ from .constraints import (
|
||||
LiteralFitsConstraint,
|
||||
MustImplementTypeClassConstraint,
|
||||
SameTypeConstraint,
|
||||
TupleMatchConstraint,
|
||||
)
|
||||
|
||||
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
||||
@ -142,9 +143,9 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
|
||||
yield from expression(ctx, arg)
|
||||
r_type.append(arg.type3)
|
||||
|
||||
yield SameTypeConstraint(
|
||||
yield TupleMatchConstraint(
|
||||
inp.type3,
|
||||
type3types.AppliedType3(type3types.tuple, r_type),
|
||||
r_type,
|
||||
comment='The type of a tuple is a combination of its members'
|
||||
)
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@ from .constraints import (
|
||||
)
|
||||
from .constraintsgenerator import phasm_type3_generate_constraints
|
||||
from .types import (
|
||||
AppliedType3,
|
||||
IntType3,
|
||||
PlaceholderForType,
|
||||
PrimitiveType3,
|
||||
@ -153,14 +152,6 @@ def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[in
|
||||
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
|
||||
return placeholder_id_map[placeholder_id]
|
||||
|
||||
if isinstance(inp, AppliedType3):
|
||||
return (
|
||||
get_printable_type_name(inp.base, placeholder_id_map)
|
||||
+ ' ('
|
||||
+ ') ('.join(get_printable_type_name(x, placeholder_id_map) for x in inp.args)
|
||||
+ ')'
|
||||
)
|
||||
|
||||
raise NotImplementedError(inp)
|
||||
|
||||
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
|
||||
|
||||
@ -93,6 +93,8 @@ class Type3Class:
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
|
||||
InternalPassAsPointer = Type3Class('InternalPassAsPointer', ['a'], methods={}, operators={})
|
||||
|
||||
Eq = Type3Class('Eq', ['a'], methods={}, operators={
|
||||
'==': 'a -> a -> bool',
|
||||
'!=': 'a -> a -> bool',
|
||||
|
||||
@ -4,7 +4,19 @@ Contains the final types for use in Phasm
|
||||
These are actual, instantiated types; not the abstract types that the
|
||||
constraint generator works with.
|
||||
"""
|
||||
from typing import Any, Dict, Iterable, List, Optional, Protocol, Set, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Generic,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Protocol,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .typeclasses import (
|
||||
Bits,
|
||||
@ -12,6 +24,7 @@ from .typeclasses import (
|
||||
Floating,
|
||||
Fractional,
|
||||
Integral,
|
||||
InternalPassAsPointer,
|
||||
IntNum,
|
||||
NatNum,
|
||||
Ord,
|
||||
@ -83,7 +96,7 @@ class Type3:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
raise NotImplementedError
|
||||
return hash(self.name)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
raise NotImplementedError
|
||||
@ -98,6 +111,9 @@ class PrimitiveType3(Type3):
|
||||
class IntType3(Type3):
|
||||
"""
|
||||
Sometimes you can have an int as type, e.g. when using static arrays
|
||||
|
||||
This is not the same as an int on the language level.
|
||||
[1.0, 1.2] :: Float[2] :: * -> Int -> *
|
||||
"""
|
||||
|
||||
__slots__ = ('value', )
|
||||
@ -107,7 +123,6 @@ class IntType3(Type3):
|
||||
def __init__(self, value: int) -> None:
|
||||
super().__init__(str(value), [])
|
||||
|
||||
assert 0 <= value
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
@ -119,6 +134,9 @@ class IntType3(Type3):
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
class PlaceholderForType:
|
||||
"""
|
||||
A placeholder type, for when we don't know the final type yet
|
||||
@ -166,64 +184,100 @@ class PlaceholderForType:
|
||||
|
||||
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
|
||||
|
||||
class AppliedType3(Type3):
|
||||
"""
|
||||
A Type3 that has been applied to another type
|
||||
"""
|
||||
__slots__ = ('base', 'args', )
|
||||
T = TypeVar('T')
|
||||
|
||||
base: PrimitiveType3
|
||||
class TypeConstructor(Generic[T]):
|
||||
"""
|
||||
The base type
|
||||
Base class for type construtors
|
||||
"""
|
||||
__slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of the type constructor
|
||||
"""
|
||||
|
||||
args: List[Type3OrPlaceholder]
|
||||
classes: Set[Type3Class]
|
||||
"""
|
||||
The applied types (or placeholders there for)
|
||||
The type classes that this constructor implements
|
||||
"""
|
||||
|
||||
def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None:
|
||||
args = [*args]
|
||||
assert args, 'Must at least one argument'
|
||||
type_classes: Set[Type3Class]
|
||||
"""
|
||||
The type classes that the constructed types implement
|
||||
"""
|
||||
|
||||
super().__init__(
|
||||
base.name
|
||||
+ ' ('
|
||||
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
|
||||
+ ')',
|
||||
[]
|
||||
)
|
||||
_cache: dict[T, PrimitiveType3]
|
||||
"""
|
||||
When constructing a type with the same arguments,
|
||||
it should produce the exact same result.
|
||||
"""
|
||||
|
||||
self.base = base
|
||||
self.args = args
|
||||
_reverse_cache: dict[PrimitiveType3, T]
|
||||
"""
|
||||
Sometimes we need to know the key that created a type.
|
||||
"""
|
||||
|
||||
@property
|
||||
def has_placeholders(self) -> bool:
|
||||
return any(
|
||||
isinstance(x, PlaceholderForType)
|
||||
for x in self.args
|
||||
)
|
||||
def __init__(self, name: str, classes: Iterable[Type3Class], type_classes: Iterable[Type3Class]) -> None:
|
||||
self.name = name
|
||||
self.classes = set(classes)
|
||||
self.type_classes = set(type_classes)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Type3):
|
||||
self._cache = {}
|
||||
self._reverse_cache = {}
|
||||
|
||||
def make_name(self, key: T) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
if not isinstance(other, AppliedType3):
|
||||
return False
|
||||
def did_construct(self, typ: PrimitiveType3) -> T | None:
|
||||
return self._reverse_cache.get(typ)
|
||||
|
||||
return (
|
||||
self.base == other.base
|
||||
and len(self.args) == len(other.args)
|
||||
and all(
|
||||
s == x
|
||||
for s, x in zip(self.args, other.args)
|
||||
)
|
||||
)
|
||||
def construct(self, key: T) -> PrimitiveType3:
|
||||
result = self._cache.get(key, None)
|
||||
if result is None:
|
||||
self._cache[key] = result = PrimitiveType3(self.make_name(key), self.type_classes)
|
||||
self._reverse_cache[result] = key
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
|
||||
return result
|
||||
|
||||
class StructType3(Type3):
|
||||
class TypeConstructor_Type(TypeConstructor[PrimitiveType3]):
|
||||
"""
|
||||
Base class type constructors of kind: * -> *
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, arg: PrimitiveType3) -> PrimitiveType3:
|
||||
raise NotImplementedError
|
||||
|
||||
class TypeConstructor_TypeInt(TypeConstructor[Tuple[PrimitiveType3, IntType3]]):
|
||||
"""
|
||||
Base class type constructors of kind: * -> Int -> *
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str:
|
||||
return f'{self.name} {key[0].name} {key[1].value}'
|
||||
|
||||
def __call__(self, arg0: PrimitiveType3, arg1: IntType3) -> PrimitiveType3:
|
||||
return self.construct((arg0, arg1))
|
||||
|
||||
class TypeConstructor_TypeStar(TypeConstructor[Tuple[PrimitiveType3, ...]]):
|
||||
"""
|
||||
Base class type constructors of variadic kind
|
||||
"""
|
||||
def __call__(self, *args: PrimitiveType3) -> PrimitiveType3:
|
||||
key: Tuple[PrimitiveType3, ...] = tuple(args)
|
||||
return self.construct(key)
|
||||
|
||||
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
|
||||
def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str:
|
||||
return f'{key[0].name}[{key[1].value}]'
|
||||
|
||||
class TypeConstructor_Tuple(TypeConstructor_TypeStar):
|
||||
def make_name(self, key: Tuple[PrimitiveType3, ...]) -> str:
|
||||
return '(' + ', '.join(x.name for x in key) + ', )'
|
||||
|
||||
class StructType3(PrimitiveType3):
|
||||
"""
|
||||
A Type3 struct with named members
|
||||
"""
|
||||
@ -315,20 +369,27 @@ f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum, Ord])
|
||||
A 32-bits IEEE 754 float, of 64 bits width.
|
||||
"""
|
||||
|
||||
bytes = PrimitiveType3('bytes', [])
|
||||
bytes_ = PrimitiveType3('bytes', [])
|
||||
"""
|
||||
This is a runtime-determined length piece of memory that can be indexed at runtime.
|
||||
"""
|
||||
|
||||
static_array = PrimitiveType3('static_array', [])
|
||||
static_array = TypeConstructor_StaticArray('static_array', [], [
|
||||
Eq,
|
||||
InternalPassAsPointer,
|
||||
])
|
||||
"""
|
||||
This is a fixed length piece of memory that can be indexed at runtime.
|
||||
A type constructor.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622
|
||||
tuple_ = TypeConstructor_Tuple('tuple', [], [
|
||||
InternalPassAsPointer,
|
||||
])
|
||||
"""
|
||||
This is a fixed length piece of memory.
|
||||
|
||||
@ -336,7 +397,7 @@ It should be applied with zero or more arguments. It has a compile time
|
||||
determined length, and each argument can be different.
|
||||
"""
|
||||
|
||||
LOOKUP_TABLE: Dict[str, Type3] = {
|
||||
LOOKUP_TABLE: Dict[str, PrimitiveType3] = {
|
||||
'none': none,
|
||||
'bool': bool_,
|
||||
'u8': u8,
|
||||
@ -347,5 +408,5 @@ LOOKUP_TABLE: Dict[str, Type3] = {
|
||||
'i64': i64,
|
||||
'f32': f32,
|
||||
'f64': f64,
|
||||
'bytes': bytes,
|
||||
'bytes': bytes_,
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ from typing import Any, Generator, Iterable, List, TextIO, Union
|
||||
from phasm import compiler
|
||||
from phasm.codestyle import phasm_render
|
||||
from phasm.runtime import calculate_alloc_size
|
||||
from phasm.type3 import typeclasses as type3classes
|
||||
from phasm.type3 import types as type3types
|
||||
|
||||
from . import runners
|
||||
@ -82,18 +83,20 @@ class Suite:
|
||||
wasm_args.append(arg)
|
||||
continue
|
||||
|
||||
if arg_typ is type3types.bytes:
|
||||
if arg_typ is type3types.bytes_:
|
||||
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
|
||||
if isinstance(arg_typ, type3types.AppliedType3):
|
||||
if arg_typ.base is type3types.static_array:
|
||||
assert isinstance(arg_typ, type3types.PrimitiveType3)
|
||||
sa_args = type3types.static_array.did_construct(arg_typ)
|
||||
if sa_args is not None:
|
||||
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
|
||||
if arg_typ.base is type3types.tuple:
|
||||
tp_args = type3types.tuple_.did_construct(arg_typ)
|
||||
if tp_args is not None:
|
||||
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
@ -103,7 +106,7 @@ class Suite:
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(arg)
|
||||
raise NotImplementedError(arg_typ, arg)
|
||||
|
||||
write_header(sys.stderr, 'Memory (pre run)')
|
||||
runner.interpreter_dump_memory(sys.stderr)
|
||||
@ -142,18 +145,7 @@ def _write_memory_stored_value(
|
||||
val_typ: type3types.Type3,
|
||||
val: Any,
|
||||
) -> int:
|
||||
if val_typ is type3types.bytes:
|
||||
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
if isinstance(val_typ, type3types.PrimitiveType3):
|
||||
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
||||
runner.interpreter_write_memory(adr, to_write)
|
||||
return len(to_write)
|
||||
|
||||
if isinstance(val_typ, type3types.AppliedType3):
|
||||
if val_typ.base in (type3types.static_array, type3types.tuple, ):
|
||||
if val_typ is type3types.bytes_:
|
||||
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
@ -163,6 +155,23 @@ def _write_memory_stored_value(
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
if isinstance(val_typ, type3types.PrimitiveType3):
|
||||
sa_args = type3types.static_array.did_construct(val_typ)
|
||||
if sa_args is not None:
|
||||
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
tp_args = type3types.tuple_.did_construct(val_typ)
|
||||
if tp_args is not None:
|
||||
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
||||
runner.interpreter_write_memory(adr, to_write)
|
||||
return len(to_write)
|
||||
|
||||
raise NotImplementedError(val_typ, val)
|
||||
|
||||
def _allocate_memory_stored_value(
|
||||
@ -170,7 +179,7 @@ def _allocate_memory_stored_value(
|
||||
val_typ: type3types.Type3,
|
||||
val: Any
|
||||
) -> int:
|
||||
if val_typ is type3types.bytes:
|
||||
if val_typ is type3types.bytes_:
|
||||
assert isinstance(val, bytes)
|
||||
|
||||
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
|
||||
@ -180,29 +189,30 @@ def _allocate_memory_stored_value(
|
||||
runner.interpreter_write_memory(adr + 4, val)
|
||||
return adr
|
||||
|
||||
if isinstance(val_typ, type3types.AppliedType3):
|
||||
if val_typ.base is type3types.static_array:
|
||||
assert isinstance(val_typ, type3types.PrimitiveType3)
|
||||
sa_args = type3types.static_array.did_construct(val_typ)
|
||||
if sa_args is not None:
|
||||
assert isinstance(val, tuple)
|
||||
|
||||
sa_type, sa_len = sa_args
|
||||
|
||||
alloc_size = calculate_alloc_size(val_typ)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int)
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
val_el_typ = val_typ.args[0]
|
||||
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||
|
||||
tuple_len_obj = val_typ.args[1]
|
||||
assert isinstance(tuple_len_obj, type3types.IntType3)
|
||||
tuple_len = tuple_len_obj.value
|
||||
tuple_len = sa_len.value
|
||||
assert tuple_len == len(val)
|
||||
|
||||
offset = adr
|
||||
for val_el_val in val:
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
|
||||
return adr
|
||||
|
||||
if val_typ.base is type3types.tuple:
|
||||
val_el_typ: type3types.Type3
|
||||
|
||||
tp_args = type3types.tuple_.did_construct(val_typ)
|
||||
if tp_args is not None:
|
||||
assert isinstance(val, tuple)
|
||||
|
||||
alloc_size = calculate_alloc_size(val_typ)
|
||||
@ -210,10 +220,10 @@ def _allocate_memory_stored_value(
|
||||
assert isinstance(adr, int)
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
assert len(val) == len(val_typ.args)
|
||||
assert len(val) == len(tp_args)
|
||||
|
||||
offset = adr
|
||||
for val_el_val, val_el_typ in zip(val, val_typ.args):
|
||||
for val_el_val, val_el_typ in zip(val, tp_args):
|
||||
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
@ -278,21 +288,24 @@ def _load_memory_stored_returned_value(
|
||||
assert isinstance(wasm_value, float), wasm_value
|
||||
return wasm_value
|
||||
|
||||
if ret_type3 is type3types.bytes:
|
||||
if ret_type3 is type3types.bytes_:
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
return _load_bytes_from_address(runner, ret_type3, wasm_value)
|
||||
|
||||
if isinstance(ret_type3, type3types.AppliedType3):
|
||||
if ret_type3.base is type3types.static_array:
|
||||
assert isinstance(ret_type3, type3types.PrimitiveType3) # Type hint
|
||||
|
||||
sa_args = type3types.static_array.did_construct(ret_type3)
|
||||
if sa_args is not None:
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
return _load_static_array_from_address(runner, ret_type3, wasm_value)
|
||||
return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value)
|
||||
|
||||
if ret_type3.base is type3types.tuple:
|
||||
tp_args = type3types.tuple_.did_construct(ret_type3)
|
||||
if tp_args is not None:
|
||||
assert isinstance(wasm_value, int), wasm_value
|
||||
|
||||
return _load_tuple_from_address(runner, ret_type3, wasm_value)
|
||||
return _load_tuple_from_address(runner, tp_args, wasm_value)
|
||||
|
||||
if isinstance(ret_type3, type3types.StructType3):
|
||||
return _load_struct_from_address(runner, ret_type3, wasm_value)
|
||||
@ -334,23 +347,28 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
|
||||
assert len(inp) == 8
|
||||
return struct.unpack('<d', inp)[0]
|
||||
|
||||
if typ is type3types.bytes:
|
||||
if typ is type3types.bytes_:
|
||||
# Note: For bytes, inp should contain a 4 byte pointer
|
||||
assert len(inp) == 4
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
return _load_bytes_from_address(runner, typ, adr)
|
||||
|
||||
if isinstance(typ, type3types.AppliedType3):
|
||||
if type3classes.InternalPassAsPointer in typ.classes:
|
||||
# Note: For applied types, inp should contain a 4 byte pointer
|
||||
assert len(inp) == 4
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
if typ.base is type3types.static_array:
|
||||
return _load_static_array_from_address(runner, typ, adr)
|
||||
assert isinstance(typ, type3types.PrimitiveType3)
|
||||
|
||||
if typ.base is type3types.tuple:
|
||||
return _load_tuple_from_address(runner, typ, adr)
|
||||
sa_args = type3types.static_array.did_construct(typ)
|
||||
if sa_args is not None:
|
||||
sa_type, sa_len = sa_args
|
||||
return _load_static_array_from_address(runner, sa_type, sa_len, adr)
|
||||
|
||||
tp_args = type3types.tuple_.did_construct(typ)
|
||||
if tp_args is not None:
|
||||
return _load_tuple_from_address(runner, tp_args, adr)
|
||||
|
||||
if isinstance(typ, type3types.StructType3):
|
||||
# Note: For structs, inp should contain a 4 byte pointer
|
||||
@ -375,11 +393,8 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
|
||||
yield all_bytes[offset:offset + size]
|
||||
offset += size
|
||||
|
||||
def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any:
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||
|
||||
assert 2 == len(typ.args)
|
||||
sub_typ, len_typ = typ.args
|
||||
def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.PrimitiveType3, len_typ: type3types.IntType3, adr: int) -> Any:
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
|
||||
|
||||
assert not isinstance(sub_typ, type3types.PlaceholderForType)
|
||||
assert isinstance(len_typ, type3types.IntType3)
|
||||
@ -396,29 +411,19 @@ def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.
|
||||
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
||||
)
|
||||
|
||||
def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||
|
||||
assert isinstance(typ, type3types.AppliedType3)
|
||||
assert typ.base is type3types.tuple
|
||||
|
||||
typ_list = [
|
||||
x
|
||||
for x in typ.args
|
||||
if not isinstance(x, type3types.PlaceholderForType)
|
||||
]
|
||||
assert len(typ_list) == len(typ.args)
|
||||
def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.PrimitiveType3, ...], adr: int) -> Any:
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n')
|
||||
|
||||
arg_sizes = [
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for x in typ_list
|
||||
for x in typ_args
|
||||
]
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return tuple(
|
||||
_unpack(runner, arg_typ, arg_bytes)
|
||||
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
||||
for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes))
|
||||
)
|
||||
|
||||
def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
|
||||
|
||||
@ -116,11 +116,11 @@ def testEntry() -> i32:
|
||||
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
'The type of a tuple is a combination of its members',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
else:
|
||||
@ -175,19 +175,14 @@ 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('struct_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
```
|
||||
@ -231,19 +226,14 @@ def select(x: $TYPE) -> (u32, ):
|
||||
```
|
||||
|
||||
```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('struct_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
```
|
||||
@ -287,11 +277,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
# FIXME: Shouldn't this be the same as for the else statement?
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
'The type of a tuple is a combination of its members',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
else:
|
||||
@ -340,19 +330,14 @@ 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('struct_'):
|
||||
expect_type_error(
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
TYPE + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
TYPE_NAME + ' must be (u32, ) instead',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
```
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@ -16,38 +14,3 @@ def testEntry(f: bytes) -> u32:
|
||||
result = Suite(code_py).run_code(b'This yet is another test')
|
||||
|
||||
assert 24 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_bytes_index_ok():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(f: bytes) -> u8:
|
||||
return f[8]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(b'This is another test')
|
||||
|
||||
assert 0x61 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_bytes_index_out_of_bounds():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(f: bytes, g: bytes) -> u8:
|
||||
return f[50]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
|
||||
|
||||
assert 0 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_bytes_index_invalid_type():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(f: bytes) -> u64:
|
||||
return f[50]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'):
|
||||
Suite(code_py).run_code(b'Short')
|
||||
|
||||
@ -1,73 +1,10 @@
|
||||
import pytest
|
||||
import wasmtime
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_static_array_index_ok():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(f: u64[3]) -> u64:
|
||||
return f[2]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code((1, 2, 3, ))
|
||||
|
||||
assert 3 == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_static_array_index_invalid_type():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(f: f32[3]) -> u64:
|
||||
return f[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'u64 must be f32 instead'):
|
||||
Suite(code_py).run_code((0.0, 1.5, 2.25, ))
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_module_constant_type_mismatch_not_subscriptable():
|
||||
code_py = """
|
||||
CONSTANT: u8 = 24
|
||||
|
||||
@exported
|
||||
def testEntry() -> u8:
|
||||
return CONSTANT[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_module_constant_type_mismatch_index_out_of_range_constant():
|
||||
code_py = """
|
||||
CONSTANT: u8[3] = (24, 57, 80, )
|
||||
|
||||
@exported
|
||||
def testEntry() -> u8:
|
||||
return CONSTANT[3]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='3 must be less or equal than 2'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_module_constant_type_mismatch_index_out_of_range_variable():
|
||||
code_py = """
|
||||
CONSTANT: u8[3] = (24, 57, 80, )
|
||||
|
||||
@exported
|
||||
def testEntry(x: u32) -> u8:
|
||||
return CONSTANT[x]
|
||||
"""
|
||||
|
||||
with pytest.raises(wasmtime.Trap):
|
||||
Suite(code_py).run_code(3)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_static_array_constant_too_few_values():
|
||||
code_py = """
|
||||
|
||||
@ -74,5 +74,5 @@ def testEntry(arg: Struct) -> (i32, i32, ):
|
||||
return arg.param
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=type_ + r' must be tuple \(i32\) \(i32\) instead'):
|
||||
with pytest.raises(Type3Exception, match=type_ + r' must be \(i32, i32, \) instead'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
116
tests/integration/test_lang/test_subscriptable.py
Normal file
116
tests/integration/test_lang/test_subscriptable.py
Normal file
@ -0,0 +1,116 @@
|
||||
import pytest
|
||||
import wasmtime
|
||||
|
||||
from phasm.type3.entry import Type3Exception
|
||||
|
||||
from ..helpers import Suite
|
||||
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('(u8, u8, )', (45, 46), 45, ),
|
||||
('u8[2]', (45, 46), 45, ),
|
||||
('bytes', b'This is a test', 84)
|
||||
])
|
||||
def test_subscript_0(type_, in_put, exp_result):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(f: {type_}) -> u8:
|
||||
return f[0]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code(in_put)
|
||||
|
||||
assert exp_result == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||
('(u8, u8, )', (45, 46), 45, ),
|
||||
('u8[2]', (45, 46), 45, ),
|
||||
('bytes', b'This is a test', 84)
|
||||
])
|
||||
def test_subscript_invalid_type(type_, in_put, exp_result):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(f: {type_}) -> u32:
|
||||
return f[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u32 must be u8 instead'):
|
||||
Suite(code_py).run_code(in_put)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_subscript_tuple_must_be_literal():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (u8, u32, u64), y: u8) -> u64:
|
||||
return x[y]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_subscript_tuple_must_be_int():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
return x[0.0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with integer literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put', [
|
||||
('(u8, u8, )', (45, 46), ),
|
||||
('u8[2]', (45, 46), ),
|
||||
# bytes isn't known at runtime so works like normal
|
||||
])
|
||||
def test_subscript_oob_constant_low(type_, in_put):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {type_}) -> u8:
|
||||
return x[-1]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple index out of range'):
|
||||
Suite(code_py).run_code(in_put)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_subscript_oob_constant_high():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
return x[4]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Tuple index out of range'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_, in_put', [
|
||||
# Cannot Subscript tuple without a constant
|
||||
('u8[2]', (45, 46), ),
|
||||
('bytes', b'This is a test', ),
|
||||
])
|
||||
def test_subscript_oob_normal(type_, in_put):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry(x: {type_}, y: u32) -> u8:
|
||||
return x[y]
|
||||
"""
|
||||
|
||||
with pytest.raises(wasmtime.Trap):
|
||||
Suite(code_py).run_code(in_put, 255)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_subscript_not_subscriptable():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: u8) -> u8:
|
||||
return x[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
|
||||
Suite(code_py).run_code()
|
||||
@ -70,25 +70,3 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_must_use_literal_for_indexing():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (u8, u32, u64), y: u8) -> u64:
|
||||
return x[y]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_must_use_integer_for_indexing():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry(x: (u8, u32, u64)) -> u64:
|
||||
return x[0.0]
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match='Must index with integer literal'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user