fix: reflect composed payload-free enum sums
This commit is contained in:
+2
-2
@@ -266,7 +266,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
|||||||
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null`
|
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null`
|
||||||
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
||||||
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
|
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
|
||||||
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, including native `A | B` compositions whose resulting cases are all payload-free, and aliases retain the canonical composed type for reflection, `field!(E, name)`, and equality; reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
|
||||||
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
|
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
|
||||||
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
|
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
|
||||||
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
|
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
|
||||||
@@ -288,7 +288,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
|||||||
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
|
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
|
||||||
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
||||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||||
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
|
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order (or left-to-right first occurrence for payload-free compositions), where outer `null` means no field default; payload-bearing sums and nominal tagged or untagged unions are not enum keys
|
||||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` contains exactly `io io.Io` and `args Args`, while the system provider remains hidden inside `std/io`
|
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` contains exactly `io io.Io` and `args Args`, while the system provider remains hidden inside `std/io`
|
||||||
- `std/process.Args` contains exactly `values [][;0]u8`; use `args.values.len` and direct indexing. Native startup scans each argument once into heap-owned slice descriptors while borrowing, not copying, the strings; index 0 is the executable name when present, and empty arguments remain empty slices. The descriptors and their borrowed views are valid until `main` returns; the native wrapper frees the descriptors on both success and error
|
- `std/process.Args` contains exactly `values [][;0]u8`; use `args.values.len` and direct indexing. Native startup scans each argument once into heap-owned slice descriptors while borrowing, not copying, the strings; index 0 is the executable name when present, and empty arguments remain empty slices. The descriptors and their borrowed views are valid until `main` returns; the native wrapper frees the descriptors on both success and error
|
||||||
|
|||||||
@@ -1731,6 +1731,9 @@ canonical_type :: proc(
|
|||||||
}
|
}
|
||||||
if item.kind == .Alias {
|
if item.kind == .Alias {
|
||||||
resolved := canonical_type(module, item.child, mapping, visiting)
|
resolved := canonical_type(module, item.child, mapping, visiting)
|
||||||
|
if types.is_valid(resolved) {
|
||||||
|
module.type_store.nodes[index].child = resolved
|
||||||
|
}
|
||||||
mapping[index] = value if !types.is_valid(resolved) else resolved
|
mapping[index] = value if !types.is_valid(resolved) else resolved
|
||||||
return mapping[index]
|
return mapping[index]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16703,6 +16703,13 @@ TokenKind :: enum(u8) {
|
|||||||
}
|
}
|
||||||
Simple :: enum { first, second }
|
Simple :: enum { first, second }
|
||||||
|
|
||||||
|
LeftError :: enum { shared, left }
|
||||||
|
RightError :: enum { shared, right }
|
||||||
|
TailError :: enum { tail }
|
||||||
|
BothErrors :: alias LeftError | RightError
|
||||||
|
NestedErrors :: alias (BothErrors | LeftError) | TailError
|
||||||
|
ErrorAlias :: alias NestedErrors
|
||||||
|
|
||||||
Names :: alias meta.EnumFieldStruct(TokenKind, ?[]u8, some!(null))
|
Names :: alias meta.EnumFieldStruct(TokenKind, ?[]u8, some!(null))
|
||||||
Flags :: alias meta.EnumFieldStruct(Simple, bool, false)
|
Flags :: alias meta.EnumFieldStruct(Simple, bool, false)
|
||||||
Direct :: alias struct_type!(.auto, {"x", "name"}, {i32, []u8}, {null, "bro"})
|
Direct :: alias struct_type!(.auto, {"x", "name"}, {i32, []u8}, {null, "bro"})
|
||||||
@@ -16768,7 +16775,53 @@ get func($E, $V type, map @Map(E, V), key E) ?V {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composed_map :: $(init(ErrorAlias, i32, {
|
||||||
|
shared = 10,
|
||||||
|
left = 20,
|
||||||
|
right = 30,
|
||||||
|
tail = 40,
|
||||||
|
}))
|
||||||
|
|
||||||
|
reflects_composed_keys func($E type) bool {
|
||||||
|
keys [4]E := {.shared, .left, .right, .tail}
|
||||||
|
match typeinfo!(E) {
|
||||||
|
.enum |info|: {
|
||||||
|
if info.fields.len != 4 { return false }
|
||||||
|
inline for info.fields |field, index| {
|
||||||
|
if field.type != E or field.index != index { return false }
|
||||||
|
if field!(E, field.name) != keys[index] { return false }
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
field!(E, "shared") == field!(NestedErrors, "shared")
|
||||||
|
and field!(E, "left") != field!(E, "right")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
|
if !$(reflects_composed_keys(ErrorAlias)) { return 27 }
|
||||||
|
keys [4]ErrorAlias := {.shared, .left, .right, .tail}
|
||||||
|
expected [4]i32 := {10, 20, 30, 40}
|
||||||
|
for keys |key, index| {
|
||||||
|
if get(&composed_map, key) |value| {
|
||||||
|
if value != expected[index] { return 28 }
|
||||||
|
} else { return 29 }
|
||||||
|
}
|
||||||
|
left LeftError := .left
|
||||||
|
right RightError := .right
|
||||||
|
if get(&composed_map, left) |value| {
|
||||||
|
if value != 20 { return 30 }
|
||||||
|
} else { return 30 }
|
||||||
|
if get(&composed_map, right) |value| {
|
||||||
|
if value != 30 { return 31 }
|
||||||
|
} else { return 32 }
|
||||||
|
partial Map(ErrorAlias, i32) := init({right = 3})
|
||||||
|
if get(&partial, .shared) |_| { return 33 }
|
||||||
|
if get(&partial, .right) |value| {
|
||||||
|
if value != 3 { return 34 }
|
||||||
|
} else { return 35 }
|
||||||
other Map(OtherTokenKind, []u8) := init(OtherTokenKind, []u8, {alpha = "other"})
|
other Map(OtherTokenKind, []u8) := init(OtherTokenKind, []u8, {alpha = "other"})
|
||||||
if !other.present[0] { return 26 }
|
if !other.present[0] { return 26 }
|
||||||
_ = ordered
|
_ = ordered
|
||||||
@@ -16873,6 +16926,53 @@ main func() i32 {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
enum_field_struct_rejects_payload_sums_and_nominal_unions :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-enum-field-struct-rejections"
|
||||||
|
main_path := "/tmp/brolang-test-enum-field-struct-rejections/main.bro"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
declarations := [?]string{
|
||||||
|
`Payload :: union(enum) { value i32 }
|
||||||
|
Empty :: enum { empty }
|
||||||
|
Keys :: alias Payload | Empty`,
|
||||||
|
`Keys :: union(enum) { first void, second void }`,
|
||||||
|
`Keys :: union { first i32, second i32 }`,
|
||||||
|
}
|
||||||
|
for declaration in declarations {
|
||||||
|
text := fmt.aprintf(`meta :: import "@std/meta"
|
||||||
|
%s
|
||||||
|
Flags :: alias meta.EnumFieldStruct(Keys, bool, false)
|
||||||
|
main func() void {
|
||||||
|
flags Flags := {}
|
||||||
|
_ = flags
|
||||||
|
}
|
||||||
|
`, declaration)
|
||||||
|
defer delete(text)
|
||||||
|
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)
|
||||||
|
found_rejection := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_rejection = found_rejection ||
|
||||||
|
strings.contains(diagnostic.message, "EnumFieldStruct key must be an enum")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_rejection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
|
std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-std-meta"
|
directory := "/tmp/brolang-test-std-meta"
|
||||||
|
|||||||
Reference in New Issue
Block a user