diff --git a/py2wasm/ourlang.py b/py2wasm/ourlang.py index 622a321..ba87aec 100644 --- a/py2wasm/ourlang.py +++ b/py2wasm/ourlang.py @@ -97,7 +97,7 @@ class TupleMember: class OurTypeTuple(OurType): """ - The tuple type, 64 bits wide + The tuple type """ __slots__ = ('members', ) @@ -842,8 +842,14 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - if node.id in our_locals: - return VariableReference(our_locals[node.id], node.id) + if node.id not in our_locals: + _raise_static_error(node, 'Undefined variable') + + act_type = our_locals[node.id] + if exp_type != act_type: + _raise_static_error(node, f'Expected {exp_type.render()}, {node.id} is actually {act_type.render()}') + + return VariableReference(act_type, node.id) if isinstance(node, ast.Tuple): if not isinstance(node.ctx, ast.Load): @@ -884,7 +890,7 @@ class OurVisitor: func = module.functions[struct_constructor.name] elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS: if not isinstance(exp_type, (OurTypeFloat32, OurTypeFloat64, )): - _raise_static_error(node, f'Cannot make square root result in {exp_type}') + _raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}') if 1 != len(node.args): _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') @@ -901,7 +907,7 @@ class OurVisitor: func = module.functions[node.func.id] if func.returns != exp_type: - _raise_static_error(node, f'Function {node.func.id} does not return {exp_type.render()}') + _raise_static_error(node, f'Expected {exp_type.render()}, {func.name} actually returns {func.returns.render()}') if len(func.posonlyargs) != len(node.args): _raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given') @@ -932,7 +938,7 @@ class OurVisitor: _raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}') if exp_type != member.type: - _raise_static_error(node, f'Expected {exp_type.render()}, got {member.type.render()} instead') + _raise_static_error(node, f'Expected {exp_type.render()}, {node.value.id}.{member.name} is actually {member.type.render()}') return AccessStructMember( VariableReference(node_typ, node.value.id), @@ -949,7 +955,8 @@ class OurVisitor: if not isinstance(node.slice.value, ast.Constant): _raise_static_error(node, 'Must subscript using a constant index') # FIXME: Implement variable indexes - if not isinstance(node.slice.value.value, int): + idx = node.slice.value.value + if not isinstance(idx, int): _raise_static_error(node, 'Must subscript using a constant integer index') if not isinstance(node.ctx, ast.Load): @@ -962,12 +969,12 @@ class OurVisitor: if not isinstance(node_typ, OurTypeTuple): _raise_static_error(node, f'Cannot take index of non-tuple {node.value.id}') - if len(node_typ.members) <= node.slice.value.value: - _raise_static_error(node, f'Index {node.slice.value.value} out of bounds for tuple {node.value.id}') + if len(node_typ.members) <= idx: + _raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}') - member = node_typ.members[node.slice.value.value] + member = node_typ.members[idx] if exp_type != member.type: - _raise_static_error(node, f'Expected {exp_type.render()}, got {member.type.render()} instead') + _raise_static_error(node, f'Expected {exp_type.render()}, {node.value.id}[{idx}] is actually {member.type.render()}') return AccessTupleMember( VariableReference(node_typ, node.value.id), diff --git a/tests/integration/test_static_checking.py b/tests/integration/test_static_checking.py new file mode 100644 index 0000000..f3de90b --- /dev/null +++ b/tests/integration/test_static_checking.py @@ -0,0 +1,56 @@ +import pytest + +from py2wasm.utils import our_process + +from py2wasm.ourlang import StaticError + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +def test_type_mismatch_function_argument(type_): + code_py = f""" +def helper(a: {type_}) -> (i32, i32, ): + return a +""" + + with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), a is actually {type_}'): + our_process(code_py, 'test') + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +def test_type_mismatch_struct_member(type_): + code_py = f""" +class Struct: + param: {type_} + +def testEntry(arg: Struct) -> (i32, i32, ): + return arg.param +""" + + with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'): + our_process(code_py, 'test') + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +def test_type_mismatch_tuple_member(type_): + code_py = f""" +def testEntry(arg: ({type_}, )) -> (i32, i32, ): + return arg[0] +""" + + with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'): + our_process(code_py, 'test') + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) +def test_type_mismatch_function_result(type_): + code_py = f""" +def helper() -> {type_}: + return 1 + +@exported +def testEntry() -> (i32, i32, ): + return helper() +""" + + with pytest.raises(StaticError, match=f'Static error on line 7: Expected \\(i32, i32, \\), helper actually returns {type_}'): + our_process(code_py, 'test')