feat: materialize computed compile-time callbacks

This commit is contained in:
2026-10-30 13:43:37 +01:00
parent c65f41db43
commit 6748b38eac
6 changed files with 204 additions and 21 deletions
+6 -4
View File
@@ -124,23 +124,25 @@ ordinary procedures also accept `undefined` as the initializer of a mutable loca
procedures returning fixed arrays or records/tuples containing `type`, such as `types proc() [2]type { return [i16, bool] }` or `config proc() struct { child type, count usize } { return { child = i16, count = 2 } }`, execute entirely at compile time. every argument is explicitly supplied, constant, cached and erased; nested/aliased results, empty shapes and dependent `$N` lengths retain exact shape and mutability. compile-time producers accept ordinary type-array/type-record inputs, and runtime generic procedures accept erased `$` parameters with these shapes. direct projections generate signatures, constructors, storage and defaults; explicit `$()` calls inside producers retain owned values. cache identity uses canonical type leaves without merging distinct nominal types, and copies cannot mutate cached inputs/results. imported diagnostics, shared type-control execution limits and repeatable compilation work through interpreter/native execution. ordinary parameters remain excluded from dependent signatures; use `$N` for lengths. runtime type-array/type-record storage and reflection remain open.
optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null, but nonnull pointer/slice/callback-bearing payloads retain the constant-materialization escape boundary.
optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null; callbacks retain source declaration identity, while nonnull data pointers and slices retain the constant-materialization escape boundary.
type-producing bodies also execute scalar/enum `match` statements and type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound.
matches also select nested types, array lengths, generic arguments, callback signatures and field defaults in type-producing bodies. for example, `return struct { values [2](match wide { true: i16, false: i8 }) }` generates concrete storage without elaborating the unselected arm. snapshots preserve loop-local type/value bindings, selected literals retain their spelling and source provenance, and nominal results are cached before their recursive fields are expanded. deferred type selection shares the producer's execution bound; failed elaboration retains its original diagnostic without leaving unchecked generated types behind. general type-valued execution remains open.
tagged-union matches in type-producing bodies support immutable by-value payload captures in statements, expressions and nested type/storage positions. for example, `match choice { .count |length|: return [length]i16, .empty: return bool }` uses a captured `usize` to generate storage. grouped variants require identical nonvoid payload types; invalid captures in unselected arms are still diagnosed. selected payloads become owned typed constants, preserving narrow integers, nested aggregates and strings across generated lengths/defaults, loop snapshots and repeatable compilation. pointer/slice/callback-bearing active payloads retain the constant-materialization escape boundary below.
tagged-union matches in type-producing bodies support immutable by-value payload captures in statements, expressions and nested type/storage positions. for example, `match choice { .count |length|: return [length]i16, .empty: return bool }` uses a captured `usize` to generate storage. grouped variants require identical nonvoid payload types; invalid captures in unselected arms are still diagnosed. selected payloads become owned typed constants, preserving narrow integers, nested aggregates and strings across generated lengths/defaults, loop snapshots and repeatable compilation. data-pointer/slice-bearing active payloads retain the constant-materialization escape boundary below.
ordinary procedures may compute integer, boolean, float and string compile-time arguments and return nested records/tuples, fixed arrays, tagged unions and explicit fallible results from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. aggregate and fallible arguments receive their declared context, including dependent `[N]T` parameters; cached specializations compare normalized values, not literal spelling or evaluation identity. returned constants become owned source literals before temporary interpreter storage is released, preserving by-value copies, exact integer types, string bytes, enum/optional fields and IEEE float bits. tagged unions retain their actual discriminant and only the active payload, including nested aggregates and void/zero-cell variants. fallible constants retain success/error identity, composed or payload-bearing errors and void success; `catch` can consume them in generated lengths, defaults and runtime expressions.
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. data pointers, slices, callbacks and untagged unions cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. untagged unions lack an active-member identity, so selecting a source variant could discard observable bytes. executed foreign calls remain interpreter traps. general type-valued execution, metadata reflection and existing generic-library acceptance remain open.
computed callbacks materialize as references to their original source declarations, never as temporary interpreter function IDs. `choose proc() @proc(value i16) i16 { return increment }` can supply `$(choose())`, compile-time arguments and generated defaults. optional callbacks preserve absence and nested presence; arrays, records, tagged payloads and both fallible channels retain exact callback signatures and specialization-cache identity. imported private targets remain bound to their originating declarations, including across repeated compilation and native emission.
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. nonnull data pointers, slices and untagged unions cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. untagged unions lack an active-member identity, so selecting a source variant could discard observable bytes. executed foreign calls remain interpreter traps. general type-valued execution and broader existing-library acceptance remain open.
`if $(condition) { ... } else { ... }` evaluates a boolean guard before expanding or checking either body, including calls to ordinary constant-input procedures. concrete generic specializations also select branches whose guards use constants, immutable constant bindings, type comparisons or substituted compile-time parameters. only the selected body is specialized and checked; both bodies are still parsed and name-resolved. selected bodies retain their scope, cleanup and return behavior, and erased guards retain ordinary type checking and source diagnostics. ordinary calls in an implicit guard stay runtime; use `$()` to request their evaluation. optional captures remain runtime, and ordinary nongeneric `if (true)` still checks both bodies.
`compile_error!(message)` requires a compile-time string and reports its owned text at the call site when that body is elaborated. unselected compile-time branches do not report it; ordinary runtime branches remain checked. messages from constant-input procedures retain their bytes through later evaluation, interpreter diagnostics and native executable emission.
`match $(subject) { ... }` selects an expression or statement arm before specializing or checking its body, including ordinary constant-input calls. concrete generic procedures also select matches on side-effect-free constant subjects, including substituted scalar/enum/tagged-union arguments and pure field/index projections. ordinary checked IR validates every pattern and capture declaration before selection; only the selected body is elaborated, while all bodies remain parsed and name-resolved. by-value payload captures retain exact types and owned strings, and selected bodies may use runtime parameters, nested matches, cleanup, returns and enclosing-loop transfers. ordinary call subjects, runtime parameters and mutable locals stay runtime, as do contextual aggregate bindings that might omit runtime defaults; ordinary nongeneric literal matches still check all arms. use `$()` to request explicit evaluation. pointer/slice/callback-bearing payloads retain the escape boundary above.
`match $(subject) { ... }` selects an expression or statement arm before specializing or checking its body, including ordinary constant-input calls. concrete generic procedures also select matches on side-effect-free constant subjects, including substituted scalar/enum/tagged-union arguments and pure field/index projections. ordinary checked IR validates every pattern and capture declaration before selection; only the selected body is elaborated, while all bodies remain parsed and name-resolved. by-value payload captures retain exact types and owned strings, and selected bodies may use runtime parameters, nested matches, cleanup, returns and enclosing-loop transfers. ordinary call subjects, runtime parameters and mutable locals stay runtime, as do contextual aggregate bindings that might omit runtime defaults; ordinary nongeneric literal matches still check all arms. use `$()` to request explicit evaluation. data-pointer/slice-bearing payloads retain the escape boundary above.
type `==` and `!=` comparisons in compile-time expressions and specializations preserve aliases, nominal declaration identity and structural pointer/array/callback signatures. composed error-set identity and specialization caching ignore member ordering, grouping and repetition. for example, `read proc($T type, value T) i16 { if (T == i16) return value else return value.count }` checks the scalar or record path for each concrete call without requiring both operations on every `T`.
+3 -2
View File
@@ -19,8 +19,8 @@ explicit fallible channels, composed error sets, `try`/`catch` and scoped LIFO
traps or compiler infrastructure failures.
cached generic specialization and type-producing procedures support explicit/inferred
compile-time arguments and generated record/array storage through both paths.
ordinary procedures can compute scalar, nested record/array, tagged-union and
fallible compile-time values using mutable locals, loops and recursive calls;
ordinary procedures can compute scalar, nested record/array, tagged-union,
fallible and callback compile-time values using mutable locals, loops and recursive calls;
explicit `$()` expressions use the same constant evaluation path. compile-time
`if`/`match` selection and type comparisons specialize only the selected body,
including owned by-value tagged payload captures in ordinary generic procedures.
@@ -142,6 +142,7 @@ test discovery and FFI remain outside these completed slices.
- [x] compound/bitwise checkpoint: arithmetic and bitwise compound updates evaluate mutable local/field/index/pointer places and their old values once before the RHS through interpreter/native execution. integer complement, and/or/xor, truncating left shifts, signed/unsigned right shifts and saturating left shifts preserve widths and distinct identity; unsigned counts retain independent types. known invalid counts diagnose even in untaken runtime branches; dynamic counts trap without native LLVM poison. checked arithmetic, float-only division, readonly diagnostics, scoped cleanup, ordinary constants, generated storage, generic branch selection and repeatable compilation retain existing semantics. type-valued `|` remains composition while runtime `|` and capture delimiters retain their grammar. full hash-map acceptance remains open.
- [x] hash-map execution checkpoint: existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` run collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups and owned aggregate values without replacement library code. interpreter checks use separate typed allocation buffers and preserve the original map on allocation failure, including cleanup; native `mem.c_allocator` checks cover all integer widths/boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys with padding ignored, content-based byte strings and custom callbacks at `-O0`/`-O2`. specialization retains `usize` collection indices, nominal/explicit pointer-cast result types, rigid slice borrowing/string context and source-known optional callbacks; ordinary checking rejects incompatible elements and readonly reversal. `unreachable` demand-traps with provenance through both paths and compile-time execution; literal-true loops distinguish escaping and nested breaks. `compile_error!` owns computed string diagnostics and skips unselected compile-time bodies. initializer inference from expected result types is covered below.
- [x] expected-result inference checkpoint: omitted compile-time type/value arguments infer from rigid runtime arguments and expected results before contextual literal defaults. typed bindings, assignments, returns, ordinary/nested calls, aggregate fields/elements/defaults, optional/fallible channels, callbacks, match arms, yielding/catch blocks, arithmetic/bitwise operands and explicit `$()` expressions retain exact checked types through interpreter/native execution. generated nominal results infer counts and private/custom callback identities; existing `hashmap.init(allocator)` initializes `AutoHashMap`, `StringHashMap` and custom `HashMap` without replacement library code. rigid conflicts, unrelated nominal generators and unresolved arguments diagnose; explicit calls and fully explicit type-producer inputs retain their contracts. owned specializations and repeat compilation are verified.
- [x] computed-callback materialization checkpoint: ordinary compile-time calls return source-bound callbacks through interpreter/native execution, including optional/nested presence, arrays/records, tagged payloads and both fallible channels. temporary IR function IDs map back to exact source declarations before evaluation storage is released. computed/direct aggregate arguments share specialization-cache identity and generated defaults; imported private targets, same-spelled functions, signature diagnostics, runtime trap provenance and repeated compilation retain their contracts. nonnull data pointers, slices and untagged unions retain their escape boundary.
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including untagged unions and storage-bearing values), 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`.
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
+49
View File
@@ -897,3 +897,52 @@ compound_updates_and_type_composition_recompile_without_retaining_rewrites test
try testing.expect_equal(i64(42), outcome.value.data.integer)
}
}
computed_callbacks_recompile_without_retaining_interpreter_function_ids 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"
`increment proc(value i16) i16 { return value + 100 }
`main proc() i16 {
` callback :: $(lib.choose())
` return callback(41)
`}
,
)
defer arraylist.deinit(&bytes)
root_source :: try add_source(&session, root, "/callbacks/app/main.hon", &bytes, &diagnostic_store)
imported := try owned_text(
`Callback :: alias @proc(value i16) i16
`bias i16 :: 1
`@hide:file
`increment proc(value i16) i16 { return value + bias }
`choose proc() Callback { return increment }
,
)
defer arraylist.deinit(&imported)
_ = try add_source(&session, dependency, "/callbacks/library/callback.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)
}
}
+26 -3
View File
@@ -87,11 +87,21 @@ lower_package proc(
root_package ast.NodeId,
diagnostic_store @mut diagnostics.Store,
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
return try lower_module(allocator, tree, resolution, checked, root_package, null, diagnostic_store)
return try lower_module(
allocator,
tree,
resolution,
checked,
root_package,
null,
null,
diagnostic_store,
)
}
#! lowers an isolated compile-time call wrapper without applying program entry-name rules.
#! the caller supplies a checked parameterless function and owns the returned IR.
#! the caller supplies a checked parameterless function, an empty `functions` list and owns the returned IR.
#! fills `functions` by IR FunctionId; global initializers map to maxval!(ast.NodeId).
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
lower_evaluation proc(
allocator mem.Allocator,
@@ -99,9 +109,19 @@ lower_evaluation proc(
resolution @sema.Resolution,
checked @typecheck.Result,
entry ast.NodeId,
functions @mut arraylist.ArrayList(ast.NodeId),
diagnostic_store @mut diagnostics.Store,
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
return try lower_module(allocator, tree, resolution, checked, tree.root, entry, diagnostic_store)
return try lower_module(
allocator,
tree,
resolution,
checked,
tree.root,
entry,
functions,
diagnostic_store,
)
}
@hide:file
@@ -112,6 +132,7 @@ lower_module proc(
checked @typecheck.Result,
root_package ast.NodeId,
entry ?ast.NodeId,
functions ?@mut arraylist.ArrayList(ast.NodeId),
diagnostic_store @mut diagnostics.Store,
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
module := ir.init(allocator)
@@ -147,12 +168,14 @@ lower_module proc(
.function: {
if (ast.function_external(tree, id) and typecheck.type_of(checked, id).kind == .none) continue
function :: try add_function(&module, ast.node_span(tree, id))
if (functions) |declarations| try arraylist.append(declarations, id)
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
}
.statement: {
if (typecheck.type_of(checked, id).kind == .type_value) continue
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
initializer :: try add_function(&module, ast.node_span(tree, id))
if (functions) |declarations| try arraylist.append(declarations, maxval!(ast.NodeId))
global :: u32(module.globals.items.len)
try arraylist.append(&module.globals, ir.Global{
type = lower_type(&state, typecheck.type_of(checked, id)),
+23 -11
View File
@@ -2332,11 +2332,16 @@ evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Erro
}
# values leaving the temporary interpreter must own no interpreter addresses or module IDs.
# rebuild contextual source literals while the module and its escaped flat cells are alive.
# rebuild contextual source literals and source-bound callback references before releasing it.
@hide:file
serialize_value proc(
state @mut State, module @ir.Module, checked @typecheck.Result,
type typecheck.Type, value ir.Value, span ast.Span,
state @mut State,
module @ir.Module,
checked @typecheck.Result,
functions []ast.NodeId,
type typecheck.Type,
value ir.Value,
span ast.Span,
) ast.NodeId ! Error {
uninitialized := value.uninitialized
if (ir.aggregate(value.type)) {
@@ -2356,7 +2361,7 @@ 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, array.element, child, span)
node :: try serialize_value(state, module, checked, functions, array.element, child, span)
if (node == no_node) return no_node
try arraylist.append(&elements, node)
}
@@ -2376,7 +2381,7 @@ serialize_value proc(
return result
}
child :: ir.aggregate_item(module, value, i)
node :: try serialize_value(state, module, checked, field.type, child, span)
node :: try serialize_value(state, module, checked, functions, field.type, child, span)
if (node == no_node) return no_node
name := field.name
if (name != null) name = try member_name(state, name?)
@@ -2391,7 +2396,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, channel, child, span)
result = try serialize_value(state, module, checked, functions, channel, child, span)
if (result == no_node) return no_node
# keep named success/error types distinct even when their members overlap.
annotation := no_node
@@ -2419,19 +2424,23 @@ serialize_value proc(
absent :: if (container.element.kind == .function)
value.data.function == maxval!(ir.FunctionId)
else value.data.unsigned == maxval!(u64)
if (!absent) return no_node
result = try ast.add_literal(state.builder, .null, span)
if (absent) result = try ast.add_literal(state.builder, .null, span)
else result = try serialize_value(state, module, checked, functions, container.element, value, span)
} 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, container.element, child, span)
payload :: try serialize_value(state, module, checked, functions, container.element, child, span)
if (payload == no_node) return no_node
name :: try ast.add_generated_identifier(state.builder, span,
try strpool.intern(state.strings, "some"), "some")
arguments [1]ast.NodeId :: [payload]
result = try ast.add_builtin_call(state.builder, span, name, arguments[..])
}
} else if (type.kind == .function) {
index :: usize(value.data.function)
if (index >= functions.len or functions[index] == no_node) return no_node
result = try reference(state, functions[index], functions[index])
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
@@ -2609,7 +2618,10 @@ evaluate_ir_body proc(state @mut State, at, body, result_type ast.NodeId) Value
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
return Value{}
}
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked, wrapper, &diagnostic_store)
functions := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&functions)
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked,
wrapper, &functions, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
@@ -2623,7 +2635,7 @@ 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, source_type, value, span)
node :: try serialize_value(state, &module, &checked, functions.items, source_type, value, span)
if (node == no_node) {
if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
+97 -1
View File
@@ -4890,6 +4890,86 @@ CASES = {
main proc() i16 { return apply(null, 40) + apply(increment, 1) }
""", b"42\n", None,
),
"comptime_computed_callbacks_preserve_optional_presence_and_calls": (
"""
Callback :: alias @proc(value i16) i16
increment proc(value i16) i16 { return value + 1 }
decrement proc(value i16) i16 { return value - 1 }
choose proc(up bool) Callback {
callback Callback := decrement
if (up) callback = increment
return callback
}
maybe proc(present bool) ?Callback {
if (present) return choose(true)
return null
}
nested proc(present bool) ??Callback {
if (present) return some!(maybe(true))
return some!(null)
}
main proc() [6]i16 {
up :: $(choose(true))
down :: $(choose(false))
optional :: $(maybe(true))
absent :: $(maybe(false))
outer :: $(nested(false))
inner :: $(nested(true))
return [up(41), down(43), (optional?)(41), ((inner?)?)(41),
(if (absent == null) i16(42) else i16(0)),
(if (outer != null and outer? == null) i16(42) else i16(0))]
}
""", b"[42, 42, 42, 42, 42, 42]\n", None,
),
"comptime_callback_aggregates_cache_source_identity_and_generate_defaults": (
"""
Callback :: alias @proc(value i16) i16
increment proc(value i16) i16 { return value + 1 }
decrement proc(value i16) i16 { return value - 1 }
Callbacks :: struct { items [2]Callback, optional ?Callback }
Choice :: union(enum) { callback Callback, empty void }
Failure :: union(enum) { callback Callback }
make proc(reverse bool) Callbacks {
if (reverse) return { items = [decrement, increment], optional = null }
return { items = [increment, decrement], optional = increment }
}
choice proc() Choice { return .callback{increment} }
result proc(fail bool) Callback ! Failure {
if (fail) return .callback{decrement}
return increment
}
Box proc($callbacks Callbacks) type {
return struct { callbacks Callbacks = callbacks }
}
main proc() [6]i16 {
first Box(make(false)) :: {}
same Box(Callbacks { items = [increment, decrement], optional = increment }) :: first
other Box(make(true)) :: {}
selected :: $(choice())
success :: $(result(false)) catch increment
failure :: $(result(true)) catch |err| err.callback
return [same.callbacks.items[0](41), same.callbacks.optional?(41),
other.callbacks.items[0](43), selected.callback(41),
success(41), failure(43)]
}
""", b"[42, 42, 42, 42, 42, 42]\n", None,
),
"comptime_computed_callback_signatures_remain_checked": (
"""
Callback :: alias @proc(value i16) i16
wrong proc(value u8) u8 { return value }
choose proc() Callback { return wrong }
main proc() i16 { return $(choose())(42) }
""", b"", b"return does not match the function result type",
),
"comptime_computed_callback_traps_keep_declaration_provenance": (
"""
Callback :: alias @proc(value i8) i8
increment proc(value i8) i8 { return value + 1 }
choose proc() Callback { return increment }
main proc() i8 { return $(choose())(127) }
""", b"", b":2:38: runtime trap: integer arithmetic overflow",
),
"comptime_branch_rejects_runtime_guard_dependencies": (
"""
answer proc(take bool) i16 {
@@ -8680,7 +8760,23 @@ def check_specialization_packages(temporary, executable):
interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:3:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 22
sizing.write_text(textwrap.dedent("""
Callback :: alias @proc(value i16) i16
bias i16 :: 1
@hide:file
increment proc(value i16) i16 { return value + bias }
choose proc() Callback { return increment }
""").strip() + "\n")
source.write_text(textwrap.dedent("""
lib :: import "../library"
increment proc(value i16) i16 { return value + 100 }
main proc() i16 {
callback :: $(lib.choose())
return callback(41)
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
return 23
def check_dependency_provenance(temporary, executable):