Constraints can now explode into more constraints

What could possibly go wrong.

All struct tests seem to pass.
This commit is contained in:
Johan B.W. de Vries 2022-12-24 15:07:21 +01:00
parent 7ce3f0f11c
commit d4d5e9e482
7 changed files with 147 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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