feat: expand composed error-set inline match arms
This commit is contained in:
@@ -122,13 +122,13 @@ optional `if`/`while` captures also execute in type-producing bodies: `if count
|
|||||||
|
|
||||||
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.
|
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 and inline-match syntax 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 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. 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.
|
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, inline-match syntax, metadata reflection 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, metadata reflection 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.
|
`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.
|
||||||
|
|
||||||
@@ -192,7 +192,9 @@ untagged `Bits :: union { integer u64, real f64 }` overlays its members in share
|
|||||||
|
|
||||||
`match value { .number |n|: n + 2, .empty: 0 }` selects one arm after evaluating its subject once. matches support enums, tagged unions, composed error sets, numeric/boolean scalars, comma-separated patterns, numeric ranges, final `else` arms, arm-local payload captures, statement bodies and expression results. checking rejects missing coverage, duplicate/overlapping patterns, unreachable arms and incompatible captures/results. block arms can return or transfer loop control; yielding value blocks and pointer captures belong to later gates.
|
`match value { .number |n|: n + 2, .empty: 0 }` selects one arm after evaluating its subject once. matches support enums, tagged unions, composed error sets, numeric/boolean scalars, comma-separated patterns, numeric ranges, final `else` arms, arm-local payload captures, statement bodies and expression results. checking rejects missing coverage, duplicate/overlapping patterns, unreachable arms and incompatible captures/results. block arms can return or transfer loop control; yielding value blocks and pointer captures belong to later gates.
|
||||||
|
|
||||||
a final `inline |tag|:` arm on a nominal enum, or `inline |payload[, tag]|:` on a tagged union, expands into one ordinary arm for each variant not covered earlier. dispatch remains runtime and evaluates the subject once. enum captures and optional union tags are immutable compile-time enum values; payloads are immutable by-value captures with each variant's exact type, including a `void` value for empty variants. either capture may be discarded with `_`. static tag matches/conditions select variant-specific operations before checking, including generic calls, nested inline arms and type-producing procedure bodies. ordinary payloads remain runtime values, and return/break/continue/deferred cleanup retain ordinary match semantics. redundant/nonfinal inline arms, invalid captures and scalar/untagged subjects are diagnosed. expansion shares the 4096-step type-control bound. composed error-set inline arms and metadata reflection remain open.
|
a final `inline |tag|:` arm on an enum, or `inline |payload[, tag]|:` on a tagged union, expands into one ordinary arm for each variant not covered earlier. dispatch remains runtime and evaluates the subject once. enum captures and optional union tags are immutable compile-time enum values; payloads are immutable by-value captures with each variant's exact type, including a `void` value for empty variants. either capture may be discarded with `_`. static tag matches/conditions select variant-specific operations before checking, including generic calls, nested inline arms and type-producing procedure bodies. ordinary payloads remain runtime values, and return/break/continue/deferred cleanup retain ordinary match semantics. redundant/nonfinal inline arms, invalid captures and scalar/untagged subjects are diagnosed. expansion shares the 4096-step type-control bound.
|
||||||
|
|
||||||
|
composed error sets use the same inline arms, including through `catch |err| match err { ... }`. sets containing only void variants use `inline |tag|:`; sets with any payload use `inline |payload[, tag]|:`, with void values for their empty members. nested compositions, transparent aliases and repeated members expand each remaining variant once. named struct errors use their declaration name as the variant tag, and payloads retain their exact struct type and by-value isolation. for example, `Missing | union(enum) { failed i8 }` can use `inline |payload, tag|: match tag { .failed: i16(payload), .missing: 0 }`. constant inputs and type-producing bodies also select generated storage from composed payloads. pointer captures and metadata reflection remain open.
|
||||||
|
|
||||||
`while` repeats a block or single statement while its `bool` condition holds, re-evaluating the condition on every iteration. guards follow the same braced/braceless convention as `if`. loop bodies introduce child scopes; their locals, including locals in nested conditional arms, begin uninitialized on each scope entry.
|
`while` repeats a block or single statement while its `bool` condition holds, re-evaluating the condition on every iteration. guards follow the same braced/braceless convention as `if`. loop bodies introduce child scopes; their locals, including locals in nested conditional arms, begin uninitialized on each scope entry.
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ including runtime places and type-control updates without runtime reflection.
|
|||||||
arrays or tuples, including loop-filled producer storage and owned defaults.
|
arrays or tuples, including loop-filled producer storage and owned defaults.
|
||||||
`inline for` expands constant ranges, fixed value/type arrays, heterogeneous
|
`inline for` expands constant ranges, fixed value/type arrays, heterogeneous
|
||||||
value/type tuples and strings with immutable exact captures and compile-time-selected exits.
|
value/type tuples and strings with immutable exact captures and compile-time-selected exits.
|
||||||
final inline match arms expand nominal enums and tagged unions with static tags
|
final inline match arms expand nominal enums, tagged unions and composed error sets
|
||||||
and exact by-value payloads, preserving runtime dispatch and scoped control flow.
|
with static tags and exact by-value payloads, preserving runtime dispatch and scoped control flow.
|
||||||
general type-valued execution, metadata reflection and generic library acceptance remain open.
|
general type-valued execution, metadata reflection and generic library acceptance remain open.
|
||||||
the driver retains frontend storage and source provenance through
|
the driver retains frontend storage and source provenance through
|
||||||
`@source/compilation`, with compilation-wide AST node identities.
|
`@source/compilation`, with compilation-wide AST node identities.
|
||||||
@@ -108,8 +108,9 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] generated-record checkpoint: `struct_type!(layout, names, types, defaults)` builds ordinary records from constant fixed arrays or tuples with unique ASCII field names, concrete types and contextual optional defaults. literal heterogeneous collections, helper results and loop-filled producer arrays preserve exact types, owned defaults/copies and cached nominal identity through interpreter/native execution. `.auto` permits empty records; `.c` requires nonempty C-compatible fields and retains native field order/layout. malformed inputs, incomplete arrays, runtime dependencies, field/default validation, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and isolation. metadata inspection, inline expansion, aggregate C calling conventions and storage-bearing defaults remain open.
|
- [x] generated-record checkpoint: `struct_type!(layout, names, types, defaults)` builds ordinary records from constant fixed arrays or tuples with unique ASCII field names, concrete types and contextual optional defaults. literal heterogeneous collections, helper results and loop-filled producer arrays preserve exact types, owned defaults/copies and cached nominal identity through interpreter/native execution. `.auto` permits empty records; `.c` requires nonempty C-compatible fields and retains native field order/layout. malformed inputs, incomplete arrays, runtime dependencies, field/default validation, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and isolation. metadata inspection, inline expansion, aggregate C calling conventions and storage-bearing defaults remain open.
|
||||||
- [x] inline-loop checkpoint: `inline for` expands constant integer ranges, fixed arrays (including type-valued arrays) and strings into separately checked runtime bodies through interpreter/native execution. exact immutable captures, `usize` collection indices, narrow inclusive maxima, selected conditional/tagged-match bodies, static break/continue tail pruning, nested inline/ordinary loops, runtime returns/errors and scoped cleanup retain ordinary semantics. empty loops skip body elaboration; runtime-dependent inputs/transfers, pointer captures, storage-bearing escapes and shared expansion bounds retain diagnostics. imported producers/provenance and repeatable compilation are verified. heterogeneous tuples and inline-match syntax are covered below; metadata iteration remains open.
|
- [x] inline-loop checkpoint: `inline for` expands constant integer ranges, fixed arrays (including type-valued arrays) and strings into separately checked runtime bodies through interpreter/native execution. exact immutable captures, `usize` collection indices, narrow inclusive maxima, selected conditional/tagged-match bodies, static break/continue tail pruning, nested inline/ordinary loops, runtime returns/errors and scoped cleanup retain ordinary semantics. empty loops skip body elaboration; runtime-dependent inputs/transfers, pointer captures, storage-bearing escapes and shared expansion bounds retain diagnostics. imported producers/provenance and repeatable compilation are verified. heterogeneous tuples and inline-match syntax are covered below; metadata iteration remains open.
|
||||||
- [x] heterogeneous inline-tuple checkpoint: `inline for` iterates constant tuples with exact per-field value/type captures and `usize` indices through interpreter/native execution. literal/helper/aliased tuples, erased generic inputs, nested tuple projections, generated storage, empty shapes, static exits, cleanup and aggregate copy isolation retain existing semantics. type-producing bodies borrow tuple places so later fields observe updates while saved copies remain isolated. runtime dependencies and storage-bearing escapes are rejected before iteration, including discarded captures and early exits; named records and ordinary tuple loops remain invalid. imported producers, nominal type leaves and repeatable compilation are verified. inline-match syntax is covered below; metadata collections remain open.
|
- [x] heterogeneous inline-tuple checkpoint: `inline for` iterates constant tuples with exact per-field value/type captures and `usize` indices through interpreter/native execution. literal/helper/aliased tuples, erased generic inputs, nested tuple projections, generated storage, empty shapes, static exits, cleanup and aggregate copy isolation retain existing semantics. type-producing bodies borrow tuple places so later fields observe updates while saved copies remain isolated. runtime dependencies and storage-bearing escapes are rejected before iteration, including discarded captures and early exits; named records and ordinary tuple loops remain invalid. imported producers, nominal type leaves and repeatable compilation are verified. inline-match syntax is covered below; metadata collections remain open.
|
||||||
- [x] nominal inline-match checkpoint: final `inline |tag|:` enum arms and `inline |payload[, tag]|:` tagged-union arms expand remaining variants through interpreter/native execution. runtime subjects evaluate once; immutable static enum/tag captures specialize nested conditions, matches and generic calls while exact by-value scalar/aggregate/string/void payloads retain runtime semantics and copy isolation. statement/expression arms, ordinary loop/optional/match captures, scoped cleanup and return/break/continue work; type-producing bodies select generated storage and update outer type/value locals, including collection captures. invalid subjects/captures, redundant/nonfinal arms, ordinary pattern checks, imported provenance, shared expansion bounds and repeatable compilation retain diagnostics and ownership. composed error-set inline arms, pointer captures and metadata reflection remain open.
|
- [x] nominal inline-match checkpoint: final `inline |tag|:` enum arms and `inline |payload[, tag]|:` tagged-union arms expand remaining variants through interpreter/native execution. runtime subjects evaluate once; immutable static enum/tag captures specialize nested conditions, matches and generic calls while exact by-value scalar/aggregate/string/void payloads retain runtime semantics and copy isolation. statement/expression arms, ordinary loop/optional/match captures, scoped cleanup and return/break/continue work; type-producing bodies select generated storage and update outer type/value locals, including collection captures. invalid subjects/captures, redundant/nonfinal arms, ordinary pattern checks, imported provenance, shared expansion bounds and repeatable compilation retain diagnostics and ownership. composed error-set inline arms are covered below; pointer captures and metadata reflection remain open.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, composed error-set inline arms and metadata reflection (`typeinfo!` and metadata collections), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
- [x] composed inline-match checkpoint: composed error sets expand remaining variants through interpreter/native execution, including nested/repeated/aliased enum, tagged-union and named struct members. all-void sets use one static tag capture; payload-bearing sets use exact immutable by-value payloads and optional static tags, including void members. catch captures, nested inline tag matches, generic calls, owned aggregate copies, deferred cleanup and error returns retain ordinary semantics. constant inputs and type-producing bodies select generated storage with cached nominal identity; struct variant names remain identifiers in generated constants rather than embedding declarations. invalid compositions/captures, narrow payload types, redundant arms, imported dependencies and repeatable compilation retain diagnostics and ownership. member discovery shares the type-control bound; pointer captures and metadata reflection remain open.
|
||||||
|
- [ ] remaining gate: general type-valued compile-time execution and metadata reflection (`typeinfo!` and metadata collections), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: 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.
|
- [ ] 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.
|
- [ ] 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.
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ recompiles_imported_inline_match_arms_without_retaining_expansions test {
|
|||||||
`main proc() i16 {
|
`main proc() i16 {
|
||||||
` values :: dep.values()
|
` values :: dep.values()
|
||||||
` if (enum_score(.skip) + enum_score(dep.mode()) != 0) return -1
|
` if (enum_score(.skip) + enum_score(dep.mode()) != 0) return -1
|
||||||
|
` if (dep.error_score(.Failure{code = 42}) + dep.error_score(.missing) != 42) return -2
|
||||||
` return (
|
` return (
|
||||||
` dep.read(dep.Choice, values[0]) + dep.read(dep.Choice, values[1])
|
` dep.read(dep.Choice, values[0]) + dep.read(dep.Choice, values[1])
|
||||||
` + dep.read(dep.Choice, values[2]) + dep.read(dep.Choice, values[3])
|
` + dep.read(dep.Choice, values[2]) + dep.read(dep.Choice, values[3])
|
||||||
@@ -448,6 +449,15 @@ recompiles_imported_inline_match_arms_without_retaining_expansions test {
|
|||||||
`Tag :: enum(i16) { number = -7, record = 3, text = 42, empty = 90 }
|
`Tag :: enum(i16) { number = -7, record = 3, text = 42, empty = 90 }
|
||||||
`Pair :: struct { values [2]i16 }
|
`Pair :: struct { values [2]i16 }
|
||||||
`Choice :: union(Tag) { text string, empty void, number i8, record Pair }
|
`Choice :: union(Tag) { text string, empty void, number i8, record Pair }
|
||||||
|
`Missing :: enum { missing }
|
||||||
|
`Failure :: struct { code i16 }
|
||||||
|
`Errors :: alias (Missing | Failure | Missing)
|
||||||
|
`fail proc(input Errors) i16 ! Errors { return input }
|
||||||
|
`error_score proc(input Errors) i16 {
|
||||||
|
` return fail(input) catch |err| match err {
|
||||||
|
` inline |payload, tag|: match tag { .Failure: payload.code, else: 0 },
|
||||||
|
` }
|
||||||
|
`}
|
||||||
`mode proc() Mode { return .answer }
|
`mode proc() Mode { return .answer }
|
||||||
`values proc() [4]Choice {
|
`values proc() [4]Choice {
|
||||||
` return [.number{19}, .record{values = [9, 11]}, .text{"ok"}, .empty]
|
` return [.number{19}, .record{values = [9, 11]}, .text{"ok"}, .empty]
|
||||||
|
|||||||
@@ -291,6 +291,21 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .catch_expr) {
|
||||||
|
expression :: ast.catch_expr(&state.tree, id)
|
||||||
|
if (expression.capture) |capture| {
|
||||||
|
try expand(state, expression.operand)
|
||||||
|
annotation :: try expression_type(state, expression.operand, 0)
|
||||||
|
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .fallible_type) {
|
||||||
|
hashmap.put(&state.capture_types, capture, ast.binary_expr(&state.tree, annotation).right) catch |err| {
|
||||||
|
match err {
|
||||||
|
.key_exists: {}
|
||||||
|
else: return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
||||||
expression :: ast.statement_expr(&state.tree, id)
|
expression :: ast.statement_expr(&state.tree, id)
|
||||||
if (
|
if (
|
||||||
@@ -707,6 +722,90 @@ expand_struct_type proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
try expand(state, id)
|
try expand(state, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# composed struct variants use declaration IDs internally, but generated syntax
|
||||||
|
# must not embed those declarations and make dependency discovery treat them as owned.
|
||||||
|
@hide:file
|
||||||
|
member_name proc(state @mut State, name ast.NodeId) ast.NodeId ! Error {
|
||||||
|
if (ast.node_class(&state.tree, name) != .statement) return name
|
||||||
|
result :: try ast.add_identifier(state.builder, ast.node_span(&state.tree, name), ast.name_id(&state.tree, name))
|
||||||
|
try refresh(state)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
# flatten source member names and payload annotations only; ordinary checking still
|
||||||
|
# validates composition, variant identity and collisions on the original sum type.
|
||||||
|
@hide:file
|
||||||
|
inline_sum_fields proc(state @mut State, input ast.NodeId, fields @mut arraylist.ArrayList(ast.NodeId)) bool ! Error {
|
||||||
|
if (!try type_step(state, input)) return false
|
||||||
|
type :: try canonical_type(state, input)
|
||||||
|
if (type == no_node) return false
|
||||||
|
kind :: ast.expr_kind(&state.tree, type)
|
||||||
|
if (kind == .sum_type) {
|
||||||
|
sum :: ast.binary_expr(&state.tree, type)
|
||||||
|
if (!try inline_sum_fields(state, sum.left, fields)) return false
|
||||||
|
return try inline_sum_fields(state, sum.right, fields)
|
||||||
|
}
|
||||||
|
if (kind == .record_type) {
|
||||||
|
for (0..ast.node_count(&state.tree)) |i| {
|
||||||
|
declaration :: ast.NodeId(i)
|
||||||
|
if (ast.node_class(&state.tree, declaration) != .statement
|
||||||
|
or ast.stmt_kind(&state.tree, declaration) != .declaration) continue
|
||||||
|
if (ast.statement_decl(&state.tree, declaration).initializer != type) continue
|
||||||
|
name :: try member_name(state, declaration)
|
||||||
|
field :: try ast.add_field(state.builder, ast.node_span(&state.tree, type), name, type, null)
|
||||||
|
try refresh(state)
|
||||||
|
try arraylist.append(fields, field)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (kind != .enum_type and kind != .union_type) return false
|
||||||
|
count :: if (kind == .enum_type) ast.enum_member_count(&state.tree, type)
|
||||||
|
else ast.record_field_count(&state.tree, type)
|
||||||
|
for (0..count) |i| {
|
||||||
|
if (!try type_step(state, input)) return false
|
||||||
|
member :: if (kind == .enum_type) ast.enum_member(&state.tree, type, i)
|
||||||
|
else ast.record_field(&state.tree, type, i)
|
||||||
|
field :: ast.field(&state.tree, member)
|
||||||
|
if (field.name == null) return false
|
||||||
|
if (kind == .union_type and field.type == null) return false
|
||||||
|
duplicate := false
|
||||||
|
for (fields.items) |previous| {
|
||||||
|
if (ast.name_id(&state.tree, ast.field(&state.tree, previous).name?)
|
||||||
|
== ast.name_id(&state.tree, field.name?)) duplicate = true
|
||||||
|
}
|
||||||
|
if (duplicate) continue
|
||||||
|
payload :: if (kind == .enum_type) try builtin(state, member, .void) else field.type?
|
||||||
|
generated :: try ast.add_field(state.builder, field.span, field.name, payload, null)
|
||||||
|
try refresh(state)
|
||||||
|
try arraylist.append(fields, generated)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
inline_match_shape proc(state @mut State, annotation ast.NodeId) ast.NodeId ! Error {
|
||||||
|
if (ast.expr_kind(&state.tree, annotation) != .sum_type) return annotation
|
||||||
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||||
|
state.type_execution_depth += 1
|
||||||
|
defer state.type_execution_depth -= 1
|
||||||
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&fields)
|
||||||
|
if (!try inline_sum_fields(state, annotation, &fields)) return no_node
|
||||||
|
tagged := false
|
||||||
|
for (fields.items) |member| {
|
||||||
|
payload :: try canonical_type(state, ast.field(&state.tree, member).type?)
|
||||||
|
if (payload == no_node) return no_node
|
||||||
|
resolved :: binding(state, payload)
|
||||||
|
if (resolved.kind != .builtin or resolved.data.builtin != .void) tagged = true
|
||||||
|
}
|
||||||
|
span :: ast.node_span(&state.tree, annotation)
|
||||||
|
result :: if (tagged) try ast.add_union_type(state.builder, span, null, true, fields.items)
|
||||||
|
else try ast.add_enum_type(state.builder, span, null, fields.items)
|
||||||
|
try refresh(state)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
# expand dispatch, not the subject: ordinary checking/lowering retains its single
|
# expand dispatch, not the subject: ordinary checking/lowering retains its single
|
||||||
# evaluation and by-value payload captures. only tags are compile-time constants.
|
# evaluation and by-value payload captures. only tags are compile-time constants.
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -752,16 +851,18 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
|
|||||||
tag_only :: kind == .union_tag_type
|
tag_only :: kind == .union_tag_type
|
||||||
members_type := annotation
|
members_type := annotation
|
||||||
if (tag_only) members_type = try canonical_type(state, ast.type_operand(&state.tree, annotation))
|
if (tag_only) members_type = try canonical_type(state, ast.type_operand(&state.tree, annotation))
|
||||||
|
if (members_type != no_node) members_type = try inline_match_shape(state, members_type)
|
||||||
if (members_type == no_node) {
|
if (members_type == no_node) {
|
||||||
try reject(state, id, .invalid_inline_match_subject)
|
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) try reject_evaluation(state, id)
|
||||||
|
else try reject(state, id, .invalid_sum_type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tagged := false
|
tagged := false
|
||||||
if (kind == .union_type) {
|
if (!tag_only and ast.expr_kind(&state.tree, members_type) == .union_type) {
|
||||||
view :: ast.union_type(&state.tree, annotation)
|
view :: ast.union_type(&state.tree, members_type)
|
||||||
tagged = view.auto_tag or view.tag != null
|
tagged = view.auto_tag or view.tag != null
|
||||||
}
|
}
|
||||||
if (kind != .enum_type and !tag_only and !tagged) {
|
if (ast.expr_kind(&state.tree, members_type) != .enum_type and !tag_only and !tagged) {
|
||||||
try reject(state, id, .invalid_inline_match_subject)
|
try reject(state, id, .invalid_inline_match_subject)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -791,8 +892,9 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
|
|||||||
tag_type := annotation
|
tag_type := annotation
|
||||||
if (static_capture != null) {
|
if (static_capture != null) {
|
||||||
if (tagged) {
|
if (tagged) {
|
||||||
union_view :: ast.union_type(&state.tree, annotation)
|
union_view :: ast.union_type(&state.tree, members_type)
|
||||||
if (union_view.tag) |tag| tag_type = try canonical_type(state, tag)
|
if (kind != .sum_type and union_view.tag != null)
|
||||||
|
tag_type = try canonical_type(state, union_view.tag?)
|
||||||
else {
|
else {
|
||||||
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
||||||
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
|
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
|
||||||
@@ -900,7 +1002,10 @@ specialize_match proc(state @mut State, id, statement ast.NodeId) bool ! Error {
|
|||||||
arm :: ast.match_arm_view(&state.tree, arm_id)
|
arm :: ast.match_arm_view(&state.tree, arm_id)
|
||||||
if (explicit or arm.capture == null or ast.match_pattern_count(&state.tree, arm_id) == 0) continue
|
if (explicit or arm.capture == null or ast.match_pattern_count(&state.tree, arm_id) == 0) continue
|
||||||
if (hashmap.get(&state.capture_types, arm.capture?) != null) continue
|
if (hashmap.get(&state.capture_types, arm.capture?) != null) continue
|
||||||
if (annotation == no_node) annotation = try expression_type(state, subject, 0)
|
if (annotation == no_node) {
|
||||||
|
annotation = try expression_type(state, subject, 0)
|
||||||
|
if (annotation != no_node) annotation = try inline_match_shape(state, annotation)
|
||||||
|
}
|
||||||
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .union_type) break
|
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .union_type) break
|
||||||
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
||||||
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
||||||
@@ -1521,14 +1626,17 @@ serialize_value proc(
|
|||||||
for (record.fields) |field, i| {
|
for (record.fields) |field, i| {
|
||||||
if (record.is_union and !ir.union_active(module, value, i)) continue
|
if (record.is_union and !ir.union_active(module, value, i)) continue
|
||||||
if (record.is_union and field.type.kind == .void) {
|
if (record.is_union and field.type.kind == .void) {
|
||||||
result = try ast.add_enum_literal(state.builder, span, field.name?)
|
name :: try member_name(state, field.name?)
|
||||||
|
result = try ast.add_enum_literal(state.builder, span, name)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
child :: ir.aggregate_item(module, value, i)
|
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, field.type, child, span)
|
||||||
if (node == no_node) return no_node
|
if (node == no_node) return no_node
|
||||||
generated :: try ast.add_field(state.builder, span, field.name, null, node)
|
name := field.name
|
||||||
|
if (name != null) name = try member_name(state, name?)
|
||||||
|
generated :: try ast.add_field(state.builder, span, name, null, node)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
try arraylist.append(&fields, generated)
|
try arraylist.append(&fields, generated)
|
||||||
}
|
}
|
||||||
@@ -1556,7 +1664,8 @@ serialize_value proc(
|
|||||||
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
||||||
for (enumeration.members) |member| {
|
for (enumeration.members) |member| {
|
||||||
if (member.magnitude == magnitude and member.negative == negative) {
|
if (member.magnitude == magnitude and member.negative == negative) {
|
||||||
result = try ast.add_enum_literal(state.builder, span, member.name)
|
name :: try member_name(state, member.name)
|
||||||
|
result = try ast.add_enum_literal(state.builder, span, name)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2096,8 +2205,10 @@ type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
|
|||||||
value :: try evaluate_ir(state, subject, annotation)
|
value :: try evaluate_ir(state, subject, annotation)
|
||||||
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_node
|
if (value.kind != .aggregate or ast.expr_kind(&state.tree, value.node) != .record_literal) return no_node
|
||||||
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
|
||||||
for (0..ast.record_field_count(&state.tree, annotation)) |i| {
|
shape :: try inline_match_shape(state, annotation)
|
||||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, i))
|
if (shape == no_node) return no_node
|
||||||
|
for (0..ast.record_field_count(&state.tree, shape)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, shape, i))
|
||||||
if (
|
if (
|
||||||
field.name != null and payload.name != null
|
field.name != null and payload.name != null
|
||||||
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)
|
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)
|
||||||
|
|||||||
+135
@@ -11,6 +11,141 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"inline_match_composed_enums_keep_static_sum_tags_and_runtime_dispatch": (
|
||||||
|
"""
|
||||||
|
First :: enum { skipped, first }
|
||||||
|
Second :: enum { second, last }
|
||||||
|
Errors :: alias (First | (Second | First))
|
||||||
|
selected proc(input Errors, calls @mut i16) Errors {
|
||||||
|
calls^ = calls^ + 1
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
known proc($tag Errors) i16 {
|
||||||
|
return match tag { .first: 19, .second: 23, else: 0 }
|
||||||
|
}
|
||||||
|
read proc(input Errors, calls @mut i16) i16 {
|
||||||
|
return match selected(input, calls) {
|
||||||
|
.skipped: 0,
|
||||||
|
inline |tag|: known(tag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() [2]i16 {
|
||||||
|
calls i16 := 0
|
||||||
|
total :: read(.skipped, &calls) + read(.first, &calls) + read(.second, &calls) + read(.last, &calls)
|
||||||
|
return [total, calls]
|
||||||
|
}
|
||||||
|
""", b"[42, 4]\n", None,
|
||||||
|
),
|
||||||
|
"inline_match_composed_payloads_catch_static_tags_and_owned_copies": (
|
||||||
|
"""
|
||||||
|
Missing :: enum { missing }
|
||||||
|
Detail :: union(enum) { number i8, text string, empty void }
|
||||||
|
Failure :: struct { values [2]mut i16 }
|
||||||
|
Errors :: alias ((Missing | Detail) | (Failure | Missing))
|
||||||
|
fail proc(input Errors) i16 ! Errors { return input }
|
||||||
|
exact proc(value i8) i16 { return i16(value) }
|
||||||
|
read proc(input Errors) i16 {
|
||||||
|
fail(input) catch |err| {
|
||||||
|
match err {
|
||||||
|
.missing: return 0,
|
||||||
|
inline |payload, tag|: {
|
||||||
|
match tag {
|
||||||
|
inline |selected|: {
|
||||||
|
if (selected == .number) { return exact(payload) }
|
||||||
|
else if (selected == .text) { return i16(payload.len) }
|
||||||
|
else if (selected == .Failure) {
|
||||||
|
copy := payload
|
||||||
|
copy.values[0] = 0
|
||||||
|
return payload.values[0] + payload.values[1]
|
||||||
|
} else {
|
||||||
|
payload
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
return read(.missing) + read(.number{19}) + read(.text{"ok"}) + read(.Failure{values = [9, 11]}) + read(.empty)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inline_match_composed_type_producers_and_constant_inputs": (
|
||||||
|
"""
|
||||||
|
Missing :: enum { missing }
|
||||||
|
Detail :: union(enum) { count usize, empty void }
|
||||||
|
Failure :: struct { count usize }
|
||||||
|
Errors :: alias (Missing | Detail | (Failure | Detail))
|
||||||
|
Storage proc(input Errors) type {
|
||||||
|
length usize := 0
|
||||||
|
match input {
|
||||||
|
inline |payload, tag|: {
|
||||||
|
if (tag == .count) { length = payload }
|
||||||
|
else if (tag == .Failure) { length = payload.count }
|
||||||
|
else if (tag == .missing) { length = 1 }
|
||||||
|
else { length = 0 }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return struct { values [length]i16 }
|
||||||
|
}
|
||||||
|
score proc(input Errors) i16 {
|
||||||
|
return match input {
|
||||||
|
inline |payload, tag|: match tag { .count: i16(payload), else: 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
known proc($input Errors) i16 { return score(input) }
|
||||||
|
main proc() i16 {
|
||||||
|
first Storage(.count{2}) :: { values = [19, 20] }
|
||||||
|
same Storage(.count{2}) :: first
|
||||||
|
record Storage(.Failure{count = 2}) :: { values = [0, 0] }
|
||||||
|
other Storage(.missing) :: { values = [0] }
|
||||||
|
empty Storage(.empty) :: { values = [] }
|
||||||
|
return same.values[0] + same.values[1] + $(score(.count{2})) + known(.count{1}) + other.values[0] + i16(empty.values.len) + record.values[0]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inline_match_composed_payload_cleanup_on_error_return": (
|
||||||
|
"""
|
||||||
|
Missing :: enum { missing }
|
||||||
|
Detail :: union(enum) { failed i16 }
|
||||||
|
Errors :: alias (Missing | Detail)
|
||||||
|
forward proc(input Errors, cleanups @mut i16) i16 ! Errors {
|
||||||
|
match input {
|
||||||
|
inline |payload, tag|: {
|
||||||
|
defer cleanups^ = cleanups^ + 1
|
||||||
|
if (tag == .failed) { return .failed{payload} }
|
||||||
|
else { return .missing }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
cleanups i16 := 0
|
||||||
|
value :: forward(.failed{41}, &cleanups) catch |err| match err {
|
||||||
|
inline |payload, tag|: match tag { .failed: payload, else: 0 },
|
||||||
|
}
|
||||||
|
return value + cleanups
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"inline_match_composed_enums_reject_second_capture": (
|
||||||
|
"A :: enum { a }\nB :: enum { b }\nmain proc() i16 { input (A | B) :: .a\nreturn match input { inline |value, tag|: 42 } }",
|
||||||
|
b"", b"inline match arm requires one enum capture",
|
||||||
|
),
|
||||||
|
"inline_match_composed_rejects_conflicting_variant_payloads": (
|
||||||
|
"A :: union(enum) { failed i8 }\nB :: union(enum) { failed i16 }\nmain proc() i16 { input (A | B) :: .failed{42}\nreturn match input { inline |payload, tag|: i16(payload) } }",
|
||||||
|
b"", b"sum composition requires compatible",
|
||||||
|
),
|
||||||
|
"inline_match_composed_preserves_narrow_payload_type": (
|
||||||
|
"A :: enum { a }\nB :: union(enum) { failed i8 }\nread proc(input (A | B)) i16 { return match input { inline |payload, tag|: match tag { .failed: payload, else: i16(0) } } }\nmain proc() i16 { return read(.failed{42}) }",
|
||||||
|
b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"inline_match_composed_rejects_fully_covered_arm": (
|
||||||
|
"A :: enum { a }\nB :: enum { b }\nmain proc() i16 { input (A | B) :: .a\nreturn match input { .a, .b: 42, inline |_|: 0 } }",
|
||||||
|
b"", b"match arm is unreachable",
|
||||||
|
),
|
||||||
"inline_match_runtime_enum_static_capture_expression_and_single_subject": (
|
"inline_match_runtime_enum_static_capture_expression_and_single_subject": (
|
||||||
"""
|
"""
|
||||||
Mode :: enum(i16) { handled = -7, first = 3, second = 8, answer = 42, zero = 90 }
|
Mode :: enum(i16) { handled = -7, first = 3, second = 8, answer = 42, zero = 90 }
|
||||||
|
|||||||
Reference in New Issue
Block a user