feat: execute collection and index iteration
This commit is contained in:
@@ -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`.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user