feat: execute tagged type-valued compile-time storage

This commit is contained in:
2026-10-31 02:45:52 +01:00
parent 4a1b30d9d5
commit ebae8e712c
5 changed files with 461 additions and 10 deletions
+2
View File
@@ -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. 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. 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. 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.
+4
View File
@@ -50,6 +50,9 @@ optional type-valued storage preserves nested presence, owned metadata payloads,
checked unwraps and immutable captures across compile-time procedure boundaries. checked unwraps and immutable captures across compile-time procedure boundaries.
fallible type-valued channels own their active payloads, propagate checked errors, fallible type-valued channels own their active payloads, propagate checked errors,
and select generated storage through `try`/`catch` and scoped cleanup. 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, readonly type-valued slices own nested metadata leaves across compile-time calls,
checked immutable-array/slice views, by-value iteration and erased arguments; checked immutable-array/slice views, by-value iteration and erased arguments;
mutable metadata borrowing remains open. 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] 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] 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] 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 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`. - [ ] 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. - [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
+102
View File
@@ -1380,3 +1380,105 @@ readonly_type_slices_recompile_owned_private_nominal_payloads test {
try testing.expect_equal(i64(42), outcome.value.data.integer) 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)
}
}
+128 -10
View File
@@ -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 { 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.expr_kind(&state.tree, type) == .enum_type) return type
if (ast.union_type(&state.tree, type).tag) |tag| return try canonical_type(state, tag) 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) members := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&members) defer arraylist.deinit(&members)
for (0..ast.record_field_count(&state.tree, type)) |i| { 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 { } else {
if (ast.expr_kind(&state.tree, value.node) != .record_literal) return no_index 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)) 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?) tag :: try ast.add_enum_literal(state.builder, span, active.name?)
dispatch = try ast.add_typed_constant(state.builder, span, tags, tag) 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) !record and !try execution_array_storage(state, array.type)
and !try metadata_sequence(state, array.type) and !try metadata_sequence(state, array.type)
) return true ) return true
if (try metadata_sequence(state, array.type) if (
and ast.expr_kind(&state.tree, array.value) == .literal_undefined) { (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, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, id)) .read_before_initialization, ast.node_span(&state.tree, id))
return false return false
@@ -4069,23 +4077,58 @@ type_record_slot proc(state @State, record, member ast.NodeId) usize {
@hide:file @hide:file
type_union_value proc(state @mut State, expression, type ast.NodeId) ast.NodeId ! Error { 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) { if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
value :: ast.typed_constant(&state.tree, expression) value :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, type, value.type)) return 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 name := no_node
input := no_node input := no_node
kind :: ast.expr_kind(&state.tree, expression) kind :: ast.expr_kind(&state.tree, expression)
if (kind == .enum_literal) name = ast.enum_literal(&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) { else if (kind == .record_literal) {
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, 0)) literal :: ast.record_literal(&state.tree, expression)
if (field.name) |member| name = member if (literal.type != null and ast.expr_kind(&state.tree, literal.type?) == .enum_literal) {
if (field.value) |value| input = value 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 slot :: if (name != no_node) type_record_slot(state, type, name) else no_index
if (slot == 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, 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 return no_node
} }
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, slot)) 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 empty :: (binding(state, payload_type).kind == .builtin
and binding(state, payload_type).data.builtin == .void) and binding(state, payload_type).data.builtin == .void)
span :: ast.node_span(&state.tree, expression) 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 and empty) input = try ast.add_literal(state.builder, .void, span)
if (input == no_node) { if (input == no_node) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .missing_field, span) 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) 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| { for (0..ast.record_field_count(&state.tree, type)) |i| {
field_id :: ast.record_field(&state.tree, type, i) field_id :: ast.record_field(&state.tree, type, i)
field :: ast.field(&state.tree, field_id) field :: ast.field(&state.tree, field_id)
if (!try type_step(state, field_id)) return false 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) { 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) state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
return false 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 { type_storage_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (is_type(state, annotation)) return expression if (is_type(state, annotation)) return expression
type :: try canonical_type(state, annotation) 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 (type != no_node and ast.expr_kind(&state.tree, type) == .record_type) {
if (!try type_record_check(state, type)) return no_node if (!try type_record_check(state, type)) return no_node
fields := arraylist.init(ast.NodeId, state.allocator) fields := arraylist.init(ast.NodeId, state.allocator)
@@ -4596,13 +4697,20 @@ type_array_update proc(
type :: try canonical_type(state, array.type) type :: try canonical_type(state, array.type)
if ( if (
type == no_node 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, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_index_assignment, ast.node_span(&state.tree, target)) .invalid_index_assignment, ast.node_span(&state.tree, target))
return no_node 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 index := no_index
element_type := no_node element_type := no_node
count := usize(0) 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? element_type = ast.field(&state.tree, ast.record_field(&state.tree, type, index)).type?
count = ast.record_field_count(&state.tree, array.value) 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? previous = ast.field(&state.tree, ast.record_field(&state.tree, array.value, index)).value?
} else { } else {
descriptor :: ast.array_type(&state.tree, type) descriptor :: ast.array_type(&state.tree, type)
+225
View File
@@ -2278,6 +2278,231 @@ CASES = {
} }
""", b"", b"initialization", """, 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": ( "metadata_slices_direct_literal_views_start_bounded_evaluation": (
""" """
Selected proc($items []type) type { return items[0] } Selected proc($items []type) type { return items[0] }