compound assignment
This commit is contained in:
@@ -3047,6 +3047,35 @@ main :: func() void {}
|
||||
testing.expect_value(t, hir_module.globals[2].static_value, i64(-3))
|
||||
}
|
||||
|
||||
@(test)
|
||||
constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) {
|
||||
text := `value :: 5 / 0
|
||||
main :: func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_division_by_zero := false
|
||||
found_overflow := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_division_by_zero = found_division_by_zero ||
|
||||
strings.contains(diagnostic.message, "division by zero in constant expression")
|
||||
found_overflow = found_overflow ||
|
||||
strings.contains(diagnostic.message, "integer constant expression exceeds signed i64 range")
|
||||
}
|
||||
testing.expect(t, found_division_by_zero)
|
||||
testing.expect(t, !found_overflow)
|
||||
}
|
||||
|
||||
@(test)
|
||||
out_of_range_negative_constants_are_diagnosed :: proc(t: ^testing.T) {
|
||||
text := `positive :: 9223372036854775808
|
||||
@@ -4728,3 +4757,477 @@ main :: func() i32 {
|
||||
testing.expect_value(t, index_address_count, 0)
|
||||
testing.expect(t, !strings.contains(llvm_text, "index_ok"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
lexer_emits_compound_assignment_and_slash_tokens :: proc(t: ^testing.T) {
|
||||
source_file := source.Source{path="test.bro", text="+= -= *= /= / *"}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, stream.items[0].kind, token.Kind.Plus_Equal)
|
||||
testing.expect_value(t, stream.items[1].kind, token.Kind.Minus_Equal)
|
||||
testing.expect_value(t, stream.items[2].kind, token.Kind.Star_Equal)
|
||||
testing.expect_value(t, stream.items[3].kind, token.Kind.Slash_Equal)
|
||||
testing.expect_value(t, stream.items[4].kind, token.Kind.Slash)
|
||||
testing.expect_value(t, stream.items[5].kind, token.Kind.Star)
|
||||
}
|
||||
|
||||
@(test)
|
||||
binary_operators_respect_multiplicative_precedence :: proc(t: ^testing.T) {
|
||||
source_file := source.Source{path="test.bro", text="value :: 1 + 2 * 3\nmain :: func() void {}\n"}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
root := module.exprs[module.globals[0].expr]
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, root.kind, ast.Expr_Kind.Add)
|
||||
testing.expect_value(t, module.exprs[root.left].integer, u64(1))
|
||||
testing.expect_value(t, module.exprs[root.right].kind, ast.Expr_Kind.Mul)
|
||||
}
|
||||
|
||||
@(test)
|
||||
division_parses_left_associatively :: proc(t: ^testing.T) {
|
||||
source_file := source.Source{path="test.bro", text="value :: 8 / 4 / 2\nmain :: func() void {}\n"}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
root := module.exprs[module.globals[0].expr]
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, root.kind, ast.Expr_Kind.Div)
|
||||
testing.expect_value(t, module.exprs[root.left].kind, ast.Expr_Kind.Div)
|
||||
testing.expect_value(t, module.exprs[root.right].integer, u64(2))
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_preserves_operation_and_rhs :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
x i32 = 0
|
||||
x += 5
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
body := module.functions[0].body
|
||||
statement := module.statements[body[1]]
|
||||
testing.expect_value(t, statement.kind, ast.Stmt_Kind.Assignment)
|
||||
testing.expect_value(t, statement.assignment_op, ast.Assignment_Op.Add)
|
||||
testing.expect(t, statement.target != ast.INVALID_EXPR)
|
||||
testing.expect_value(t, module.exprs[statement.target].kind, ast.Expr_Kind.Name)
|
||||
testing.expect_value(t, module.exprs[statement.expr].kind, ast.Expr_Kind.Integer)
|
||||
testing.expect_value(t, module.exprs[statement.expr].integer, u64(5))
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_evaluates_lvalue_once :: proc(t: ^testing.T) {
|
||||
// A compound assignment to an indexed lvalue must compute the element address
|
||||
// once and reuse it for the load and the store, rather than re-lowering the
|
||||
// lvalue (which would re-evaluate any side-effecting index subexpression).
|
||||
text := `bump :: func() usize {
|
||||
return 1
|
||||
}
|
||||
main :: func() i32 {
|
||||
values [3]mut i32 = [10, 20, 30]
|
||||
values[bump()] += 5
|
||||
return 0
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
call_count := 0
|
||||
index_address_count := 0
|
||||
for function in ir_module.functions {
|
||||
if !function.is_main {
|
||||
continue
|
||||
}
|
||||
for instruction in function.instructions {
|
||||
#partial switch instruction.op {
|
||||
case .Call: call_count += 1
|
||||
case .Index_Address: index_address_count += 1
|
||||
case:
|
||||
}
|
||||
}
|
||||
}
|
||||
// `bump()` is the lvalue's index. The fix shares one address between the load
|
||||
// and the store, so the side-effecting index runs exactly once and a single
|
||||
// Index_Address is emitted; the buggy double-lowering produced two of each.
|
||||
testing.expect_value(t, call_count, 1)
|
||||
testing.expect_value(t, index_address_count, 1)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_evaluates_nested_locations_once :: proc(t: ^testing.T) {
|
||||
text := `Box :: struct {
|
||||
value i32
|
||||
}
|
||||
row :: func() usize {
|
||||
return 0
|
||||
}
|
||||
column :: func() usize {
|
||||
return 1
|
||||
}
|
||||
pointer_for :: func(value @mut i32) @mut i32 {
|
||||
return value
|
||||
}
|
||||
main :: func() i32 {
|
||||
matrix [2]mut [2]mut i32 = [[1, 2], [3, 4]]
|
||||
(matrix[row()])[column()] += 1
|
||||
boxes [2]mut Box = [Box { value = 5 }, Box { value = 6 }]
|
||||
boxes[row()].value += 1
|
||||
value i32 = 7
|
||||
pointer_for(&value)^ += 1
|
||||
return 0
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
call_count := 0
|
||||
call_names: [4]string
|
||||
index_address_count := 0
|
||||
field_address_count := 0
|
||||
for function in ir_module.functions {
|
||||
if !function.is_main {
|
||||
continue
|
||||
}
|
||||
for instruction in function.instructions {
|
||||
#partial switch instruction.op {
|
||||
case .Call:
|
||||
function_id := ir.as_function(instruction.target)
|
||||
if call_count < len(call_names) &&
|
||||
function_id != ir.INVALID_FUNCTION &&
|
||||
int(function_id) < len(hir_module.functions) {
|
||||
call_names[call_count] = symbol.resolve(
|
||||
&symbols,
|
||||
hir_module.functions[function_id].name,
|
||||
)
|
||||
}
|
||||
call_count += 1
|
||||
case .Index_Address: index_address_count += 1
|
||||
case .Field_Address: field_address_count += 1
|
||||
case:
|
||||
}
|
||||
}
|
||||
}
|
||||
// row(), column(), the second row(), and pointer_for() each run once. The
|
||||
// nested matrix target needs two index addresses; the indexed field needs
|
||||
// one index address and one field address.
|
||||
testing.expect_value(t, call_count, 4)
|
||||
testing.expect_value(t, call_names, [4]string{"row", "column", "row", "pointer_for"})
|
||||
testing.expect_value(t, index_address_count, 3)
|
||||
testing.expect_value(t, field_address_count, 1)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_supports_pointer_add_only :: proc(t: ^testing.T) {
|
||||
valid_text := `main :: func() i32 {
|
||||
values [3]mut i32 = [10, 20, 30]
|
||||
pointer *mut i32 = (&values).ptr
|
||||
pointer += 1
|
||||
offset usize = 1
|
||||
pointer += offset
|
||||
return pointer^
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=valid_text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
pointer_add_count := 0
|
||||
for function in ir_module.functions {
|
||||
if !function.is_main {
|
||||
continue
|
||||
}
|
||||
for instruction in function.instructions {
|
||||
pointer_add_count += 1 if instruction.op == .Pointer_Add else 0
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, pointer_add_count, 2)
|
||||
|
||||
invalid_text := `main :: func() void {
|
||||
values [1]mut i32 = [10]
|
||||
pointer *mut i32 = (&values).ptr
|
||||
pointer -= 1
|
||||
}
|
||||
`
|
||||
invalid_source := source.Source{path="invalid.bro", text=invalid_text}
|
||||
invalid_diagnostics := source.init_diagnostics(&invalid_source)
|
||||
defer source.destroy_diagnostics(&invalid_diagnostics)
|
||||
invalid_symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&invalid_symbols)
|
||||
invalid_stream := lexer.lex(&invalid_source, &invalid_diagnostics, &invalid_symbols)
|
||||
defer delete(invalid_stream.items)
|
||||
invalid_ast := parser.parse(&invalid_stream, &invalid_source, &invalid_diagnostics)
|
||||
defer ast.destroy_module(&invalid_ast)
|
||||
invalid_hir := checker.check(&invalid_ast, &invalid_diagnostics, &invalid_symbols)
|
||||
defer hir.destroy_module(&invalid_hir)
|
||||
|
||||
found := false
|
||||
for diagnostic in invalid_diagnostics.items {
|
||||
found = found || strings.contains(
|
||||
diagnostic.message,
|
||||
"many-item pointers only support '+=' compound assignment",
|
||||
)
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T) {
|
||||
text := `main :: func() i32 {
|
||||
signed i32 = 24
|
||||
signed += 6
|
||||
signed -= 2
|
||||
signed *= 3
|
||||
signed /= 4
|
||||
unsigned u32 = 24
|
||||
unsigned += 6
|
||||
unsigned -= 2
|
||||
unsigned *= 3
|
||||
unsigned /= 4
|
||||
float f64 = 24.0
|
||||
float += 6.0
|
||||
float -= 2.0
|
||||
float *= 3.0
|
||||
float /= 4.0
|
||||
return signed
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
operation_counts: [hir.Assignment_Op]int
|
||||
for statement_id in hir_module.functions[0].body {
|
||||
statement := hir_module.statements[statement_id]
|
||||
if statement.kind == .Assignment {
|
||||
operation_counts[statement.assignment_op] += 1
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, operation_counts[.Add], 3)
|
||||
testing.expect_value(t, operation_counts[.Sub], 3)
|
||||
testing.expect_value(t, operation_counts[.Mul], 3)
|
||||
testing.expect_value(t, operation_counts[.Div], 3)
|
||||
|
||||
add_count := 0
|
||||
sub_count := 0
|
||||
mul_count := 0
|
||||
div_count := 0
|
||||
for instruction in ir_module.functions[0].instructions {
|
||||
#partial switch instruction.op {
|
||||
case .Add_Checked: add_count += 1
|
||||
case .Sub_Checked: sub_count += 1
|
||||
case .Mul_Checked: mul_count += 1
|
||||
case .Div_Checked: div_count += 1
|
||||
case:
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, add_count, 3)
|
||||
testing.expect_value(t, sub_count, 3)
|
||||
testing.expect_value(t, mul_count, 3)
|
||||
testing.expect_value(t, div_count, 3)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_rejects_narrowing_and_mixed_numeric_families :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
narrow i8 = 1
|
||||
wide i32 = 2
|
||||
narrow += wide
|
||||
signed i32 = 3
|
||||
unsigned u32 = 4
|
||||
signed += unsigned
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_narrowing := false
|
||||
found_mixed_family := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_narrowing = found_narrowing ||
|
||||
strings.contains(diagnostic.message, "cannot implicitly convert i32 to i8")
|
||||
found_mixed_family = found_mixed_family ||
|
||||
strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
|
||||
}
|
||||
testing.expect(t, found_narrowing)
|
||||
testing.expect(t, found_mixed_family)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-compound"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/compound_assignment", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 23)
|
||||
}
|
||||
|
||||
@(test)
|
||||
binary_arithmetic_rejects_non_numeric_operands :: proc(t: ^testing.T) {
|
||||
text := `main :: func() i32 {
|
||||
a i32 = 1
|
||||
b u32 = 2
|
||||
_ = a / b
|
||||
return 0
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compound_assignment_requires_writable_target :: proc(t: ^testing.T) {
|
||||
text := `main :: func() i32 {
|
||||
x :: 5
|
||||
x += 1
|
||||
return x
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "assignment target is not writable")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
|
||||
text := `main :: func() i32 {
|
||||
a i32 = 10
|
||||
b i32 = 3
|
||||
c i32 = a - b
|
||||
return c / b
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, strings.contains(llvm_text, "@llvm.ssub.with.overflow.i32"))
|
||||
testing.expect(t, strings.contains(llvm_text, "sdiv i32"))
|
||||
testing.expect(t, strings.contains(llvm_text, "divzero_trap"))
|
||||
testing.expect(t, strings.contains(llvm_text, "divovf_trap"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user