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
+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)
}
}
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 {
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)