unsigned integer constraint (uint)

This commit is contained in:
2026-07-18 00:12:59 +02:00
parent b09787029d
commit ef91f13e7b
15 changed files with 4618 additions and 2849 deletions
+188 -1
View File
@@ -1485,6 +1485,7 @@ main func() void {
_ = maxval!(u8, u16)
_ = minval!(1)
_ = maxval!(int)
_ = maxval!(uint)
_ = maxval!(f32)
_ = maxval!(bool)
_ = maxval!(Named)
@@ -1513,7 +1514,7 @@ main func() void {
}
testing.expect_value(t, bad_arity, 2)
testing.expect(t, bad_type)
testing.expect_value(t, bad_target, 5)
testing.expect_value(t, bad_target, 6)
}
@(test)
@@ -12876,3 +12877,189 @@ dependency_passes test {
testing.expect(t, strings.contains(output, root_path))
testing.expect(t, strings.contains(output, "3 passed, 1 failed"))
}
@(test)
uint_constraint_parses_as_type_and_comptime_value :: proc(t: ^testing.T) {
text := `value uint :: 1
Constraint :: uint
`
source_file := source.Source{path="uint.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, len(module.globals), 2)
testing.expect_value(t, module.globals[0].type, types.UINT)
testing.expect_value(t, module.exprs[module.globals[1].expr].kind, ast.Expr_Kind.Type)
testing.expect_value(t, module.exprs[module.globals[1].expr].type, types.UINT)
}
@(test)
uint_constraint_uses_smallest_unsigned_types_and_widens :: proc(t: ^testing.T) {
text := `Zero uint :: 0
Byte uint :: 255
Word uint :: 256
Dword uint :: 65536
Maximum uint :: 18446744073709551615
Counter uint = 1
widen func() uint {
value uint = 1
value = 1000
return value
}
widen_global func() void { Counter = 1000 }
main func() void {
_ = Zero
_ = Byte
_ = Word
_ = Dword
_ = Maximum
_ = widen()
widen_global()
}
`
source_file := source.Source{path="uint_types.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)
expected := []types.Type{types.U8, types.U8, types.U16, types.U32, types.U64, types.U16}
for value_type, index in expected {
testing.expect_value(t, hir_module.globals[index].type, value_type)
}
widen_symbol := symbol.intern(&symbols, "widen")
found_widen := false
for function in hir_module.functions {
if function.name != widen_symbol {
continue
}
found_widen = true
testing.expect_value(t, function.result, types.U16)
testing.expect_value(t, function.locals[0].type, types.U16)
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, found_widen)
}
@(test)
uint_constraint_infers_boundaries_calls_and_records :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-uint-constraint"
main_path := "/tmp/brolang-test-uint-constraint/main.bro"
output := "/tmp/brolang-test-uint-constraint-output"
text := `Box :: struct { value uint }
identity func(value uint) uint { return value }
whole func(value int) int { return value }
max_value func() uint { return 18446744073709551615 }
make_box func(value usize) Box { return Box{value = value} }
main func() i32 {
zero uint :: 0
byte uint :: 255
word uint :: 256
word_max uint :: 65535
dword uint :: 65536
maximum uint :: 18446744073709551615
platform usize = 7
c_value c_uint = c_uint(9)
box Box = make_box(7)
signed isize = -1
if (identity(zero) != 0) return 1
if (identity(byte) != 255) return 2
if (identity(word) != 256) return 3
if (identity(word_max) != 65535) return 4
if (identity(dword) != 65536) return 5
if (identity(maximum) != 18446744073709551615) return 6
if (box.value != 7) return 7
if (signed != -1) return 8
if (identity(platform) != 7) return 9
if (identity(c_value) != c_value) return 10
if (whole(word) != 256) return 11
if (max_value() != 18446744073709551615) return 12
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_value(t, state.exit_code, 0)
}
@(test)
uint_constraint_rejects_other_families_and_recovers_records_as_u64 :: proc(t: ^testing.T) {
text := `Unresolved :: struct { value uint }
Conflict :: struct { value uint }
take func(value uint) uint { return value }
bad_result func() uint { return -1 }
bad func(signed i32, decimal f64) void {
_ = take(signed)
_ = take(decimal)
negative uint :: -1
a Conflict = Conflict{value = signed}
_ = negative
_ = a
}
Overflow uint :: 18446744073709551616
NegativeGlobal uint :: -1
main func() void {
bad(1, 1.0)
_ = bad_result()
}
`
source_file := source.Source{path="bad_uint.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)
signed_error := false
float_error := false
negative_error := false
field_error := false
overflow_error := false
global_error := false
result_error := false
for diagnostic in diagnostics.items {
signed_error = signed_error || strings.contains(diagnostic.message, "cannot pass i32 to 'uint' parameter 'value'")
float_error = float_error || strings.contains(diagnostic.message, "cannot pass f64 to 'uint' parameter 'value'")
negative_error = negative_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for local 'negative'")
field_error = field_error || strings.contains(diagnostic.message, "type i32 does not satisfy the 'uint' constraint for field 'Conflict.value'")
overflow_error = overflow_error || strings.contains(diagnostic.message, "magnitude does not fit in u64")
global_error = global_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for global 'NegativeGlobal'")
result_error = result_error || strings.contains(diagnostic.message, "could not resolve a concrete result type for 'bad_result'")
}
unresolved, unresolved_ok := named_record_field_type(&hir_module, &symbols, "Unresolved", "value")
testing.expect(t, signed_error && float_error && negative_error && field_error && overflow_error && global_error && result_error)
testing.expect(t, unresolved_ok)
testing.expect_value(t, unresolved, types.U64)
}