feat: materialize readonly typed compile-time slices

This commit is contained in:
2026-10-30 17:02:21 +01:00
parent cc39c96fc0
commit 32a7a1de24
10 changed files with 741 additions and 40 deletions
+24 -1
View File
@@ -84,6 +84,7 @@ ExprKind :: enum {
catch_expr
typed_constant
union_constant
slice_constant
comptime_expr
literal_void
literal_undefined
@@ -198,6 +199,7 @@ NodeKind :: enum {
stmt_block
expr_typed_constant
expr_union_constant
expr_slice_constant
expr_comptime
literal_float_value
literal_void
@@ -624,6 +626,17 @@ add_union_constant proc(
}, at)
}
#! owns a readonly slice's typed fixed-array backing, including any sentinel.
add_slice_constant proc(
builder @mut Builder, at Span, type, elements NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_slice_constant,
data0 = NodeData{ node_id = type },
data1 = NodeData{ node_id = elements },
}, at)
}
add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_comptime,
@@ -1613,6 +1626,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_catch: .catch_expr
.expr_typed_constant: .typed_constant
.expr_union_constant: .union_constant
.expr_slice_constant: .slice_constant
.expr_comptime: .comptime_expr
.expr_block: .block_expr
.literal_float_value: .literal_float
@@ -1675,6 +1689,15 @@ union_constant proc(tree @Tree, id NodeId) TypedConstantView {
}
}
slice_constant proc(tree @Tree, id NodeId) TypedConstantView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_slice_constant)
return TypedConstantView{
type = node.data0.node_id,
value = node.data1.node_id,
}
}
catch_expr proc(tree @Tree, id NodeId) CatchExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_catch)
@@ -2030,7 +2053,7 @@ node_layout proc(node Node) NodeLayout {
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
.literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_union_constant,
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_union_constant, .expr_slice_constant,
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
layout.direct_children = 6
.expr_pointer_type: layout.direct_children = 4
+68
View File
@@ -1007,6 +1007,74 @@ computed_byte_slices_recompile_without_borrowing_evaluation_storage test {
}
}
computed_nonbyte_slices_recompile_owned_private_elements_and_callbacks 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"
`Code :: distinct i16
`Entry :: struct { value bool }
`increment proc(value Code) i16 { return i16(value) + 100 }
`main proc() i16 {
` entries :: $(lib.make())
` if (entries.len != 2 or entries[0].nested.len != 2) return -1
` if (entries[0].nested[0] != -300 or entries[0].nested[1] != 1024) return -2
` if (entries[1].nested.len != 1 or entries[1].nested[0] != 1024) return -3
` if (i16(entries[0].value) != 40 or i16(entries[1].value) != 41) return -4
` if (entries[1].run(entries[1].value) != 43) return -5
` return entries[0].run(entries[0].value)
`}
,
)
defer arraylist.deinit(&bytes)
root_source :: try add_source(&session, root, "/nonbyte_slices/app/main.hon", &bytes, &diagnostic_store)
imported := try owned_text(
`@hide:file
`Code :: distinct i16
`@hide:file
`Callback :: alias @proc(value Code) i16
`@hide:file
`Entry :: struct { value Code, run Callback, nested []i16 }
`bias i16 :: 2
`@hide:file
`increment proc(value Code) i16 { return i16(value) + bias }
`make proc() []Entry {
` values [2]i16 :: [-300, 1024]
` entries [2]Entry :: [
` { value = 40, run = increment, nested = values[..] },
` { value = 41, run = increment, nested = values[1..] },
` ]
` return entries[..]
`}
,
)
defer arraylist.deinit(&imported)
_ = try add_source(&session, dependency, "/nonbyte_slices/library/items.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)
}
}
computed_untagged_unions_recompile_without_borrowing_storage_bytes test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
+21 -7
View File
@@ -108,20 +108,34 @@ EvaluationMemory :: struct {
frames arraylist.ArrayList(Frame)
}
#! borrows one checked byte from retained evaluation storage; null means an invalid pointer/range.
evaluation_byte proc(outcome @Outcome, module @ir.Module, pointer ir.Value, index usize) ?ir.Value {
if (outcome.evaluation_memory == null) return null
#! borrows one checked typed element from retained evaluation storage; null means an invalid pointer/range.
#! aggregate storage may borrow scratch valid only until the next evaluation accessor call.
evaluation_element proc(
outcome @Outcome,
module @ir.Module,
pointer ir.Value,
index usize,
type ir.Type,
shape ir.ArrayId,
) ?ir.Value {
if (outcome.evaluation_memory == null or pointer.type != .pointer) return null
memory :: &outcome.evaluation_memory?[0]
handle :: pointer.data.unsigned
if (handle >= u64(memory.pointers.items.len)) return null
location := memory.pointers.items[usize(handle)]
if (index > maxval!(usize) - location.byte_offset) return null
location.byte_offset += index
if (pointer_address(module, location, .u8, ir.no_array)) |address|
stride :: ir.storage_size(module, type, shape)
if (stride != 0 and index > divtrunc!(maxval!(usize) - location.byte_offset, stride)) return null
location.byte_offset += index * stride
if (pointer_address(module, location, type, shape)) |address|
return read_address(module, address, memory.scratch)
return null
}
#! borrows one checked byte from retained evaluation storage; null means an invalid pointer/range.
evaluation_byte proc(outcome @Outcome, module @ir.Module, pointer ir.Value, index usize) ?ir.Value {
return evaluation_element(outcome, module, pointer, index, .u8, ir.no_array)
}
#! executes typed IR with invocation/global storage released on every exit.
#! aggregate results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
run proc(
@@ -789,7 +803,7 @@ pointer_address proc(module @ir.Module, pointer Pointer, type ir.Type, shape ir.
base := Address{
type = pointer.root_type, array_type = pointer.root_array, cells = &[],
root = pointer.root, root_type = pointer.root_type, root_array = pointer.root_array,
readonly = pointer.readonly, bytes = pointer.bytes,
readonly = pointer.readonly, bytes = pointer.bytes, raw = pointer.root^.raw,
}
if (pointer.bytes.len != 0) {
base.type = type
+75
View File
@@ -48,6 +48,9 @@ State :: struct {
arguments arraylist.ArrayList(ir.ValueId)
cleanups arraylist.ArrayList(ast.NodeId)
records []mut ir.ArrayId
slice_elements arraylist.ArrayList(typecheck.Type)
slice_backings arraylist.ArrayList(ast.NodeId)
evaluation_functions ?@mut arraylist.ArrayList(ast.NodeId) = null
function usize = 0
block ir.BlockId = ir.BlockId(0)
terminated bool = false
@@ -156,11 +159,16 @@ lower_module proc(
storage = storage,
arrays = arrays,
records = records,
slice_elements = arraylist.init(typecheck.Type, allocator),
slice_backings = arraylist.init(ast.NodeId, allocator),
evaluation_functions = functions,
arguments = arraylist.init(ir.ValueId, allocator),
cleanups = arraylist.init(ast.NodeId, allocator),
}
defer arraylist.deinit(&state.arguments)
defer arraylist.deinit(&state.cleanups)
defer arraylist.deinit(&state.slice_elements)
defer arraylist.deinit(&state.slice_backings)
# allocate every package identity before lowering any initializer or body.
for (0..ast.package_decl_count(tree)) |i| {
@@ -204,6 +212,22 @@ lower_module proc(
.import, .invalid: {}
}
}
backing_index := usize(0)
while (backing_index < state.slice_backings.items.len) {
try lower_slice_backing(&state, state.slice_backings.items[backing_index])
backing_index += 1
}
# source element shapes are metadata, not record layout dependencies. defer
# them until recursive records have cached their completed physical layout.
cursor := usize(0)
while (cursor < state.slice_elements.items.len) {
type :: state.slice_elements.items[cursor]
child :: typecheck.array_type(checked, type.array_type).element
shape :: state.arrays[usize(type.array_type)]
element_shape :: try array_shape(&state, child)
module.arrays.items[usize(shape)].element_array = element_shape
cursor += 1
}
return module
}
@@ -308,6 +332,10 @@ prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.
}
shape :: try ir.add_record(state.module, fields[..])
state.arrays[index] = shape
if (type.kind == .slice) {
state.module.arrays.items[usize(shape)].element = lower_type(state, descriptor.element)
try arraylist.append(&state.slice_elements, type)
}
return shape
}
@@ -448,6 +476,19 @@ lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diag
finish(state, value, declaration.span)
}
@hide:file
lower_slice_backing proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
global :: state.module.globals.items[usize(state.storage[usize(id)].index)]
state.function = usize(global.initializer)
state.block = ir.BlockId(0)
state.terminated = false
state.module.functions.items[state.function].result = global.type
state.module.functions.items[state.function].result_array_type = global.array_type
backing :: ast.slice_constant(state.tree, id).value
value :: try lower_expression(state, backing)
if (!state.terminated) finish(state, value, ast.node_span(state.tree, id))
}
@hide:file
lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
@@ -2098,6 +2139,40 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (trap_type(state, id)) return ir.no_value
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
}
.slice_constant: {
if (trap_type(state, id)) return ir.no_value
if (state.storage[usize(id)].kind == .none) {
backing :: ast.slice_constant(tree, id).value
initializer :: try add_function(state.module, span)
if (state.evaluation_functions) |declarations|
try arraylist.append(declarations, maxval!(ast.NodeId))
global :: u32(state.module.globals.items.len)
try arraylist.append(&state.module.globals, ir.Global{
type = lower_type(state, typecheck.type_of(state.checked, backing)),
array_type = try array_shape(state, typecheck.type_of(state.checked, backing)),
initializer = initializer,
span = span,
})
state.storage[usize(id)] = Storage{ kind = .global, index = global }
try arraylist.append(&state.slice_backings, id)
}
location :: state.storage[usize(id)]
global :: state.module.globals.items[usize(location.index)]
address :: try emit(state, ir.Instruction{
op = .address_global, type = .address, pointee = global.type,
array_type = global.array_type, slot = location.index, span = span,
})
pointer :: try emit(state, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = address,
pointee = global.type, array_type = global.array_type, span = span,
})
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type)
backing :: ast.slice_constant(tree, id).value
elements :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, backing).array_type)
extra :: if (descriptor.sentinel != null) usize(1) else usize(0)
len :: elements.len - extra
return try record_pair(state, shape, pointer, try usize_constant(state, len, span), span)
}
.union_constant: {
if (trap_type(state, id)) return ir.no_value
bytes :: ast.union_constant(tree, id).value
+29
View File
@@ -1301,6 +1301,14 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.slice_constant: {
value :: ast.slice_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.try_expr: {
operand :: ast.type_operand(tree, id)
try collect_expression(state, operand)
@@ -2178,6 +2186,27 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
if (!descriptor.is_union or descriptor.tag_type.kind != .none)
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.slice_constant: {
value :: ast.slice_constant(tree, id)
type :: state.cells[usize(id)].type
backing :: state.types[usize(value.value)]
if (type.kind == .invalid or backing.kind == .invalid) return
if (
type.kind != .slice or backing.kind != .array
or ast.expr_kind(tree, value.value) != .typed_constant
) {
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
return
}
descriptor :: state.arrays.items[usize(type.array_type)]
elements :: state.arrays.items[usize(backing.array_type)]
if (
descriptor.mutable or elements.mutable
or !same_type(state, descriptor.element, elements.element)
or (descriptor.sentinel != null and elements.len == 0)
or ast.expr_kind(tree, ast.typed_constant(tree, value.value).value) != .array_literal
) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
.unary: {
expression :: ast.unary_expr(tree, id)
+5
View File
@@ -551,6 +551,11 @@ resolve_expr proc(
try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value)
}
.slice_constant: {
value :: ast.slice_constant(tree, id)
try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value)
}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
.literal_undefined: {}
.enum_literal: {}
+181 -18
View File
@@ -1707,7 +1707,7 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
}
match ast.expr_kind(&state.tree, id) {
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
.union_constant: return true
.union_constant, .slice_constant: return true
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
.enum_literal: return true
.record_literal: {
@@ -2359,8 +2359,96 @@ union_bytes_materializable proc(checked @typecheck.Result, type typecheck.Type,
}
}
# rebuild exact slice backing annotations without retaining temporary checker IDs.
@hide:file
serialized_type proc(
state @mut State,
checked @typecheck.Result,
type typecheck.Type,
span ast.Span,
depth usize,
) ast.NodeId ! Error {
if (depth >= 128) return no_node
source := type.nominal
if (source == no_node and type.kind == .record) {
record :: typecheck.record_type(checked, type.record_type)
source = if (record.declaration != no_node) record.declaration else record.source
}
if (source == no_node and type.kind == .enumeration)
source = typecheck.enum_type(checked, type.enum_type).declaration
if (source == no_node and type.kind == .function) source = type.function
if (source != no_node) {
if (ast.node_class(&state.tree, source) == .function) {
params := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&params)
for (0..ast.function_param_count(&state.tree, source)) |i|
try arraylist.append(&params, ast.function_param(&state.tree, source, i))
source = try ast.add_function_type(state.builder, span,
ast.callable_result(&state.tree, source), params.items)
try refresh(state)
}
return (try materialize(state, Value{ kind = .type, node = source })).node
}
result := no_node
if (type.kind == .array or type.kind == .slice or type.kind == .optional or type.kind == .fallible) {
descriptor :: typecheck.array_type(checked, type.array_type)
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
if (child == no_node) return no_node
if (type.kind == .array) {
count :: try ast.add_integer(state.builder, span, u64(descriptor.len))
result = try ast.add_array_type(state.builder, span, count, child, descriptor.mutable)
} else if (type.kind == .slice) {
sentinel ?ast.NodeId := null
if (descriptor.sentinel) |value| {
unsigned :: (descriptor.element.kind == .u8 or descriptor.element.kind == .u16
or descriptor.element.kind == .u32 or descriptor.element.kind == .u64 or descriptor.element.kind == .usize)
negative :: value < 0 and !unsigned
magnitude :: if (negative) u64(-(value + 1)) + 1 else ptrcast!(@u64, &value)^
literal := try ast.add_integer(state.builder, span, magnitude)
if (negative) literal = try ast.add_unary(state.builder, span, .negate, literal)
sentinel = literal
}
result = try ast.add_slice_type(state.builder, span, child, descriptor.mutable, sentinel)
} else if (type.kind == .optional) result = try ast.add_optional_type(state.builder, span, child)
else {
error_type :: try serialized_type(state, checked, descriptor.error_type, span, depth + 1)
if (error_type == no_node) return no_node
result = try ast.add_binary_type(state.builder, span, child, error_type, true)
}
} else if (type.kind == .pointer) {
descriptor :: typecheck.pointer_type(checked, type.pointer_type)
child :: try serialized_type(state, checked, descriptor.element, span, depth + 1)
if (child == no_node) return no_node
result = try ast.add_pointer_type(state.builder, span, child, descriptor.mutable, descriptor.many)
} else {
kind sema.BuiltinType :: match type.kind {
.int: .i64
.i8: .i8
.i16: .i16
.i32: .i32
.isize: .isize
.u8: .u8
.u16: .u16
.u32: .u32
.u64: .u64
.usize: .usize
.float: .f64
.bool: .bool
.string: .string
.void: .void
.anyopaque: .anyopaque
else: return no_node
}
result = try ast.add_identifier(state.builder, span, strpool.no_str)
try refresh(state)
state.bindings.items[usize(result)] = sema.Binding{ kind = .builtin, data = sema.BindingData{ builtin = kind } }
}
try refresh(state)
return result
}
# values leaving temporary evaluation must own no interpreter addresses or module IDs.
# snapshot readonly bytes and rebuild source literals/references before releasing their backing.
# snapshot readonly storage and rebuild source literals/references before releasing their backing.
@hide:file
serialize_value proc(
state @mut State,
@@ -2371,7 +2459,15 @@ serialize_value proc(
type typecheck.Type,
value ir.Value,
span ast.Span,
remaining @mut usize,
) ast.NodeId ! Error {
if (remaining^ == 0 or state.depth >= 128) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
return no_node
}
remaining^ -= 1
state.depth += 1
defer state.depth -= 1
uninitialized := value.uninitialized
if (ir.aggregate(value.type)) {
if (module.arrays.items[usize(value.array_type)].is_union)
@@ -2386,13 +2482,64 @@ serialize_value proc(
result := no_node
if (type.kind == .slice) {
slice :: typecheck.array_type(checked, type.array_type)
if (slice.mutable or slice.element.kind != .u8 or slice.element.nominal != no_node) return no_node
if (slice.mutable) return no_node
pointer :: ir.aggregate_item(module, value, 0)
length :: ir.aggregate_item(module, value, 1)
if (pointer.uninitialized or length.uninitialized) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span)
return no_node
}
if (slice.element.kind != .u8 or slice.element.nominal != no_node) {
shape :: module.arrays.items[usize(value.array_type)]
count :: usize(length.data.unsigned)
extra :: if (slice.sentinel != null) usize(1) else usize(0)
if (count > remaining^ or extra > remaining^ - count) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_execution_limit, span)
return no_node
}
if (count != 0 and interpreter.evaluation_element(outcome, module, pointer,
count - 1, shape.element, shape.element_array) == null) return no_node
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (0..(count + extra)) |index| {
borrowed :: interpreter.evaluation_element(outcome, module, pointer,
index, shape.element, shape.element_array)
if (borrowed == null) return no_node
child := borrowed?
# recursive slice reads may overwrite the accessor's raw-view scratch.
scratch_backed :: ir.aggregate(child.type) and child.raw
owned []mut ir.Value := &[]
if (scratch_backed) {
owned = try mem.alloc(ir.Value, state.allocator, child.data.elements.len)
memcopy!(owned, child.data.elements)
child.data = ir.ValueData{ elements = owned }
}
defer if (scratch_backed) mem.free(state.allocator, owned)
node :: try serialize_value(state, module, checked, outcome, functions,
slice.element, child, span, remaining)
if (node == no_node) return no_node
if (index == count and slice.sentinel != null) {
# checker sentinels retain full-width integer bits, including u64 maxima.
if (child.data.integer != slice.sentinel?) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .sentinel_mismatch, span)
return no_node
}
}
try arraylist.append(&elements, node)
}
annotation :: try serialized_type(state, checked, type, span, 0)
if (annotation == no_node) return no_node
child_type :: if (type.nominal == no_node) ast.slice_type(&state.tree, annotation).element
else try serialized_type(state, checked, slice.element, span, 0)
if (child_type == no_node) return no_node
length_node :: try ast.add_integer(state.builder, span, u64(count + extra))
array_type :: try ast.add_array_type(state.builder, span, length_node, child_type, false)
literal :: try ast.add_array_literal(state.builder, span, elements.items)
backing :: try ast.add_typed_constant(state.builder, span, array_type, literal)
result = try ast.add_slice_constant(state.builder, span, annotation, backing)
try refresh(state)
return result
}
if (
length.data.unsigned != 0
and interpreter.evaluation_byte(outcome, module, pointer, usize(length.data.unsigned - 1)) == null
@@ -2437,7 +2584,17 @@ serialize_value proc(
defer arraylist.deinit(&elements)
for (0..array.len) |i| {
child :: ir.aggregate_item(module, value, i)
node :: try serialize_value(state, module, checked, outcome, functions, array.element, child, span)
node :: try serialize_value(
state,
module,
checked,
outcome,
functions,
array.element,
child,
span,
remaining,
)
if (node == no_node) return no_node
try arraylist.append(&elements, node)
}
@@ -2474,7 +2631,7 @@ serialize_value proc(
return result
}
child :: ir.aggregate_item(module, value, i)
node :: try serialize_value(state, module, checked, outcome, functions, field.type, child, span)
node :: try serialize_value(state, module, checked, outcome, functions, field.type, child, span, remaining)
if (node == no_node) return no_node
name := field.name
if (name != null) name = try member_name(state, name?)
@@ -2489,7 +2646,7 @@ serialize_value proc(
index :: if (failed) usize(1) else usize(0)
channel :: if (failed) container.error_type else container.element
child :: ir.aggregate_item(module, value, index)
result = try serialize_value(state, module, checked, outcome, functions, channel, child, span)
result = try serialize_value(state, module, checked, outcome, functions, channel, child, span, remaining)
if (result == no_node) return no_node
# keep named success/error types distinct even when their members overlap.
annotation := no_node
@@ -2518,12 +2675,12 @@ serialize_value proc(
value.data.function == maxval!(ir.FunctionId)
else value.data.unsigned == maxval!(u64)
if (absent) result = try ast.add_literal(state.builder, .null, span)
else result = try serialize_value(state, module, checked, outcome, functions, container.element, value, span)
else result = try serialize_value(state, module, checked, outcome, functions, container.element, value, span, remaining)
} else if (!ir.aggregate_item(module, value, 0).data.boolean) {
result = try ast.add_literal(state.builder, .null, span)
} else {
child :: ir.aggregate_item(module, value, 1)
payload :: try serialize_value(state, module, checked, outcome, functions, container.element, child, span)
payload :: try serialize_value(state, module, checked, outcome, functions, container.element, child, span, remaining)
if (payload == no_node) return no_node
name :: try ast.add_generated_identifier(state.builder, span,
try strpool.intern(state.strings, "some"), "some")
@@ -2557,9 +2714,11 @@ constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
kind :: ast.expr_kind(&state.tree, left)
if (kind != ast.expr_kind(&state.tree, right)) return false
if (kind == .literal_void) return true
if (kind == .typed_constant or kind == .union_constant) {
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right) else ast.typed_constant(&state.tree, right)
if (kind == .typed_constant or kind == .union_constant or kind == .slice_constant) {
a :: if (kind == .union_constant) ast.union_constant(&state.tree, left)
else if (kind == .slice_constant) ast.slice_constant(&state.tree, left) else ast.typed_constant(&state.tree, left)
b :: if (kind == .union_constant) ast.union_constant(&state.tree, right)
else if (kind == .slice_constant) ast.slice_constant(&state.tree, right) else ast.typed_constant(&state.tree, right)
return try same_type_context(state, a.type, b.type, true) and try constant_equal(state, a.value, b.value)
}
if (kind == .slice) {
@@ -2605,8 +2764,9 @@ constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
kind :: ast.expr_kind(&state.tree, id)
hash :: enum_index(kind)
if (kind == .literal_void) return hash
if (kind == .typed_constant or kind == .union_constant) {
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.typed_constant(&state.tree, id)
if (kind == .typed_constant or kind == .union_constant or kind == .slice_constant) {
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id)
else if (kind == .slice_constant) ast.slice_constant(&state.tree, id) else ast.typed_constant(&state.tree, id)
return mix(mix(hash, try type_hash_context(state, value.type, true)), try constant_hash(state, value.value))
}
if (kind == .slice) {
@@ -2743,7 +2903,8 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
}
value :: outcome.value
source_type :: typecheck.type_of(&checked, result_type)
node :: try serialize_value(state, &module, &checked, &outcome, functions.items, source_type, value, span)
remaining := usize(100000)
node :: try serialize_value(state, &module, &checked, &outcome, functions.items, source_type, value, span, &remaining)
if (node == no_node) {
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
@@ -2751,7 +2912,8 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
}
if (
(ir.aggregate(value.type) and source_type.kind != .slice)
or (source_type.kind == .slice and ast.expr_kind(&state.tree, node) == .slice)
or (source_type.kind == .slice and (ast.expr_kind(&state.tree, node) == .slice
or ast.expr_kind(&state.tree, node) == .slice_constant))
or source_type.kind == .enumeration or source_type.kind == .void
)
return Value{ kind = .aggregate, node = node, annotation = result_type }
@@ -4847,8 +5009,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
return Value{}
}
match kind {
.union_constant: {
value :: ast.union_constant(&state.tree, id)
.union_constant, .slice_constant: {
value :: if (kind == .union_constant) ast.union_constant(&state.tree, id) else ast.slice_constant(&state.tree, id)
return Value{ kind = .aggregate, node = id, annotation = value.type }
}
.typed_constant: {
@@ -5335,8 +5497,9 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
return try builtin(state, id, .type)
.typed_constant, .union_constant: {
.typed_constant, .union_constant, .slice_constant: {
annotation :: if (kind == .union_constant) ast.union_constant(&state.tree, id).type
else if (kind == .slice_constant) ast.slice_constant(&state.tree, id).type
else ast.typed_constant(&state.tree, id).type
return if (is_type(state, annotation)) annotation else try canonical_type(state, annotation)
}