feat: expand nominal inline match arms
This commit is contained in:
@@ -136,7 +136,7 @@ ordinary procedures may compute integer, boolean, float and string compile-time
|
||||
|
||||
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`.
|
||||
|
||||
`field!(base, name)` selects a member using a compile-time string, without runtime reflection metadata. names may come from constants, constant-input procedures or erased generic parameters; only the name is evaluated at compile time, while the base retains ordinary selection semantics. records, tuples (canonical decimal names such as `"0"`), enum/error-set members, tagged payloads, callbacks and sequence `"len"`/`"ptr"` members reuse exact typing, single base evaluation, address-taking and writable-place checks. type-producing bodies also snapshot selector names for nested aggregate updates, preserving earlier by-value copies. unknown members, runtime/nonstring names and invalid calls are diagnosed; tagged payload reads still check the active field. `typeinfo!` and inline expansion remain open.
|
||||
`field!(base, name)` selects a member using a compile-time string, without runtime reflection metadata. names may come from constants, constant-input procedures or erased generic parameters; only the name is evaluated at compile time, while the base retains ordinary selection semantics. records, tuples (canonical decimal names such as `"0"`), enum/error-set members, tagged payloads, callbacks and sequence `"len"`/`"ptr"` members reuse exact typing, single base evaluation, address-taking and writable-place checks. type-producing bodies also snapshot selector names for nested aggregate updates, preserving earlier by-value copies. unknown members, runtime/nonstring names and invalid calls are diagnosed; tagged payload reads still check the active field. `typeinfo!` and metadata collections remain open.
|
||||
|
||||
`struct_type!(layout, names, types, defaults)` generates an ordinary record from constant fixed arrays or tuples of equal length. for example, `struct_type!(.auto, ["value", "bias"], [i16, i8], [null, 2])` requires a `value` initializer and supplies an `i8` default for `bias`. literal collections retain each field's context, including heterogeneous types and defaults; computed collections can come from helpers and loop-filled producer locals. names must be unique ASCII identifiers. each default is checked as `?Field`: absent means required, present supplies an owned field value. generated fields retain ordinary exact typing, aggregate copy isolation, field selection and checked updates through interpreter/native execution. producer specializations cache nominal identity; imported producers and repeated compilation retain source provenance and fresh owned values. incomplete arrays and runtime dependencies are rejected, and construction shares the type-control execution bound.
|
||||
|
||||
@@ -190,7 +190,9 @@ enums use `Kind :: enum { number, empty }`, qualified members such as `Kind.numb
|
||||
|
||||
untagged `Bits :: union { integer u64, real f64 }` overlays its members in shared storage. keyed construction selects exactly one member; reading another member reinterprets the same bytes. tagged unions use `union(enum)` or `union(Kind)`; an explicit enum must contain every union variant. construct them with `Choice { number = 42 }`, contextual `.number{42}`, `.point{x = 40, y = 2}` for record payloads, or `.empty` for `void` payloads. field reads/writes validate the active tag. nested aggregates copy by value, including across calls. CLI union results print `{payload}` (or `{}` for a void payload); untagged results print the first member.
|
||||
|
||||
`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, pointer captures and inline specialization 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.
|
||||
|
||||
`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,6 +45,8 @@ including runtime places and type-control updates without runtime reflection.
|
||||
arrays or tuples, including loop-filled producer storage and owned defaults.
|
||||
`inline for` expands constant ranges, fixed value/type arrays, heterogeneous
|
||||
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
|
||||
and exact by-value payloads, preserving runtime dispatch and scoped control flow.
|
||||
general type-valued execution, metadata reflection and generic library acceptance remain open.
|
||||
the driver retains frontend storage and source provenance through
|
||||
`@source/compilation`, with compilation-wide AST node identities.
|
||||
@@ -104,9 +106,10 @@ test discovery and FFI remain outside these completed slices.
|
||||
- [x] type-record storage checkpoint: initialized records/tuples containing type values live inside type/type-array producers, including local schemas/defaults, typed/contextual/inferred construction, nested records/arrays, checked field/index reads and updates, whole-value replacement and immutable by-value collection captures. persistent snapshots isolate copies/captures while borrowed array places observe later updates; type leaves retain nominal identity and ordinary value leaves reuse checked IR. generated storage/defaults, invalid field/type/default diagnostics, readonly places, narrow overflow, imported producers/provenance, cached nominal results and repeatable compilation work through interpreter/native execution. field validation/construction shares the type-control bound. type-record procedure inputs/results are covered below; runtime type-valued records, partial record initialization, metadata reflection and inline expansion remain open.
|
||||
- [x] type-record procedure checkpoint: records/tuples containing type values cross compile-time procedure inputs/results, including nested arrays/records, aliases, defaults and dependent annotations. compile-time producers accept ordinary constant inputs; runtime generics accept erased `$` metadata parameters. owned snapshots isolate argument/result copies, exact type/value leaves retain checked diagnostics, and normalized cache keys preserve nominal generated types. generated storage/defaults, imported producers/provenance, shared execution bounds and repeatable compilation work through interpreter/native execution. runtime metadata storage, partial record initialization, reflection and inline expansion 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 are covered below; metadata iteration and inline-match syntax 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. metadata collections and inline-match syntax remain open.
|
||||
- [ ] remaining gate: general type-valued compile-time execution, inline match expansion 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] 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] 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.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
|
||||
+36
-7
@@ -62,6 +62,7 @@ ExprKind :: enum {
|
||||
union_type
|
||||
distinct_type
|
||||
alias_expr
|
||||
union_tag_type
|
||||
enum_literal
|
||||
match_expr
|
||||
index
|
||||
@@ -148,6 +149,7 @@ NodeKind :: enum {
|
||||
expr_union_type
|
||||
expr_distinct_type
|
||||
expr_alias
|
||||
expr_union_tag_type
|
||||
expr_enum_literal
|
||||
expr_match
|
||||
match_arm
|
||||
@@ -345,6 +347,9 @@ MatchArmView :: struct {
|
||||
body NodeId
|
||||
expression bool
|
||||
catch_all bool
|
||||
is_inline bool = false
|
||||
tag_capture ?NodeId = null
|
||||
tag_capture_present bool = false
|
||||
}
|
||||
|
||||
IndexView :: struct {
|
||||
@@ -795,6 +800,13 @@ add_type_operand proc(builder @mut Builder, span Span, operand NodeId, transpare
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_union_tag_type proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_union_tag_type,
|
||||
main = NodeData{ node_id = operand },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_enum_literal proc(builder @mut Builder, span Span, member NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_enum_literal,
|
||||
@@ -823,6 +835,7 @@ add_match_arm proc(
|
||||
start :: try add_extra(builder, raw_capture)
|
||||
_ = try add_extra(builder, expression_flag)
|
||||
_ = try add_extra(builder, catch_all_flag)
|
||||
_ = try add_extra(builder, u32(no_node))
|
||||
for (patterns) |pattern| _ = try add_extra(builder, u32(pattern))
|
||||
return try add_node(builder, Node{
|
||||
kind = .match_arm,
|
||||
@@ -832,6 +845,18 @@ add_match_arm proc(
|
||||
}, span)
|
||||
}
|
||||
|
||||
set_match_arm_inline proc(
|
||||
builder @mut Builder, id NodeId, tag_capture ?NodeId, tag_capture_present bool,
|
||||
) void {
|
||||
node :: get_node(&builder.storage, id)
|
||||
debug.assert(node.kind == .match_arm)
|
||||
extra :: usize(node.data0.extra)
|
||||
builder.storage.extra.items[extra + 1] = builder.storage.extra.items[extra + 1] | 2
|
||||
if (tag_capture_present)
|
||||
builder.storage.extra.items[extra + 1] = builder.storage.extra.items[extra + 1] | 4
|
||||
builder.storage.extra.items[extra + 3] = if (tag_capture) |capture| u32(capture) else u32(no_node)
|
||||
}
|
||||
|
||||
add_record_literal proc(
|
||||
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
||||
) NodeId ! mem.AllocError {
|
||||
@@ -1440,6 +1465,7 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_union_type: .union_type
|
||||
.expr_distinct_type: .distinct_type
|
||||
.expr_alias: .alias_expr
|
||||
.expr_union_tag_type: .union_tag_type
|
||||
.expr_enum_literal: .enum_literal
|
||||
.expr_match: .match_expr
|
||||
.expr_index: .index
|
||||
@@ -1664,7 +1690,7 @@ union_type proc(tree @Tree, id NodeId) UnionTypeView {
|
||||
|
||||
type_operand proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type or node.kind == .expr_try)
|
||||
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type or node.kind == .expr_try or node.kind == .expr_union_tag_type)
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
@@ -1699,21 +1725,24 @@ match_arm_view proc(tree @Tree, id NodeId) MatchArmView {
|
||||
return MatchArmView{
|
||||
capture = maybe_node_id(tree.storage.extra.items[extra]),
|
||||
body = node.main.node_id,
|
||||
expression = tree.storage.extra.items[extra + 1] != 0,
|
||||
expression = (tree.storage.extra.items[extra + 1] & 1) != 0,
|
||||
catch_all = tree.storage.extra.items[extra + 2] != 0,
|
||||
is_inline = (tree.storage.extra.items[extra + 1] & 2) != 0,
|
||||
tag_capture = maybe_node_id(tree.storage.extra.items[extra + 3]),
|
||||
tag_capture_present = (tree.storage.extra.items[extra + 1] & 4) != 0,
|
||||
}
|
||||
}
|
||||
|
||||
match_pattern_count proc(tree @Tree, id NodeId) usize {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .match_arm)
|
||||
return usize(node.data1.extra - node.data0.extra - 3)
|
||||
return usize(node.data1.extra - node.data0.extra - 4)
|
||||
}
|
||||
|
||||
match_pattern proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(index < match_pattern_count(tree, id))
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 3 + index])
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 4 + index])
|
||||
}
|
||||
|
||||
record_field_count proc(tree @Tree, id NodeId) usize {
|
||||
@@ -1860,7 +1889,7 @@ node_layout proc(node Node) NodeLayout {
|
||||
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
|
||||
layout.direct_children = 6
|
||||
.expr_pointer_type: layout.direct_children = 4
|
||||
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_enum_literal,
|
||||
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_union_tag_type, .expr_enum_literal,
|
||||
.expr_try, .expr_comptime: layout.direct_children = 1
|
||||
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
|
||||
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
|
||||
@@ -1892,8 +1921,8 @@ node_layout proc(node Node) NodeLayout {
|
||||
layout.extra_pointers = 6
|
||||
layout.extra_start = usize(node.data0.extra)
|
||||
layout.extra_count = usize(node.data1.extra - node.data0.extra)
|
||||
layout.extra_children = 1
|
||||
layout.list_start = 3
|
||||
layout.extra_children = 9
|
||||
layout.list_start = 4
|
||||
layout.list_end = layout.extra_count
|
||||
}
|
||||
.expr_catch: {
|
||||
|
||||
@@ -415,6 +415,87 @@ recompiles_imported_inline_for_without_retaining_expansions test {
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
recompiles_imported_inline_match_arms_without_retaining_expansions 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)
|
||||
root_bytes := try owned_text(
|
||||
`dep :: import "../dependency"
|
||||
`known proc($tag dep.Mode) i16 {
|
||||
` if (tag == .answer) return 0 else return false
|
||||
`}
|
||||
`enum_score proc(input dep.Mode) i16 {
|
||||
` return match input { .skip: 0, inline |tag|: known(tag) }
|
||||
`}
|
||||
`main proc() i16 {
|
||||
` values :: dep.values()
|
||||
` if (enum_score(.skip) + enum_score(dep.mode()) != 0) return -1
|
||||
` return (
|
||||
` dep.read(dep.Choice, values[0]) + dep.read(dep.Choice, values[1])
|
||||
` + dep.read(dep.Choice, values[2]) + dep.read(dep.Choice, values[3])
|
||||
` )
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&root_bytes)
|
||||
dependency_bytes := try owned_text(
|
||||
`Mode :: enum(i16) { skip = -7, answer = 42 }
|
||||
`Tag :: enum(i16) { number = -7, record = 3, text = 42, empty = 90 }
|
||||
`Pair :: struct { values [2]i16 }
|
||||
`Choice :: union(Tag) { text string, empty void, number i8, record Pair }
|
||||
`mode proc() Mode { return .answer }
|
||||
`values proc() [4]Choice {
|
||||
` return [.number{19}, .record{values = [9, 11]}, .text{"ok"}, .empty]
|
||||
`}
|
||||
`narrow proc(value i8) i16 { return i16(value) }
|
||||
`pair proc(value Pair) i16 { return value.values[0] + value.values[1] }
|
||||
`read proc($T type, input T) i16 {
|
||||
` match input {
|
||||
` inline |payload, tag|: {
|
||||
` match tag {
|
||||
` inline |selected|: {
|
||||
` if (selected == .number) { return narrow(payload) }
|
||||
` else if (selected == .record) { return pair(payload) }
|
||||
` else if (selected == .text) { return i16(payload.len) }
|
||||
` else {
|
||||
` payload
|
||||
` return 1
|
||||
` }
|
||||
` },
|
||||
` }
|
||||
` },
|
||||
` }
|
||||
`}
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&dependency_bytes)
|
||||
root_source :: try add_source(&session, root, "app/main.hon", &root_bytes, &diagnostic_store)
|
||||
_ = try add_source(&session, dependency, "dependency/values.hon", &dependency_bytes, &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 (0..modules.len) |index| {
|
||||
outcome := try interpreter.run(mem.c_allocator, &modules[index], &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
recompiles_generated_records_without_borrowing_previous_ir test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
|
||||
@@ -88,6 +88,8 @@ Code :: enum {
|
||||
non_exhaustive_match
|
||||
unreachable_match_arm
|
||||
invalid_match_capture
|
||||
invalid_inline_match_subject
|
||||
invalid_inline_match_capture
|
||||
inactive_union_field
|
||||
invalid_pointer_type
|
||||
invalid_address_operand
|
||||
@@ -226,6 +228,8 @@ messages :: $enummap.init(Code, []u8, {
|
||||
non_exhaustive_match = "match is not exhaustive",
|
||||
unreachable_match_arm = "match arm is unreachable",
|
||||
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
|
||||
invalid_inline_match_subject = "inline match arm requires an enum or tagged union",
|
||||
invalid_inline_match_capture = "inline match arm requires one enum capture or one payload capture and an optional tag",
|
||||
inactive_union_field = "tagged union field is not active",
|
||||
invalid_pointer_type = "pointer requires a supported concrete pointee type",
|
||||
invalid_address_operand = "address-of requires addressable storage",
|
||||
|
||||
@@ -1889,7 +1889,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
||||
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .union_tag_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
||||
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||
}
|
||||
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
|
||||
|
||||
+51
-26
@@ -1048,27 +1048,39 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
start :: current(state).span
|
||||
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&patterns)
|
||||
catch_all :: next_is(state, .else)
|
||||
is_inline :: next_is(state, .inline)
|
||||
if (is_inline) state.next_token += 1
|
||||
catch_all :: !is_inline and next_is(state, .else)
|
||||
capture ?ast.NodeId := null
|
||||
tag_capture ?ast.NodeId := null
|
||||
tag_capture_present := false
|
||||
if (catch_all) state.next_token += 1
|
||||
else {
|
||||
else if (!is_inline) {
|
||||
while true {
|
||||
try arraylist.append(&patterns, try parse_guard_expr(state))
|
||||
if (!next_is(state, .comma)) break
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
}
|
||||
if next_is(state, .pipe) {
|
||||
}
|
||||
if (is_inline or (!catch_all and next_is(state, .pipe))) {
|
||||
_ = try consume(state, .pipe)
|
||||
# captures are by value; pointer capture tokens are not identifiers.
|
||||
token :: try consume(state, .ident)
|
||||
if (
|
||||
token.span.end != token.span.start + 1
|
||||
or state.builder.source[usize(token.span.start)] != '_'
|
||||
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
if (is_inline and next_is(state, .comma)) {
|
||||
state.next_token += 1
|
||||
# pointer payload captures are outside the current runtime representation.
|
||||
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
|
||||
token :: try consume(state, .ident)
|
||||
tag_capture_present = true
|
||||
tag :: try consume(state, .ident)
|
||||
if (
|
||||
token.span.end != token.span.start + 1
|
||||
or state.builder.source[usize(token.span.start)] != '_'
|
||||
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
_ = try consume(state, .pipe)
|
||||
tag.span.end != tag.span.start + 1
|
||||
or state.builder.source[usize(tag.span.start)] != '_'
|
||||
) tag_capture = try ast.add_identifier(state.builder, tag.span, tag.data.str_id)
|
||||
}
|
||||
_ = try consume(state, .pipe)
|
||||
}
|
||||
_ = try consume(state, .colon)
|
||||
skip_newlines(state)
|
||||
@@ -1079,24 +1091,37 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
.open_curly, .return, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
body ast.NodeId := undefined
|
||||
expression := false
|
||||
if (statement_body or named_binding_starts(state)) {
|
||||
body :: try parse_arm(state, false)
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
body = try parse_arm(state, false)
|
||||
} else {
|
||||
value :: try parse_expr(state)
|
||||
if next_is(state, .equal) {
|
||||
statement :: try parse_assignment(state, value, false)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body = try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||
} else {
|
||||
body = value
|
||||
expression = true
|
||||
}
|
||||
tree :: ast.view(state.builder, value)
|
||||
if (expression and ast.expr_kind(&tree, value) == .match_expr
|
||||
and ast.match_arm_count(&tree, value) > 0) {
|
||||
nested :: ast.match_arm_view(&tree, ast.match_arm(&tree, value, 0))
|
||||
if (!nested.expression) {
|
||||
statement :: try ast.add_expression_stmt(state.builder, value)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body = try ast.add_block(state.builder, ast.builder_span(state.builder, value), statements[..])
|
||||
expression = false
|
||||
}
|
||||
}
|
||||
}
|
||||
value :: try parse_expr(state)
|
||||
if next_is(state, .equal) {
|
||||
statement :: try parse_assignment(state, value, false)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
}
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
|
||||
}, patterns.items, capture, value, true, catch_all)
|
||||
arm :: try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, expression, catch_all)
|
||||
if (is_inline) ast.set_match_arm_inline(state.builder, arm, tag_capture, tag_capture_present)
|
||||
return arm
|
||||
}
|
||||
|
||||
@hide:file
|
||||
|
||||
@@ -1899,3 +1899,79 @@ rejects_pointer_match_captures_and_preserves_siblings test {
|
||||
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
|
||||
}
|
||||
|
||||
parses_and_clones_inline_match_captures_and_body_forms test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`run proc(value int) void {
|
||||
` match value { .if, .return: 1, inline |payload, tag|: payload }
|
||||
` match value { inline |_, _|: return }
|
||||
` match value { inline |_|: value = 1 }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
builder := ast.init_builder(mem.c_allocator, source)
|
||||
defer ast.deinit_builder(&builder)
|
||||
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||
original :: ast.view(&builder, root)
|
||||
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
|
||||
defer mem.free(mem.c_allocator, mapping)
|
||||
mem.set(mapping, maxval!(ast.NodeId))
|
||||
cloned :: try ast.clone_subtree(&builder, root, mapping)
|
||||
tree :: ast.view(&builder, root)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
roots [2]ast.NodeId :: [root, cloned]
|
||||
for (roots) |package| {
|
||||
body :: ast.function_decl(&tree, ast.child(&tree, package, 0)).body
|
||||
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||
ordinary :: ast.match_arm(&tree, matched, 0)
|
||||
try testing.expect_equal(2, ast.match_pattern_count(&tree, ordinary))
|
||||
try testing.expect(!ast.match_arm_view(&tree, ordinary).is_inline)
|
||||
arm_id :: ast.match_arm(&tree, matched, 1)
|
||||
arm :: ast.match_arm_view(&tree, arm_id)
|
||||
try testing.expect(arm.is_inline and arm.expression and arm.tag_capture_present)
|
||||
try testing.expect_equal(0, ast.match_pattern_count(&tree, arm_id))
|
||||
try testing.expect_equal(3, ast.child_count(&tree, arm_id))
|
||||
try testing.expect_equal(arm.body, ast.child(&tree, arm_id, 0))
|
||||
try testing.expect_equal(arm.capture?, ast.child(&tree, arm_id, 1))
|
||||
try testing.expect_equal(arm.tag_capture?, ast.child(&tree, arm_id, 2))
|
||||
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, arm.capture?)))
|
||||
try testing.expect_equal("tag", ast.source_text(&tree, ast.node_span(&tree, arm.tag_capture?)))
|
||||
for (usize(1)..usize(3)) |i| {
|
||||
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, body, i))
|
||||
discarded :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
|
||||
try testing.expect(discarded.is_inline and !discarded.expression)
|
||||
try testing.expect(discarded.capture == null and discarded.tag_capture == null)
|
||||
try testing.expect_equal(i == 1, discarded.tag_capture_present)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rejects_malformed_inline_match_captures test {
|
||||
sources [8][]u8 :: [
|
||||
"run proc() void { match value { inline ||: 1 } }",
|
||||
"run proc() void { match value { inline |a, b, c|: 1 } }",
|
||||
"run proc() void { match value { inline |@a|: 1 } }",
|
||||
"run proc() void { match value { inline |*a|: 1 } }",
|
||||
"run proc() void { match value { inline |a, @b|: 1 } }",
|
||||
"run proc() void { match value { inline |a: 1 } }",
|
||||
"run proc() void { match value { inline a|: 1 } }",
|
||||
"run proc() void { match value { inline |a,|: 1 } }",
|
||||
]
|
||||
for (sources) |source| {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,7 +1315,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
.array_type: {
|
||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||
}
|
||||
.record_type, .union_type, .enum_type, .distinct_type,
|
||||
.record_type, .union_type, .enum_type, .distinct_type, .union_tag_type,
|
||||
.pointer_type, .function_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
||||
type := try check_type_expression(state, id, state.cells[usize(id)].nominal)
|
||||
if (type.kind != .invalid) {
|
||||
@@ -3536,7 +3536,7 @@ type_syntax proc(kind ast.ExprKind) bool {
|
||||
kind == .record_type or kind == .union_type or kind == .enum_type
|
||||
or kind == .distinct_type or kind == .alias_expr
|
||||
or kind == .pointer_type or kind == .function_type or kind == .slice_type or kind == .optional_type
|
||||
or kind == .fallible_type or kind == .sum_type
|
||||
or kind == .fallible_type or kind == .sum_type or kind == .union_tag_type
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3576,6 +3576,21 @@ check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type !
|
||||
state.cells[usize(id)].type = type
|
||||
return type
|
||||
}
|
||||
if (kind == .union_tag_type) {
|
||||
operand :: try check_annotation(state, ast.type_operand(state.tree, id), false)
|
||||
if (operand.kind == .invalid) return operand
|
||||
if (operand.kind == .record and operand.nominal == ast.NodeId(maxval!(u32))) {
|
||||
descriptor :: state.records.items[usize(operand.record_type)]
|
||||
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
|
||||
tag :: if (descriptor.declared_tag_type.kind == .enumeration)
|
||||
descriptor.declared_tag_type else descriptor.tag_type
|
||||
state.cells[usize(id)].type = tag
|
||||
return tag
|
||||
}
|
||||
}
|
||||
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_inline_match_subject,
|
||||
ast.node_span(state.tree, id)))
|
||||
}
|
||||
cached := state.cells[usize(id)].type
|
||||
if (cached.kind == .type_value) {
|
||||
cached.kind = cached.value_kind
|
||||
|
||||
@@ -525,7 +525,7 @@ resolve_expr proc(
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
|
||||
.literal_undefined: {}
|
||||
.enum_literal: {}
|
||||
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.distinct_type, .alias_expr, .union_tag_type, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.enum_type: {
|
||||
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
||||
for (0..ast.enum_member_count(tree, id)) |i| {
|
||||
@@ -544,6 +544,7 @@ resolve_expr proc(
|
||||
arm_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
||||
defer hashmap.deinit(&arm_scope.names)
|
||||
if (arm.capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
||||
if (arm.tag_capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
||||
if (arm.expression) try resolve_expr(state, &arm_scope, arm.body)
|
||||
else try resolve_block(state, &arm_scope, arm.body)
|
||||
}
|
||||
|
||||
@@ -931,3 +931,44 @@ canonicalizes_imported_aliases_and_namespace_alias_selections test {
|
||||
namespace :: ast.package_decl_at(&tree, root, 3)
|
||||
try testing.expect_equal(BindingKind.namespace, binding(&resolution, namespace).kind)
|
||||
}
|
||||
|
||||
resolves_inline_match_captures_and_all_bodies test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`inspect proc(value int) void {
|
||||
` match value { inline |payload, tag|: {
|
||||
` payload
|
||||
` tag
|
||||
` } }
|
||||
` match value { inline |same, same|: same }
|
||||
` match value { inline |payload, value|: value }
|
||||
` match value { .if: 1, inline |_|: missing }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &pool, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
resolution := try resolve(mem.c_allocator, &tree, &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
first_match :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||
first :: ast.match_arm_view(&tree, ast.match_arm(&tree, first_match, 0))
|
||||
payload_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, first.body, 0))
|
||||
tag_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, first.body, 1))
|
||||
try testing.expect_equal(first.capture?, binding(&resolution, payload_ref).data.declaration)
|
||||
try testing.expect_equal(first.tag_capture?, binding(&resolution, tag_ref).data.declaration)
|
||||
for (usize(1)..usize(3)) |i| {
|
||||
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, i))
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, matched, 0))
|
||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, arm.tag_capture?).kind)
|
||||
}
|
||||
last_match :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 3))
|
||||
last :: ast.match_arm_view(&tree, ast.match_arm(&tree, last_match, 1))
|
||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, last.body).kind)
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ State :: struct {
|
||||
instances arraylist.ArrayList(Instance)
|
||||
cache hashmap.AutoHashMap(usize, usize)
|
||||
expanded arraylist.ArrayList(u8)
|
||||
capture_types hashmap.AutoHashMap(ast.NodeId, ast.NodeId)
|
||||
diagnostics @mut diagnostics.Store
|
||||
depth usize = 0
|
||||
original_count usize
|
||||
@@ -123,6 +124,7 @@ elaborate proc(
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .comptime_expr) needed = true
|
||||
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
|
||||
and ast.for_stmt(&original, id).is_inline) needed = true
|
||||
if (class == .match_arm and ast.match_arm_view(&original, id).is_inline) needed = true
|
||||
if (class == .expression and ast.expr_kind(&original, id) == .builtin_call) {
|
||||
name :: ast.builtin_call(&original, id).name
|
||||
text :: ast.source_text(&original, ast.node_span(&original, name))
|
||||
@@ -145,6 +147,7 @@ elaborate proc(
|
||||
allocator = allocator, builder = builder, tree = ast.view(builder, root), strings = strings,
|
||||
bindings = arraylist.init(allocator), declarations = arraylist.init(allocator),
|
||||
instances = arraylist.init(allocator), cache = hashmap.init(allocator),
|
||||
capture_types = hashmap.init(allocator),
|
||||
expanded = arraylist.init(allocator), diagnostics = diagnostic_store,
|
||||
original_count = ast.node_count(&original),
|
||||
rewrites = &result,
|
||||
@@ -155,6 +158,7 @@ elaborate proc(
|
||||
defer arraylist.deinit(&state.declarations)
|
||||
defer arraylist.deinit(&state.expanded)
|
||||
defer hashmap.deinit(&state.cache)
|
||||
defer hashmap.deinit(&state.capture_types)
|
||||
defer {
|
||||
for (state.instances.items) |instance| mem.free(allocator, instance.arguments)
|
||||
arraylist.deinit(&state.instances)
|
||||
@@ -238,6 +242,55 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
try expand_inline_for(state, id)
|
||||
return
|
||||
}
|
||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .for_stmt) {
|
||||
loop :: ast.for_stmt(&state.tree, id)
|
||||
try expand(state, loop.iterable)
|
||||
if (!loop.pointer_capture and loop.capture != null) {
|
||||
iterable :: try expression_type(state, loop.iterable, 0)
|
||||
element := no_node
|
||||
if (ast.expr_kind(&state.tree, loop.iterable) == .range) {
|
||||
element = try expression_type(state, ast.range_expr(&state.tree, loop.iterable).start, 0)
|
||||
} else if (iterable != no_node) {
|
||||
kind :: ast.expr_kind(&state.tree, iterable)
|
||||
if (kind == .array_type) element = ast.array_type(&state.tree, iterable).element
|
||||
if (kind == .slice_type) element = ast.slice_type(&state.tree, iterable).element
|
||||
if (kind == .pointer_type) {
|
||||
array :: try canonical_type(state, ast.pointer_type(&state.tree, iterable).element)
|
||||
if (array != no_node and ast.expr_kind(&state.tree, array) == .array_type)
|
||||
element = ast.array_type(&state.tree, array).element
|
||||
}
|
||||
if (binding(state, iterable).kind == .builtin
|
||||
and binding(state, iterable).data.builtin == .string)
|
||||
element = try builtin(state, id, .u8)
|
||||
}
|
||||
if (element != no_node) {
|
||||
hashmap.put(&state.capture_types, loop.capture?, element) catch |err| {
|
||||
match err {
|
||||
.key_exists: {}
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (class == .statement
|
||||
and (ast.stmt_kind(&state.tree, id) == .if_stmt or ast.stmt_kind(&state.tree, id) == .while_stmt)) {
|
||||
looping :: ast.stmt_kind(&state.tree, id) == .while_stmt
|
||||
capture :: if (looping) ast.while_stmt(&state.tree, id).capture else ast.if_stmt(&state.tree, id).capture
|
||||
condition :: if (looping) ast.while_stmt(&state.tree, id).condition else ast.if_stmt(&state.tree, id).condition
|
||||
if (capture) |name| {
|
||||
annotation :: try expression_type(state, condition, 0)
|
||||
if (annotation != no_node and ast.expr_kind(&state.tree, annotation) == .optional_type) {
|
||||
child :: ast.type_operand(&state.tree, annotation)
|
||||
hashmap.put(&state.capture_types, name, child) catch |err| {
|
||||
match err {
|
||||
.key_exists: {}
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (class == .statement and ast.stmt_kind(&state.tree, id) == .expression) {
|
||||
expression :: ast.statement_expr(&state.tree, id)
|
||||
if (
|
||||
@@ -654,12 +707,215 @@ expand_struct_type proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
try expand(state, id)
|
||||
}
|
||||
|
||||
# expand dispatch, not the subject: ordinary checking/lowering retains its single
|
||||
# evaluation and by-value payload captures. only tags are compile-time constants.
|
||||
@hide:file
|
||||
expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! Error {
|
||||
inline_arm := no_node
|
||||
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||
candidate :: ast.match_arm(&state.tree, id, i)
|
||||
if (ast.match_arm_view(&state.tree, candidate).is_inline) {
|
||||
inline_arm = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if (inline_arm == no_node) return
|
||||
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||
state.type_execution_depth += 1
|
||||
defer state.type_execution_depth -= 1
|
||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||
arm :: ast.match_arm_view(&state.tree, inline_arm)
|
||||
for (0..usize(2)) |i| {
|
||||
capture :: if (i == 0) arm.capture else arm.tag_capture
|
||||
if (capture) |name| {
|
||||
resolved :: binding(state, name)
|
||||
if (resolved.kind == .invalid) {
|
||||
state.evaluation_diagnostic = resolved.data.diagnostic
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
count :: ast.match_arm_count(&state.tree, id)
|
||||
if (ast.match_arm(&state.tree, id, count - 1) != inline_arm) {
|
||||
try reject(state, id, .unreachable_match_arm)
|
||||
return
|
||||
}
|
||||
subject :: ast.match_expr(&state.tree, id).subject
|
||||
annotation := subject_type
|
||||
if (annotation == no_node) annotation = try expression_type(state, subject, 0)
|
||||
if (annotation == no_node) {
|
||||
try reject(state, id, .invalid_inline_match_subject)
|
||||
return
|
||||
}
|
||||
kind :: ast.expr_kind(&state.tree, annotation)
|
||||
tag_only :: kind == .union_tag_type
|
||||
members_type := annotation
|
||||
if (tag_only) members_type = try canonical_type(state, ast.type_operand(&state.tree, annotation))
|
||||
if (members_type == no_node) {
|
||||
try reject(state, id, .invalid_inline_match_subject)
|
||||
return
|
||||
}
|
||||
tagged := false
|
||||
if (kind == .union_type) {
|
||||
view :: ast.union_type(&state.tree, annotation)
|
||||
tagged = view.auto_tag or view.tag != null
|
||||
}
|
||||
if (kind != .enum_type and !tag_only and !tagged) {
|
||||
try reject(state, id, .invalid_inline_match_subject)
|
||||
return
|
||||
}
|
||||
if (!tagged and arm.tag_capture_present) {
|
||||
try reject(state, id, .invalid_inline_match_capture)
|
||||
return
|
||||
}
|
||||
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(&arms)
|
||||
for (0..(count - 1)) |i| {
|
||||
prior :: ast.match_arm(&state.tree, id, i)
|
||||
if (ast.match_arm_view(&state.tree, prior).catch_all) {
|
||||
try reject(state, id, .unreachable_match_arm)
|
||||
return
|
||||
}
|
||||
try arraylist.append(&arms, prior)
|
||||
}
|
||||
execution := TypeExecution{
|
||||
locals = arraylist.init(state.allocator),
|
||||
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
|
||||
first_owned = ast.NodeId(0), result_type = no_node,
|
||||
}
|
||||
defer arraylist.deinit(&execution.locals)
|
||||
defer mem.free(state.allocator, execution.mapping)
|
||||
span :: ast.node_span(&state.tree, inline_arm)
|
||||
static_capture :: if (tagged) arm.tag_capture else arm.capture
|
||||
tag_type := annotation
|
||||
if (static_capture != null) {
|
||||
if (tagged) {
|
||||
union_view :: ast.union_type(&state.tree, annotation)
|
||||
if (union_view.tag) |tag| tag_type = try canonical_type(state, tag)
|
||||
else {
|
||||
concrete :: try materialize(state, Value{ kind = .type, node = annotation })
|
||||
tag_type = try ast.add_union_tag_type(state.builder, span, concrete.node)
|
||||
try refresh(state)
|
||||
}
|
||||
}
|
||||
concrete_tag :: try materialize(state, Value{ kind = .type, node = tag_type })
|
||||
tag_type = concrete_tag.node
|
||||
}
|
||||
union_members :: tagged or tag_only
|
||||
members :: if (union_members) ast.record_field_count(&state.tree, members_type)
|
||||
else ast.enum_member_count(&state.tree, members_type)
|
||||
for (0..members) |i| {
|
||||
member :: if (union_members) ast.record_field(&state.tree, members_type, i)
|
||||
else ast.enum_member(&state.tree, members_type, i)
|
||||
field :: ast.field(&state.tree, member)
|
||||
if (field.name == null or (tagged and field.type == null)) {
|
||||
try reject(state, id, .invalid_record_field_type)
|
||||
return
|
||||
}
|
||||
covered := false
|
||||
for (arms.items) |prior| {
|
||||
for (0..ast.match_pattern_count(&state.tree, prior)) |j| {
|
||||
pattern :: ast.match_pattern(&state.tree, prior, j)
|
||||
if (ast.expr_kind(&state.tree, pattern) == .enum_literal
|
||||
and ast.name_id(&state.tree, ast.enum_literal(&state.tree, pattern))
|
||||
== ast.name_id(&state.tree, field.name?)) covered = true
|
||||
}
|
||||
}
|
||||
if (covered) continue
|
||||
if (!try type_step(state, inline_arm)) {
|
||||
try reject_evaluation(state, id)
|
||||
return
|
||||
}
|
||||
execution.locals.items = execution.locals.items[..0]
|
||||
mem.set(execution.mapping, no_node)
|
||||
literal :: try ast.add_enum_literal(state.builder, span, field.name?)
|
||||
if (static_capture) |capture| {
|
||||
# runtime patterns use the union's dispatch enum, not its declared tag enum.
|
||||
tag_literal :: try ast.add_enum_literal(state.builder, span, field.name?)
|
||||
tag :: try ast.add_typed_constant(state.builder, span, tag_type, tag_literal)
|
||||
try refresh(state)
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = capture, value = tag, annotation = tag_type,
|
||||
})
|
||||
}
|
||||
payload_capture ?ast.NodeId := null
|
||||
if (tagged and arm.capture != null) {
|
||||
payload_type :: try canonical_type(state, field.type?)
|
||||
if (payload_type == no_node) {
|
||||
try reject(state, id, .invalid_record_field_type)
|
||||
return
|
||||
}
|
||||
if (binding(state, payload_type).kind == .builtin
|
||||
and binding(state, payload_type).data.builtin == .void) {
|
||||
value :: try ast.add_literal(state.builder, .void, span)
|
||||
try refresh(state)
|
||||
try arraylist.append(&execution.locals, TypeLocal{
|
||||
declaration = arm.capture?, value = value, annotation = payload_type,
|
||||
})
|
||||
} else {
|
||||
name :: arm.capture?
|
||||
payload_capture = try ast.add_identifier(state.builder,
|
||||
ast.node_span(&state.tree, name), ast.name_id(&state.tree, name))
|
||||
try refresh(state)
|
||||
state.bindings.items[usize(payload_capture?)] = sema.Binding{
|
||||
kind = .declaration, data = sema.BindingData{ declaration = payload_capture? },
|
||||
}
|
||||
execution.mapping[usize(name)] = payload_capture?
|
||||
hashmap.put(&state.capture_types, payload_capture?, payload_type) catch |err| {
|
||||
match err {
|
||||
.key_exists: unreachable
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
body :: try type_snapshot(state, &execution, arm.body)
|
||||
patterns [1]ast.NodeId :: [literal]
|
||||
generated :: try ast.add_match_arm(state.builder, span, patterns[..],
|
||||
payload_capture, body, arm.expression, false)
|
||||
try refresh(state)
|
||||
try arraylist.append(&arms, generated)
|
||||
}
|
||||
if (arms.items.len == count - 1) {
|
||||
try reject(state, id, .unreachable_match_arm)
|
||||
return
|
||||
}
|
||||
replacement :: try ast.add_match(state.builder, ast.node_span(&state.tree, id), subject, arms.items)
|
||||
try replace(state, id, replacement)
|
||||
try refresh(state)
|
||||
}
|
||||
|
||||
# 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 {
|
||||
explicit :: ast.expr_kind(&state.tree, ast.match_expr(&state.tree, id).subject) == .comptime_expr
|
||||
try expand_inline_match(state, id, no_node)
|
||||
if (ast.expr_kind(&state.tree, id) != .match_expr) return true
|
||||
subject :: ast.match_expr(&state.tree, id).subject
|
||||
explicit :: ast.expr_kind(&state.tree, subject) == .comptime_expr
|
||||
annotation := no_node
|
||||
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||
arm_id :: ast.match_arm(&state.tree, id, i)
|
||||
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 (hashmap.get(&state.capture_types, arm.capture?) != null) continue
|
||||
if (annotation == no_node) annotation = try expression_type(state, subject, 0)
|
||||
if (annotation == no_node or ast.expr_kind(&state.tree, annotation) != .union_type) break
|
||||
pattern :: ast.match_pattern(&state.tree, arm_id, 0)
|
||||
if (ast.expr_kind(&state.tree, pattern) != .enum_literal) continue
|
||||
slot :: type_record_slot(state, annotation, ast.enum_literal(&state.tree, pattern))
|
||||
if (slot == no_index) continue
|
||||
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, slot))
|
||||
if (field.type) |type| {
|
||||
hashmap.put(&state.capture_types, arm.capture?, type) catch |err| {
|
||||
match err {
|
||||
.key_exists: unreachable
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!explicit and usize(id) < state.original_count) return false
|
||||
try expand(state, subject)
|
||||
if (!explicit and !branch_constant(state, subject, 0)) return false
|
||||
@@ -753,7 +1009,7 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
||||
.pointer_type: return branch_constant(state, ast.pointer_type(&state.tree, id).element, depth + 1)
|
||||
.optional_type, .alias_expr: return branch_constant(state, ast.type_operand(&state.tree, id), depth + 1)
|
||||
.record_type, .union_type, .enum_type, .distinct_type, .array_type, .slice_type, .function_type,
|
||||
.fallible_type, .sum_type: return true
|
||||
.fallible_type, .sum_type, .union_tag_type: return true
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
@@ -1587,7 +1843,7 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
|
||||
}
|
||||
|
||||
@hide:file
|
||||
bind_type_snapshot proc(state @mut State, execution @TypeExecution, id, start ast.NodeId) void {
|
||||
bind_type_snapshot proc(state @mut State, execution @TypeExecution, id, start ast.NodeId) void ! Error {
|
||||
if (usize(id) >= execution.mapping.len) return
|
||||
target :: execution.mapping[usize(id)]
|
||||
if (target == no_node or target < start) return
|
||||
@@ -1598,8 +1854,16 @@ bind_type_snapshot proc(state @mut State, execution @TypeExecution, id, start as
|
||||
resolved.data.declaration = execution.mapping[usize(declaration)]
|
||||
}
|
||||
state.bindings.items[usize(target)] = resolved
|
||||
if (hashmap.get(&state.capture_types, id)) |annotation| {
|
||||
hashmap.put(&state.capture_types, target, annotation) catch |err| {
|
||||
match err {
|
||||
.key_exists: {}
|
||||
else: return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for (0..ast.child_count(&state.tree, id)) |i|
|
||||
bind_type_snapshot(state, execution, ast.child(&state.tree, id, i), start)
|
||||
try bind_type_snapshot(state, execution, ast.child(&state.tree, id, i), start)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1608,7 +1872,7 @@ type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId
|
||||
start :: ast.NodeId(ast.node_count(&state.tree))
|
||||
result :: try ast.clone_subtree(state.builder, id, execution.mapping)
|
||||
try refresh(state)
|
||||
bind_type_snapshot(state, execution, id, start)
|
||||
try bind_type_snapshot(state, execution, id, start)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1768,6 +2032,8 @@ expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
|
||||
# the same rules as runtime matching, without checking unexecuted type bodies.
|
||||
@hide:file
|
||||
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||
try expand_inline_match(state, id, no_node)
|
||||
if (ast.expr_kind(&state.tree, id) != .match_expr) return no_index
|
||||
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)
|
||||
@@ -2751,6 +3017,21 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
||||
.expression: {
|
||||
expression :: ast.statement_expr(&state.tree, statement)
|
||||
if (ast.expr_kind(&state.tree, expression) != .match_expr) return .invalid
|
||||
subject_type := no_node
|
||||
arms :: ast.match_arm_count(&state.tree, expression)
|
||||
if (arms > 0
|
||||
and ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, expression, arms - 1)).is_inline) {
|
||||
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, expression).subject)
|
||||
subject_type = try expression_type(state, subject, 0)
|
||||
}
|
||||
before :: ast.node_count(&state.tree)
|
||||
try expand_inline_match(state, expression, subject_type)
|
||||
if (ast.expr_kind(&state.tree, expression) != .match_expr) return .invalid
|
||||
if (ast.node_count(&state.tree) != before) {
|
||||
previous :: execution.mapping.len
|
||||
execution.mapping = try mem.realloc(state.allocator, execution.mapping, ast.node_count(&state.tree))
|
||||
mem.set(execution.mapping[previous..], no_node)
|
||||
}
|
||||
snapshot :: try type_snapshot(state, execution, expression)
|
||||
index :: try type_match_arm(state, snapshot)
|
||||
if (index == no_index) return .invalid
|
||||
@@ -3061,7 +3342,7 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||
.literal_null: return Value{ kind = .null, node = id }
|
||||
.literal_void: return Value{ kind = .aggregate, node = id, annotation = try builtin(state, id, .void) }
|
||||
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
||||
.enum_type, .union_type, .distinct_type, .fallible_type, .sum_type:
|
||||
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type:
|
||||
return Value{ kind = .type, node = id }
|
||||
.alias_expr: return try evaluate(state, ast.type_operand(&state.tree, id))
|
||||
.unary: {
|
||||
@@ -3205,6 +3486,8 @@ same_type_context proc(state @mut State, left, right ast.NodeId, metadata bool)
|
||||
return try type_subset(state, a, b) and try type_subset(state, b, a)
|
||||
if (kind != ast.expr_kind(&state.tree, b)) return false
|
||||
match kind {
|
||||
.union_tag_type: return try same_type_context(state,
|
||||
ast.type_operand(&state.tree, a), ast.type_operand(&state.tree, b), metadata)
|
||||
.record_type: {
|
||||
if (!metadata and (!type_record_storage(state, a, 0) or !type_record_storage(state, b, 0))) return false
|
||||
if (!structural_type_record(state, a) or !structural_type_record(state, b)) return false
|
||||
@@ -3338,6 +3621,8 @@ type_hash_context proc(state @mut State, id ast.NodeId, metadata bool) usize ! E
|
||||
kind :: ast.expr_kind(&state.tree, node)
|
||||
hash := enum_index(kind)
|
||||
match kind {
|
||||
.union_tag_type: return mix(hash,
|
||||
try type_hash_context(state, ast.type_operand(&state.tree, node), metadata))
|
||||
.record_type: {
|
||||
if ((!metadata and !type_record_storage(state, node, 0))
|
||||
or !structural_type_record(state, node)) return mix(hash, usize(node))
|
||||
@@ -3401,6 +3686,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
if (resolved.kind == .builtin and resolved.data.builtin != .type) return try builtin(state, id, .type)
|
||||
if (resolved.kind == .declaration) {
|
||||
target :: resolved.data.declaration
|
||||
if (hashmap.get(&state.capture_types, target)) |annotation| return try canonical_type(state, annotation)
|
||||
class :: ast.node_class(&state.tree, target)
|
||||
if (class == .parameter) {
|
||||
annotation :: ast.param(&state.tree, target).type
|
||||
@@ -3426,7 +3712,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
kind :: ast.expr_kind(&state.tree, id)
|
||||
match kind {
|
||||
.record_type, .array_type, .slice_type, .pointer_type, .function_type, .optional_type,
|
||||
.enum_type, .union_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
||||
.enum_type, .union_type, .union_tag_type, .distinct_type, .fallible_type, .sum_type, .alias_expr:
|
||||
return try builtin(state, id, .type)
|
||||
.typed_constant: return try canonical_type(state, ast.typed_constant(&state.tree, id).type)
|
||||
.literal_int: return try builtin(state, id, .i64)
|
||||
|
||||
+296
@@ -11,6 +11,290 @@ HONEY = ROOT / "build/honey"
|
||||
|
||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||
CASES = {
|
||||
"inline_match_runtime_enum_static_capture_expression_and_single_subject": (
|
||||
"""
|
||||
Mode :: enum(i16) { handled = -7, first = 3, second = 8, answer = 42, zero = 90 }
|
||||
selected proc(value Mode, calls @mut i16) Mode {
|
||||
calls^ = calls^ + 1
|
||||
return value
|
||||
}
|
||||
remaining proc($tag Mode) i16 {
|
||||
if (tag == .answer) { return 42 }
|
||||
else if (tag == .zero) { return 0 }
|
||||
else { return false }
|
||||
}
|
||||
read proc(value Mode, calls @mut i16) i16 {
|
||||
return match selected(value, calls) {
|
||||
.handled: 0,
|
||||
.first, .second: 0,
|
||||
inline |tag|: remaining(tag),
|
||||
}
|
||||
}
|
||||
main proc() [2]i16 {
|
||||
calls i16 := 0
|
||||
total :: (
|
||||
read(.handled, &calls) + read(.first, &calls) + read(.second, &calls)
|
||||
+ read(.answer, &calls) + read(.zero, &calls)
|
||||
)
|
||||
return [total, calls]
|
||||
}
|
||||
""", b"[42, 5]\n", None,
|
||||
),
|
||||
"inline_match_generic_heterogeneous_payloads_static_tags_and_nested_selection": (
|
||||
"""
|
||||
Tag :: enum(i16) { handled = -7, first = 3, second = 8, number = 42, record = 90, text = 100, empty = 120 }
|
||||
Pair :: struct { values [2]i16 }
|
||||
Choice :: union(Tag) { text string, empty void, record Pair, number i8, handled bool, second bool, first bool }
|
||||
narrow proc($tag Tag, value i8) i16 {
|
||||
if (tag == .number) return i16(value) else return false
|
||||
}
|
||||
pair proc(value Pair) i16 { return value.values[0] + value.values[1] }
|
||||
read proc($T type, input T) i16 {
|
||||
return match input {
|
||||
.handled: 0,
|
||||
.first, .second: 0,
|
||||
inline |payload, tag|: match tag {
|
||||
.number: narrow(tag, payload),
|
||||
.record: pair(payload),
|
||||
.text: i16(payload.len),
|
||||
.empty: 1,
|
||||
else: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
nested proc(input Choice) i16 {
|
||||
match input {
|
||||
.handled, .first, .second: return 0,
|
||||
inline |payload, tag|: {
|
||||
match tag {
|
||||
inline |selected|: {
|
||||
if (selected == .number) { return narrow(tag, payload) }
|
||||
else if (selected == .record) { return pair(payload) }
|
||||
else if (selected == .text) { return i16(payload.len) }
|
||||
else {
|
||||
payload
|
||||
return 1
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
main proc() i16 {
|
||||
if (read(Choice, .handled{false}) + read(Choice, .first{true}) + read(Choice, .second{false}) != 0) return -1
|
||||
if (nested(.number{19}) + nested(.record{values = [9, 11]}) + nested(.text{"ok"}) + nested(.empty) != 42) return -2
|
||||
return (
|
||||
read(Choice, .number{19}) + read(Choice, .record{values = [9, 11]})
|
||||
+ read(Choice, .text{"ok"}) + read(Choice, .empty)
|
||||
)
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_union_subject_once_and_payload_copy_isolation_without_tag": (
|
||||
"""
|
||||
Pair :: struct { values [2]mut i16 }
|
||||
Choice :: union(enum) { record Pair, other Pair }
|
||||
exact proc(value Pair) i16 { return value.values[0] + value.values[1] }
|
||||
selected proc(input @Choice, calls @mut i16) Choice {
|
||||
calls^ = calls^ + 1
|
||||
return input^
|
||||
}
|
||||
main proc() i16 {
|
||||
input Choice := .record{values = [19, 23]}
|
||||
calls i16 := 0
|
||||
match selected(&input, &calls) {
|
||||
inline |payload|: {
|
||||
input.record.values[0] = 0
|
||||
copied := payload
|
||||
copied.values[1] = 0
|
||||
if (calls != 1 or input.record.values[0] != 0 or copied.values[1] != 0) return -1
|
||||
return exact(payload)
|
||||
},
|
||||
}
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_return_break_continue_run_arm_cleanup": (
|
||||
"""
|
||||
Mode :: enum { skip, add, stop }
|
||||
finish proc(input Mode, cleanups @mut i16) i16 {
|
||||
match input {
|
||||
.skip, .stop: return 0,
|
||||
inline |tag|: {
|
||||
defer cleanups^ = cleanups^ + 1
|
||||
if (tag == .add) return 19 else return false
|
||||
},
|
||||
}
|
||||
}
|
||||
main proc() i16 {
|
||||
total i16 := 0
|
||||
cleanups i16 := 0
|
||||
for [Mode.skip, .add, .stop, .add] |input| {
|
||||
match input {
|
||||
inline |tag|: {
|
||||
defer cleanups = cleanups + 1
|
||||
if (tag == .skip) { continue }
|
||||
else if (tag == .stop) { break }
|
||||
else { total = total + 19 }
|
||||
},
|
||||
}
|
||||
}
|
||||
result :: finish(.add, &cleanups)
|
||||
return total + result + cleanups
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_rejects_scalar_subject": (
|
||||
"main proc() i16 { return match i16(42) { inline |value|: value } }",
|
||||
b"", b"inline match arm requires an enum or tagged union",
|
||||
),
|
||||
"inline_match_rejects_untagged_union_subject": (
|
||||
"Choice :: union { number i16 }\nmain proc() i16 { input Choice :: { number = 42 }\nreturn match input { inline |value|: value } }",
|
||||
b"", b"inline match arm requires an enum or tagged union",
|
||||
),
|
||||
"inline_match_rejects_second_enum_capture": (
|
||||
"Mode :: enum { answer }\nmain proc() i16 { return match Mode.answer { inline |value, tag|: 42 } }",
|
||||
b"", b"inline match arm requires one enum capture",
|
||||
),
|
||||
"inline_match_rejects_redundant_remaining_arm": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { return match Mode.first { .first, .second: 42, inline |_|: 0 } }",
|
||||
b"", b"match arm is unreachable",
|
||||
),
|
||||
"inline_match_rejects_following_arm": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { return match Mode.first { inline |_|: 42, .second: 0 } }",
|
||||
b"", b"match arm is unreachable",
|
||||
),
|
||||
"inline_match_rejects_arm_after_else": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { return match Mode.first { else: 42, inline |_|: 0 } }",
|
||||
b"", b"match arm is unreachable",
|
||||
),
|
||||
"inline_match_checks_preceding_duplicate_patterns": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { return match Mode.first { .first: 42, .first: 0, inline |_|: 0 } }",
|
||||
b"", b"duplicate or overlapping match pattern",
|
||||
),
|
||||
"inline_match_rejects_duplicate_capture_names": (
|
||||
"Choice :: union(enum) { number i8 }\nmain proc() i16 { input Choice :: .number{42}\nreturn match input { inline |value, value|: 42 } }",
|
||||
b"", b"duplicate name or forbidden shadowing",
|
||||
),
|
||||
"inline_match_rejects_shadowed_enum_capture": (
|
||||
"Mode :: enum { answer }\nmain proc() i16 { value :: 42\nreturn match Mode.answer { inline |value|: 42 } }",
|
||||
b"", b"duplicate name or forbidden shadowing",
|
||||
),
|
||||
"inline_match_rejects_shadowed_tag_capture": (
|
||||
"Choice :: union(enum) { number i8 }\nmain proc() i16 { tag :: 42\ninput Choice :: .number{42}\nreturn match input { inline |value, tag|: 42 } }",
|
||||
b"", b"duplicate name or forbidden shadowing",
|
||||
),
|
||||
"inline_match_enum_capture_is_immutable": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { match Mode.first { inline |value|: value = .second }\nreturn 42 }",
|
||||
b"", b"assignment requires a mutable local binding",
|
||||
),
|
||||
"inline_match_payload_capture_is_immutable": (
|
||||
"Choice :: union(enum) { number i8 }\nmain proc() i16 { input Choice :: .number{42}\nmatch input { inline |value|: value = 0 }\nreturn 42 }",
|
||||
b"", b"assignment requires a mutable local binding",
|
||||
),
|
||||
"inline_match_tag_capture_is_immutable": (
|
||||
"Choice :: union(enum) { number i8, empty void }\nmain proc() i16 { input Choice :: .number{42}\nmatch input { inline |value, tag|: tag = .empty }\nreturn 42 }",
|
||||
b"", b"assignment requires a mutable local binding",
|
||||
),
|
||||
"inline_match_payload_preserves_narrow_type": (
|
||||
"Choice :: union(enum) { number i8 }\nread proc(input Choice) i16 { match input { inline |value|: return value } }\nmain proc() i16 { return read(.number{42}) }",
|
||||
b"", b"return does not match",
|
||||
),
|
||||
"inline_match_payload_is_not_a_comptime_binding": (
|
||||
"Choice :: union(enum) { number i8 }\nknown proc($value i8) i16 { return i16(value) }\nread proc(input Choice) i16 { return match input { inline |value|: known(value) } }\nmain proc() i16 { return read(.number{42}) }",
|
||||
b"", b"compile-time",
|
||||
),
|
||||
"inline_match_resolves_unselected_bodies": (
|
||||
"Mode :: enum { first, second }\nmain proc() i16 { return match Mode.first { inline |tag|: match tag { .first: 42, .second: missing } } }",
|
||||
b"42\n", b"unknown name",
|
||||
),
|
||||
"inline_match_rejects_pointer_capture_syntax": (
|
||||
"Choice :: union(enum) { number i8 }\nmain proc() i16 { input Choice :: .number{42}\nreturn match input { inline |*value|: 42 } }",
|
||||
b"", b"unexpected token",
|
||||
),
|
||||
"inline_match_nested_optional_loop_and_ordinary_match_captures": (
|
||||
"""
|
||||
Mode :: enum { skip, answer }
|
||||
Choice :: union(enum) { mode Mode }
|
||||
score proc(value Mode) i16 {
|
||||
return match value { inline |tag|: match tag { .skip: 0, .answer: 14 } }
|
||||
}
|
||||
read proc(input ?Mode, choice Choice) i16 {
|
||||
total i16 := 0
|
||||
if input |value| {
|
||||
total = match value { inline |tag|: score(tag) }
|
||||
}
|
||||
pending ?Mode := input
|
||||
while pending |value| {
|
||||
total = total + match value { inline |tag|: score(tag) }
|
||||
pending = null
|
||||
}
|
||||
match choice {
|
||||
.mode |value|: {
|
||||
total = total + match value { inline |tag|: score(tag) }
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
main proc() i16 { return read(.answer, .mode{.answer}) + read(null, .mode{.skip}) }
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_type_producer_selects_generated_storage": (
|
||||
"""
|
||||
Mode :: enum { small, wide }
|
||||
Storage proc(input Mode) type {
|
||||
match input { inline |tag|: {
|
||||
if (tag == .small) { return struct { value i8 = 19 } }
|
||||
else { return struct { value i16 = 23 } }
|
||||
} }
|
||||
}
|
||||
main proc() i16 {
|
||||
small Storage(.small) :: {}
|
||||
wide Storage(.wide) :: {}
|
||||
return i16(small.value) + wide.value
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_auto_tag_nested_expansion_infers_exact_payload_generics": (
|
||||
"""
|
||||
Choice :: union(enum) { number i8, empty void }
|
||||
identity proc($T type, value T) T { return value }
|
||||
read proc(input Choice) i16 {
|
||||
match input { inline |payload, tag|: match tag {
|
||||
inline |value|: {
|
||||
if (value == .number) { return i16(identity(payload)) }
|
||||
else {
|
||||
payload
|
||||
return 2
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
main proc() i16 { return read(.number{40}) + read(.empty) }
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_match_type_producer_updates_outer_type_and_value_locals": (
|
||||
"""
|
||||
Choice :: union(enum) { number u8, empty void }
|
||||
Storage proc(input Choice) type {
|
||||
length usize := 1
|
||||
Element := i8
|
||||
for [input] |item| {
|
||||
match item { inline |payload, tag|: {
|
||||
if (tag == .number) {
|
||||
length = usize(payload)
|
||||
Element = i16
|
||||
} else { length = 2 }
|
||||
} }
|
||||
}
|
||||
return struct { values [length]Element }
|
||||
}
|
||||
main proc() i16 {
|
||||
value Storage(.number{2}) :: { values = [19, 23] }
|
||||
return value.values[0] + value.values[1]
|
||||
}
|
||||
""", b"42\n", None,
|
||||
),
|
||||
"inline_for_heterogeneous_tuple_exact_captures_and_nested_values": (
|
||||
"""
|
||||
Choice :: union(enum) { number i8, empty void }
|
||||
@@ -6979,6 +7263,18 @@ def check_dependency_provenance(temporary, executable):
|
||||
return 42
|
||||
}
|
||||
""", 5, 16, False),
|
||||
("""
|
||||
Choice :: union(enum) { number int, empty void }
|
||||
fail proc() int {
|
||||
input Choice :: .number{9223372036854775807}
|
||||
return match input {
|
||||
inline |payload, tag|: match tag {
|
||||
.number: payload + 1,
|
||||
.empty: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
""", 8, 22, False),
|
||||
("""
|
||||
fail proc() int {
|
||||
a :: [42]
|
||||
|
||||
Reference in New Issue
Block a user