feat: execute collection and index iteration

This commit is contained in:
2026-10-27 15:08:37 +01:00
parent 0f5d3c222a
commit 0aca64a77e
9 changed files with 546 additions and 17 deletions
+5 -1
View File
@@ -102,6 +102,8 @@ type-producing bodies support mutable type and value locals, direct binding assi
field/index assignments also update initialized, materializable aggregate locals in type-producing bodies, including nested record fields and mutable fixed-array elements. for example, `counts [2]mut usize := [0, 0]` followed by `counts[1] = 2` can generate `[counts[1]]i16` storage. each update uses ordinary checked IR and returns a new owned constant: earlier copies remain unchanged, while later loop iterations, generated lengths and defaults observe the update. ordinary mutability, index-type/bounds, field and exact value-type checks still apply, including immutable payload captures and narrow arithmetic overflow. imported producers and repeated compilation retain source diagnostics and nominal cache identity; pointer/slice-backed mutation and type-valued aggregate storage remain open. field/index assignments also update initialized, materializable aggregate locals in type-producing bodies, including nested record fields and mutable fixed-array elements. for example, `counts [2]mut usize := [0, 0]` followed by `counts[1] = 2` can generate `[counts[1]]i16` storage. each update uses ordinary checked IR and returns a new owned constant: earlier copies remain unchanged, while later loop iterations, generated lengths and defaults observe the update. ordinary mutability, index-type/bounds, field and exact value-type checks still apply, including immutable payload captures and narrow arithmetic overflow. imported producers and repeated compilation retain source diagnostics and nominal cache identity; pointer/slice-backed mutation and type-valued aggregate storage remain open.
type-producing bodies also iterate materializable fixed arrays and strings with immutable by-value element captures and optional `usize` indices: `for counts |count, index| { length = length + count + index }` can compute generated storage. nested loops, aggregate captures, empty collections, break/continue and early returns share the type-control execution bound. array places retain their entry-time indices and observe later element updates; temporary arrays and strings are evaluated once. ordinary constant-input procedures additionally support slice and pointer captures through checked IR, provided only materializable results escape. type-producing loop bodies still reject storage-bearing captures rather than retaining temporary interpreter addresses; type-valued elements and `inline for` remain open.
optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null, but nonnull pointer/slice/callback-bearing payloads retain the constant-materialization escape boundary. optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null, but nonnull pointer/slice/callback-bearing payloads retain the constant-materialization escape boundary.
type-producing bodies also execute scalar/enum `match` statements and type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound. 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.
@@ -174,7 +176,9 @@ untagged `Bits :: union { integer u64, real f64 }` overlays its members in share
`for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable value with the bounds' exact integer type in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum of each integer type. `for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable value with the bounds' exact integer type in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum of each integer type.
range bounds that are compound or unary expressions require their own parentheses, such as `(-2)..(limit + 1)`. a braceless body also requires parentheses around the entire iterable: `for (0..10) |i| total = total + i`. current execution supports direct integer ranges only; first-class range values, collection iteration, pointer captures and index captures remain unimplemented. [programs/range_loop.hon](programs/range_loop.hon) skips `5` while summing `0..10`; `./build/honey run programs/range_loop.hon` prints `40`. range bounds that are compound or unary expressions require their own parentheses, such as `(-2)..(limit + 1)`. a braceless body also requires parentheses around the entire iterable: `for (0..10) |i| total = total + i`. first-class range values and range pointer/index captures remain unimplemented. [programs/range_loop.hon](programs/range_loop.hon) skips `5` while summing `0..10`; `./build/honey run programs/range_loop.hon` prints `40`.
collection loops accept arrays, slices, strings and single pointers to arrays: `for values |value, index| { ... }`. element captures are immutable by-value copies with the exact element type; optional indices have type `usize`, and either capture may be discarded with `_`. `|@value|` borrows an element through a single pointer, writable only when the source storage permits mutation. the iterable and its length are evaluated once, then each iteration reads its current element: array lvalues are borrowed, temporary arrays are retained once, and slice rebinding does not change the active loop's view. empty collections execute no body, including arrays of zero-cell elements. collection loops share range-loop cleanup, break/continue and return handling through the interpreter and native backend.
`break` exits the nearest enclosing `for` or `while`; `continue` restarts a `while` condition or advances a `for` range through its guarded update. both are operand-free statements permitted within loop bodies and nested conditional arms; labels and values are unsupported. controls outside loops produce keyword-spanned diagnostics, including in untaken or unreachable code, and trap only if reached. code after a transfer is still resolved and type-checked but does not execute on that path. body locals are reset on re-entry after `continue`; loop transfers do not guarantee a function return. both controls lower to existing IR jumps, without new interpreter instructions or loop frames. [programs/loop_control.hon](programs/loop_control.hon) skips an iteration and exits early; `./build/honey run programs/loop_control.hon` prints `12`. `break` exits the nearest enclosing `for` or `while`; `continue` restarts a `while` condition or advances a `for` range through its guarded update. both are operand-free statements permitted within loop bodies and nested conditional arms; labels and values are unsupported. controls outside loops produce keyword-spanned diagnostics, including in untaken or unreachable code, and trap only if reached. code after a transfer is still resolved and type-checked but does not execute on that path. body locals are reset on re-entry after `continue`; loop transfers do not guarantee a function return. both controls lower to existing IR jumps, without new interpreter instructions or loop frames. [programs/loop_control.hon](programs/loop_control.hon) skips an iteration and exits early; `./build/honey run programs/loop_control.hon` prints `12`.
+7 -3
View File
@@ -27,8 +27,11 @@ including owned by-value tagged payload captures in ordinary generic procedures.
type-producing procedures also accept ordinary type-producing procedures also accept ordinary
constant inputs, cached and erased like explicit compile-time arguments. mutable constant inputs, cached and erased like explicit compile-time arguments. mutable
type/value locals, direct and aggregate field/index assignments, branches, type/value locals, direct and aggregate field/index assignments, branches,
scalar/enum/tagged-union matching and bounded integer-range/while loops now generate types and storage. optional scalar/enum/tagged-union matching and bounded integer-range/collection/while loops now generate types and storage. optional
if/while captures and tagged payload matches select nested constructors, lengths, signatures and defaults; if/while captures and tagged payload matches select nested constructors, lengths, signatures and defaults;
array/slice/string/pointer-to-array collection loops support exact copy/pointer
captures and `usize` indices through both paths; type-producing bodies iterate
materializable arrays/strings by value without retaining interpreter addresses.
general type-valued execution, reflection and generic library acceptance remain open. general type-valued execution, 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.
@@ -79,8 +82,9 @@ test discovery and FFI remain outside these completed slices.
- [x] generic match-specialization checkpoint: side-effect-free constant scalar/enum/tagged-union subjects in concrete generic procedures and explicit `match $(subject)` expressions/statements select an arm before body specialization/checking. reuse ordinary checked IR for pattern types, ranges, overlap, exhaustiveness and every arm's capture validation; only the selected body is elaborated. exact by-value payload types/owned strings, runtime parameters, nested expression matches, cleanup, return/loop transfers, imported producers and repeatable compilation work through interpreter/native execution. ordinary call/runtime/mutable subjects stay runtime; contextual aggregates with possible runtime defaults are not implicitly evaluated. all bodies remain parsed/name-resolved; storage-bearing payloads retain the constant-materialization escape boundary. - [x] generic match-specialization checkpoint: side-effect-free constant scalar/enum/tagged-union subjects in concrete generic procedures and explicit `match $(subject)` expressions/statements select an arm before body specialization/checking. reuse ordinary checked IR for pattern types, ranges, overlap, exhaustiveness and every arm's capture validation; only the selected body is elaborated. exact by-value payload types/owned strings, runtime parameters, nested expression matches, cleanup, return/loop transfers, imported producers and repeatable compilation work through interpreter/native execution. ordinary call/runtime/mutable subjects stay runtime; contextual aggregates with possible runtime defaults are not implicitly evaluated. all bodies remain parsed/name-resolved; storage-bearing payloads retain the constant-materialization escape boundary.
- [x] optional type-control checkpoint: type-producing bodies execute optional if/while guards with immutable by-value payload captures. ordinary checked IR validates guards; selected scalar/aggregate payloads retain exact types and owned strings across nested captures, generated storage/defaults, loop updates/transfers and early returns. absent optionals skip unselected bodies; invalid guards, capture mutation/shadowing, overflow and execution bounds retain diagnostics. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. null data/function pointers materialize correctly; nonnull storage-bearing payloads retain the constant-materialization escape boundary. - [x] optional type-control checkpoint: type-producing bodies execute optional if/while guards with immutable by-value payload captures. ordinary checked IR validates guards; selected scalar/aggregate payloads retain exact types and owned strings across nested captures, generated storage/defaults, loop updates/transfers and early returns. absent optionals skip unselected bodies; invalid guards, capture mutation/shadowing, overflow and execution bounds retain diagnostics. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. null data/function pointers materialize correctly; nonnull storage-bearing payloads retain the constant-materialization escape boundary.
- [x] aggregate type-update checkpoint: type-producing bodies update initialized, materializable aggregate locals through nested record fields and mutable fixed-array indices. ordinary checked IR validates and executes each assignment, retaining exact value types, mutability (including immutable payload captures), index types/bounds, unknown-field and narrow-overflow diagnostics. new owned constants preserve earlier by-value copies while later loops, generated storage and defaults observe updates. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. pointer/slice-backed mutation, uninitialized aggregate construction and type-valued aggregate storage remain open. - [x] aggregate type-update checkpoint: type-producing bodies update initialized, materializable aggregate locals through nested record fields and mutable fixed-array indices. ordinary checked IR validates and executes each assignment, retaining exact value types, mutability (including immutable payload captures), index types/bounds, unknown-field and narrow-overflow diagnostics. new owned constants preserve earlier by-value copies while later loops, generated storage and defaults observe updates. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. pointer/slice-backed mutation, uninitialized aggregate construction and type-valued aggregate storage remain open.
- [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates. - [x] collection-iteration checkpoint: arrays, slices, strings and single pointers-to-arrays support immutable exact-type copy captures, storage-qualified pointer captures and optional `usize` indices through interpreter/native execution. iterables/bounds evaluate once, array lvalues remain borrowed and later element updates are visible; temporary arrays, aggregate copy isolation, zero-cell/empty collections, nested loops and cleanup across transfers retain ordinary semantics. ordinary constant-input procedures reuse the same checked IR; type-producing bodies iterate materializable arrays/strings with by-value captures, fixed entry-time place indices, generated storage, nominal cache identity, dependency diagnostics, shared execution bounds and repeatable compilation. storage-bearing type-control captures and type-valued elements retain their existing boundaries.
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required 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. - [ ] 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.
- [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime. - [ ] self-hosting proof: bootstrap-built Honey builds the production compiler (excluding test-only imports); that executable rebuilds the compiler; the rebuilt compiler passes representative interpreter/native examples and CLI/error scenarios without Bro at runtime.
+17
View File
@@ -146,6 +146,18 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` bias i16 = config.bias + i16(original.counts[1]) ` bias i16 = config.bias + i16(original.counts[1])
` } ` }
`} `}
`Collected proc() type {
` counts [2]mut usize := [1, 1]
` length usize := 0
` for counts |amount, index| {
` counts[1] = 2
` length = length + amount
` if (index == 0) continue
` }
` Element := i8
` for "ok" |byte| { if (byte == u8(107)) Element = i16 }
` return [length]Element
`}
`read_selected proc($choice Choice, value i16) i16 { `read_selected proc($choice Choice, value i16) i16 {
` match choice { ` match choice {
` .payload |detail|: { ` .payload |detail|: {
@@ -173,6 +185,11 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` if (optional[0] + optional[1] != 40) return 0 ` if (optional[0] + optional[1] != 40) return 0
` updated Updated() :: { values = optional } ` updated Updated() :: { values = optional }
` if (updated.bias != 42 or updated.values[1] != 21) return 0 ` if (updated.bias != 42 or updated.values[1] != 21) return 0
` collected Collected() :: [19, 21, 2]
` same_collected Collected() :: collected
` total i16 := 0
` for same_collected |item| { total = total + item }
` if (total != 42) return 0
` payload :: packet.choices[0].payload ` payload :: packet.choices[0].payload
` if (payload.values[0] != 40 or payload.values[1] != 2 or payload.text.len != 2) return 0 ` if (payload.values[0] != 40 or payload.values[1] != 2 or payload.text.len != 2) return 0
` match packet.choices[1] { .empty: {}, .payload: return 0 } ` match packet.choices[1] { .empty: {}, .payload: return 0 }
+1 -1
View File
@@ -176,7 +176,7 @@ messages :: $enummap.init(Code, []u8, {
break_outside_loop = "break requires an enclosing loop", break_outside_loop = "break requires an enclosing loop",
continue_outside_loop = "continue requires an enclosing loop", continue_outside_loop = "continue requires an enclosing loop",
invalid_range_bound = "range bounds must have compatible integer types", invalid_range_bound = "range bounds must have compatible integer types",
invalid_for_iterable = "for-loop iteration currently requires a direct integer range", invalid_for_iterable = "for-loop iteration requires an array, slice, string, single pointer to an array, or direct integer range",
range_value_not_supported = "first-class range values are not implemented", range_value_not_supported = "first-class range values are not implemented",
range_pointer_capture = "range loops do not support pointer captures", range_pointer_capture = "range loops do not support pointer captures",
range_index_capture = "range loops do not support index captures", range_index_capture = "range loops do not support index captures",
+80 -5
View File
@@ -482,13 +482,17 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
} }
.for_stmt: { .for_stmt: {
loop :: ast.for_stmt(state.tree, statement) loop :: ast.for_stmt(state.tree, statement)
is_range :: ast.expr_kind(state.tree, loop.iterable) == .range
family :: typecheck.type_of(state.checked, loop.iterable).range_element family :: typecheck.type_of(state.checked, loop.iterable).range_element
type :: lower_type(state, typecheck.Type{ kind = family }) type ir.Type :: if (is_range) lower_type(state, typecheck.Type{ kind = family }) else .usize
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array) slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) } state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)] if (loop.capture) |capture| {
if (is_range) state.storage[usize(capture)] = state.storage[usize(statement)]
else try predeclare_capture(state, capture)
}
if (loop.index) |index| { if (loop.index) |index| {
index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .int, ir.no_array) index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .usize, ir.no_array)
state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) } state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) }
} }
try predeclare_locals(state, loop.body) try predeclare_locals(state, loop.body)
@@ -705,8 +709,7 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
loop :: ast.for_stmt(state.tree, id) loop :: ast.for_stmt(state.tree, id)
if (ast.expr_kind(state.tree, loop.iterable) != .range) { if (ast.expr_kind(state.tree, loop.iterable) != .range) {
_ = try lower_expression(state, loop.iterable) try lower_collection_for(state, id)
if (!state.terminated) _ = trap_type(state, id)
return return
} }
interval :: ast.range_expr(state.tree, loop.iterable) interval :: ast.range_expr(state.tree, loop.iterable)
@@ -765,6 +768,78 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
state.terminated = false state.terminated = false
} }
@hide:file
lower_collection_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
loop :: ast.for_stmt(state.tree, id)
parts :: try slice_parts(state, loop.iterable)
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
slot :: state.storage[usize(id)].index
zero :: try usize_constant(state, 0, loop.span)
_ = try emit(state, ir.Instruction{ op = .store_local, type = .usize, left = zero, slot = slot, span = loop.span })
origin :: state.block
condition :: try add_block(state)
body :: try add_block(state)
update :: try add_block(state)
exit :: try add_block(state)
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
kind = .jump, target = condition, span = loop.span,
}
state.block = condition
current :: try emit(state, ir.Instruction{ op = .load_local, type = .usize, slot = slot, span = loop.span })
condition_value :: try emit(state, ir.Instruction{ op = .less, type = .bool, left = current, right = parts.bound, span = loop.span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .branch, value = condition_value, target = body, alternative = exit, span = loop.span,
}
previous_loop :: state.loop
state.loop = LoopTargets{ continue_target = update, exit = exit, cleanup_depth = state.cleanups.items.len }
defer state.loop = previous_loop
state.block = body
if (loop.capture) |capture| {
if (!trap_binding(state, capture) and !trap_type(state, capture)) {
runtime :: lower_type(state, parts.element)
shape :: try array_shape(state, parts.element)
pointer :: try emit(state, ir.Instruction{
op = .pointer_offset, type = .pointer, left = parts.pointer, right = current,
pointee = runtime, array_type = shape, span = loop.span,
})
value := pointer
if (!loop.pointer_capture) {
address :: try pointer_address(state, pointer, parts.element, loop.span)
value = try emit(state, ir.Instruction{
op = .load_address, type = runtime, array_type = shape, left = address, span = loop.span,
})
}
checked :: typecheck.type_of(state.checked, capture)
_ = try emit(state, ir.Instruction{
op = .store_local, type = lower_type(state, checked), array_type = try array_shape(state, checked),
left = value, slot = state.storage[usize(capture)].index, span = loop.span,
})
}
}
if (loop.index) |index| {
if (!state.terminated and !trap_binding(state, index) and !trap_type(state, index)) {
_ = try emit(state, ir.Instruction{
op = .store_local, type = .usize, left = current, slot = state.storage[usize(index)].index, span = loop.span,
})
}
}
if (!state.terminated) try lower_block(state, loop.body, true)
if (!state.terminated) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = update, span = loop.span,
}
state.block = update
state.terminated = false
last :: try emit(state, ir.Instruction{ op = .load_local, type = .usize, slot = slot, span = loop.span })
one :: try usize_constant(state, 1, loop.span)
next :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = last, right = one, span = loop.span })
_ = try emit(state, ir.Instruction{ op = .store_local, type = .usize, left = next, slot = slot, span = loop.span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = condition, span = loop.span,
}
state.block = exit
state.terminated = false
}
@hide:file @hide:file
lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree tree :: state.tree
+65 -4
View File
@@ -174,6 +174,7 @@ Cell :: struct {
literal ast.NodeId = ast.NodeId(maxval!(u32)) literal ast.NodeId = ast.NodeId(maxval!(u32))
producer ast.NodeId = ast.NodeId(maxval!(u32)) producer ast.NodeId = ast.NodeId(maxval!(u32))
capture ast.NodeId = ast.NodeId(maxval!(u32)) capture ast.NodeId = ast.NodeId(maxval!(u32))
for_loop ast.NodeId = ast.NodeId(maxval!(u32))
record_checking bool = false record_checking bool = false
record_pointer_depth usize = 0 record_pointer_depth usize = 0
nominal bool = false nominal bool = false
@@ -382,6 +383,7 @@ check_elaborated proc(
} }
} }
# calls use the signature result before contextual literal inference. # calls use the signature result before contextual literal inference.
try prepare_for_captures(&state, false)
try prepare_optional_captures(&state, false) try prepare_optional_captures(&state, false)
for (0..count) |i| { for (0..count) |i| {
id :: ast.NodeId(i) id :: ast.NodeId(i)
@@ -553,7 +555,7 @@ check_elaborated proc(
if (cells[i].role != .for_stmt) continue if (cells[i].role != .for_stmt) continue
iterable :: ast.for_stmt(tree, ast.NodeId(i)).iterable iterable :: ast.for_stmt(tree, ast.NodeId(i)).iterable
actual :: types[usize(iterable)] actual :: types[usize(iterable)]
if (actual.kind != .invalid and actual.kind != .range) { if (actual.kind != .invalid and actual.kind != .range and !collection_type(&state, actual)) {
try fail(&state, iterable, .invalid_for_iterable, ast.node_span(tree, iterable)) try fail(&state, iterable, .invalid_for_iterable, ast.node_span(tree, iterable))
} }
} }
@@ -1073,12 +1075,13 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
state.cells[usize(loop.iterable)].for_iterable = true state.cells[usize(loop.iterable)].for_iterable = true
try collect_expression(state, loop.iterable) try collect_expression(state, loop.iterable)
try depend(state, loop.iterable, statement) try depend(state, loop.iterable, statement)
if (loop.pointer_capture) { direct_range :: ast.expr_kind(tree, loop.iterable) == .range
if (direct_range and loop.pointer_capture) {
state.cells[usize(statement)].type = invalid_type(try diagnostics.add( state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
state.diagnostics, .range_pointer_capture, loop.span, state.diagnostics, .range_pointer_capture, loop.span,
)) ))
} }
if (loop.index_capture) { if (direct_range and loop.index_capture) {
diagnostic :: try diagnostics.add(state.diagnostics, .range_index_capture, loop.span) diagnostic :: try diagnostics.add(state.diagnostics, .range_index_capture, loop.span)
if (state.cells[usize(statement)].type.kind != .invalid) { if (state.cells[usize(statement)].type.kind != .invalid) {
state.cells[usize(statement)].type = invalid_type(diagnostic) state.cells[usize(statement)].type = invalid_type(diagnostic)
@@ -1086,9 +1089,18 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
} }
if (loop.capture) |capture| { if (loop.capture) |capture| {
try collect_capture(state, capture, loop.body) try collect_capture(state, capture, loop.body)
if (!direct_range) {
state.cells[usize(capture)].for_loop = statement
if (state.cells[usize(capture)].type.kind != .invalid) state.cells[usize(capture)].type = Type{}
}
state.cells[usize(loop.iterable)].capture = capture state.cells[usize(loop.iterable)].capture = capture
} }
if (loop.index) |index| try collect_capture(state, index, loop.body) if (loop.index) |index| {
try collect_capture(state, index, loop.body)
if (!direct_range and state.cells[usize(index)].type.kind != .invalid) {
state.cells[usize(index)].type = Type{ kind = .usize }
}
}
try collect_block(state, loop.body, return_annotation, true) try collect_block(state, loop.body, return_annotation, true)
try depend(state, loop.body, id) try depend(state, loop.body, id)
} }
@@ -1763,6 +1775,15 @@ indexable_type proc(state @State, type Type) bool {
return (pointer.many and pointer.element.kind != .anyopaque) or pointer.element.kind == .array return (pointer.many and pointer.element.kind != .anyopaque) or pointer.element.kind == .array
} }
@hide:file
collection_type proc(state @State, type Type) bool {
if (type.nominal != ast.NodeId(maxval!(u32))) return false
if (type.kind == .array or type.kind == .slice or type.kind == .string) return true
if (type.kind != .pointer) return false
pointer :: state.pointers.items[usize(type.pointer_type)]
return !pointer.many and pointer.element.kind == .array and pointer.element.nominal == ast.NodeId(maxval!(u32))
}
@hide:file @hide:file
addressable_target proc(state @mut State, id ast.NodeId) bool { addressable_target proc(state @mut State, id ast.NodeId) bool {
kind :: ast.expr_kind(state.tree, id) kind :: ast.expr_kind(state.tree, id)
@@ -2676,6 +2697,28 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
if (state.cells[index].type.kind == .invalid) return state.cells[index].type if (state.cells[index].type.kind == .invalid) return state.cells[index].type
state.cells[index].visiting = true state.cells[index].visiting = true
defer state.cells[index].visiting = false defer state.cells[index].visiting = false
if (state.cells[usize(id)].role == .loop_capture and state.cells[usize(id)].for_loop != ast.NodeId(maxval!(u32))) {
loop :: ast.for_stmt(state.tree, state.cells[usize(id)].for_loop)
actual :: try infer_shape(state, loop.iterable, defaults)
if (actual.kind == .invalid or state.types[usize(loop.iterable)].kind == .invalid) {
type :: if (actual.kind == .invalid) actual else state.types[usize(loop.iterable)]
seed(state, id, type)
return type
}
if (!collection_type(state, actual)) {
if (actual.kind != .none) {
try fail(state, loop.iterable, .invalid_for_iterable, ast.node_span(state.tree, loop.iterable))
seed(state, id, state.types[usize(loop.iterable)])
}
return group_type(state, id)
}
descriptor :: try sequence_type(state, loop.iterable, defaults)
type :: if (loop.pointer_capture)
try intern_pointer(state, PointerType{ element = descriptor.element, mutable = descriptor.mutable, many = false })
else descriptor.element
seed(state, id, type)
return type
}
if (state.cells[usize(id)].role == .expression) { if (state.cells[usize(id)].role == .expression) {
kind :: ast.expr_kind(state.tree, id) kind :: ast.expr_kind(state.tree, id)
if (declaration_reference(state.tree, id) and has_narrowing(state, id)) { if (declaration_reference(state.tree, id) and has_narrowing(state, id)) {
@@ -2685,6 +2728,14 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
return narrowed return narrowed
} }
} }
if (declaration_reference(state.tree, id)) {
binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture) {
type :: try infer_shape(state, binding.data.declaration, defaults)
if (type.kind != .none) seed(state, id, type)
return group_type(state, id)
}
}
if (kind == .try_expr or kind == .catch_expr) return try infer_fallible_expression(state, id, defaults) if (kind == .try_expr or kind == .catch_expr) return try infer_fallible_expression(state, id, defaults)
if (kind == .builtin_call) return try infer_builtin(state, id, defaults) if (kind == .builtin_call) return try infer_builtin(state, id, defaults)
if (kind == .slice) return try infer_slice(state, id, defaults) if (kind == .slice) return try infer_slice(state, id, defaults)
@@ -2937,6 +2988,15 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
return group_type(state, id) return group_type(state, id)
} }
@hide:file
prepare_for_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
for 0..(state.cells.len) |i| {
if (state.cells[i].role == .loop_capture and state.cells[i].for_loop != ast.NodeId(maxval!(u32))) {
_ = try infer_shape(state, ast.NodeId(i), defaults)
}
}
}
@hide:file @hide:file
prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) { prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
@@ -2965,6 +3025,7 @@ prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.Allo
@hide:file @hide:file
prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) { prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
try prepare_for_captures(state, defaults)
try prepare_optional_captures(state, defaults) try prepare_optional_captures(state, defaults)
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue
+1 -1
View File
@@ -1855,7 +1855,7 @@ range_values_nonrange_iterables_and_capture_modes_have_explicit_errors test {
` local :: 0..3 ` local :: 0..3
` for 3 |_| { absent_scalar } ` for 3 |_| { absent_scalar }
` for true |_| {} ` for true |_| {}
` for "text" |_| {} ` for 2.5 |_| {}
` for scalar() |_| {} ` for scalar() |_| {}
` for stored |_| {} ` for stored |_| {}
` for 0..3 |@item| { item } ` for 0..3 |@item| { item }
+144 -1
View File
@@ -1617,10 +1617,153 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
return .next return .next
} }
# retain an array place, but freeze its indices at loop entry. snapshots then read
# the current owned root after body assignments without re-evaluating the place.
@hide:file
type_collection_place proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
resolved :: binding(state, id)
if (resolved.kind == .declaration) {
for (execution.locals.items) |local| {
if (local.declaration == resolved.data.declaration) return id
}
return no_node
}
kind :: ast.expr_kind(&state.tree, id)
if (kind != .selection and kind != .index) return no_node
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
place :: try type_collection_place(state, execution, base)
if (place == no_node) return no_node
result := no_node
if (kind == .selection) {
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
place, ast.selection_expr(&state.tree, id).member)
} else {
index :: try type_snapshot(state, execution, ast.index_expr(&state.tree, id).index)
value :: try type_execution_value(state, index, try builtin(state, index, .usize))
if (value == no_node) return no_node
result = try ast.add_index(state.builder, ast.node_span(&state.tree, id), place, value)
}
try refresh(state)
return result
}
@hide:file
type_collection_read proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) ast.NodeId ! Error {
if (binding(state, id).kind == .declaration) return try type_snapshot(state, execution, id)
kind :: ast.expr_kind(&state.tree, id)
base :: if (kind == .selection) ast.selection_expr(&state.tree, id).base else ast.index_expr(&state.tree, id).base
value :: try type_collection_read(state, execution, base)
result := no_node
if (kind == .selection)
result = try ast.add_selection(state.builder, ast.node_span(&state.tree, id),
value, ast.selection_expr(&state.tree, id).member)
else result = try ast.add_index(state.builder, ast.node_span(&state.tree, id),
value, ast.index_expr(&state.tree, id).index)
try refresh(state)
return result
}
@hide:file
execute_type_collection proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
loop :: ast.for_stmt(&state.tree, statement)
span :: ast.node_span(&state.tree, statement)
if (loop.capture) |capture| {
if (binding(state, capture).kind == .invalid) {
state.evaluation_diagnostic = binding(state, capture).data.diagnostic
return .invalid
}
}
if (loop.index) |index| {
if (binding(state, index).kind == .invalid) {
state.evaluation_diagnostic = binding(state, index).data.diagnostic
return .invalid
}
}
snapshot :: try type_snapshot(state, execution, loop.iterable)
if (!try expand_type_matches(state, snapshot)) return .invalid
try expand(state, snapshot)
annotation :: try expression_type(state, snapshot, 0)
if (annotation == no_node) return .invalid
collection :: try type_execution_value(state, snapshot, annotation)
if (collection == no_node) return .invalid
# validate the loop contract even for empty collections, without executing its
# type-valued body. the synthetic body exits before reading any element.
stop :: try ast.add_break_stmt(state.builder, span)
stops [1]ast.NodeId :: [stop]
body :: try ast.add_block(state.builder, span, stops[..])
selector :: try ast.add_for_stmt(state.builder, span, collection, loop.capture, loop.index,
loop.pointer_capture, loop.index_capture, body)
length := no_node
if (ast.expr_kind(&state.tree, annotation) == .array_type) length = ast.array_type(&state.tree, annotation).count?
else {
value :: try evaluate(state, collection)
if (value.kind == .string) {
text :: ast.literal_text(&state.tree, value.node)
cursor := usize(1)
count := u64(0)
while (cursor < text.len - 1) {
_ = ast.string_next(text, &cursor) catch { return .invalid }
count += 1
}
length = try ast.add_integer(state.builder, span, count)
} else length = try ast.add_integer(state.builder, span, 0)
}
returned :: try ast.add_return_stmt(state.builder, span, length)
statements [2]ast.NodeId :: [selector, returned]
wrapper :: try ast.add_block(state.builder, span, statements[..])
try refresh(state)
count :: try evaluate_ir_body(state, statement, wrapper, try builtin(state, statement, .usize))
if (count.kind != .integer) return .invalid
if (loop.pointer_capture and loop.capture != null) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
return .invalid
}
place := no_node
if (ast.expr_kind(&state.tree, annotation) == .array_type)
place = try type_collection_place(state, execution, loop.iterable)
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) return .invalid
saved :: execution.locals.items.len
defer execution.locals.items = execution.locals.items[..saved]
execution.loop_depth += 1
defer execution.loop_depth -= 1
for (0..usize(count.magnitude)) |i| {
if (!try type_step(state, statement)) return .invalid
execution.locals.items = execution.locals.items[..saved]
index :: try ast.add_integer(state.builder, span, u64(i))
try refresh(state)
if (loop.capture) |capture| {
current := collection
if (place != no_node) current = try type_collection_read(state, execution, place)
element :: try ast.add_index(state.builder, span, current, index)
try refresh(state)
element_type :: try expression_type(state, element, 0)
value :: try type_execution_value(state, element, element_type)
if (value == no_node) return .invalid
try arraylist.append(&execution.locals, TypeLocal{
declaration = capture, value = value, annotation = element_type,
})
}
if (loop.index) |capture| {
type :: try builtin(state, capture, .usize)
value :: try type_execution_value(state, index, type)
if (value == no_node) return .invalid
try arraylist.append(&execution.locals, TypeLocal{
declaration = capture, value = value, annotation = type,
})
}
flow :: try execute_type_block(state, execution, loop.body)
if (flow == .returned or flow == .invalid) return flow
if (flow == .break) return .next
}
return .next
}
@hide:file @hide:file
execute_type_for proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error { execute_type_for proc(state @mut State, execution @mut TypeExecution, statement ast.NodeId) TypeFlow ! Error {
loop :: ast.for_stmt(&state.tree, statement) loop :: ast.for_stmt(&state.tree, statement)
if (loop.pointer_capture or loop.index_capture or ast.expr_kind(&state.tree, loop.iterable) != .range) return .invalid if (ast.expr_kind(&state.tree, loop.iterable) != .range)
return try execute_type_collection(state, execution, statement)
if (loop.pointer_capture or loop.index_capture) return .invalid
bounds :: ast.range_expr(&state.tree, loop.iterable) bounds :: ast.range_expr(&state.tree, loop.iterable)
start :: try type_snapshot(state, execution, bounds.start) start :: try type_snapshot(state, execution, bounds.start)
end :: try type_snapshot(state, execution, bounds.end) end :: try type_snapshot(state, execution, bounds.end)
+226 -1
View File
@@ -11,6 +11,200 @@ 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 = {
"collection_iteration_borrows_array_and_reads_each_element": (
"""
main proc() [2]i16 {
values [3]mut i16 := [19, 1, 100]
total i16 := 0
for values |value, index| {
if (index == 0) values[1] = 22
if (index == 2) break
total = total + value + i16(index)
continue
}
return [total, values[1]]
}
""", b"[42, 22]\n", None,
),
"collection_iteration_pointer_captures_slices_and_array_pointers": (
"""
main proc() [3]i16 {
values [2]mut i16 := [19, 21]
for (&values) |@value, index| { value^ = value^ + i16(index) }
view []mut i16 :: values[..]
for view |@value| { value^ = value^ + 1 }
total i16 := 0
for view |value| { total = total + value }
return [total - 1, values[0], values[1]]
}
""", b"[42, 20, 23]\n", None,
),
"collection_iteration_evaluates_entry_once_and_retains_slice_bound": (
"""
data proc(calls @mut i16) [2]i16 {
calls^ = calls^ + 1
return [19, 22]
}
main proc() [3]i16 {
calls i16 := 0
total i16 := 0
for data(&calls) |value, index| { total = total + value + i16(index) }
values [2]mut i16 := [20, 22]
view []mut i16 := values[..]
sum i16 := 0
for view |value| {
view = values[0..0]
sum = sum + value
}
return [total, calls, sum]
}
""", b"[42, 1, 42]\n", None,
),
"collection_iteration_aggregate_copies_nested_and_zero_cell_shapes": (
"""
Empty :: struct {}
Item :: struct { values [2]mut i16 }
main proc() [3]i16 {
items [2]mut Item := [{ values = [19, 21] }, { values = [0, 0] }]
total i16 := 0
for items |item, index| {
copied := item
copied.values[0] = copied.values[0] + 1
for copied.values |value| { total = total + value }
if (index == 0) items[1].values[1] = 0
}
empty [0]i16 :: []
for empty |_| { return [-1, -1, -1] }
cells [2]Empty :: [{}, {}]
count i16 := 0
for cells |cell, index| {
saved Empty :: cell
count = count + i16(index) + 1
}
return [total, items[0].values[0], count]
}
""", b"[42, 19, 3]\n", None,
),
"collection_iteration_strings_and_cleanup_on_transfers": (
"""
main proc() [2]i16 {
values [3]i16 :: [20, 22, 99]
total i16 := 0
cleanups i16 := 0
for values |value, index| {
defer cleanups = cleanups + 1
if (index == 2) break
total = total + value
continue
}
for "a\\0b" |byte, index| {
if (index == 1 and byte != 0) return [-1, -1]
if (index == 2 and byte != u8(98)) return [-2, -2]
}
return [total, cleanups]
}
""", b"[42, 3]\n", None,
),
"collection_iteration_ordinary_constant_procedure": (
"""
compute proc() i16 {
values [2]mut i16 := [19, 21]
view []mut i16 :: values[..]
for view |@value| { value^ = value^ + 1 }
total i16 := 0
for values |value| { total = total + value }
return total
}
main proc() i16 { return $(compute()) }
""", b"42\n", None,
),
"type_execution_collection_iteration_observes_updates_and_freezes_places": (
"""
Storage proc() type {
rows [2]mut [2]mut usize := [[1, 1], [9, 9]]
selected usize := 0
length usize := 0
for rows[selected] |value, index| {
selected = 1
(rows[0])[1] = 2
length = length + value
if (index == 0) continue
break
}
Result := i8
for "a\\0b" |byte, index| {
if (byte == 0 and index == 1) Result = i16
}
return struct { values [length]Result }
}
main proc() i16 {
value Storage() :: { values = [19, 21, 2] }
same Storage() :: value
return same.values[0] + same.values[1] + same.values[2]
}
""", b"42\n", None,
),
"type_execution_collection_nested_aggregate_captures_and_empty_body": (
"""
Storage proc() type {
items [2]struct { values [2]usize, text string } :: [
{ values = [1, 0], text = "a" }, { values = [0, 1], text = "b" },
]
length usize := 0
for items |item| {
for item.values |value| { length = length + value }
}
empty [0]i16 :: []
for empty |_| { return i8(128) }
for items |@_, index| { if (index == 1) break }
return [length]i16
}
main proc() i16 {
values Storage() :: [20, 22]
return values[0] + values[1]
}
""", b"42\n", None,
),
"collection_iteration_rejects_scalar_iterable": (
"main proc() i16 { for 1.5 |_| { return 0 }\nreturn 42 }",
b"", b"for-loop iteration requires",
),
"collection_iteration_rejects_capture_rebinding": (
"main proc() i16 { values [1]i16 :: [1]\nfor values |value| { value = 42 }\nreturn 0 }",
b"", b"assignment requires a mutable local binding",
),
"collection_iteration_rejects_index_rebinding": (
"main proc() usize { values [1]i16 :: [1]\nfor values |_, index| { index = 42 }\nreturn 0 }",
b"", b"assignment requires a mutable local binding",
),
"collection_iteration_rejects_readonly_array_pointer_mutation": (
"main proc() i16 { values [1]mut i16 :: [1]\nfor values |@value| { value^ = 42 }\nreturn 0 }",
b"", b"mutable pointee",
),
"collection_iteration_rejects_readonly_slice_pointer_mutation": (
"main proc() i16 { values [1]mut i16 := [1]\nview []i16 :: values[..]\nfor view |@value| { value^ = 42 }\nreturn 0 }",
b"", b"mutable pointee",
),
"type_execution_collection_iteration_preserves_element_type": (
"""
Bad proc() type {
values [1]i8 :: [127]
for values |value| { next :: value + 1 }
return i16
}
main proc() Bad() { return 0 }
""", b"", b"integer arithmetic overflow",
),
"type_execution_collection_iteration_preserves_step_bound": (
"""
Bad proc() type {
cells [1]i16 :: [0]
for cells |_| { while (true) {} }
return i16
}
main proc() Bad() { return 0 }
""", b"", b"compile-time execution",
),
"type_execution_aggregate_updates_preserve_copies_and_generate_defaults": ( "type_execution_aggregate_updates_preserve_copies_and_generate_defaults": (
""" """
Config :: struct { values [2]mut i16, count usize, text string } Config :: struct { values [2]mut i16, count usize, text string }
@@ -4520,7 +4714,38 @@ def check_specialization_packages(temporary, executable):
interpreted = run(str(HONEY), "run", str(app)) interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:3:".encode()), outcome(interpreted) assert interpreted.stderr.startswith(f"{sizing.resolve()}:3:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted) assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 10 sizing.write_text(textwrap.dedent("""
Collected proc() type {
values [2]usize :: [1, 1]
length usize := 0
for values |value, index| { length = length + value + index }
return [length]i16
}
""").strip() + "\n")
source.write_text(textwrap.dedent("""
lib :: import "../library"
main proc() i16 {
values lib.Collected() :: [19, 21, 2]
same lib.Collected() :: values
total i16 := 0
for same |value| { total = total + value }
return total
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
sizing.write_text(textwrap.dedent("""
Collected proc() type {
values [1]i8 :: [127]
for values |value| { next :: value + 1 }
return i16
}
""").strip() + "\n")
source.write_text('lib :: import "../library"\nmain proc() lib.Collected() { return 0 }\n')
check(app, executable, (b"", b"integer arithmetic overflow"))
interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:3:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 12
def check_dependency_provenance(temporary, executable): def check_dependency_provenance(temporary, executable):