feat: execute tagged type-valued compile-time storage
This commit is contained in:
@@ -120,6 +120,8 @@ optionals containing types also stay in owned compile-time storage: `?type`, nes
|
||||
|
||||
fallible channels containing types also execute entirely at compile time: `type ! Error`, metadata records/arrays in either channel, and nested optionals retain owned active payloads. every producer input is explicit and constant; erased generic inputs, whole-value replacement and saved copies preserve exact nominal leaves and content-based specialization identity. `try` checks the complete error subset before propagating; `catch` captures an immutable error snapshot and selects generated types or runtime handlers. named metadata structs and tagged unions compose with ordinary error enums without runtime reflection. returns snapshot before scoped LIFO `defer`/`errdefer`; fallible `void` also permits bare returns and successful fallthrough. incomplete storage, invalid channels/payloads, capture mutation and runtime inputs diagnose. imported private types survive source/session disposal and repeated compilation. runtime locals still cannot store metadata aggregates; keep those values inside compile-time producers or pass them directly to erased inputs.
|
||||
|
||||
tagged unions containing types use the same owned compile-time storage. `Choice :: union(enum) { child type, empty void }` supports typed/contextual construction, active payload reads and nested field/index updates, immutable match captures and whole-value variant replacement. direct payload writes require the selected variant to be active; they never switch its tag. saved copies, constant-input procedure arguments/results and erased generic inputs preserve payloads and nominal type identity while generating runtime storage/defaults. `undefined` unions have no active variant until a whole-value assignment initializes them, including inside records and arrays. explicit enum tags must contain every variant, but unused tag members do not become match cases. malformed tags, nominal initializer conflicts, inactive/uninitialized access, readonly updates and narrow overflow retain checked diagnostics. untagged unions containing types remain invalid: metadata has no overlapping runtime layout.
|
||||
|
||||
fixed type arrays may also start with `undefined` inside type/type-array producers: `types [2]mut type := undefined`, then fill `types[index] = i16` in a loop. nested and whole-array updates preserve shape, mutability and independent initialization state in local copies; `.len` is available before elements initialize. reading an uninitialized type leaf, returning an incomplete array or passing one to a compile-time procedure diagnoses a read before initialization. empty shapes are valid, but every dimension must be explicit and nonnegative. construction charges each expanded cell against the shared execution bound, including nested zero-leaf shapes.
|
||||
|
||||
ordinary fixed value arrays use the same construction inside type/type-array producers: `counts [2]mut usize := undefined`, then fill indexed elements or replace whole rows/arrays. initialized leaves retain exact scalar, string, optional, record and tagged-union values; local copies retain independent partial initialization state. generated lengths/defaults and compile-time arguments can use initialized elements, while incomplete arrays cannot enter checked IR or procedure arguments. array `.len` and empty shapes do not read leaves. constant materialization of mutable slices or nonnull data pointers remains unsupported.
|
||||
|
||||
@@ -50,6 +50,9 @@ optional type-valued storage preserves nested presence, owned metadata payloads,
|
||||
checked unwraps and immutable captures across compile-time procedure boundaries.
|
||||
fallible type-valued channels own their active payloads, propagate checked errors,
|
||||
and select generated storage through `try`/`catch` and scoped cleanup.
|
||||
tagged type-valued unions own active payloads across compile-time calls and erased
|
||||
arguments; checked payload updates preserve saved copies, and whole-value assignments
|
||||
initialize undefined storage or select another variant.
|
||||
readonly type-valued slices own nested metadata leaves across compile-time calls,
|
||||
checked immutable-array/slice views, by-value iteration and erased arguments;
|
||||
mutable metadata borrowing remains open.
|
||||
@@ -165,6 +168,7 @@ test discovery and FFI remain outside these completed slices.
|
||||
- [x] optional type-storage checkpoint: `?type` and optional type-valued records/arrays use owned compile-time snapshots, including nested `some!(null)`, contextual payload construction, mutable whole-value/field/index replacement, immutable if/while/conditional captures, null comparisons and checked unwraps. constant-input procedures and erased generic arguments preserve specialization-cache identity and nominal leaves while generating runtime storage through interpreter/native execution. undefined presence, incomplete payloads, non-type values, capture mutation and runtime dependencies retain diagnostics. imported private nominal types, session/source disposal and repeat compilation preserve ownership. fallible type-valued channels are covered below; general type-valued execution and broader constant materialization remain open.
|
||||
- [x] fallible type-storage checkpoint: explicit success/error channels containing types use owned compile-time snapshots, including nested optional/record/array payloads, whole-value updates, independent copies, constant-input procedures and erased generic inputs. `try` validates error subsets and propagates through enclosing producers; `catch` binds immutable exact error payloads and selects generated types or runtime handlers. composed metadata errors preserve named variants, nominal leaves and cache identity. returned snapshots precede scoped LIFO `defer`/`errdefer` cleanup; fallible `void` supports bare returns and successful fallthrough. incomplete/uninitialized channels, invalid payloads/compositions, capture mutation and runtime dependencies retain diagnostics. imported private types, source/session disposal and repeat compilation preserve ownership through interpreter/native execution. runtime metadata storage, general type-valued execution and broader materialization remain open.
|
||||
- [x] readonly type-slice checkpoint: `[]type` and slices of type-valued records/arrays use owned compile-time snapshots. contextual literals, immutable readonly array views, nested slice views, checked `usize` bounds/indexing, `.len`, empty shapes and ordinary/inline by-value iteration preserve exact nominal leaves. constant-input procedure parameters/results, erased generic inputs, optional/fallible nesting and normalized cache keys generate runtime storage/defaults through interpreter/native execution. mutable annotations/backing, metadata sentinels, invalid ranges/indices, readonly writes, incomplete leaves and runtime dependencies retain diagnostics. imported private types, source/session disposal, shared construction bounds and repeat compilation preserve ownership. mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
||||
- [x] tagged type-storage checkpoint: tagged unions containing type leaves own active payload snapshots through typed/contextual construction, direct and nested field/index updates, immutable by-value matches and whole-value variant replacement. constant-input procedure arguments/results, erased generic inputs and normalized cache keys preserve nominal leaves and saved copies while generating runtime storage/defaults through interpreter/native execution. undefined union locals and nested record/array storage require whole-value initialization; inactive reads/writes, malformed explicit tags, nominal initializer conflicts, readonly updates, incomplete results and narrow overflow retain diagnostics. explicit sparse tag enums may contain unused members without adding match cases. imported private types, source/session disposal and repeat compilation preserve ownership. untagged metadata layouts remain invalid; mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
||||
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||
|
||||
@@ -1380,3 +1380,105 @@ readonly_type_slices_recompile_owned_private_nominal_payloads test {
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
tagged_type_metadata_recompiles_owned_private_nominal_payloads test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
modules [2]mut ir.Module := undefined
|
||||
{
|
||||
session := init(mem.c_allocator)
|
||||
defer deinit(&session)
|
||||
root :: try add_package(&session)
|
||||
dependency :: try add_package(&session)
|
||||
bytes := try owned_text(
|
||||
`lib :: import "../library"
|
||||
`Child :: struct { value bool }
|
||||
`Storage proc() type {
|
||||
` saved :: $(lib.make())
|
||||
` if (lib.Box(saved) != lib.Box(lib.make())) return bool
|
||||
` if (lib.Box(saved) != lib.Box(lib.literal())) return bool
|
||||
` Selected :: saved.payload.children[0]?
|
||||
` if (Selected == Child) return bool
|
||||
` return lib.Snapshot(saved)
|
||||
`}
|
||||
`main proc() i16 {
|
||||
` Selected :: lib.Selected(lib.make())
|
||||
` value Selected :: { value = 40 }
|
||||
` if (lib.read(lib.make(), value) != 42) return -4
|
||||
` snapshot Storage() :: {
|
||||
` original = { values = [{ value = 19 }, { value = 21 }] },
|
||||
` nested = { values = [true] },
|
||||
` replacement = { values = [{ value = 2 }] },
|
||||
` }
|
||||
` if (!snapshot.nested.values[0]) return -5
|
||||
` return (snapshot.original.values[0].value
|
||||
` + snapshot.original.values[1].value + snapshot.replacement.values[0].value)
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&bytes)
|
||||
root_source :: try add_source(&session, root, "/tagged_types/app/main.hon", &bytes, &diagnostic_store)
|
||||
imported := try owned_text(
|
||||
`@hide:file
|
||||
`Child :: struct { value i16 }
|
||||
`@hide:file
|
||||
`Config :: struct { children [2]mut ?type, count usize }
|
||||
`@hide:file
|
||||
`Metadata :: union(enum) { payload Config, fallback type }
|
||||
`literal proc() Metadata {
|
||||
` return { payload = { children = [Child, null], count = 2 } }
|
||||
`}
|
||||
`make proc() Metadata {
|
||||
` value Metadata := undefined
|
||||
` value = Metadata { payload = { children = [bool, null], count = 1 } }
|
||||
` value.payload.children[0] = Child
|
||||
` value.payload.count = 2
|
||||
` saved :: value
|
||||
` value.payload.children[0] = bool
|
||||
` value = Metadata { fallback = bool }
|
||||
` return saved
|
||||
`}
|
||||
`Box proc(input Metadata) type {
|
||||
` match input {
|
||||
` .payload |info|: {
|
||||
` T :: info.children[0]?
|
||||
` return struct { values [info.count]T }
|
||||
` },
|
||||
` .fallback |ChildType|: return struct { values [1]ChildType },
|
||||
` }
|
||||
`}
|
||||
`Snapshot proc(input Metadata) type {
|
||||
` changed := input
|
||||
` saved :: changed
|
||||
` changed.payload.children[0] = bool
|
||||
` changed.payload.count = 1
|
||||
` nested :: changed
|
||||
` changed = Metadata { fallback = Child }
|
||||
` return struct { original Box(saved), nested Box(nested), replacement Box(changed) }
|
||||
`}
|
||||
`Selected proc(input Metadata) type { return input.payload.children[0]? }
|
||||
`read proc($input Metadata, value Selected(input)) i16 {
|
||||
` return value.value + i16(input.payload.count)
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&imported)
|
||||
_ = try add_source(&session, dependency, "/tagged_types/library/types.hon", &imported, &diagnostic_store)
|
||||
try arraylist.append(&session.imports, Import{
|
||||
node = declaration(&session, root_source, 0).node,
|
||||
target = dependency,
|
||||
})
|
||||
modules[0] = try compile(&session, root, &diagnostic_store)
|
||||
errdefer ir.deinit(&modules[0])
|
||||
modules[1] = try compile(&session, root, &diagnostic_store)
|
||||
}
|
||||
defer ir.deinit(&modules[0])
|
||||
defer ir.deinit(&modules[1])
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
for modules |module| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +779,12 @@ metadata_package_contains proc(state @State, package ast.NodeId, source diagnost
|
||||
metadata_tags proc(state @mut State, type ast.NodeId) ast.NodeId ! Error {
|
||||
if (ast.expr_kind(&state.tree, type) == .enum_type) return type
|
||||
if (ast.union_type(&state.tree, type).tag) |tag| return try canonical_type(state, tag)
|
||||
return try metadata_variant_tags(state, type)
|
||||
}
|
||||
|
||||
# dispatch covers union variants, not unused members of an explicit tag enum.
|
||||
@hide:file
|
||||
metadata_variant_tags proc(state @mut State, type ast.NodeId) ast.NodeId ! Error {
|
||||
members := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&members)
|
||||
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
||||
@@ -3719,7 +3725,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
} else {
|
||||
if (ast.expr_kind(&state.tree, value.node) != .record_literal) return no_index
|
||||
active :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
||||
tags :: try metadata_tags(state, try inline_match_shape(state, annotation))
|
||||
tags :: try metadata_variant_tags(state, try inline_match_shape(state, annotation))
|
||||
tag :: try ast.add_enum_literal(state.builder, span, active.name?)
|
||||
dispatch = try ast.add_typed_constant(state.builder, span, tags, tag)
|
||||
}
|
||||
@@ -3950,8 +3956,10 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
|
||||
!record and !try execution_array_storage(state, array.type)
|
||||
and !try metadata_sequence(state, array.type)
|
||||
) return true
|
||||
if (try metadata_sequence(state, array.type)
|
||||
and ast.expr_kind(&state.tree, array.value) == .literal_undefined) {
|
||||
if (
|
||||
(try metadata_sequence(state, array.type) or record)
|
||||
and ast.expr_kind(&state.tree, array.value) == .literal_undefined
|
||||
) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.read_before_initialization, ast.node_span(&state.tree, id))
|
||||
return false
|
||||
@@ -4069,23 +4077,58 @@ type_record_slot proc(state @State, record, member ast.NodeId) usize {
|
||||
|
||||
@hide:file
|
||||
type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
|
||||
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), type, expression)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
||||
value :: ast.typed_constant(&state.tree, expression)
|
||||
if (try same_type(state, type, value.type)) return expression
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.type_mismatch, ast.node_span(&state.tree, expression))
|
||||
return no_node
|
||||
}
|
||||
name := no_node
|
||||
input := no_node
|
||||
kind :: ast.expr_kind(&state.tree, expression)
|
||||
if (kind == .enum_literal) name = ast.enum_literal(&state.tree, expression)
|
||||
else if (kind == .record_literal and ast.record_field_count(&state.tree, expression) == 1) {
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, 0))
|
||||
if (field.name) |member| name = member
|
||||
if (field.value) |value| input = value
|
||||
else if (kind == .record_literal) {
|
||||
literal :: ast.record_literal(&state.tree, expression)
|
||||
if (literal.type != null and ast.expr_kind(&state.tree, literal.type?) == .enum_literal) {
|
||||
name = ast.enum_literal(&state.tree, literal.type?)
|
||||
count :: ast.record_field_count(&state.tree, expression)
|
||||
if (count == 1 and ast.field(&state.tree, ast.record_field(&state.tree, expression, 0)).name == null) {
|
||||
input = ast.field(&state.tree, ast.record_field(&state.tree, expression, 0)).value?
|
||||
} else {
|
||||
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&fields)
|
||||
for (0..count) |i| try arraylist.append(&fields, ast.record_field(&state.tree, expression, i))
|
||||
input = try ast.add_record_literal(state.builder, ast.node_span(&state.tree, expression), null, fields.items)
|
||||
try refresh(state)
|
||||
}
|
||||
} else {
|
||||
if (literal.type != null and !try same_type(state, type, literal.type?)) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.type_mismatch, ast.node_span(&state.tree, expression))
|
||||
return no_node
|
||||
}
|
||||
if (ast.record_field_count(&state.tree, expression) != 1) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.union_initializer_count, ast.node_span(&state.tree, expression))
|
||||
return no_node
|
||||
}
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, 0))
|
||||
if (field.name) |member| name = member
|
||||
if (field.value) |value| input = value
|
||||
}
|
||||
}
|
||||
slot :: if (name != no_node) type_record_slot(state, type, name) else no_index
|
||||
if (slot == no_index) {
|
||||
code diagnostics.Code :: if (name != no_node) .unknown_field else .union_initializer_count
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.union_initializer_count, ast.node_span(&state.tree, expression))
|
||||
code, ast.node_span(&state.tree, expression))
|
||||
return no_node
|
||||
}
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, slot))
|
||||
@@ -4093,6 +4136,12 @@ type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId
|
||||
empty :: (binding(state, payload_type).kind == .builtin
|
||||
and binding(state, payload_type).data.builtin == .void)
|
||||
span :: ast.node_span(&state.tree, expression)
|
||||
if (
|
||||
empty and kind == .record_literal
|
||||
and ast.record_literal(&state.tree, expression).type != null
|
||||
and ast.expr_kind(&state.tree, ast.record_literal(&state.tree, expression).type?) == .enum_literal
|
||||
and ast.record_field_count(&state.tree, expression) == 0
|
||||
) input = no_node
|
||||
if (input == no_node and empty) input = try ast.add_literal(state.builder, .void, span)
|
||||
if (input == no_node) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .missing_field, span)
|
||||
@@ -4180,10 +4229,53 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
||||
}
|
||||
return try type_record_check(state, descriptor.element)
|
||||
}
|
||||
tagged :: ast.expr_kind(&state.tree, type) == .union_type
|
||||
tag := no_node
|
||||
if (tagged) {
|
||||
descriptor :: ast.union_type(&state.tree, type)
|
||||
if (!descriptor.auto_tag and descriptor.tag == null) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_record_field_type, ast.node_span(&state.tree, type))
|
||||
return false
|
||||
}
|
||||
if (descriptor.tag) |tag_annotation| {
|
||||
tag = try canonical_type(state, tag_annotation)
|
||||
if (tag == no_node or ast.expr_kind(&state.tree, tag) != .enum_type) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_union_tag, ast.node_span(&state.tree, tag_annotation))
|
||||
return false
|
||||
}
|
||||
if (!try type_record_check(state, tag)) return false
|
||||
}
|
||||
}
|
||||
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
||||
field_id :: ast.record_field(&state.tree, type, i)
|
||||
field :: ast.field(&state.tree, field_id)
|
||||
if (!try type_step(state, field_id)) return false
|
||||
if (tagged) {
|
||||
if (field.type == null or field.value != null) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_record_field_type, field.span)
|
||||
return false
|
||||
}
|
||||
if (field.name == null) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||
return false
|
||||
}
|
||||
if (tag != no_node) {
|
||||
found := false
|
||||
for (0..ast.enum_member_count(&state.tree, tag)) |j| {
|
||||
member :: ast.field(&state.tree, ast.enum_member(&state.tree, tag, j))
|
||||
if (member.name != null and ast.name_id(&state.tree, member.name?) == ast.name_id(&state.tree, field.name?)) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_union_tag, field.span)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (field.name != null and type_record_slot(state, type, field.name?) != i) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||
return false
|
||||
@@ -4352,6 +4444,15 @@ type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error
|
||||
type_storage_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
||||
if (is_type(state, annotation)) return expression
|
||||
type :: try canonical_type(state, annotation)
|
||||
if (
|
||||
type != no_node and ast.expr_kind(&state.tree, type) == .union_type
|
||||
and type_record_storage(state, type, 0)
|
||||
) {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), type, expression)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
if (type != no_node and ast.expr_kind(&state.tree, type) == .record_type) {
|
||||
if (!try type_record_check(state, type)) return no_node
|
||||
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||
@@ -4596,13 +4697,20 @@ type_array_update proc(
|
||||
type :: try canonical_type(state, array.type)
|
||||
if (
|
||||
type == no_node
|
||||
or (ast.expr_kind(&state.tree, type) != .record_type and ast.expr_kind(&state.tree, type) != .array_type)
|
||||
or (ast.expr_kind(&state.tree, type) != .record_type and ast.expr_kind(&state.tree, type) != .array_type
|
||||
and (ast.expr_kind(&state.tree, type) != .union_type or !type_record_storage(state, type, 0)))
|
||||
) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
||||
return no_node
|
||||
}
|
||||
record :: ast.expr_kind(&state.tree, type) == .record_type
|
||||
tagged :: ast.expr_kind(&state.tree, type) == .union_type
|
||||
record :: ast.expr_kind(&state.tree, type) == .record_type or tagged
|
||||
if (tagged and ast.expr_kind(&state.tree, array.value) == .literal_undefined) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.read_before_initialization, ast.node_span(&state.tree, target))
|
||||
return no_node
|
||||
}
|
||||
index := no_index
|
||||
element_type := no_node
|
||||
count := usize(0)
|
||||
@@ -4617,6 +4725,16 @@ type_array_update proc(
|
||||
}
|
||||
element_type = ast.field(&state.tree, ast.record_field(&state.tree, type, index)).type?
|
||||
count = ast.record_field_count(&state.tree, array.value)
|
||||
if (tagged) {
|
||||
active :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, 0))
|
||||
member :: ast.field(&state.tree, ast.record_field(&state.tree, type, index)).name?
|
||||
if (active.name == null or ast.name_id(&state.tree, active.name?) != ast.name_id(&state.tree, member)) {
|
||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||
.inactive_union_field, ast.node_span(&state.tree, first))
|
||||
return no_node
|
||||
}
|
||||
index = 0
|
||||
}
|
||||
previous = ast.field(&state.tree, ast.record_field(&state.tree, array.value, index)).value?
|
||||
} else {
|
||||
descriptor :: ast.array_type(&state.tree, type)
|
||||
|
||||
+225
@@ -2278,6 +2278,231 @@ CASES = {
|
||||
}
|
||||
""", b"", b"initialization",
|
||||
),
|
||||
"tagged_metadata_active_payload_updates_own_saved_copies": (
|
||||
"""
|
||||
Detail :: struct { child type, rows [2]mut struct { child type, count usize } }
|
||||
Choice :: union(enum) { child type, detail Detail, empty void }
|
||||
Storage proc() type {
|
||||
direct Choice := .child{bool}
|
||||
direct_saved :: direct
|
||||
direct.child = i16
|
||||
if (direct_saved.child != bool or direct.child != i16) return bool
|
||||
value Choice := .detail{
|
||||
child = i8,
|
||||
rows = [{ child = bool, count = 1 }, { child = i16, count = 2 }],
|
||||
}
|
||||
saved :: value
|
||||
value.detail.child = bool
|
||||
value.detail.rows[1].child = u8
|
||||
value.detail.rows[1].count = 3
|
||||
if (saved.detail.child != i8 or saved.detail.rows[1].child != i16) return bool
|
||||
if (value.detail.rows[1].child != u8 or value.detail.rows[1].count != 3) return bool
|
||||
value = .child{i8}
|
||||
if (value.child != i8) return bool
|
||||
value = .empty
|
||||
return match value {
|
||||
.empty: [saved.detail.rows[1].count]saved.detail.rows[1].child,
|
||||
.child, .detail: bool,
|
||||
}
|
||||
}
|
||||
main proc() Storage() { return [40, 2] }
|
||||
""", b"[40, 2]\n", None,
|
||||
),
|
||||
"tagged_metadata_undefined_storage_whole_replacement": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
Container :: struct { choice Choice }
|
||||
selected proc() type {
|
||||
local Choice := undefined
|
||||
record Container := undefined
|
||||
values [2]mut Choice := undefined
|
||||
local = .child{bool}
|
||||
record.choice = .child{i16}
|
||||
values[0] = local
|
||||
values[1] = record.choice
|
||||
saved :: values
|
||||
local = .empty
|
||||
record.choice = .empty
|
||||
values[1] = .child{u8}
|
||||
if (saved[0].child != bool or values[1].child != u8) return bool
|
||||
return saved[1].child
|
||||
}
|
||||
main proc() selected() { return 42 }
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"tagged_metadata_procedure_capture_erasure_generated_defaults_and_cache": (
|
||||
"""
|
||||
Id :: distinct i16
|
||||
Other :: distinct i16
|
||||
Detail :: struct { child type, count usize, bias i16 }
|
||||
Choice :: union(enum) { detail Detail, child type, empty void }
|
||||
make proc(T type) Choice { return .detail{ child = T, count = 2, bias = 2 } }
|
||||
pass proc(input Choice) Choice {
|
||||
match input {
|
||||
.detail |payload|: {
|
||||
copied := payload
|
||||
copied.child = bool
|
||||
copied.count = 3
|
||||
return .detail{payload}
|
||||
},
|
||||
.child |T|: return .child{T},
|
||||
.empty: return .empty,
|
||||
}
|
||||
}
|
||||
Storage proc(input Choice) type {
|
||||
return match input {
|
||||
.detail |payload|: struct {
|
||||
values [payload.count]payload.child
|
||||
bias i16 = payload.bias
|
||||
},
|
||||
.child |T|: struct { value T },
|
||||
.empty: struct {},
|
||||
}
|
||||
}
|
||||
score proc($input Choice, value Storage(input)) i16 {
|
||||
return i16(value.values[0]) + i16(value.values[1]) + value.bias
|
||||
}
|
||||
main proc() i16 {
|
||||
Selected :: alias Storage(pass(make(Id)))
|
||||
if (Selected != Storage(make(Id))) return -1
|
||||
if (Selected == Storage(make(Other))) return -2
|
||||
if (Selected == Storage(.child{Id})) return -3
|
||||
value Selected :: { values = [Id(19), Id(21)] }
|
||||
same Storage(make(Id)) :: value
|
||||
return score(pass(make(Id)), same)
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"tagged_metadata_sparse_explicit_tags_match_variants_not_unused_tags": (
|
||||
"""
|
||||
Tag :: enum(i16) { unused = -90, empty = -7, child = 42 }
|
||||
Choice :: union(Tag) { child type, empty void }
|
||||
choose proc(present bool) Choice {
|
||||
if (present) return .child{i16}
|
||||
return .empty
|
||||
}
|
||||
Selected proc(input Choice) type {
|
||||
return match input { .child |T|: T, .empty: bool }
|
||||
}
|
||||
main proc() i16 {
|
||||
empty Selected(choose(false)) :: true
|
||||
value Selected(choose(true)) :: 42
|
||||
if (!empty) return 0
|
||||
return value
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"tagged_metadata_reject_inactive_payload_write": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
Bad proc() type {
|
||||
value Choice := .empty
|
||||
value.child = i16
|
||||
return value.child
|
||||
}
|
||||
main proc() Bad() { return 42 }
|
||||
""", b"", b"tagged union field is not active",
|
||||
),
|
||||
"tagged_metadata_reject_inactive_payload_read": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
Bad proc() type { value Choice :: .empty
|
||||
return value.child }
|
||||
main proc() Bad() { return 42 }
|
||||
""", b"", b"tagged union field is not active",
|
||||
),
|
||||
"tagged_metadata_reject_wrong_nominal_union_literal": (
|
||||
"""
|
||||
Choice :: union(enum) { child type }
|
||||
Other :: union(enum) { child type }
|
||||
make proc() Choice { return Other { child = i16 } }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"incompatible value type",
|
||||
),
|
||||
"tagged_metadata_reject_unknown_member": (
|
||||
"""
|
||||
Choice :: union(enum) { child type }
|
||||
make proc() Choice { return Choice { missing = i16 } }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"record has no such field",
|
||||
),
|
||||
"tagged_metadata_reject_zero_initializers": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
make proc() Choice { return Choice {} }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"union construction requires exactly one member",
|
||||
),
|
||||
"tagged_metadata_reject_multiple_initializers": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, other type }
|
||||
make proc() Choice { return Choice { child = i16, other = bool } }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"union construction requires exactly one member",
|
||||
),
|
||||
"tagged_metadata_reject_explicit_tag_missing_variant": (
|
||||
"""
|
||||
Tag :: enum { other }
|
||||
Choice :: union(Tag) { child type }
|
||||
make proc() Choice { return .child{i16} }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"union tag must be an enum containing every union variant",
|
||||
),
|
||||
"tagged_metadata_reject_non_enum_tag": (
|
||||
"""
|
||||
Choice :: union(i16) { child type }
|
||||
make proc() Choice { return .child{i16} }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"union tag must be an enum containing every union variant",
|
||||
),
|
||||
"tagged_metadata_reject_untagged_overlapping_layout": (
|
||||
"""
|
||||
Choice :: union { detail struct { children [1]type }, count usize }
|
||||
make proc() Choice { return Choice { detail = { children = [i16] } } }
|
||||
main proc() make().detail.children[0] { return 42 }
|
||||
""", b"", b"record fields require a concrete value type",
|
||||
),
|
||||
"tagged_metadata_reject_undefined_result": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
make proc() Choice { value Choice := undefined
|
||||
return value }
|
||||
main proc() make().child { return 42 }
|
||||
""", b"", b"read before initialization",
|
||||
),
|
||||
"tagged_metadata_reject_undefined_payload_read": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, empty void }
|
||||
Bad proc() type { value Choice := undefined
|
||||
return value.child }
|
||||
main proc() Bad() { return 42 }
|
||||
""", b"", b"read before initialization",
|
||||
),
|
||||
"tagged_metadata_preserve_narrow_ordinary_payload_overflow": (
|
||||
"""
|
||||
Choice :: union(enum) { child type, small i8 }
|
||||
Bad proc() type {
|
||||
value Choice := .small{127}
|
||||
value.small = value.small + 1
|
||||
return i16
|
||||
}
|
||||
main proc() Bad() { return 42 }
|
||||
""", b"", b"integer arithmetic overflow",
|
||||
),
|
||||
"tagged_metadata_reject_mutating_match_capture": (
|
||||
"""
|
||||
Choice :: union(enum) { detail struct { child type }, empty void }
|
||||
Bad proc() type {
|
||||
value Choice :: .detail{ child = i16 }
|
||||
match value {
|
||||
.detail |payload|: { payload.child = bool
|
||||
return payload.child },
|
||||
.empty: return bool,
|
||||
}
|
||||
}
|
||||
main proc() Bad() { return 42 }
|
||||
""", b"", b"indexed assignment requires a writable array element",
|
||||
),
|
||||
"metadata_slices_direct_literal_views_start_bounded_evaluation": (
|
||||
"""
|
||||
Selected proc($items []type) type { return items[0] }
|
||||
|
||||
Reference in New Issue
Block a user