struct type construction

This commit is contained in:
2026-07-19 21:35:48 +02:00
parent 944648fce6
commit 59bbb197e0
15 changed files with 331415 additions and 253804 deletions
+324
View File
@@ -13723,3 +13723,327 @@ main func() void {
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
anonymous_keyed_records_infer_structural_types_and_preserve_tuples :: proc(t: ^testing.T) {
text := `first :: {x = 1, name = "bro"}
second :: {x = 2, name = "hey"}
empty :: {}
pair :: {1, 2}
main func() i32 {
if first.x + second.x != 3 { return 1 }
if first.name.len != 3 or second.name.len != 3 { return 2 }
return pair.0 - 1
}
`
source_file := source.Source{path="anonymous_records.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)
first_expr := ast_module.exprs[ast_module.globals[0].expr]
empty_expr := ast_module.exprs[ast_module.globals[2].expr]
pair_expr := ast_module.exprs[ast_module.globals[3].expr]
testing.expect_value(t, first_expr.kind, ast.Expr_Kind.Struct_Literal)
testing.expect(t, !first_expr.tuple)
testing.expect(t, empty_expr.tuple)
testing.expect(t, pair_expr.tuple)
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, hir_module.globals[1].type))
testing.expect(t, types.is_record(hir_module.globals[0].type, &hir_module.types))
empty_type, empty_ok := types.node(&hir_module.types, hir_module.globals[2].type)
pair_type, pair_ok := types.node(&hir_module.types, hir_module.globals[3].type)
testing.expect(t, empty_ok && empty_type.tuple)
testing.expect(t, pair_ok && pair_type.tuple)
bare_source := source.Source{path="bare_anonymous_record.bro", text="bad :: {value = 1, missing}\n"}
bare_diagnostics := source.init_diagnostics(&bare_source)
defer source.destroy_diagnostics(&bare_diagnostics)
bare_symbols := symbol.init_table()
defer symbol.destroy_table(&bare_symbols)
bare_stream := lexer.lex(&bare_source, &bare_diagnostics, &bare_symbols)
defer delete(bare_stream.items)
bare_module := parser.parse(&bare_stream, &bare_source, &bare_diagnostics)
defer ast.destroy_module(&bare_module)
found_bare := false
for diagnostic in bare_diagnostics.items {
found_bare = found_bare || strings.contains(diagnostic.message, "anonymous record fields require '= value'")
}
testing.expect(t, found_bare)
}
@(test)
enum_field_struct_and_contextual_anonymous_records_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-enum-field-struct"
main_path := "/tmp/brolang-test-enum-field-struct/main.bro"
output := "/tmp/brolang-test-enum-field-struct-output"
text := `meta :: import "@std/meta"
TokenKind :: enum(u8) {
ident = 3
int = 8
eof = 21
}
Simple :: enum { first, second }
Names :: alias meta.EnumFieldStruct(TokenKind, ?[]u8, some!(none))
Flags :: alias meta.EnumFieldStruct(Simple, bool, false)
Direct :: alias struct_type!(.auto, {"x", "name"}, {i32, []u8}, {none, "bro"})
CPoint :: alias struct_type!(.c, {"x"}, {c_int}, {none})
ArrayInput :: alias struct_type!(.auto, ["value"], [i32], [7])
Empty :: alias struct_type!(.auto, {}, {}, {})
direct_position struct_type!(.auto, {"value"}, {i32}, {none}) = {value = 5}
make_direct func() struct_type!(.auto, {"value"}, {i32}, {none}) {
return {value = 6}
}
Generated func($T type) type {
names [1]mut []u8 = undefined
field_types [1]mut type = undefined
defaults [1]mut ?T = undefined
names[0] = "value"
field_types[0] = T
defaults[0] = 42
return struct_type!(.auto, names, field_types, defaults)
}
GeneratedInt :: alias Generated(i32)
ordered Names = {}
Map func($E, $V type) type { return struct { count usize } }
init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(none))) Map(E, V) {
count usize = 0
match typeinfo!(E) {
.enum |info|: expand for info.fields |field| {
if field!(values, field.name) |_| { count += 1 }
}
else: compile_error!("EnumMap key must be an enum")
}
return Map(E, V) {count = count}
}
main func() i32 {
_ = ordered
inferred :: {x = 40, name = "bro"}
if inferred.x != 40 or inferred.name.len != 3 { return 1 }
direct Direct = {x = 7}
if direct.x != 7 or direct.name.len != 3 { return 10 }
generated GeneratedInt = {}
if generated.value != 42 { return 11 }
generated_again Generated(i32) = generated
if generated_again.value != 42 { return 15 }
array_input ArrayInput = {}
if array_input.value != 7 { return 16 }
empty_record Empty = {}
_ = empty_record
if direct_position.value != 5 or make_direct().value != 6 { return 17 }
c_point CPoint = {x = 9}
if c_point.x != 9 { return 12 }
nested ??i32 = some!(none)
if nested |inner| {
if inner |_| { return 13 }
} else { return 14 }
names Names = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
if value.len != 10 { return 2 }
} else { return 3 }
if field!(names, "int") |value| {
if value.len != 7 { return 4 }
} else { return 5 }
if field!(names, "eof") |_| { return 6 }
empty Names = {}
if field!(empty, "ident") |_| { return 7 }
flags Flags = {second = true}
if flags.first or !flags.second { return 8 }
map Map(TokenKind, []u8) = init({
ident = "identifier",
int = "integer",
})
if map.count != 2 { return 9 }
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))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
module, loaded := loader.load(
directory, &sources, &diagnostics, &symbols, project_root_path=".",
)
defer ast.destroy_module(&module)
hir_module := checker.check(&module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
names_type := types.INVALID
ordered_symbol := symbol.intern(&symbols, "ordered")
for global in hir_module.globals {
if global.name == ordered_symbol {
names_type = types.resolve_alias(global.type, &hir_module.types)
break
}
}
names_fields := types.fields_for(&hir_module.types, names_type)
testing.expect_value(t, len(names_fields), 3)
if len(names_fields) == 3 {
testing.expect_value(t, names_fields[0].name, u32(symbol.intern(&symbols, "ident")))
testing.expect_value(t, names_fields[1].name, u32(symbol.intern(&symbols, "int")))
testing.expect_value(t, names_fields[2].name, u32(symbol.intern(&symbols, "eof")))
}
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)
std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-std-meta"
main_path := "/tmp/brolang-test-std-meta/main.bro"
output := "/tmp/brolang-test-std-meta-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
text := `test import "@std/meta"
`
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{}, ".", .Test,
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
anonymous_records_and_struct_type_report_targeted_errors :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-bad-struct-type"
main_path := "/tmp/brolang-test-bad-struct-type/main.bro"
text := `meta :: import "@std/meta"
E :: enum { value }
Fields :: alias struct_type!(.auto, {"value"}, {i32}, {none})
RequiredEnum :: alias meta.EnumFieldStruct(E, i32, none)
NotEnum :: alias meta.EnumFieldStruct(i32, i32, none)
BadField :: alias struct_type!(.auto, {"value"}, {void}, {none})
BadComptimeField :: alias struct_type!(.auto, {"T"}, {type}, {none})
BadDefault :: alias struct_type!(.auto, {"value"}, {i32}, {"no"})
UnstableDefault :: alias struct_type!(.auto, {"value"}, {i32}, {undefined})
BadLengths :: alias struct_type!(.auto, {"one", "two"}, {i32}, {none})
BadNames :: alias struct_type!(.auto, {"not-valid"}, {i32}, {none})
DuplicateNames :: alias struct_type!(.auto, {"same", "same"}, {i32, i32}, {none, none})
BadLayout :: alias struct_type!(.packed, {"value"}, {i32}, {none})
BadC :: alias struct_type!(.c, {"value"}, {[]u8}, {none})
EmptyC :: alias struct_type!(.c, {}, {}, {})
unknown Fields = {missing = 1}
duplicate Fields = {value = 1, value = 2}
missing Fields = {}
missing_enum RequiredEnum = {}
not_enum NotEnum = {}
bad_field BadField = {}
bad_comptime_field BadComptimeField = {}
bad_default BadDefault = {}
unstable_default UnstableDefault = {}
bad_lengths BadLengths = {}
bad_names BadNames = {}
duplicate_names DuplicateNames = {}
bad_layout BadLayout = {}
bad_c BadC = {}
empty_c EmptyC = {}
main func() void {
_ = some!(1)
_ = unknown
_ = duplicate
_ = missing
_ = missing_enum
_ = not_enum
_ = bad_field
_ = bad_comptime_field
_ = bad_default
_ = unstable_default
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
found_non_enum := false
found_bad_field := false
found_bad_comptime_field := false
found_bad_default := false
found_unstable := false
found_unknown := false
found_duplicate := false
found_lengths := false
found_names := false
found_layout := false
found_c := false
found_some_context := false
found_missing := false
found_empty_c := false
for diagnostic in diagnostics.items {
found_non_enum = found_non_enum || strings.contains(diagnostic.message, "EnumFieldStruct key must be an enum")
found_bad_field = found_bad_field || strings.contains(diagnostic.message, "field 'value' has invalid value type")
found_bad_comptime_field = found_bad_comptime_field || strings.contains(diagnostic.message, "field 'T' has invalid value type")
found_bad_default = found_bad_default || strings.contains(diagnostic.message, "cannot implicitly convert")
found_unstable = found_unstable || strings.contains(diagnostic.message, "must have a stable comptime identity")
found_unknown = found_unknown || strings.contains(diagnostic.message, "unknown struct field 'missing'")
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate initializer for struct field 'value'")
found_lengths = found_lengths || strings.contains(diagnostic.message, "collection lengths differ")
found_names = found_names || strings.contains(diagnostic.message, "field names must be valid") || strings.contains(diagnostic.message, "duplicate struct_type! field name")
found_layout = found_layout || strings.contains(diagnostic.message, "layout must be .auto or .c")
found_c = found_c || strings.contains(diagnostic.message, "invalid C-layout type")
found_some_context = found_some_context || strings.contains(diagnostic.message, "some! requires an optional context")
found_missing = found_missing || strings.contains(diagnostic.message, "missing initializer for struct field 'value'")
found_empty_c = found_empty_c || strings.contains(diagnostic.message, "c struct types require at least one field")
}
testing.expect(t, found_non_enum)
testing.expect(t, found_bad_field)
testing.expect(t, found_bad_comptime_field)
testing.expect(t, found_bad_default)
testing.expect(t, found_unstable)
testing.expect(t, found_unknown)
testing.expect(t, found_duplicate)
testing.expect(t, found_lengths)
testing.expect(t, found_names)
testing.expect(t, found_layout)
testing.expect(t, found_c)
testing.expect(t, found_some_context)
testing.expect(t, found_missing)
testing.expect(t, found_empty_c)
}