Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added artifacts/after-binaryop-assignment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/before-binaryop-assignment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 4 additions & 33 deletions src/irx/builders/llvmliteir.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
41 changes: 41 additions & 0 deletions tests/test_binary_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
37 changes: 37 additions & 0 deletions tests/test_variable_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
Loading