More tests and fixes

This commit is contained in:
Johan B.W. de Vries 2023-11-11 15:19:33 +01:00
parent ff0286bcf6
commit dff5feed86
12 changed files with 318 additions and 86 deletions

View File

@ -135,12 +135,30 @@ class SameTypeConstraint(ConstraintBase):
first_type = known_types[0] first_type = known_types[0]
for typ in known_types[1:]: for typ in known_types[1:]:
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3): if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
if len(first_type.args) != len(typ.args): 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) 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: if first_type.base != typ.base:
return Error('Mismatch between applied types base', comment=self.comment) 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): for first_type_arg, typ_arg in zip(first_type.args, typ.args):
new_constraint_list.append(SameTypeConstraint( new_constraint_list.append(SameTypeConstraint(
first_type_arg, typ_arg first_type_arg, typ_arg
@ -365,11 +383,11 @@ class LiteralFitsConstraint(ConstraintBase):
try: try:
self.literal.value.to_bytes(bts, 'big', signed=sgn) self.literal.value.to_bytes(bts, 'big', signed=sgn)
except OverflowError: except OverflowError:
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information return Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information
return None return None
return Error('Must be integer') # FIXME: Add line information return Error('Must be integer', comment=self.comment) # FIXME: Add line information
if self.type3.name in float_table: if self.type3.name in float_table:
_ = float_table[self.type3.name] _ = float_table[self.type3.name]
@ -379,13 +397,13 @@ class LiteralFitsConstraint(ConstraintBase):
return None return None
return Error('Must be real') # FIXME: Add line information 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): if isinstance(self.literal.value, bytes):
return None return None
return Error('Must be bytes') # FIXME: Add line information return Error('Must be bytes', comment=self.comment) # FIXME: Add line information
res: NewConstraintList res: NewConstraintList

View File

@ -91,6 +91,9 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = new_constraint_list constraint_list = new_constraint_list
if verbose:
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
if constraint_list: if constraint_list:
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations') raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')

View File

@ -24,15 +24,11 @@ I want to receive a type error on an invalid assignment on a $TYPE module consta
In order to make debugging easier In order to make debugging easier
```py ```py
CONSTANT: $OTHER_TYPE = $VAL0 CONSTANT: (u32, ) = $VAL0
@exported
def testEntry() -> i32:
return 0
``` ```
```py ```py
if TYPE_NAME.startswith('tuple_'): if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
expect_type_error( expect_type_error(
'Tuple element count mismatch', 'Tuple element count mismatch',
'The given literal must fit the expected type', 'The given literal must fit the expected type',
@ -43,3 +39,263 @@ else:
'The given literal must fit the expected type', 'The given literal must fit the expected type',
) )
``` ```
# function_result_is_literal_ok
As a developer
I want to use return a literal from a function
In order to define constants in a more dynamic way
```py
def drop_arg_return_9(x: $TYPE) -> i32:
return 9
def constant() -> $TYPE:
return $VAL0
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
expect(9)
```
# function_result_is_literal_bad
As a developer
I want to receive a type error when returning a $TYPE literal for a function that doesn't return that type
In order to make debugging easier
```py
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def constant() -> (u32, ):
return $VAL0
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
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',
)
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
```
# function_result_is_module_constant_ok
As a developer
I want to use return a $TYPE module constant from a function
In order to use my module constants in return statements
```py
CONSTANT: $TYPE = $VAL0
def helper(x: $TYPE) -> i32:
return 9
def constant() -> $TYPE:
return CONSTANT
@exported
def testEntry() -> i32:
return helper(constant())
```
```py
expect(9)
```
# function_result_is_module_constant_bad
As a developer
I want to receive a type error when returning a $TYPE module constant for a function that doesn't return that type
In order to make debugging easier
```py
CONSTANT: $TYPE = $VAL0
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def constant() -> (u32, ):
return CONSTANT
@exported
def testEntry() -> i32:
return drop_arg_return_9(constant())
```
```py
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',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
'The type of the value returned from function constant should match its return type',
)
```
# function_result_is_arg_ok
As a developer
I want to use return a $TYPE function argument
In order to make it possible to select a value using a function
```py
CONSTANT: $TYPE = $VAL0
def drop_arg_return_9(x: $TYPE) -> i32:
return 9
def select(x: $TYPE) -> $TYPE:
return x
@exported
def testEntry() -> i32:
return drop_arg_return_9(select(CONSTANT))
```
```py
expect(9)
```
# function_result_is_arg_bad
As a developer
I want to receive a type error when returning a $TYPE argument for a function that doesn't return that type
In order to make debugging easier
```py
def drop_arg_return_9(x: (u32, )) -> i32:
return 9
def select(x: $TYPE) -> (u32, ):
return x
```
```py
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 select should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be tuple (u32) instead',
'The type of the value returned from function select should match its return type',
)
```
# function_arg_literal_ok
As a developer
I want to use a $TYPE literal by passing it to a function
In order to use a pre-existing function with the values I specify
```py
def helper(x: $TYPE) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper($VAL0)
```
```py
expect(9)
```
# function_arg_literal_bad
As a developer
I want to receive a type error when passing a $TYPE literal to a function that does not accept it
In order to make debugging easier
```py
def helper(x: (u32, )) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper($VAL0)
```
```py
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',
)
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
```
# function_arg_module_constant_def_ok
As a developer
I want to use a $TYPE module constant by passing it to a function
In order to use my defined value with a pre-existing function
```py
CONSTANT: $TYPE = $VAL0
def helper(x: $TYPE) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper(CONSTANT)
```
```py
expect(9)
```
# function_arg_module_constant_def_bad
As a developer
I want to receive a type error when passing a $TYPE module constant to a function that does not accept it
In order to make debugging easier
```py
CONSTANT: $TYPE = $VAL0
def helper(x: (u32, )) -> i32:
return 9
@exported
def testEntry() -> i32:
return helper(CONSTANT)
```
```py
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 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',
'The type of the value passed to argument x of function helper should match the type of that argument',
)
```

View File

@ -110,8 +110,6 @@ def main():
if 'TYPE_NAME' not in settings: if 'TYPE_NAME' not in settings:
settings['TYPE_NAME'] = settings['TYPE'] settings['TYPE_NAME'] = settings['TYPE']
settings['OTHER_TYPE'] = '(u32, )'
generate_code(markdown, template, settings) generate_code(markdown, template, settings)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,4 @@
{
"TYPE": "bytes",
"VAL0": "b'ABCDEFG'"
}

View File

@ -0,0 +1,4 @@
{
"TYPE": "f32",
"VAL0": "1000000.125"
}

View File

@ -0,0 +1,4 @@
{
"TYPE": "f64",
"VAL0": "1000000.125"
}

View File

@ -0,0 +1,4 @@
{
"TYPE": "i32",
"VAL0": "1000000"
}

View File

@ -0,0 +1,4 @@
{
"TYPE": "i64",
"VAL0": "1000000"
}

View File

@ -0,0 +1,5 @@
{
"TYPE_NAME": "static_array_u64_32",
"TYPE": "u64[32]",
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
}

View File

@ -0,0 +1,4 @@
{
"TYPE": "u64",
"VAL0": "1000000"
}

View File

@ -44,49 +44,6 @@ def testEntry() -> u8:
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_module_constant_int(type_):
code_py = f"""
CONSTANT: {type_} = 13
@exported
def testEntry() -> {type_}:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_module_constant_float(type_):
code_py = f"""
CONSTANT: {type_} = 32.125
@exported
def testEntry() -> {type_}:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert 32.125 == result.returned_value
@pytest.mark.integration_test
def test_module_constant_type_failure():
code_py = """
CONSTANT: u8 = 1000
@exported
def testEntry() -> u32:
return 14
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation @pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
def test_logical_left_shift(type_): def test_logical_left_shift(type_):
@ -338,20 +295,6 @@ def testEntry() -> {type_}:
assert 5 == result.returned_value assert 5 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_function_argument(type_):
code_py = f"""
@exported
def testEntry(a: {type_}) -> {type_}:
return a
"""
result = Suite(code_py).run_code(125)
assert 125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.skip('TODO') @pytest.mark.skip('TODO')
def test_explicit_positive_number(): def test_explicit_positive_number():
@ -378,21 +321,6 @@ def testEntry() -> i32:
assert -19 == result.returned_value assert -19 == result.returned_value
@pytest.mark.integration_test
def test_call_no_args():
code_py = """
def helper() -> i32:
return 19
@exported
def testEntry() -> i32:
return helper()
"""
result = Suite(code_py).run_code()
assert 19 == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test
def test_call_pre_defined(): def test_call_pre_defined():
code_py = """ code_py = """