feat: execute readonly type-valued compile-time slices

This commit is contained in:
2026-10-30 21:30:36 +01:00
parent 15cbebe40f
commit 4a1b30d9d5
5 changed files with 610 additions and 20 deletions
+84
View File
@@ -1296,3 +1296,87 @@ fallible_type_metadata_recompiles_owned_private_nominal_payloads test {
try testing.expect_equal(i64(42), outcome.value.data.integer)
}
}
readonly_type_slices_recompile_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 :: lib.types()[0]
` if (Selected == Child or saved[0].child != Selected) return bool
` return lib.Box(saved)
`}
`main proc() i16 {
` value lib.types()[0] :: { value = 40 }
` if (lib.read(lib.make(), value) != 42) return -1
` box Storage() :: { values = [{ value = 19 }, { value = 21 }] }
` same lib.Box(lib.literal()) :: box
` return same.values[0].value + same.values[1].value + same.bias
`}
,
)
defer arraylist.deinit(&bytes)
root_source :: try add_source(&session, root, "/readonly_type_slices/app/main.hon", &bytes, &diagnostic_store)
imported := try owned_text(
`@hide:file
`Child :: struct { value i16 }
`@hide:file
`Config :: struct { child type, count usize }
`types proc() []type { return [Child] }
`literal proc() []Config {
` return [{ child = Child, count = 2 }, { child = Child, count = 2 }]
`}
`make proc() []Config {
` rows [3]Config := [
` { child = Child, count = 99 },
` { child = Child, count = 2 },
` { child = Child, count = 2 },
` ]
` frozen :: rows
` saved :: frozen[1..][..2]
` rows = [
` { child = bool, count = 7 },
` { child = bool, count = 7 },
` { child = bool, count = 7 },
` ]
` return saved
`}
`Box proc(input []Config) type {
` return struct { values [input[0].count]input[0].child, bias i16 = i16(input[1].count) }
`}
`read proc($input []Config, value input[0].child) i16 {
` return value.value + i16(input[1].count)
`}
,
)
defer arraylist.deinit(&imported)
_ = try add_source(&session, dependency, "/readonly_type_slices/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)
}
}
+157 -20
View File
@@ -3122,6 +3122,35 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
}
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) {
kind :: ast.expr_kind(&state.tree, id)
if (kind == .slice) {
base :: ast.slice_expr(&state.tree, id).base
root := base
while (true) {
root_kind :: ast.expr_kind(&state.tree, root)
if (root_kind == .selection) root = ast.selection_expr(&state.tree, root).base
else if (root_kind == .index) root = ast.index_expr(&state.tree, root).base
else if (root_kind == .unary and ast.unary_expr(&state.tree, root).operator == .unwrap)
root = ast.unary_expr(&state.tree, root).operand
else if (root_kind == .builtin_call
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, root).name), "field")
and ast.call_arg_count(&state.tree, root) == 2)
root = ast.call_arg(&state.tree, root, 0)
else break
}
place :: binding(state, root)
if (place.kind == .declaration
and ast.node_class(&state.tree, place.data.declaration) == .statement
and ast.statement_decl(&state.tree, place.data.declaration).mutability == .variable) {
annotation :: try expression_type(state, base, 0)
if (type_record_storage(state, annotation, 0)
and ast.expr_kind(&state.tree, annotation) == .array_type) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_value_escape, ast.node_span(&state.tree, id))
execution.pending_flow = .invalid
return
}
}
}
if (kind == .block_expr) {
execution.mapping[usize(id)] = try execute_type_value_block(state, execution,
ast.block_expr(&state.tree, id), no_node)
@@ -3874,6 +3903,15 @@ expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error {
type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
if (ast.node_class(&state.tree, id) != .expression) return true
kind :: ast.expr_kind(&state.tree, id)
if (kind == .slice) {
annotation :: try expression_type(state, id, 0)
if (!try metadata_sequence(state, annotation)) return true
value :: try type_slice_value(state, id, annotation)
if (value == no_node) return false
try replace(state, id, value)
try refresh(state)
return true
}
if (kind == .unary and ast.unary_expr(&state.tree, id).operator == .unwrap) {
operand :: ast.unary_expr(&state.tree, id).operand
annotation :: try expression_type(state, operand, 0)
@@ -3912,6 +3950,12 @@ 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) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, id))
return false
}
selected := no_node
selected_type := no_node
if (record) {
@@ -3936,7 +3980,7 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
selected_type = ast.field(&state.tree, ast.record_field(&state.tree, type, slot)).type?
} else if (kind == .index) {
index :: try type_array_index(state, ast.index_expr(&state.tree, id).index,
ast.array_element_count(&state.tree, array.value))
ast.array_element_count(&state.tree, array.value), false)
if (index == no_index) return false
selected = ast.array_element(&state.tree, array.value, index)
if (!type_record_storage(state, array.type, 0))
@@ -4114,8 +4158,15 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
.invalid_sum_type, ast.node_span(&state.tree, type))
return false
}
if (ast.expr_kind(&state.tree, type) == .slice_type)
return try type_record_check(state, ast.slice_type(&state.tree, type).element)
if (ast.expr_kind(&state.tree, type) == .slice_type) {
descriptor :: ast.slice_type(&state.tree, type)
if (descriptor.mutable or descriptor.sentinel != null) {
code diagnostics.Code :: if (descriptor.mutable) .comptime_value_escape else .invalid_operand
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, type))
return false
}
return try type_record_check(state, descriptor.element)
}
if (ast.expr_kind(&state.tree, type) == .array_type) {
if (!try type_step(state, type)) return false
descriptor :: ast.array_type(&state.tree, type)
@@ -4231,7 +4282,7 @@ type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.
}
@hide:file
type_array_index proc(state @mut State, expression ast.NodeId, count usize) usize ! Error {
type_array_index proc(state @mut State, expression ast.NodeId, count usize, boundary bool) usize ! Error {
usize_type :: try builtin(state, expression, .usize)
actual :: try expression_type(state, expression, 0)
if (!flexible(state, expression) and (actual == no_node or !try same_type(state, actual, usize_type))) {
@@ -4241,9 +4292,9 @@ type_array_index proc(state @mut State, expression ast.NodeId, count usize) usiz
}
value :: try evaluate_ir(state, expression, usize_type)
if (value.kind != .integer) return no_index
if (value.magnitude >= u64(count)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.index_out_of_bounds, ast.node_span(&state.tree, expression))
if (value.magnitude > u64(count) or (!boundary and value.magnitude == u64(count))) {
code diagnostics.Code :: if (boundary) .invalid_slice_bounds else .index_out_of_bounds
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, expression))
return no_index
}
return usize(value.magnitude)
@@ -4428,6 +4479,100 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N
return result
}
# readonly metadata views own their leaves. mutable backing needs borrowing semantics,
# not a snapshot that silently stops observing later element writes.
@hide:file
type_slice_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
state.type_execution_depth += 1
defer state.type_execution_depth -= 1
type :: try canonical_type(state, annotation)
if (!try type_record_check(state, type)) return no_node
descriptor :: ast.slice_type(&state.tree, type)
span :: ast.node_span(&state.tree, expression)
kind :: ast.expr_kind(&state.tree, expression)
if (kind == .literal_undefined) {
result :: try ast.add_typed_constant(state.builder, span, type, expression)
try refresh(state)
return result
}
if (kind == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, type, actual.type)) return expression
}
source := expression
start := usize(0)
end := no_index
if (kind == .slice) {
view :: ast.slice_expr(&state.tree, expression)
if (view.sentinel != null) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_operand, span)
return no_node
}
source = view.base
}
if (ast.expr_kind(&state.tree, source) == .array_literal) {
for (0..ast.array_element_count(&state.tree, source)) |_| {
if (!try type_step(state, expression)) return no_node
}
length :: try ast.add_integer(state.builder, span, u64(ast.array_element_count(&state.tree, source)))
array :: try ast.add_array_type(state.builder, span, length, descriptor.element, false)
try refresh(state)
source = try type_array_value(state, source, array)
} else {
actual :: try expression_type(state, source, 0)
if (actual == no_node) return no_node
actual_kind :: ast.expr_kind(&state.tree, actual)
if ((actual_kind != .array_type and actual_kind != .slice_type)
or (actual_kind == .array_type and kind != .slice)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
return no_node
}
mutable :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).mutable
else ast.slice_type(&state.tree, actual).mutable
element :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).element
else ast.slice_type(&state.tree, actual).element
if (mutable) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
return no_node
}
if (!try same_type(state, descriptor.element, element)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
return no_node
}
source = try type_execution_value(state, source, actual)
}
if (source == no_node) return no_node
values :: ast.typed_constant(&state.tree, source).value
if (ast.expr_kind(&state.tree, values) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
return no_node
}
count :: ast.array_element_count(&state.tree, values)
end = count
if (kind == .slice) {
view :: ast.slice_expr(&state.tree, expression)
if (view.start) |bound| start = try type_array_index(state, bound, count, true)
if (start == no_index) return no_node
if (view.end) |bound| end = try type_array_index(state, bound, count, true)
if (end == no_index) return no_node
if (start > end) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_slice_bounds, span)
return no_node
}
}
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (start..end) |i| {
if (!try type_step(state, expression)) return no_node
try arraylist.append(&elements, ast.array_element(&state.tree, values, i))
}
literal :: try ast.add_array_literal(state.builder, span, elements.items)
result :: try ast.add_typed_constant(state.builder, span, type, literal)
try refresh(state)
return result
}
# rebuild only the field/index path so previous snapshots retain their owned values.
@hide:file
type_array_update proc(
@@ -4483,7 +4628,7 @@ type_array_update proc(
expression :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index)
if (!try expand_type_matches(state, expression)) return no_node
count = ast.array_element_count(&state.tree, array.value)
index = try type_array_index(state, expression, count)
index = try type_array_index(state, expression, count, false)
if (index == no_index) return no_node
element_type = descriptor.element
previous = ast.array_element(&state.tree, array.value, index)
@@ -4691,6 +4836,8 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
declaration :: ast.statement_decl(&state.tree, resolved.data.declaration)
if (declaration.mutability != .constant or !try type_step(state, expression)) return no_node
if (declaration.type != null and !try same_type(state, annotation, declaration.type?)) {
if (try metadata_sequence(state, annotation))
return try type_slice_value(state, expression, annotation)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, expression))
return no_node
@@ -4708,18 +4855,8 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
}
return value
}
if (
!is_type(state, annotation) and type_record_storage(state, annotation, 0)
and ast.expr_kind(&state.tree, try canonical_type(state, annotation)) == .slice_type
) {
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, annotation, actual.type)) return expression
}
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, expression))
return no_node
}
if (try metadata_sequence(state, annotation))
return try type_slice_value(state, expression, annotation)
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) {
type :: try canonical_type(state, annotation)
if (type != no_node and ast.expr_kind(&state.tree, type) == .optional_type)