feat: execute tagged type-valued compile-time storage
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user