unary minus

This commit is contained in:
2026-06-12 17:55:07 +02:00
parent 66e41e1d9a
commit 94e56f6888
14 changed files with 378 additions and 18 deletions
+216 -5
View File
@@ -78,7 +78,7 @@ main :: func() void { _ = value }
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, value_count, 2)
testing.expect_value(t, module.imports[0].path, "../math")
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(42))
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, u64(42))
testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol)
}
@@ -86,6 +86,9 @@ main :: func() void { _ = value }
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
testing.expect_value(t, size_of(source.Span), 12)
testing.expect_value(t, size_of(token.Token), 24)
testing.expect_value(t, size_of(ast.Expr), 64)
testing.expect_value(t, size_of(hir.Expr), 88)
testing.expect_value(t, size_of(ir.Instruction), 88)
source_index, source_ok := source.source_index(source.Source_Id(0), 1)
testing.expect_value(t, source_index, 0)
@@ -271,7 +274,41 @@ pratt_parser_preserves_left_associative_addition_shape :: proc(t: ^testing.T) {
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].kind, ast.Expr_Kind.Add)
testing.expect_value(t, module.exprs[root.right].integer, i64(3))
testing.expect_value(t, module.exprs[root.right].integer, u64(3))
}
@(test)
pratt_parser_handles_prefix_negation_precedence :: proc(t: ^testing.T) {
text := `identity :: func(value i8) i8 { return value }
loose :: -1 + 2
grouped :: -(1 + 2)
called :: -identity(1)
chained :: --1
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)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
loose := module.exprs[module.globals[0].expr]
grouped := module.exprs[module.globals[1].expr]
called := module.exprs[module.globals[2].expr]
chained := module.exprs[module.globals[3].expr]
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, loose.kind, ast.Expr_Kind.Add)
testing.expect_value(t, module.exprs[loose.left].kind, ast.Expr_Kind.Negate)
testing.expect_value(t, grouped.kind, ast.Expr_Kind.Negate)
testing.expect_value(t, module.exprs[grouped.left].kind, ast.Expr_Kind.Add)
testing.expect_value(t, called.kind, ast.Expr_Kind.Negate)
testing.expect_value(t, module.exprs[called.left].kind, ast.Expr_Kind.Call)
testing.expect_value(t, chained.kind, ast.Expr_Kind.Negate)
testing.expect_value(t, module.exprs[chained.left].kind, ast.Expr_Kind.Negate)
}
nested_expression_source :: proc(call: bool, depth: int) -> string {
@@ -296,6 +333,17 @@ nested_expression_source :: proc(call: bool, depth: int) -> string {
return strings.clone(strings.to_string(builder))
}
nested_negation_source :: proc(depth: int) -> string {
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "value :: ")
for _ in 0..<depth {
strings.write_byte(&builder, '-')
}
strings.write_string(&builder, "1\nmain :: func() void {}\n")
return strings.clone(strings.to_string(builder))
}
parse_nesting_result :: proc(text: string) -> (count: int, found_budget: bool) {
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
@@ -327,6 +375,17 @@ parser_enforces_explicit_expression_nesting_budget :: proc(t: ^testing.T) {
_, found = parse_nesting_result(over_limit)
testing.expect(t, found)
}
at_limit := nested_negation_source(parser.MAX_EXPRESSION_NESTING)
defer delete(at_limit)
count, found := parse_nesting_result(at_limit)
testing.expect_value(t, count, 0)
testing.expect(t, !found)
over_limit := nested_negation_source(parser.MAX_EXPRESSION_NESTING+1)
defer delete(over_limit)
_, found = parse_nesting_result(over_limit)
testing.expect(t, found)
}
@(test)
@@ -1267,6 +1326,49 @@ checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
testing.expect(t, !state.success)
}
@(test)
checked_runtime_negation_traps_for_every_signed_width :: proc(t: ^testing.T) {
Case :: struct {
type_name: string,
magnitude: string,
}
cases := [?]Case{
{type_name="i8", magnitude="128"},
{type_name="i16", magnitude="32768"},
{type_name="i32", magnitude="2147483648"},
{type_name="i64", magnitude="9223372036854775808"},
}
for test_case in cases {
directory := fmt.aprintf("/tmp/brolang-test-negate-overflow-%s", test_case.type_name)
main_path := fmt.aprintf("%s/main.bro", directory)
output := fmt.aprintf("/tmp/brolang-test-negate-overflow-output-%s", test_case.type_name)
builder := strings.builder_make()
fmt.sbprintf(
&builder,
"negate :: func(value %s) %s {{ return -value }}\nmain :: func() void {{ _ = negate(-%s) }}\n",
test_case.type_name,
test_case.type_name,
test_case.magnitude,
)
text := strings.clone(strings.to_string(builder))
strings.builder_destroy(&builder)
_ = os2.remove_all(directory)
_ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
status := compiler_core.compile_package(directory, output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect(t, !state.success)
_ = os.remove(output)
_ = os2.remove_all(directory)
delete(text)
delete(output)
delete(main_path)
delete(directory)
}
}
@(test)
constant_that_does_not_fit_context_produces_trap_executable :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-constant-context-error"
@@ -1393,7 +1495,114 @@ maximum_signed_i64_literal_parses_exactly :: proc(t: ^testing.T) {
defer ast.destroy_module(&module)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(9223372036854775807))
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, u64(9223372036854775807))
}
@(test)
negative_constants_fold_and_accept_signed_i64_minimum :: proc(t: ^testing.T) {
text := `minimum :: -9223372036854775808
grouped i64 :: -(9223372036854775808)
folded :: -(1 + 2)
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)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, types.equal(hir_module.globals[0].type, types.I64))
testing.expect(t, types.equal(hir_module.globals[1].type, types.I64))
testing.expect(t, types.equal(hir_module.globals[2].type, types.I8))
testing.expect_value(t, hir_module.globals[0].static_value, i64(-9223372036854775807-1))
testing.expect_value(t, hir_module.globals[1].static_value, i64(-9223372036854775807-1))
testing.expect_value(t, hir_module.globals[2].static_value, i64(-3))
}
@(test)
out_of_range_negative_constants_are_diagnosed :: proc(t: ^testing.T) {
text := `positive :: 9223372036854775808
below_minimum :: -9223372036854775809
double_minimum :: --9223372036854775808
maximum_u64 :: 18446744073709551615
beyond_u64 :: 18446744073709551616
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_signed_range := 0
found_u64_range := false
for diagnostic in diagnostics.items {
found_signed_range += 1 if strings.contains(diagnostic.message, "exceeds signed i64 range") else 0
found_u64_range = found_u64_range || strings.contains(diagnostic.message, "magnitude does not fit in u64")
}
testing.expect_value(t, found_signed_range, 4)
testing.expect(t, found_u64_range)
}
@(test)
runtime_negation_preserves_operand_type_before_result_widening :: proc(t: ^testing.T) {
text := `negate_i8 :: func(value i8) i8 {
return -value
}
widen_after_negate :: func(value i8) i16 {
return -value
}
main :: func() void {
_ = negate_i8(1)
_ = widen_after_negate(1)
}
`
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.i8"))
found_widen := false
for function in hir_module.functions {
if symbol.resolve(&symbols, function.name) != "widen_after_negate" {
continue
}
statement := hir_module.statements[function.body[0]]
widen := hir_module.exprs[statement.expr]
negate := hir_module.exprs[widen.left]
testing.expect_value(t, widen.kind, hir.Expr_Kind.Widen)
testing.expect(t, types.equal(widen.type, types.I16))
testing.expect_value(t, negate.kind, hir.Expr_Kind.Negate)
testing.expect(t, types.equal(negate.type, types.I8))
found_widen = true
}
testing.expect(t, found_widen)
}
@(test)
@@ -1445,10 +1654,11 @@ malformed_hir_references_lower_to_valid_trapped_llvm :: proc(t: ^testing.T) {
malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) {
module := ir.init_module()
defer ir.destroy_module(&module)
instructions := make([]ir.Instruction, 3)
instructions := make([]ir.Instruction, 4)
instructions[0] = ir.Instruction{op=.Store, type=types.I8, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[1] = ir.Instruction{op=.Add_Checked, type=types.I32, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[2] = ir.Instruction{op=.Return, type=types.I32, a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[2] = ir.Instruction{op=.Neg_Checked, type=types.I16, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[3] = ir.Instruction{op=.Return, type=types.I32, a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
append(&module.functions, ir.Function{
link_name=strings.clone("main"),
calling_convention=.C,
@@ -1469,6 +1679,7 @@ malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(text, "call void @bro.trap"))
testing.expect(t, strings.contains(text, "%v0 = add i8 0, -86"))
testing.expect(t, strings.contains(text, "%v1 = add i32 0, -1431655766"))
testing.expect(t, strings.contains(text, "%v2 = add i16 0, -21846"))
testing.expect(t, strings.contains(text, "@bro.trap(ptr %message, i64 %length) noreturn"))
testing.expect(t, !strings.contains(text, "%v-1"))
llvm_path := "/tmp/brolang-test-malformed-recovery.ll"