feat: specialize constant matches in generic procedures

Reuse checked match selection for generic constant subjects and explicit match $(). Preserve owned captures, runtime scope and control transfers; keep ordinary calls, mutable subjects and potential runtime defaults unprobed. Update README and TODO checkpoint boundaries.

Verified: bro build; bro test (250 passed); python3 tests/native.py (555 interpreter/native comparisons at -O0/-O2, allocator, LLVM and CLI checks); direct interpreter/native constant-match smoke (42).
This commit is contained in:
2026-10-27 12:53:19 +01:00
parent 8d1a98f982
commit c3e10b3bed
5 changed files with 345 additions and 10 deletions
+4 -2
View File
@@ -102,16 +102,18 @@ type-producing bodies support mutable type and value locals, direct binding assi
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 storage and broader generic-body match specialization remain open.
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 storage and inline expansion remain 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.
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, broader compile-time match/`inline` expansion, reflection, generated type/default arrays and existing generic-library acceptance remain open.
`$(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, inline match/loop expansion, reflection, generated type/default arrays and existing generic-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.
`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.
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`.
callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage.
+4 -2
View File
@@ -22,7 +22,8 @@ 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;
explicit `$()` expressions use the same constant evaluation path. compile-time
`if` selection and type comparisons specialize only the selected body.
`if`/`match` selection and type comparisons specialize only the selected body,
including owned by-value tagged payload captures in ordinary generic procedures.
type-producing procedures also accept ordinary
constant inputs, cached and erased like explicit compile-time arguments. mutable
type/value locals, direct assignments, branches, scalar/enum/tagged-union matching
@@ -75,7 +76,8 @@ test discovery and FFI remain outside these completed slices.
- [x] nested type-match checkpoint: matches select types inside constructors, array lengths, generic arguments, local annotations, callback signatures and field defaults in type-producing bodies. selection precedes ordinary expansion, preserves literal spelling/source provenance and loop snapshots, and caches nominal results before expanding recursive fields. selected-body and pattern diagnostics, failed deferred elaboration, imported producers and repeatable compilation work through interpreter/native execution; deferred selection shares the producer's type-control bound.
- [x] tagged/fallible constant checkpoint: explicit `$()` expressions, generic value parameters and type-procedure inputs materialize tagged unions and both fallible channels through interpreter/native execution. preserve active tags/payloads, nested raw aggregate projections, string ownership, void/zero-cell variants, composed/payload errors, declared channel types, normalized cache identity and repeatable compilation. `catch` computes generated lengths and runtime values. untagged unions and storage-bearing active payloads remain escape diagnostics rather than retaining temporary interpreter handles.
- [x] tagged type-match checkpoint: type-producing bodies execute tagged-union matches with immutable by-value payload captures in statement, expression and nested storage positions. ordinary match checking validates every arm's captures, including grouped variants; selected payloads retain exact scalar/aggregate types and owned strings. generated storage/defaults, loop snapshots/transfers, cache identity, selected-body diagnostics, imported producers, dependency provenance and repeatable compilation work through interpreter/native execution. storage-bearing payloads retain the constant-materialization escape boundary.
- [ ] remaining gate: general type-valued compile-time execution, broader compile-time match/inline expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
- [x] generic match-specialization checkpoint: side-effect-free constant scalar/enum/tagged-union subjects in concrete generic procedures and explicit `match $(subject)` expressions/statements select an arm before body specialization/checking. reuse ordinary checked IR for pattern types, ranges, overlap, exhaustiveness and every arm's capture validation; only the selected body is elaborated. exact by-value payload types/owned strings, runtime parameters, nested expression matches, cleanup, return/loop transfers, imported producers and repeatable compilation work through interpreter/native execution. ordinary call/runtime/mutable subjects stay runtime; contextual aggregates with possible runtime defaults are not implicitly evaluated. all bodies remain parsed/name-resolved; storage-bearing payloads retain the constant-materialization escape boundary.
- [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. 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.
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
+14 -1
View File
@@ -125,6 +125,19 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` .empty: return bool,
` }
`}
`read_selected proc($choice Choice, value i16) i16 {
` match choice {
` .payload |detail|: {
` copied :: detail
` if (copied.text.len != 2) return -1
` return match detail.values[1] {
` 2: match true { true: value + detail.values[1], false: value.missing },
` else: value.missing,
` }
` }
` .empty: return value.missing,
` }
`}
`main proc() i16 {
` if $(i16 != i16) { return $(i8(128)) }
` values :: $(data())
@@ -143,7 +156,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` .failed |detail|: detail.code + i16(detail.text.len),
` }
` if (error_code != 7) return 0
` return identity(same.values[0]) + same.values[$(usize(1))] + same.bias
` return read_selected(packet.choices[0], identity(same.values[0]) + same.values[$(usize(1))] + same.bias - 2)
`}
,
)
+82 -4
View File
@@ -197,6 +197,17 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
if (state.expanded.items[usize(id)] != 0) return
state.expanded.items[usize(id)] = 1
class :: ast.node_class(&state.tree, id)
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
expression :: ast.statement_expr(&state.tree, id)
if (
ast.expr_kind(&state.tree, expression) == .match_expr
and try specialize_match(state, expression, id)
) return
}
if (
class == .expression and ast.expr_kind(&state.tree, id) == .match_expr
and try specialize_match(state, id, no_node)
) return
if (class == .statement and ast.stmt_kind(&state.tree, id) == .if_stmt) {
conditional :: ast.if_stmt(&state.tree, id)
explicit :: ast.expr_kind(&state.tree, conditional.condition) == .comptime_expr
@@ -297,6 +308,44 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
}
}
# runtime bodies share the type-producer selector, but retain ordinary runtime
# parameters and control transfers in the selected arm.
@hide:file
specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
subject :: ast.match_expr(&state.tree, id).subject
explicit :: ast.expr_kind(&state.tree, subject) == .comptime_expr
if (!explicit and usize(id) < state.original_count) return false
try expand(state, subject)
if (!explicit and !branch_constant(state, subject, 0)) return false
state.evaluation_diagnostic = diagnostics.no_diagnostic
index :: try type_match_arm(state, id)
if (index == no_index) {
try reject_evaluation(state, id)
return true
}
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, index))
selected :: try type_match_body(state, id, arm)
if (selected == no_node or (statement == no_node and !arm.expression)) {
try reject_evaluation(state, id)
return true
}
try expand(state, selected)
if (statement != no_node) {
replacement := no_node
if (arm.expression) replacement = try ast.add_expression_stmt(state.builder, selected)
else replacement = try ast.add_block_stmt(state.builder, selected)
try replace(state, statement, replacement)
} else {
resolved :: binding(state, selected)
try remember(state, id)
ast.replace_node_from_source(state.builder, id, selected)
try refresh(state)
state.bindings.items[usize(id)] = resolved
}
try refresh(state)
return true
}
# only probe side-effect-free expressions. ordinary calls stay runtime unless the
# source requests evaluation with $(); probing must not execute FFI or emit traps.
@hide:file
@@ -309,14 +358,38 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
if (ast.node_class(&state.tree, target) == .function) return true
if (ast.node_class(&state.tree, target) != .statement) return false
declaration :: ast.statement_decl(&state.tree, target)
initializer :: ast.expr_kind(&state.tree, declaration.initializer)
# contextual aggregates may omit defaults with runtime calls.
if (declaration.type != null and (initializer == .record_literal or initializer == .array_literal)) return false
return (
declaration.mutability == .constant
and branch_constant(state, declaration.initializer, depth + 1)
)
}
match ast.expr_kind(&state.tree, id) {
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: return true
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void: return true
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
.enum_literal: return true
.record_literal: {
# typed literals may omit defaults; materialized constants are complete
# contextual literals inside a typed_constant wrapper.
for (0..ast.record_field_count(&state.tree, id)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
if (field.value == null or !branch_constant(state, field.value?, depth + 1)) return false
}
return ast.record_literal(&state.tree, id).type == null
}
.array_literal: {
for (0..ast.array_element_count(&state.tree, id)) |i| {
if (!branch_constant(state, ast.array_element(&state.tree, id, i), depth + 1)) return false
}
return true
}
.selection: return branch_constant(state, ast.selection_expr(&state.tree, id).base, depth + 1)
.index: {
view :: ast.index_expr(&state.tree, id)
return branch_constant(state, view.base, depth + 1) and branch_constant(state, view.index, depth + 1)
}
.unary: {
view :: ast.unary_expr(&state.tree, id)
return (
@@ -1119,6 +1192,7 @@ nominal_type_value proc(state @State, id ast.NodeId) bool {
@hide:file
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
if (!try expand_type_matches(state, ast.match_expr(&state.tree, id).subject)) return no_index
try expand(state, ast.match_expr(&state.tree, id).subject)
arms := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&arms)
patterns := arraylist.init(ast.NodeId, state.allocator)
@@ -1149,6 +1223,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
for (0..ast.match_pattern_count(&state.tree, arm_id)) |j| {
pattern :: ast.match_pattern(&state.tree, arm_id, j)
if (!try expand_type_matches(state, pattern)) return no_index
try expand(state, pattern)
try arraylist.append(&patterns, pattern)
}
span :: ast.node_span(&state.tree, arm_id)
@@ -1161,6 +1236,9 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
selector :: try ast.add_match(state.builder, ast.node_span(&state.tree, id),
ast.match_expr(&state.tree, id).subject, arms.items)
try refresh(state)
# selector arms contain only indices. do not specialize this synthetic match
# again while its checked evaluation expands the wrapper.
state.expanded.items[usize(selector)] = 1
selected :: try evaluate_ir(state, selector, try builtin(state, id, .usize))
if (selected.kind != .integer) return no_index
return usize(selected.magnitude)
@@ -1191,8 +1269,7 @@ type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
}
@hide:file
type_match_expression_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.NodeId ! Error {
if (!arm.expression) return no_node
type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.NodeId ! Error {
if (arm.capture == null) return arm.body
value :: try type_match_capture(state, id)
if (value == no_node) return no_node
@@ -1218,7 +1295,8 @@ type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Erro
index :: try type_match_arm(state, expression)
if (index == no_index) return no_node
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, index))
expression = try type_match_expression_body(state, expression, arm)
if (!arm.expression) return no_node
expression = try type_match_body(state, expression, arm)
if (expression == no_node) return no_node
}
return expression
+241 -1
View File
@@ -11,6 +11,230 @@ HONEY = ROOT / "build/honey"
# Explicit expectations guard against frontend bugs shared by both execution paths.
CASES = {
"generic_match_scalar_enum_and_range_selection": (
"""
Mode :: enum { pair, other }
answer proc($mode Mode, $count u8, value i16) i16 {
match true {
true: {
selected :: count
match selected {
0..2: return value.missing,
2..=3: {
return match mode {
.pair: value + 2,
.other: value.missing,
}
}
else: return false,
}
}
false: return value.missing,
}
}
main proc() i16 { return answer(.pair, 2, 40) }
""", b"42\n", None,
),
"generic_match_expression_selects_each_instance": (
"""
read proc($take bool, value i16) i16 {
return match take {
true: match take { true: value, false: value.missing },
false: value + 2,
}
}
main proc() i16 { return read(true, 20) + read(false, 20) }
""", b"42\n", None,
),
"generic_match_tagged_capture_exact_scalar_type": (
"""
Choice :: union(enum) { small i8, empty void }
exact proc(value i8) i16 { return i16(value) }
read proc($choice Choice, value i16) i16 {
return match choice {
.small |small|: exact(small) + value,
.empty: value.missing,
}
}
main proc() i16 { return read(.small{2}, 40) }
""", b"42\n", None,
),
"generic_match_tagged_capture_nested_owned_values": (
"""
Inner :: union(enum) { small i8, empty void }
Detail :: struct { inner Inner, values [2]i16, text string }
Choice :: union(enum) { detail Detail, unused void }
exact proc(value i8) i16 { return i16(value) }
read proc($choice Choice, value i16) i16 {
match choice {
.detail |detail|: {
copied Detail :: detail
if (copied.text.len != 5 or copied.text[0] != 111) return -1
return match detail.inner {
.small |small|: exact(small) + detail.values[0] + detail.values[1] + value,
.empty: value.missing,
}
}
.unused: return value.missing,
}
}
main proc() i16 {
return read(.detail{ inner = .small{2}, values = [19, 20], text = "owned" }, 1)
}
""", b"42\n", None,
),
"generic_match_selected_return_runs_cleanup": (
"""
read proc($take bool, total @mut i16, value i16) i16 {
defer total^ = total^ + 1
match take {
true: {
owned :: ["owned", "copy"]
defer total^ = total^ + i16(owned[1].len)
return value
}
false: return value.missing,
}
}
main proc() i16 {
total i16 := 0
value :: read(true, &total, 37)
return value + total
}
""", b"42\n", None,
),
"generic_match_selected_loop_transfers_run_cleanup": (
"""
read proc($take bool) i16 {
total i16 := 0
for (0..3) |index| {
match take {
true: {
owned :: ["owned", "copy"]
defer total = total + i16(owned[1].len)
if (index == 0) continue
break
}
false: return total.missing,
}
total = -100
}
return total + 34
}
main proc() i16 { return read(true) }
""", b"42\n", None,
),
"explicit_comptime_match_ordinary_call_without_templates": (
"""
selected proc() bool { return true }
main proc() i16 {
bias :: match $(selected()) { true: 2, false: false.missing }
match $(selected()) {
true: return 40 + bias,
false: return false,
}
}
""", b"42\n", None,
),
"generic_match_ordinary_call_still_checks_both_arms": (
"""
selected proc() bool { return true }
read proc($unused bool, value i16) i16 {
match selected() { true: return value, false: return false }
}
main proc() i16 { return read(true, 42) }
""", b"42\n", b"return does not match",
),
"ordinary_literal_match_still_checks_both_arms": (
"""
main proc() i16 {
match true { true: return 42, false: return false }
}
""", b"42\n", b"return does not match",
),
"generic_match_record_default_call_stays_runtime": (
"""
default_value proc() u8 { return 2 }
Choice :: struct { count u8, bias u8 = default_value() }
read proc($unused bool, value i16) i16 {
choice Choice :: { count = 2 }
match choice.bias { 2: return value, else: return false }
}
main proc() i16 { return read(true, 42) }
""", b"42\n", b"return does not match",
),
"generic_match_runtime_subject_still_checks_both_arms": (
"""
read proc($unused bool, take bool, value i16) i16 {
match take { true: return value, false: return false }
}
main proc() i16 { return read(true, true, 42) }
""", b"42\n", b"return does not match",
),
"generic_match_mutable_local_still_checks_both_arms": (
"""
read proc($take bool, value i16) i16 {
selected := take
match selected { true: return value, false: return false }
}
main proc() i16 { return read(true, 42) }
""", b"42\n", b"return does not match",
),
"generic_match_runtime_subject_runs_once": (
"""
selected proc(count @mut i16) bool {
count^ = count^ + 1
return true
}
read proc($unused bool, count @mut i16) i16 {
return match selected(count) { true: 41, false: 0 }
}
main proc() i16 {
count i16 := 0
value :: read(true, &count)
return value + count
}
""", b"42\n", None,
),
"generic_match_checks_untaken_overlap": (
"""
read proc($value u8) i16 {
return match value { 0: 42, 1..=3: 0, 3..5: 0, else: 0 }
}
main proc() i16 { return read(0) }
""", b"", b"duplicate or overlapping match pattern",
),
"generic_match_checks_exhaustiveness": (
"""
read proc($value bool) i16 { return match value { true: 42 } }
main proc() i16 { return read(true) }
""", b"", b"match is not exhaustive",
),
"generic_match_checks_untaken_capture_types": (
"""
Choice :: union(enum) { count usize, small i8, empty void }
read proc($choice Choice) i16 {
return match choice { .count, .small |value|: i16(value), .empty: 42 }
}
main proc() i16 { return read(.empty) }
""", b"", b"payload capture requires compatible nonvoid tagged union variants",
),
"generic_match_preserves_untaken_resolution_errors": (
"""
read proc($value bool) i16 {
return match value { true: 42, false: missing }
}
main proc() i16 { return read(true) }
""", b"42\n", b"unknown name",
),
"explicit_comptime_match_rejects_runtime_subject": (
"""
read proc(take bool) i16 {
return match $(take) { true: 42, false: 0 }
}
main proc() i16 { return read(true) }
""", b"", b"generic argument must be a compile-time value",
),
"type_match_tagged_capture_preserves_resolution_errors": (
"""
data proc() [2]i16 { return [40, 2] }
@@ -3858,6 +4082,14 @@ def check_specialization_packages(temporary, executable):
while (result < 2) result = result + 1
return result
}
selected proc($choice Choice, value i16) i16 {
match choice {
.count |length|: {
return match length { 2: value + i16(length), else: value.missing }
}
.empty: return value.missing,
}
}
""").strip() + "\n")
source = app / "main.hon"
source.write_text(textwrap.dedent("""
@@ -3873,6 +4105,14 @@ def check_specialization_packages(temporary, executable):
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
source.write_text(textwrap.dedent("""
lib :: import "../library"
main proc() i16 {
value lib.Captured(i16, .count{2}) :: { values = [20, 20] }
return lib.selected(.count{2}, value.values[0] + value.values[1])
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
source.write_text(textwrap.dedent("""
lib :: import "../library"
Box proc($T type) type { return struct { value T } }
@@ -3933,7 +4173,7 @@ def check_specialization_packages(temporary, executable):
interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:6:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 5
return 6
def check_dependency_provenance(temporary, executable):