diff --git a/README.md b/README.md index c97f3ca..4423387 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,9 @@ field/index assignments also update initialized, materializable aggregate locals 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. -`inline for` expands constant integer ranges, fixed arrays (including arrays of `type`) and strings into separately checked runtime bodies. captures are immutable exact-type constants; collection indices are `usize`. for example, `inline for [i16, bool] |T| { if (T == i16) { value T :: 42 } else { value T :: true } }` checks only each iteration's selected branch. bounds/iterables are computed before expansion, empty loops skip body elaboration, and nested expansions share the 4096-step type-control bound. `break` and `continue` targeting the inline loop must be selected entirely at compile time; selected exits prune the remaining body and `break` skips subsequent iterations. nested ordinary loops retain runtime transfers, while runtime returns, errors and scoped cleanup retain ordinary semantics. runtime-dependent inputs, pointer captures and storage-bearing values that cannot escape constant evaluation are diagnosed. heterogeneous tuple iteration, metadata collections and inline-match syntax remain open. +`inline for` expands constant integer ranges, fixed arrays (including arrays of `type`), heterogeneous tuples and strings into separately checked runtime bodies. captures are immutable exact-type constants; collection indices are `usize`. for example, `inline for [i16, bool] |T| { if (T == i16) { value T :: 42 } else { value T :: true } }` checks only each iteration's selected branch. bounds/iterables are computed before expansion, empty loops skip body elaboration, and nested expansions share the 4096-step type-control bound. `break` and `continue` targeting the inline loop must be selected entirely at compile time; selected exits prune the remaining body and `break` skips subsequent iterations. nested ordinary loops retain runtime transfers, while runtime returns, errors and scoped cleanup retain ordinary semantics. runtime-dependent inputs, pointer captures and storage-bearing values that cannot escape constant evaluation are diagnosed. metadata collections and inline-match syntax remain open. + +inline tuples may mix types and values, including nested aggregates, strings, optionals and tagged unions. parenthesize tuple literals in loop headers, such as `inline for ({i16, usize(2), "ok"}) |item, index| { ... }`; use a compile-time index condition to select operations for each field's exact type. constant helper results, tuple projections and erased generic tuple parameters use the same owned snapshots. the entire tuple must be materializable before iteration, even with discarded captures or an early `break`. type-producing bodies borrow local tuple places, observing later field updates without changing earlier copies or captures. named records are not tuple collections, and ordinary `for` still requires homogeneous sequence storage. initialized fixed arrays of `type` live entirely inside compile-time execution, including nested arrays, explicit/inferred annotations, `.len`, checked `usize` indexing and collection captures. for example, `types [2]mut type := [i8, bool]`, then `types[1] = i16` and `return [types.len]types[1]`, generates `[2]i16` without runtime type metadata. whole-array and nested indexed assignments preserve shape and mutability at every level; copies and loop captures retain earlier values while borrowed array places observe later updates. @@ -120,13 +122,13 @@ optional `if`/`while` captures also execute in type-producing bodies: `if count type-producing bodies also execute scalar/enum `match` statements and type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound. -matches also select nested types, array lengths, generic arguments, callback signatures and field defaults in type-producing bodies. for example, `return struct { values [2](match wide { true: i16, false: i8 }) }` generates concrete storage without elaborating the unselected arm. snapshots preserve loop-local type/value bindings, selected literals retain their spelling and source provenance, and nominal results are cached before their recursive fields are expanded. deferred type selection shares the producer's execution bound; failed elaboration retains its original diagnostic without leaving unchecked generated types behind. general type-valued storage and inline expansion remain open. +matches also select nested types, array lengths, generic arguments, callback signatures and field defaults in type-producing bodies. for example, `return struct { values [2](match wide { true: i16, false: i8 }) }` generates concrete storage without elaborating the unselected arm. snapshots preserve loop-local type/value bindings, selected literals retain their spelling and source provenance, and nominal results are cached before their recursive fields are expanded. deferred type selection shares the producer's execution bound; failed elaboration retains its original diagnostic without leaving unchecked generated types behind. general type-valued execution and inline-match syntax remain open. tagged-union matches in type-producing bodies support immutable by-value payload captures in statements, expressions and nested type/storage positions. for example, `match choice { .count |length|: return [length]i16, .empty: return bool }` uses a captured `usize` to generate storage. grouped variants require identical nonvoid payload types; invalid captures in unselected arms are still diagnosed. selected payloads become owned typed constants, preserving narrow integers, nested aggregates and strings across generated lengths/defaults, loop snapshots and repeatable compilation. pointer/slice/callback-bearing active payloads retain the constant-materialization escape boundary below. ordinary procedures may compute integer, boolean, float and string compile-time arguments and return nested records/tuples, fixed arrays, tagged unions and explicit fallible results from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. aggregate and fallible arguments receive their declared context, including dependent `[N]T` parameters; cached specializations compare normalized values, not literal spelling or evaluation identity. returned constants become owned source literals before temporary interpreter storage is released, preserving by-value copies, exact integer types, string bytes, enum/optional fields and IEEE float bits. tagged unions retain their actual discriminant and only the active payload, including nested aggregates and void/zero-cell variants. fallible constants retain success/error identity, composed or payload-bearing errors and void success; `catch` can consume them in generated lengths, defaults and runtime expressions. -`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. data pointers, slices, callbacks and untagged unions cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. untagged unions lack an active-member identity, so selecting a source variant could discard observable bytes. executed foreign calls remain interpreter traps. general type-valued execution, inline match/loop expansion, reflection, generated type/default arrays and existing generic-library acceptance remain open. +`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. data pointers, slices, callbacks and untagged unions cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. untagged unions lack an active-member identity, so selecting a source variant could discard observable bytes. executed foreign calls remain interpreter traps. general type-valued execution, inline-match syntax, metadata reflection and existing generic-library acceptance remain open. `if $(condition) { ... } else { ... }` evaluates a boolean guard before expanding or checking either body, including calls to ordinary constant-input procedures. concrete generic specializations also select branches whose guards use constants, immutable constant bindings, type comparisons or substituted compile-time parameters. only the selected body is specialized and checked; both bodies are still parsed and name-resolved. selected bodies retain their scope, cleanup and return behavior, and erased guards retain ordinary type checking and source diagnostics. ordinary calls in an implicit guard stay runtime; use `$()` to request their evaluation. optional captures remain runtime, and ordinary nongeneric `if (true)` still checks both bodies. @@ -138,7 +140,7 @@ type `==` and `!=` comparisons in compile-time expressions and specializations p `struct_type!(layout, names, types, defaults)` generates an ordinary record from constant fixed arrays or tuples of equal length. for example, `struct_type!(.auto, ["value", "bias"], [i16, i8], [null, 2])` requires a `value` initializer and supplies an `i8` default for `bias`. literal collections retain each field's context, including heterogeneous types and defaults; computed collections can come from helpers and loop-filled producer locals. names must be unique ASCII identifiers. each default is checked as `?Field`: absent means required, present supplies an owned field value. generated fields retain ordinary exact typing, aggregate copy isolation, field selection and checked updates through interpreter/native execution. producer specializations cache nominal identity; imported producers and repeated compilation retain source provenance and fresh owned values. incomplete arrays and runtime dependencies are rejected, and construction shares the type-control execution bound. -layout accepts `.auto` or `.c`, including constant enum members such as `Layout.c`. C-layout records must be nonempty; fields may contain concrete scalars, data pointers, optional data pointers, explicitly backed enums, nonempty fixed arrays and nested C-layout records. distinct types and non-C representations are rejected. this does not add aggregate C calling conventions, storage-bearing constant materialization, metadata inspection or inline expansion. +layout accepts `.auto` or `.c`, including constant enum members such as `Layout.c`. C-layout records must be nonempty; fields may contain concrete scalars, data pointers, optional data pointers, explicitly backed enums, nonempty fixed arrays and nested C-layout records. distinct types and non-C representations are rejected. this does not add aggregate C calling conventions, storage-bearing constant materialization or metadata inspection. callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage. diff --git a/TODO.md b/TODO.md index 8b94040..69eab53 100644 --- a/TODO.md +++ b/TODO.md @@ -43,8 +43,8 @@ field/index updates and owned copies/captures inside compile-time producers. including runtime places and type-control updates without runtime reflection. `struct_type!` generates ordinary/C-layout records from constant name/type/default arrays or tuples, including loop-filled producer storage and owned defaults. -`inline for` expands constant ranges, fixed value/type arrays and strings into -runtime bodies with immutable exact captures and compile-time-selected exits. +`inline for` expands constant ranges, fixed value/type arrays, heterogeneous +value/type tuples and strings with immutable exact captures and compile-time-selected exits. general type-valued execution, metadata reflection and generic library acceptance remain open. the driver retains frontend storage and source provenance through `@source/compilation`, with compilation-wide AST node identities. @@ -104,8 +104,9 @@ test discovery and FFI remain outside these completed slices. - [x] type-record storage checkpoint: initialized records/tuples containing type values live inside type/type-array producers, including local schemas/defaults, typed/contextual/inferred construction, nested records/arrays, checked field/index reads and updates, whole-value replacement and immutable by-value collection captures. persistent snapshots isolate copies/captures while borrowed array places observe later updates; type leaves retain nominal identity and ordinary value leaves reuse checked IR. generated storage/defaults, invalid field/type/default diagnostics, readonly places, narrow overflow, imported producers/provenance, cached nominal results and repeatable compilation work through interpreter/native execution. field validation/construction shares the type-control bound. type-record procedure inputs/results are covered below; runtime type-valued records, partial record initialization, metadata reflection and inline expansion remain open. - [x] type-record procedure checkpoint: records/tuples containing type values cross compile-time procedure inputs/results, including nested arrays/records, aliases, defaults and dependent annotations. compile-time producers accept ordinary constant inputs; runtime generics accept erased `$` metadata parameters. owned snapshots isolate argument/result copies, exact type/value leaves retain checked diagnostics, and normalized cache keys preserve nominal generated types. generated storage/defaults, imported producers/provenance, shared execution bounds and repeatable compilation work through interpreter/native execution. runtime metadata storage, partial record initialization, reflection and inline expansion remain open. - [x] generated-record checkpoint: `struct_type!(layout, names, types, defaults)` builds ordinary records from constant fixed arrays or tuples with unique ASCII field names, concrete types and contextual optional defaults. literal heterogeneous collections, helper results and loop-filled producer arrays preserve exact types, owned defaults/copies and cached nominal identity through interpreter/native execution. `.auto` permits empty records; `.c` requires nonempty C-compatible fields and retains native field order/layout. malformed inputs, incomplete arrays, runtime dependencies, field/default validation, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and isolation. metadata inspection, inline expansion, aggregate C calling conventions and storage-bearing defaults remain open. - - [x] inline-loop checkpoint: `inline for` expands constant integer ranges, fixed arrays (including type-valued arrays) and strings into separately checked runtime bodies through interpreter/native execution. exact immutable captures, `usize` collection indices, narrow inclusive maxima, selected conditional/tagged-match bodies, static break/continue tail pruning, nested inline/ordinary loops, runtime returns/errors and scoped cleanup retain ordinary semantics. empty loops skip body elaboration; runtime-dependent inputs/transfers, pointer captures, storage-bearing escapes and shared expansion bounds retain diagnostics. imported producers/provenance and repeatable compilation are verified. heterogeneous tuple/metadata iteration and inline-match syntax remain open. - - [ ] remaining gate: general type-valued compile-time execution, heterogeneous inline collections, inline match expansion and metadata reflection (`typeinfo!` and metadata collections), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates. + - [x] inline-loop checkpoint: `inline for` expands constant integer ranges, fixed arrays (including type-valued arrays) and strings into separately checked runtime bodies through interpreter/native execution. exact immutable captures, `usize` collection indices, narrow inclusive maxima, selected conditional/tagged-match bodies, static break/continue tail pruning, nested inline/ordinary loops, runtime returns/errors and scoped cleanup retain ordinary semantics. empty loops skip body elaboration; runtime-dependent inputs/transfers, pointer captures, storage-bearing escapes and shared expansion bounds retain diagnostics. imported producers/provenance and repeatable compilation are verified. heterogeneous tuples are covered below; metadata iteration and inline-match syntax remain open. + - [x] heterogeneous inline-tuple checkpoint: `inline for` iterates constant tuples with exact per-field value/type captures and `usize` indices through interpreter/native execution. literal/helper/aliased tuples, erased generic inputs, nested tuple projections, generated storage, empty shapes, static exits, cleanup and aggregate copy isolation retain existing semantics. type-producing bodies borrow tuple places so later fields observe updates while saved copies remain isolated. runtime dependencies and storage-bearing escapes are rejected before iteration, including discarded captures and early exits; named records and ordinary tuple loops remain invalid. imported producers, nominal type leaves and repeatable compilation are verified. metadata collections and inline-match syntax remain open. + - [ ] remaining gate: general type-valued compile-time execution, inline match expansion and metadata reflection (`typeinfo!` and metadata collections), broader constant materialization (including untagged unions and storage-bearing values), plus existing-library acceptance. required expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates. - [ ] remaining source constructs: expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library. - [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support. diff --git a/source/compilation/compilation.test.hon b/source/compilation/compilation.test.hon index dcf1a71..3b5786d 100644 --- a/source/compilation/compilation.test.hon +++ b/source/compilation/compilation.test.hon @@ -365,6 +365,14 @@ recompiles_imported_inline_for_without_retaining_expansions test { ` if (!value) return 0 ` } ` } + ` inline for dep.metadata() |item, index| { + ` if (index == 0) { + ` if (item != i16) return 0 + ` } else if (index == 1) { + ` exact i8 :: item + ` if (exact != 42) return 0 + ` } else if (item.len != 2) return 0 + ` } ` inline for (i8(0)..i8(2)) |outer| { ` inline for [i8(0), i8(1)] |inner| { ` for (0..usize(2)) |index| { @@ -381,6 +389,7 @@ recompiles_imported_inline_for_without_retaining_expansions test { dependency_bytes := try owned_text( `values proc() [2]i16 { return [17, 20] } `types proc() [2]type { return [i16, bool] } + `metadata proc() struct { type, i8, string } { return {i16, 42, "ok"} } , ) defer arraylist.deinit(&dependency_bytes) diff --git a/source/diagnostics/diagnostics.hon b/source/diagnostics/diagnostics.hon index 38e4e15..b44041a 100644 --- a/source/diagnostics/diagnostics.hon +++ b/source/diagnostics/diagnostics.hon @@ -180,7 +180,7 @@ messages :: $enummap.init(Code, []u8, { break_outside_loop = "break requires an enclosing loop", continue_outside_loop = "continue requires an enclosing loop", invalid_range_bound = "range bounds must have compatible integer types", - invalid_for_iterable = "for-loop iteration requires an array, slice, string, single pointer to an array, or direct integer range", + invalid_for_iterable = "for-loop iteration requires an array, slice, string, single pointer to an array, direct integer range, or inline tuple", range_value_not_supported = "first-class range values are not implemented", range_pointer_capture = "range loops do not support pointer captures", range_index_capture = "range loops do not support index captures", diff --git a/source/sema/specialize/specialize.hon b/source/sema/specialize/specialize.hon index ded1e78..cd85205 100644 --- a/source/sema/specialize/specialize.hon +++ b/source/sema/specialize/specialize.hon @@ -2787,7 +2787,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat return .next } -# retain an array place, but freeze its indices at loop entry. snapshots then read +# retain a collection 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 { @@ -2854,10 +2854,21 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta try expand(state, snapshot) annotation :: try expression_type(state, snapshot, 0) if (annotation == no_node) return .invalid + tuple :: loop.is_inline and ast.expr_kind(&state.tree, annotation) == .record_type + if tuple { + for (0..ast.record_field_count(&state.tree, annotation)) |i| { + if (ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)).name != null) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_for_iterable, span) + return .invalid + } + } + } collection :: try type_execution_value(state, snapshot, annotation) if (collection == no_node) return .invalid count := Value{} - if (try execution_array_storage(state, annotation)) { + if tuple { + count = Value{ kind = .integer, magnitude = u64(ast.record_field_count(&state.tree, annotation)) } + } else if try execution_array_storage(state, annotation) { count = Value{ kind = .integer, magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) } } else { @@ -2895,7 +2906,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta return .invalid } place := no_node - if (ast.expr_kind(&state.tree, annotation) == .array_type) + if (tuple or 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 @@ -2907,12 +2918,15 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta execution.locals.items = execution.locals.items[..saved] index :: try ast.add_integer(state.builder, span, u64(i)) try refresh(state) - if (loop.capture) |capture| { + 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) + element :: if (tuple) try ast.add_selection(state.builder, span, current, index) + else try ast.add_index(state.builder, span, current, index) try refresh(state) - element_type :: try expression_type(state, element, 0) + element_type :: if (tuple) + ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)).type? + else 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{ @@ -3519,14 +3533,9 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId ! ) return try builtin(state, id, .usize) if (base_kind != .record_type and base_kind != .union_type) return no_node - for (0..ast.record_field_count(&state.tree, base)) |i| { - field :: ast.field(&state.tree, ast.record_field(&state.tree, base, i)) - if (field.name) |name| { - if (ast.name_id(&state.tree, name) == ast.name_id(&state.tree, view.member)) - return try canonical_type(state, field.type?) - } - } - return no_node + slot :: type_record_slot(state, base, view.member) + if (slot == no_index) return no_node + return try canonical_type(state, ast.field(&state.tree, ast.record_field(&state.tree, base, slot)).type?) } .index: { view :: ast.index_expr(&state.tree, id) diff --git a/tests/native.py b/tests/native.py index 207fe6a..5d0a832 100644 --- a/tests/native.py +++ b/tests/native.py @@ -11,6 +11,179 @@ HONEY = ROOT / "build/honey" # Explicit expectations guard against frontend bugs shared by both execution paths. CASES = { + "inline_for_heterogeneous_tuple_exact_captures_and_nested_values": ( + """ + Choice :: union(enum) { number i8, empty void } + values proc() struct { i8, bool, string, [2]i16, Choice, ?i16 } { + return {19, true, "ok", [10, 11], .number{2}, 0} + } + narrow proc(value i8) i16 { return i16(value) } + main proc() i16 { + total i16 := 0 + inline for values() |value, index| { + at usize :: index + if (at == 0) { + total = total + narrow(value) + } else if (at == 1) { + if (!value) return -1 + } else if (at == 2) { + text string :: value + if (text.len != 2) return -2 + } else if (at == 3) { + total = total + value[0] + value[1] + } else if (at == 4) { + match value { .number |payload|: { total = total + narrow(payload) }, .empty: {} } + } else total = total + value? + } + return total + } + """, b"42\n", None, + ), + "inline_for_mixed_type_value_tuple_generates_runtime_storage": ( + """ + Child :: struct { value i16 = 40 } + Metadata :: alias struct { type, usize, type, string } + metadata proc() Metadata { return {Child, 2, i16, "ok"} } + answer proc($input Metadata, bias i16) i16 { + total i16 := 0 + inline for input |item, index| { + if (index == 0) { + value item :: {} + total = total + value.value + } else if (index == 1) { + values [item]i16 :: [0, bias] + total = total + values[1] + } else if (index == 2) { + value item :: total + total = value + } else if (item.len != 2) return -1 + } + return total + } + main proc() i16 { return answer(metadata(), 2) } + """, b"42\n", None, + ), + "inline_for_tuple_literals_empty_shapes_and_static_transfers": ( + """ + main proc() i16 { + total i16 := 0 + cleanups i16 := 0 + inline for ({}) |_| { return false } + inline for ({i8(19), true, "stop", i16(99)}) |item, index| { + defer cleanups = cleanups + 1 + if (index == 0) { + total = total + i16(item) + } else if (index == 1) { + continue + } else if (index == 2) { + break + } else total = false + total = total + 20 + } + return total + cleanups + } + """, b"42\n", None, + ), + "inline_for_type_producer_tuple_places_observe_updates_and_isolate_copies": ( + """ + Storage proc() type { + config := {i8, usize(1), "before"} + saved :: config + length usize := 0 + inline for config |item, index| { + if (index == 0) { + if (item != i8) return bool + config.0 = i16 + config.1 = 2 + config.2 = "after" + } else if (index == 1) { + length = item + } else if (item.len != 5) return bool + } + if (saved.0 != i8 or saved.1 != 1 or (saved.2).len != 6) return bool + Element :: config.0 + return struct { values [length]Element } + } + main proc() i16 { + value Storage() :: { values = [19, 23] } + return value.values[0] + value.values[1] + } + """, b"42\n", None, + ), + "inline_for_nested_tuple_projections_and_aggregate_copy_isolation": ( + """ + Item :: struct { values [2]mut i16 } + tuples proc() struct { struct { Item, bool }, string } { + return {{Item { values = [19, 23] }, true}, "ok"} + } + main proc() i16 { + input :: tuples() + total i16 := 0 + inline for input.0 |item, index| { + if (index == 0) { + copy := item + copy.values[0] = 0 + total = item.values[0] + item.values[1] + } else if (!item) return -1 + } + return total + } + """, b"42\n", None, + ), + "inline_for_tuple_rejects_runtime_dependency_even_with_discard_capture": ( + """ + walk proc(value i16) i16 { + inline for ({i8(1), value}) |_| { break } + return 42 + } + main proc() i16 { return walk(42) } + """, b"", b"compile-time", + ), + "inline_for_tuple_rejects_named_records": ( + """ + main proc() i16 { + inline for ({number = i16(42)}) |value| { return value } + return 0 + } + """, b"", b"for-loop iteration requires", + ), + "inline_for_tuple_capture_is_immutable": ( + """ + main proc() i16 { + inline for ({i8(1), true}) |value, index| { + if (index == 0) value = 42 + } + return 0 + } + """, b"", b"assignment requires a mutable local binding", + ), + "inline_for_tuple_preserves_narrow_overflow": ( + """ + main proc() i16 { + inline for ({i8(127), true}) |value, index| { + if (index == 0) next :: value + 1 + } + return 42 + } + """, b"", b"integer arithmetic overflow", + ), + "inline_for_tuple_rejects_storage_escape_before_break": ( + """ + values proc() struct { i16, []u8 } { return {42, "borrowed"} } + main proc() i16 { + inline for values() |_| { break } + return 42 + } + """, b"", b"escape", + ), + "ordinary_for_still_rejects_heterogeneous_tuple": ( + """ + main proc() i16 { + for ({i16(42), true}) |_| {} + return 42 + } + """, b"", b"for-loop iteration requires", + ), "inline_for_range_runtime_calls_and_constant_branches": ( """ add proc(value i16, bias i16) i16 { return value + bias }