disambiguate enum blocks and complete distinct type operations

This commit is contained in:
2026-08-01 23:57:35 +02:00
parent 91aa601464
commit b9526b5f06
34 changed files with 1128 additions and 1963359 deletions
+199 -20
View File
@@ -313,7 +313,9 @@ main func() i32 {
@(test)
typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
text := `main func() void {
text := `D :: distinct u8
main func() void {
_ = ~D(1)
_ = ~u8(0)
_ = (u8(240) & u8(204)) xor u8(15)
_ = u8(129) << 1
@@ -332,12 +334,17 @@ typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
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)
for statement_id in hir_module.functions[0].body {
d_type := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "D")))
for statement_id, index in hir_module.functions[0].body {
statement := hir_module.statements[statement_id]
testing.expect(t, statement.expr != hir.INVALID_EXPR)
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
if index == 0 {
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Bit_Not)
testing.expect_value(t, hir_module.exprs[statement.expr].type, d_type)
} else {
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
}
}
}
@@ -353,7 +360,7 @@ main func() void {
_ = true & false
_ = 1.0 | 2.0
_ = ~p
_ = ~d
_ = d
_ = ~e
_ = u8(1) & i8(1)
_ = u8(1) << signed_count
@@ -410,6 +417,32 @@ main func() i32 {
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
runtime_distinct_ordinary_overshift_traps :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-bitwise-overshift"
main_path := "/tmp/brolang-test-distinct-bitwise-overshift/main.bro"
output := "/tmp/brolang-test-distinct-bitwise-overshift-output"
text := `D :: distinct u8
shift func(value D, count u8) D { return value << count }
main func() i32 {
_ = shift(D(1), 8)
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state, stdout, stderr, err := os2.process_exec(os2.Process_Desc{command=[]string{output}}, context.allocator)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, err == nil)
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
@@ -1704,6 +1737,11 @@ integer_bound_builtins_compile_and_run :: proc(t: ^testing.T) {
main_path := "/tmp/brolang-test-integer-bounds/main.bro"
output := "/tmp/brolang-test-integer-bounds-output"
text := `MAX_U64 u64 :: maxval!(u64)
Signed :: distinct i16
Unsigned :: distinct u16
Inner :: distinct u8
Outer :: distinct Inner
maximum func($T type) T {
return maxval!(T)
@@ -1720,6 +1758,16 @@ main func() i32 {
if (maximum(u16) != 65535) return 8
if (minval!(c_int) != -2147483648) return 9
if (maxval!(c_ulong) != 18446744073709551615) return 10
signed_min Signed = minval!(Signed)
unsigned_max Unsigned = maxval!(Unsigned)
nested_max Outer = maxval!(Outer)
generic_min Signed = maximum(Signed)
generic_nested Outer = maximum(Outer)
if i16(signed_min) != -32768 { return 11 }
if u16(unsigned_max) != 65535 { return 12 }
if u8(nested_max) != 255 { return 13 }
if i16(generic_min) != 32767 { return 14 }
if u8(generic_nested) != 255 { return 15 }
return 0
}
`
@@ -1736,7 +1784,9 @@ main func() i32 {
@(test)
integer_bound_builtins_reject_invalid_targets :: proc(t: ^testing.T) {
text := `Named :: distinct u8
text := `BadFloat :: distinct f32
BadBool :: distinct bool
BadAggregate :: distinct [2]u8
Choice :: enum { one }
main func() void {
@@ -1747,7 +1797,9 @@ main func() void {
_ = maxval!(uint)
_ = maxval!(f32)
_ = maxval!(bool)
_ = maxval!(Named)
_ = maxval!(BadFloat)
_ = maxval!(BadBool)
_ = maxval!(BadAggregate)
_ = maxval!(Choice)
}
`
@@ -1773,7 +1825,7 @@ main func() void {
}
testing.expect_value(t, bad_arity, 2)
testing.expect(t, bad_type)
testing.expect_value(t, bad_target, 6)
testing.expect_value(t, bad_target, 8)
}
@(test)
@@ -3284,6 +3336,8 @@ milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
main_path := "/tmp/brolang-test-format-errors/main.bro"
text := `io :: import "@std/io"
process :: import "@std/process"
Aggregate :: distinct [2]u8
main func(init process.Init) void {
writer io.Writer :: io.stdout(init.io)
@@ -3299,6 +3353,7 @@ main func(init process.Init) void {
io.print(writer, "{e}", {1,}) catch |_| {}
io.print(writer, "{c}", {i16(65),}) catch |_| {}
io.print(writer, "}", {}) catch |_| {}
io.print(writer, "{}", {Aggregate([1, 2]),}) catch |_| {}
}
`
_ = os2.remove_all(directory)
@@ -3317,7 +3372,7 @@ main func(init process.Init) void {
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := [10]bool{}
found := [11]bool{}
for diagnostic in diagnostics.items {
message := diagnostic.message
found[0] = found[0] || strings.contains(message, "arguments must be a tuple")
@@ -3330,6 +3385,7 @@ main func(init process.Init) void {
found[7] = found[7] || strings.contains(message, "integer format requires an integer argument")
found[8] = found[8] || strings.contains(message, "float format requires a float argument")
found[9] = found[9] || strings.contains(message, "'{c}' requires an unsigned integer that fits in u8")
found[10] = found[10] || strings.contains(message, "io.print '{}' does not support this argument type")
}
testing.expect(t, loaded)
for present in found {
@@ -3402,7 +3458,7 @@ milestone_39_stable_values_and_richer_formatting_compile_and_run :: proc(t: ^tes
testing.expect_value(
t,
string(stdout),
"true -42 1.5 .running bro 2.5 1010 12 ff FF A 1.5000000000000000e+00 {} -9223372036854775808 0 inf nan 1.50000000e+00\n",
"true -42 1.5 .running bro 2.5 1010 12 ff FF A 1.5000000000000000e+00 {} -9223372036854775808 0 inf nan 1.50000000e+00\n-42 -42 1010 12 ff FF A 1.50000000e+00 7\n",
)
testing.expect_value(t, string(stderr), "debug=.idle 2a\n")
}
@@ -7927,6 +7983,28 @@ checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
checked_distinct_addition_traps_on_backing_overflow :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-overflow"
main_path := "/tmp/brolang-test-distinct-overflow/main.bro"
output := "/tmp/brolang-test-distinct-overflow-output"
text := `D :: distinct i8
add func(left, right D) D { return left + right }
main func() i32 {
_ = add(D(127), D(1))
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
checked_runtime_negation_traps_for_every_signed_width :: proc(t: ^testing.T) {
@@ -10708,6 +10786,32 @@ parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(diagnostics.items[0].message, "parenthesized"))
}
@(test)
parser_separates_contextual_enum_literal_from_if_block :: proc(t: ^testing.T) {
text := `Kind :: enum {
newline
other
}
main func() void {
kind Kind = .other
if kind != .newline {
}
}
`
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)
testing.expect_value(t, module.statements[module.functions[0].body[1]].kind, ast.Stmt_Kind.If)
}
@(test)
parser_accepts_braceless_while_bodies :: proc(t: ^testing.T) {
text := `ready func() bool { return false }
@@ -13125,6 +13229,7 @@ OtherID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct UserID
Flag :: distinct bool
static_id UserID :: UserID(42)
take func(value UserID) UserID {
return value
@@ -13137,11 +13242,27 @@ main func() i32 {
point PointID :: PointID(Point { x = 1, y = 2 })
bytes Bytes :: Bytes([3, 4])
wrapped WrappedID :: WrappedID(id)
sum UserID = id + 1
matches bool = id == 7
raw u32 = u32(id)
wide usize = usize(id)
real f64 = f64(id)
inner UserID = UserID(wrapped)
terminal u32 = u32(wrapped)
flag_matches bool = Flag(true) == Flag(true)
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
_ = sum
_ = matches
_ = raw
_ = wide
_ = real
_ = inner
_ = terminal
_ = flag_matches
return 0
}
`
@@ -13177,17 +13298,33 @@ main func() i32 {
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i32 42"))
testing.expect(t, strings.contains(llvm_text, "select i1 true, i32"))
nominal_hir_add := false
for hir_expr in hir_module.exprs {
nominal_hir_add = nominal_hir_add || hir_expr.kind == .Add && hir_expr.type == user_id
}
testing.expect(t, nominal_hir_add)
retype_count := 0
nominal_ir_add := false
u32_cast, usize_cast, f64_cast := false, false, false
for function in ir_module.functions {
for instruction in function.instructions {
retype_count += 1 if instruction.op == .Retype else 0
nominal_ir_add = nominal_ir_add || instruction.op == .Add_Checked && instruction.type == user_id
if instruction.op == .Scalar_Cast {
u32_cast = u32_cast || instruction.type == types.U32
usize_cast = usize_cast || instruction.type == types.USIZE
f64_cast = f64_cast || instruction.type == types.F64
}
}
}
testing.expect_value(t, retype_count, 4)
testing.expect(t, nominal_ir_add)
testing.expect(t, u32_cast && usize_cast && f64_cast)
testing.expect(t, retype_count >= 5)
}
@(test)
distinct_types_reject_implicit_conversions_operators_and_invalid_backings :: proc(t: ^testing.T) {
distinct_types_reject_implicit_conversions_and_invalid_backings :: proc(t: ^testing.T) {
text := `Opaque :: opaque
UserID :: distinct u32
OtherID :: distinct u32
@@ -13207,9 +13344,14 @@ main func() void {
_ = UserID()
_ = UserID(1, 2)
left UserID :: UserID(5)
right UserID :: UserID(6)
_ = left + right
_ = left == right
raw_operand u32 :: u32(6)
other_operand OtherID :: OtherID(6)
_ = left + raw_operand
_ = raw_operand + left
_ = left + other_operand
_ = left == raw_operand
_ = raw_operand == left
_ = left == other_operand
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -14616,12 +14758,12 @@ dependency_passes test {
defer delete(stderr)
output := string(stderr)
testing.expect_value(t, state.exit_code, 1)
testing.expect(t, strings.contains(output, "root.root_passes [ok]"))
testing.expect(t, strings.contains(output, "root.root_fails [failed]"))
testing.expect(t, strings.contains(output, "root.root_passes...[ok]"))
testing.expect(t, strings.contains(output, "root.root_fails...[failed]"))
testing.expect(t, strings.contains(output, "expected 42, found 41"))
testing.expect(t, strings.contains(output, "root.root_continues [ok]"))
testing.expect(t, strings.contains(output, "root.root_errors [failed]"))
testing.expect(t, strings.contains(output, "dependency.dependency_passes [ok]"))
testing.expect(t, strings.contains(output, "root.root_continues...[ok]"))
testing.expect(t, strings.contains(output, "root.root_errors...[failed]"))
testing.expect(t, strings.contains(output, "dependency.dependency_passes...[ok]"))
testing.expect(t, strings.contains(output, root_path))
testing.expect(t, strings.contains(output, "3 passed, 2 failed"))
}
@@ -15233,6 +15375,43 @@ main func() i32 {
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
distinct_reflection_reports_immediate_backing :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-reflection"
main_path := "/tmp/brolang-test-distinct-reflection/main.bro"
output := "/tmp/brolang-test-distinct-reflection-output"
text := `meta :: import "@std/meta"
Inner :: distinct u16
Outer :: distinct Inner
OuterAlias :: alias Outer
matches func($Distinct, $Backing type) bool {
match typeinfo!(Distinct) {
.distinct |backing|: return backing == Backing
else: return false
}
}
main func() i32 {
if !$(matches(Inner, u16)) { return 1 }
if !$(matches(Outer, Inner)) { return 2 }
if !$(matches(OuterAlias, Inner)) { return 3 }
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
static_string_map_infers_array_size_and_preserves_promoted_backing :: proc(t: ^testing.T) {