diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index edfdca6..449619a 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -135,12 +135,30 @@ 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 len(first_type.args) != len(typ.args): - return Error('Mismatch between applied types argument count', comment=self.comment) + 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 @@ -365,11 +383,11 @@ class LiteralFitsConstraint(ConstraintBase): 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 Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information 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: _ = float_table[self.type3.name] @@ -379,13 +397,13 @@ class LiteralFitsConstraint(ConstraintBase): 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 isinstance(self.literal.value, bytes): 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 diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index c054077..c14c393 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -91,6 +91,9 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: constraint_list = new_constraint_list + if verbose: + print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes) + if constraint_list: raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations') diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index 43636a0..c53ab86 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -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 ```py -CONSTANT: $OTHER_TYPE = $VAL0 - -@exported -def testEntry() -> i32: - return 0 +CONSTANT: (u32, ) = $VAL0 ``` ```py -if TYPE_NAME.startswith('tuple_'): +if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'): expect_type_error( 'Tuple element count mismatch', 'The given literal must fit the expected type', @@ -43,3 +39,263 @@ else: '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', + ) +``` diff --git a/tests/integration/test_lang/generator.py b/tests/integration/test_lang/generator.py index 5538fd5..57562fb 100644 --- a/tests/integration/test_lang/generator.py +++ b/tests/integration/test_lang/generator.py @@ -110,8 +110,6 @@ def main(): if 'TYPE_NAME' not in settings: settings['TYPE_NAME'] = settings['TYPE'] - settings['OTHER_TYPE'] = '(u32, )' - generate_code(markdown, template, settings) if __name__ == '__main__': diff --git a/tests/integration/test_lang/generator_bytes.json b/tests/integration/test_lang/generator_bytes.json new file mode 100644 index 0000000..bde3ec8 --- /dev/null +++ b/tests/integration/test_lang/generator_bytes.json @@ -0,0 +1,4 @@ +{ + "TYPE": "bytes", + "VAL0": "b'ABCDEFG'" +} diff --git a/tests/integration/test_lang/generator_f32.json b/tests/integration/test_lang/generator_f32.json new file mode 100644 index 0000000..2fbc930 --- /dev/null +++ b/tests/integration/test_lang/generator_f32.json @@ -0,0 +1,4 @@ +{ + "TYPE": "f32", + "VAL0": "1000000.125" +} diff --git a/tests/integration/test_lang/generator_f64.json b/tests/integration/test_lang/generator_f64.json new file mode 100644 index 0000000..f57ecea --- /dev/null +++ b/tests/integration/test_lang/generator_f64.json @@ -0,0 +1,4 @@ +{ + "TYPE": "f64", + "VAL0": "1000000.125" +} diff --git a/tests/integration/test_lang/generator_i32.json b/tests/integration/test_lang/generator_i32.json new file mode 100644 index 0000000..14e1b83 --- /dev/null +++ b/tests/integration/test_lang/generator_i32.json @@ -0,0 +1,4 @@ +{ + "TYPE": "i32", + "VAL0": "1000000" +} diff --git a/tests/integration/test_lang/generator_i64.json b/tests/integration/test_lang/generator_i64.json new file mode 100644 index 0000000..a4d6439 --- /dev/null +++ b/tests/integration/test_lang/generator_i64.json @@ -0,0 +1,4 @@ +{ + "TYPE": "i64", + "VAL0": "1000000" +} diff --git a/tests/integration/test_lang/generator_static_array_u64_32.json b/tests/integration/test_lang/generator_static_array_u64_32.json new file mode 100644 index 0000000..5d65de6 --- /dev/null +++ b/tests/integration/test_lang/generator_static_array_u64_32.json @@ -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, )" +} diff --git a/tests/integration/test_lang/generator_u64.json b/tests/integration/test_lang/generator_u64.json new file mode 100644 index 0000000..174ca14 --- /dev/null +++ b/tests/integration/test_lang/generator_u64.json @@ -0,0 +1,4 @@ +{ + "TYPE": "u64", + "VAL0": "1000000" +} diff --git a/tests/integration/test_lang/test_primitives.py b/tests/integration/test_lang/test_primitives.py index 41a8211..47633e1 100644 --- a/tests/integration/test_lang/test_primitives.py +++ b/tests/integration/test_lang/test_primitives.py @@ -44,49 +44,6 @@ def testEntry() -> u8: with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): 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.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation def test_logical_left_shift(type_): @@ -338,20 +295,6 @@ def testEntry() -> {type_}: assert 5 == 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.skip('TODO') def test_explicit_positive_number(): @@ -378,21 +321,6 @@ def testEntry() -> i32: 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 def test_call_pre_defined(): code_py = """