diff --git a/artifacts/after-binaryop-assignment.png b/artifacts/after-binaryop-assignment.png new file mode 100644 index 00000000..8ae717a3 Binary files /dev/null and b/artifacts/after-binaryop-assignment.png differ diff --git a/artifacts/before-binaryop-assignment.png b/artifacts/before-binaryop-assignment.png new file mode 100644 index 00000000..1c04b220 Binary files /dev/null and b/artifacts/before-binaryop-assignment.png differ diff --git a/src/irx/builders/llvmliteir.py b/src/irx/builders/llvmliteir.py index 4495f8f7..5beaddf8 100644 --- a/src/irx/builders/llvmliteir.py +++ b/src/irx/builders/llvmliteir.py @@ -908,39 +908,10 @@ def visit(self, node: astx.BinaryOp) -> None: type: astx.BinaryOp """ if node.op_code == "=": - # Special case '=' because we don't want to emit the lhs as an - # expression. - # Assignment requires the lhs to be an identifier. - # This assumes we're building without RTTI because LLVM builds - # that way by default. - # If you build LLVM with RTTI, this can be changed to a - # dynamic_cast for automatic error checking. - var_lhs = node.lhs - - if not isinstance(var_lhs, astx.VariableExprAST): - raise Exception("destination of '=' must be a variable") - - lhs_name = var_lhs.get_name() - if lhs_name in self.const_vars: - raise Exception( - f"Cannot assign to '{lhs_name}': declared as constant" - ) - # Codegen the rhs. - self.visit(node.rhs) - llvm_rhs = safe_pop(self.result_stack) - - if not llvm_rhs: - raise Exception("codegen: Invalid rhs expression.") - - llvm_lhs = self.named_values.get(var_lhs.get_name()) - - if not llvm_lhs: - raise Exception("codegen: Invalid lhs variable name") - - self._llvm.ir_builder.store(llvm_rhs, llvm_lhs) - result = llvm_rhs - self.result_stack.append(result) - return + raise Exception( + "Assignment '=' should not be handled in BinaryOp. " + "Use VariableAssignment instead." + ) self.visit(node.lhs) llvm_lhs = safe_pop(self.result_stack) diff --git a/tests/test_binary_op.py b/tests/test_binary_op.py index ec11a03f..de99fcbf 100644 --- a/tests/test_binary_op.py +++ b/tests/test_binary_op.py @@ -255,3 +255,44 @@ def test_binary_op_logical_and_or( module.block.append(main_fn) check_result("build", builder, module, expected_output=expect) + + +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_binary_op_rejects_assignment_operator( + builder_class: type[Builder], +) -> None: + """ + title: Verify BinaryOp does not handle assignment. + parameters: + builder_class: + type: type[Builder] + """ + builder = builder_class() + module = builder.module() + + decl = astx.InlineVariableDeclaration( + name="x", + type_=astx.Int32(), + value=astx.LiteralInt32(0), + mutability=astx.MutabilityKind.mutable, + ) + invalid_assign = astx.BinaryOp( + op_code="=", lhs=astx.Identifier("x"), rhs=astx.LiteralInt32(5) + ) + + proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=astx.Int32() + ) + fn_block = astx.Block() + fn_block.append(decl) + fn_block.append(invalid_assign) + fn_block.append(astx.FunctionReturn(astx.LiteralInt32(0))) + fn_main = astx.FunctionDef(prototype=proto, body=fn_block) + + module.block.append(fn_main) + + with pytest.raises( + Exception, + match="Assignment '=' should not be handled in BinaryOp", + ): + builder.translate(module) diff --git a/tests/test_variable_assignment.py b/tests/test_variable_assignment.py index bf0b28b2..d57535b1 100644 --- a/tests/test_variable_assignment.py +++ b/tests/test_variable_assignment.py @@ -7,6 +7,7 @@ from irx.builders.base import Builder from irx.builders.llvmliteir import LLVMLiteIR +from irx.system import PrintExpr from .conftest import check_result @@ -66,3 +67,39 @@ def test_variable_assignment( expected_output = "42" check_result("build", builder, module, expected_output=expected_output) + + +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_variable_assignment_prints_updated_value( + builder_class: type[Builder], +) -> None: + """ + title: Verify VariableAssignment remains the valid assignment path. + parameters: + builder_class: + type: type[Builder] + """ + builder = builder_class() + module = builder.module() + + decl = astx.InlineVariableDeclaration( + name="x", + type_=astx.Int32(), + value=astx.LiteralInt32(0), + mutability=astx.MutabilityKind.mutable, + ) + assignment = astx.VariableAssignment(name="x", value=astx.LiteralInt32(5)) + + proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=astx.Int32() + ) + fn_block = astx.Block() + fn_block.append(decl) + fn_block.append(assignment) + fn_block.append(PrintExpr(astx.Identifier("x"))) + fn_block.append(astx.FunctionReturn(astx.LiteralInt32(0))) + fn_main = astx.FunctionDef(prototype=proto, body=fn_block) + + module.block.append(fn_main) + + check_result("build", builder, module, expected_output="5")