fix comptime specialization, implement io.print

This commit is contained in:
2026-07-15 08:24:50 +02:00
parent 0b2055d64b
commit 1165cfb7c0
7 changed files with 733 additions and 155 deletions
+204 -1
View File
@@ -2425,6 +2425,7 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
testing.expect(t, !strings.contains(llvm_text, "format_field_name"))
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
testing.expect(t, !strings.contains(llvm_text, "RecordInfo"))
@@ -2441,7 +2442,7 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, string(stdout), "")
testing.expect_value(t, string(stderr), "tuple=40/bro, limits=-9223372036854775808/18446744073709551615")
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
}
@(test)
@@ -2531,6 +2532,171 @@ milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testi
testing.expect(t, found)
}
@(test)
milestone_37_comptime_undefined_aggregates_support_full_initialization :: proc(t: ^testing.T) {
text := `Token :: struct {
text []u8
count usize
}
Partial :: struct { initialized i32, text []u8 }
make_tokens func() [2]mut Token {
tokens [2]mut Token = undefined
tokens[0] = Token {text = "a", count = 1}
tokens[1].text = "bro"
tokens[1].count = 3
return tokens
}
read_initialized_sibling func() i32 {
value Partial = undefined
value.initialized = 42
return value.initialized
}
answer :: $read_initialized_sibling()
main func() i32 {
total usize = 0
inline for make_tokens() |token| {
total += token.text.len + token.count
}
if answer != 42 or total != 8 {
return 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)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
milestone_37_comptime_undefined_values_cannot_be_observed :: proc(t: ^testing.T) {
text := `Bad :: struct { value i32, text []u8 }
read_scalar func() i32 {
value i32 = undefined
return value
}
return_partial func() Bad {
value Bad = undefined
value.value = 1
return value
}
take_bad func(value Bad) i32 {
return value.value
}
pass_partial func() i32 {
value Bad = undefined
value.value = 1
return take_bad(value)
}
bad_scalar :: $read_scalar()
bad_record :: $return_partial()
bad_argument :: $pass_partial()
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_read := false
found_return := false
found_pass := false
for diagnostic in diagnostics.items {
found_read = found_read || strings.contains(diagnostic.message, "cannot read an undefined value at comptime")
found_return = found_return || strings.contains(diagnostic.message, "comptime function returned an undefined value")
found_pass = found_pass || strings.contains(diagnostic.message, "cannot pass an undefined value at comptime")
}
testing.expect(t, found_read)
testing.expect(t, found_return)
testing.expect(t, found_pass)
}
@(test)
milestone_37_inline_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
text := `identity func($T type, value T) T {
return value
}
main func() i32 {
total i64 = 0
inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
inline for row |value| {
total += i64(identity(value))
}
}
return i32(total - 10)
}
`
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)
identity_symbol := symbol.intern(&symbols, "identity")
specializations := 0
for function in hir_module.functions {
specializations += 1 if function.name == identity_symbol else 0
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, specializations, 4)
}
@(test)
milestone_37_inline_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
text := `take_i8 func(value i8) void { _ = value }
main func() void {
inline for {i8(1), "skip"} |value, index| {
if index == 1 {
continue
}
take_i8(value)
}
inline for {i8(1), "stop"} |value, index| {
if index == 1 {
break
}
take_i8(value)
}
}
`
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)
}
@(test)
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
@@ -2877,6 +3043,36 @@ main func() void {
testing.expect(t, found_function)
}
@(test)
generic_parameter_usage_is_source_based :: proc(t: ^testing.T) {
text := `choose func($N usize, used, unused i32) i32 {
if N > 0 {
return used
}
return 0
}
main func() void {
_ = choose(0, 1, 2)
}
`
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), 1)
testing.expect_value(t, diagnostics.items[0].severity, source.Severity.Warning)
testing.expect(t, strings.contains(diagnostics.items[0].message, "unused parameter 'unused'"))
testing.expect(t, !strings.contains(diagnostics.items[0].message, "unused parameter 'used'"))
}
@(test)
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
text := `a func(value int) i32 {
@@ -3204,6 +3400,7 @@ unknown func($T type) T { value T = undefined; return value }
partial func($T type, $N usize, value T) T { return value }
use_ignored func($T type, value Ignored(T)) i32 { return value }
use_alias func($T type, value BoxAlias(T)) T { return value.value }
mapping_fail func($A usize, value i32, $B usize) void {}
main func() void {
a i32 :: 1
b u32 :: 2
@@ -3213,6 +3410,7 @@ main func() void {
_ = use_ignored(a)
box Box(i32) :: Box(i32) { value = 1 }
_ = use_alias(box)
mapping_fail(true, false)
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -3230,12 +3428,16 @@ main func() void {
found_conflict := false
found_unknown := false
found_partial := false
found_candidate_failures := false
unrecoverable := 0
for diagnostic in diagnostics.items {
message := diagnostic.message
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
found_partial = found_partial || strings.contains(message, "cannot infer comptime parameter 'N'")
found_candidate_failures = found_candidate_failures ||
strings.contains(message, "candidate 1: cannot infer comptime parameter 'B'") &&
strings.contains(message, "candidate 2: cannot infer comptime parameter 'A'")
if strings.contains(message, "cannot infer comptime parameter 'T'") {
unrecoverable += 1
}
@@ -3243,6 +3445,7 @@ main func() void {
testing.expect(t, found_conflict)
testing.expect(t, found_unknown)
testing.expect(t, found_partial)
testing.expect(t, found_candidate_failures)
testing.expect(t, unrecoverable >= 3)
}