diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 1fbc7e4..70fee26 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -6,6 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer from typing import Generator from . import ourlang +from .type3 import types as type3types from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder def phasm_render(inp: ourlang.Module) -> str: @@ -34,6 +35,12 @@ def type3(inp: Type3OrPlaceholder) -> str: """ assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR + if isinstance(inp, type3types.AppliedType3) and inp.base is type3types.tuple: + return '(' + ', '.join( + type3(x) + for x in inp.args + ) + ', )' + return inp.name def struct_definition(inp: ourlang.StructDefinition) -> str: diff --git a/phasm/compiler.py b/phasm/compiler.py index 500f182..2629ade 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -186,6 +186,12 @@ 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.StructType3): + assert inp.variable.data_block is not None, 'Structs must be memory stored' + assert inp.variable.data_block.address is not None, 'Value not allocated' + wgn.i32.const(inp.variable.data_block.address) + return + # TODO: Broken after new type system # if isinstance(inp.type, typing.TypeTuple): # assert isinstance(inp.definition.constant, ourlang.ConstantTuple) @@ -634,7 +640,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes: data_list: List[bytes] = [] for constant in block.data: - assert constant.type3 is not None + assert isinstance(constant.type3, type3types.Type3), (id(constant), type3types.TYPE3_ASSERTION_ERROR) if constant.type3 is type3types.u8: assert isinstance(constant.value, int) diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 9b7d73e..2643c97 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -33,7 +33,9 @@ class RequireTypeSubstitutes: SubstitutionMap = Dict[types.PlaceholderForType, types.Type3] -CheckResult = Union[None, SubstitutionMap, Error, RequireTypeSubstitutes] +NewConstraintList = List['ConstraintBase'] + +CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes] HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]] @@ -231,74 +233,82 @@ class LiteralFitsConstraint(ConstraintBase): 'f64': None, } - def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct]) -> CheckResult: - if isinstance(type3, types.PlaceholderForType): - if type3 not in smap: - return RequireTypeSubstitutes() + if isinstance(self.type3, types.PlaceholderForType): + if self.type3 not in smap: + return RequireTypeSubstitutes() - type3 = smap[type3] + self.type3 = smap[self.type3] - val = literal.value + if self.type3.name in int_table: + bts, sgn = int_table[self.type3.name] - if type3.name in int_table: - bts, sgn = int_table[type3.name] - - if isinstance(val, int): - try: - val.to_bytes(bts, 'big', signed=sgn) - except OverflowError: - return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information - - return None - - return Error('Must be integer') # FIXME: Add line information - - - if type3.name in float_table: - _ = float_table[type3.name] - - if isinstance(val, float): - # FIXME: Bit check - - return None - - return Error('Must be real') # FIXME: Add line information - - if isinstance(type3, types.AppliedType3) and type3.base is types.tuple: - if not isinstance(literal, ourlang.ConstantTuple): - return Error('Must be tuple') - - assert isinstance(val, list) # type hint - - if len(type3.args) != len(val): - return Error('Tuple element count mismatch') - - for elt_typ, elt_lit in zip(type3.args, val): - res = _check(elt_typ, elt_lit) - if res is not None: - return res + if isinstance(self.literal.value, int): + try: + self.literal.value.to_bytes(bts, 'big', signed=sgn) + except OverflowError: + return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information return None - if isinstance(type3, types.StructType3): - if not isinstance(literal, ourlang.ConstantStruct): - return Error('Must be struct') + return Error('Must be integer') # FIXME: Add line information - assert isinstance(val, list) # type hint + if self.type3.name in float_table: + _ = float_table[self.type3.name] - if len(type3.members) != len(val): - return Error('Struct element count mismatch') - - for elt_typ, elt_lit in zip(type3.members.values(), val): - res = _check(elt_typ, elt_lit) - if res is not None: - return res + if isinstance(self.literal.value, float): + # FIXME: Bit check return None - raise NotImplementedError(type3, literal) + return Error('Must be real') # FIXME: Add line information - return _check(self.type3, self.literal) + res: NewConstraintList + + if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple: + if not isinstance(self.literal, ourlang.ConstantTuple): + return Error('Must be tuple') + + if len(self.type3.args) != len(self.literal.value): + return Error('Tuple element count mismatch') + + res = [] + + res.extend( + LiteralFitsConstraint(x, y) + for x, y in zip(self.type3.args, self.literal.value) + ) + res.extend( + SameTypeConstraint(x, y.type3) + for x, y in zip(self.type3.args, self.literal.value) + ) + + return res + + if isinstance(self.type3, types.StructType3): + if not isinstance(self.literal, ourlang.ConstantStruct): + return Error('Must be struct') + + if self.literal.struct_name != self.type3.name: + return Error('Struct mismatch') + + + if len(self.type3.members) != len(self.literal.value): + return Error('Struct element count mismatch') + + res = [] + + res.extend( + LiteralFitsConstraint(x, y) + for x, y in zip(self.type3.members.values(), self.literal.value) + ) + res.extend( + SameTypeConstraint(x_t, y.type3, comment=f'{self.literal.struct_name}.{x_n}') + for (x_n, x_t, ), y in zip(self.type3.members.items(), self.literal.value) + ) + + return res + + raise NotImplementedError(self.type3, self.literal) def human_readable(self) -> HumanReadableRet: return ( diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index d28754a..1ffc610 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -107,6 +107,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas return if isinstance(inp, ourlang.AccessStructMember): + yield from expression(ctx, inp.varref) yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3, comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}') return diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index c1b68e3..f119d95 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -65,6 +65,14 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: print('-> Back on the todo list') continue + if isinstance(check_result, list): + new_constraint_list.extend(check_result) + + if verbose: + print_constraint(placeholder_id_map, constraint) + print(f'-> Resulted in {len(check_result)} new constraints') + continue + raise NotImplementedError(constraint, check_result) if not new_constraint_list: diff --git a/tests/integration/test_lang/test_struct.py b/tests/integration/test_lang/test_struct.py index cffd29e..bcef772 100644 --- a/tests/integration/test_lang/test_struct.py +++ b/tests/integration/test_lang/test_struct.py @@ -7,6 +7,25 @@ from ..constants import ( ) from ..helpers import Suite +@pytest.mark.integration_test +def test_module_constant_def(): + code_py = """ +class SomeStruct: + value0: u8 + value1: u32 + value2: u64 + +CONSTANT: SomeStruct = SomeStruct(250, 250000, 250000000) + +@exported +def testEntry() -> i32: + return 0 +""" + + result = Suite(code_py).run_code() + + assert 0 == result.returned_value + @pytest.mark.integration_test @pytest.mark.parametrize('type_', ALL_INT_TYPES) def test_module_constant(type_): @@ -85,6 +104,29 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32: assert 545 == result.returned_value +@pytest.mark.integration_test +def test_returned_struct(): + code_py = """ +class CheckedValue: + value: u8 + +CONSTANT: CheckedValue = CheckedValue(199) + +def helper() -> CheckedValue: + return CONSTANT + +def helper2(x: CheckedValue) -> u8: + return x.value + +@exported +def testEntry() -> u8: + return helper2(helper()) +""" + + result = Suite(code_py).run_code() + + assert 199 == result.returned_value + @pytest.mark.integration_test def test_type_mismatch_arg_module_constant(): code_py = """ @@ -94,7 +136,7 @@ class Struct: STRUCT: Struct = Struct(1) """ - with pytest.raises(Type3Exception, match='todo'): + with pytest.raises(Type3Exception, match='Must be real'): Suite(code_py).run_code() @pytest.mark.integration_test diff --git a/tests/integration/test_lang/test_tuple.py b/tests/integration/test_lang/test_tuple.py index a08b382..1e29da8 100644 --- a/tests/integration/test_lang/test_tuple.py +++ b/tests/integration/test_lang/test_tuple.py @@ -5,6 +5,20 @@ from phasm.type3.entry import Type3Exception from ..constants import COMPLETE_NUMERIC_TYPES, TYPE_MAP from ..helpers import Suite +@pytest.mark.integration_test +def test_module_constant_def(): + code_py = """ +CONSTANT: (u8, u32, u64, ) = (250, 250000, 250000000, ) + +@exported +def testEntry() -> i32: + return 0 +""" + + result = Suite(code_py).run_code() + + assert 0 == result.returned_value + @pytest.mark.integration_test @pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ]) def test_module_constant_1(type_):