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:
Johan B.W. de Vries 2025-04-11 15:50:52 +02:00
parent 87866cff55
commit 234bfaa8df
19 changed files with 473 additions and 479 deletions

13
TODO.md
View File

@ -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?

View File

@ -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:

View File

@ -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,14 +295,8 @@ 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
return wasm.WasmTypeInt32()
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,24 +439,9 @@ 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)
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)
if isinstance(inp.type3, type3types.PrimitiveType3):
expression(wgn, inp.variable.constant)
return
raise NotImplementedError(expression, inp)
@ -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)

View File

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

View File

@ -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')

View File

@ -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)

View File

@ -48,21 +48,20 @@ 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 less than the length
# The offset is outside the allocated bytes
g.unreachable(comment='Out of bounds')
g.local.get(adr)
g.i32.const(4) # Bytes header
g.i32.add()
g.local.get(ofs)
g.i32.add()
g.i32.load8_u()
g.return_()
# The offset is less than the length
# The offset is outside the allocated bytes
g.i32.const(0)
g.local.get(adr)
g.i32.const(4) # Bytes header
g.i32.add()
g.local.get(ofs)
g.i32.add()
g.i32.load8_u()
g.return_()
return i32('return') # To satisfy mypy

View File

@ -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 (

View File

@ -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'
)

View File

@ -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:

View File

@ -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',

View File

@ -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):
raise NotImplementedError
self._cache = {}
self._reverse_cache = {}
if not isinstance(other, AppliedType3):
return False
def make_name(self, key: T) -> str:
raise NotImplementedError
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 did_construct(self, typ: PrimitiveType3) -> T | None:
return self._reverse_cache.get(typ)
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.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
class StructType3(Type3):
return result
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_,
}

View File

@ -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,27 +145,33 @@ def _write_memory_stored_value(
val_typ: type3types.Type3,
val: Any,
) -> int:
if val_typ is type3types.bytes:
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, ):
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.StructType3):
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):
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:

View File

@ -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',
)
```

View File

@ -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')

View File

@ -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 = """

View File

@ -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()

View 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()

View File

@ -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()