From 443de49d551b4d581a10a9e6d52f04520f13eb01 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Wed, 28 Oct 2026 00:51:15 +0100 Subject: [PATCH] feat: construct uninitialized compile-time value arrays --- README.md | 8 +- TODO.md | 7 +- source/compilation/compilation.test.hon | 16 ++ source/sema/specialize/specialize.hon | 72 ++++++--- tests/native.py | 201 +++++++++++++++++++++++- 5 files changed, 274 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 17f82e0..7591e4f 100644 --- a/README.md +++ b/README.md @@ -98,15 +98,17 @@ main proc() i16 { the specialization checkpoint supports type arguments and constant integer, boolean, string, callback and null value arguments; declared value-parameter types survive specialization, including narrow checked arithmetic and optional captures/unwrap. integer arguments are range-checked even when unused, and erased argument expressions still receive ordinary checking. immutable constant expressions may use unary signs, `!`, `+`, `-`, `*`, comparisons and logical operators. type-producing procedures accept ordinary parameters as well as `$` parameters: every argument must be supplied explicitly and evaluate to a constant, and every input participates in specialization caching. for example, `Fixed proc(T type, count usize) type { return struct { values [count]T } }` accepts `Fixed(i16, 2)` and reuses its nominal type for equivalent inputs. use `$` when a parameter must be visible in subsequent signature annotations. runaway specialization is diagnosed at 128 nested evaluation/specialization calls or 4096 instances. -type-producing bodies support mutable type and value locals, direct binding assignment, scoped blocks, conditional/early returns, integer range and while loops, break/continue and nested/recursive type-producing calls. for example, start with `Result := T`, repeat `Result = [2]Result` in a loop, then `return Result` to generate nested array storage. snapshots retain each local's current value without changing the source AST; scalar operations reuse checking, typed IR and interpreter execution, preserving exact types and overflow diagnostics. type-control execution is limited to 4096 statement, loop-guard and uninitialized type-array construction steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. cleanup and type-valued record storage in type-producing bodies remain outside this checkpoint. +type-producing bodies support mutable type and value locals, direct binding assignment, scoped blocks, conditional/early returns, integer range and while loops, break/continue and nested/recursive type-producing calls. for example, start with `Result := T`, repeat `Result = [2]Result` in a loop, then `return Result` to generate nested array storage. snapshots retain each local's current value without changing the source AST; scalar operations reuse checking, typed IR and interpreter execution, preserving exact types and overflow diagnostics. type-control execution is limited to 4096 statement, loop-guard and uninitialized array construction steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. cleanup and type-valued record storage in type-producing bodies remain outside this checkpoint. -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 record 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. array snapshots preserve earlier copies; scalar and record-field operations reuse ordinary checked IR. later loop iterations, generated lengths and defaults observe updates. 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 record 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; `inline for` remains open. 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. -fixed type arrays may also start with `undefined` inside type/type-array producers: `types [2]mut type := undefined`, then fill `types[index] = i16` in a loop. nested and whole-array updates preserve shape, mutability and independent initialization state in local copies; `.len` is available before elements initialize. reading an uninitialized type leaf, returning an incomplete array or passing one to a compile-time procedure diagnoses a read before initialization. empty shapes are valid, but every dimension must be explicit and nonnegative. construction charges each expanded cell against the shared execution bound, including nested zero-leaf shapes. `undefined` does not enable runtime storage or ordinary compile-time value-array construction. +fixed type arrays may also start with `undefined` inside type/type-array producers: `types [2]mut type := undefined`, then fill `types[index] = i16` in a loop. nested and whole-array updates preserve shape, mutability and independent initialization state in local copies; `.len` is available before elements initialize. reading an uninitialized type leaf, returning an incomplete array or passing one to a compile-time procedure diagnoses a read before initialization. empty shapes are valid, but every dimension must be explicit and nonnegative. construction charges each expanded cell against the shared execution bound, including nested zero-leaf shapes. + +ordinary fixed value arrays use the same construction inside type/type-array producers: `counts [2]mut usize := undefined`, then fill indexed elements or replace whole rows/arrays. initialized leaves retain exact scalar, string, optional, record and tagged-union values; local copies retain independent partial initialization state. generated lengths/defaults and compile-time arguments can use initialized elements, while incomplete arrays cannot enter checked IR or procedure arguments. array `.len` and empty shapes do not read leaves. this does not enable runtime `undefined` storage, partial record-field initialization, uninitialized arrays in ordinary value-returning procedures, or storage-bearing constant materialization such as nonnull pointers/slices. procedures returning fixed arrays of `type`, such as `types proc() [2]type { return [i16, bool] }`, also execute entirely at compile time. every argument is explicitly supplied, constant, cached and erased; nested/aliased results, empty shapes and dependent `$N` lengths retain exact shape and mutability. type-producing procedures accept ordinary type-array inputs, and runtime generic procedures accept erased `$types [N]type` parameters. direct projections such as `types()[0]` generate signatures, constructors, storage and defaults; explicit `$()` calls inside producers retain owned array values. cache identity uses canonical type leaves without merging distinct nominal types, and copies cannot mutate cached inputs/results. imported diagnostics, shared type-control execution limits and repeatable compilation work through interpreter/native execution. ordinary parameters remain excluded from dependent signatures; use `$N` for lengths. runtime type-array storage, type-valued record storage and reflection remain open. diff --git a/TODO.md b/TODO.md index 915ab79..3dbe7de 100644 --- a/TODO.md +++ b/TODO.md @@ -34,7 +34,7 @@ captures and `usize` indices through both paths; type-producing bodies iterate materializable arrays/strings by value without retaining interpreter addresses. type-valued fixed arrays support nested copies, indexed/whole updates, checked projections and by-value collection captures in type-producing bodies; -`undefined` arrays track initialization per leaf while loops fill generated types. +`undefined` type/value arrays track initialization per leaf while loops fill generated types/defaults. type-array parameters and results cross compile-time procedure boundaries with owned snapshots, cached nominal identity and erased arguments. `field!` selects record/tuple/enum and sequence members using compile-time names, @@ -88,12 +88,13 @@ test discovery and FFI remain outside these completed slices. - [x] tagged type-match checkpoint: type-producing bodies execute tagged-union matches with immutable by-value payload captures in statement, expression and nested storage positions. ordinary match checking validates every arm's captures, including grouped variants; selected payloads retain exact scalar/aggregate types and owned strings. generated storage/defaults, loop snapshots/transfers, cache identity, selected-body diagnostics, imported producers, dependency provenance and repeatable compilation work through interpreter/native execution. 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] 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 record storage remain open; type-valued arrays are covered below. + - [x] aggregate type-update checkpoint: type-producing bodies update initialized, materializable aggregate locals through nested record fields and mutable fixed-array indices. ordinary checking validates assignments, 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, partial record construction and type-valued record storage remain open; type-valued and uninitialized value arrays are covered below. - [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 retain their existing boundary; type-valued elements are covered below. - [x] type-array storage checkpoint: type-producing bodies own initialized fixed arrays of type values, including explicit/inferred nested arrays, `.len`, checked `usize` indexing, whole/indexed updates and immutable by-value collection captures. persistent snapshots preserve copies and exact shape/mutability while borrowed array places observe later updates. selected types generate nested storage, constructors, signatures and defaults through interpreter/native execution, retaining selected-body and index/type/mutability diagnostics, imported provenance, nominal cache identity, shared bounds and repeatable compilation. uninitialized type arrays are covered below; type-valued record storage and reflection remain open. - [x] type-array procedure checkpoint: fixed arrays of type values cross compile-time procedure inputs/results, including nested/aliased results, dependent `$N` lengths, empty shapes and erased `$types` parameters in runtime generic procedures. all inputs to type/type-array producers are explicit constants; owned snapshots preserve by-value isolation, exact shape/mutability and canonical/nominal cache identity. projections generate signatures, constructors, storage and defaults through interpreter/native execution. malformed inputs/results, runtime dependencies, imported provenance, shared execution bounds and repeatable compilation retain diagnostics and isolation. runtime type-array storage, type-valued record storage and reflection remain open. - [x] field-selection reflection checkpoint: `field!(base, name)` evaluates/erases a constant string name and reuses ordinary selection through interpreter/native execution. records/tuples, enum/error-set members, tagged payloads, callbacks, pointer-qualified places and sequence `.len`/`.ptr` retain exact types, single base evaluation, address-taking, mutability and active-field checks. type-producing bodies snapshot changing selector names for nested aggregate updates without losing by-value isolation. runtime/nonstring names, malformed calls, missing members and noncanonical/overflowing tuple ordinals retain diagnostics; full metadata reflection and inline expansion remain open. - - [x] uninitialized type-array checkpoint: fixed type arrays in type/type-array producers start with `undefined` and fill through loop-driven indexed, nested and whole-array updates. persistent local copies retain independent initialization state; `.len` works before leaves initialize. premature leaf reads and incomplete procedure arguments/results diagnose read-before-initialization without caching invalid values. explicit nonnegative dimensions, empty shapes, ordinary mutability/shape checks, nominal identity and repeatable compilation work through interpreter/native execution. construction shares the type-control bound per expanded cell, including nested zero-leaf shapes. runtime undefined storage, ordinary value-array construction, type-valued record storage and metadata reflection remain open. + - [x] uninitialized type-array checkpoint: fixed type arrays in type/type-array producers start with `undefined` and fill through loop-driven indexed, nested and whole-array updates. persistent local copies retain independent initialization state; `.len` works before leaves initialize. premature leaf reads and incomplete procedure arguments/results diagnose read-before-initialization without caching invalid values. explicit nonnegative dimensions, empty shapes, ordinary mutability/shape checks, nominal identity and repeatable compilation work through interpreter/native execution. construction shares the type-control bound per expanded cell, including nested zero-leaf shapes. ordinary value-array construction is covered below; runtime undefined storage, type-valued record storage and metadata reflection remain open. + - [x] uninitialized value-array checkpoint: ordinary fixed arrays in type/type-array producers start with `undefined`, fill nested indices or replace whole arrays/rows, and generate storage/defaults through interpreter/native execution. owned snapshots preserve independent partial initialization in local copies; initialized scalar/string/optional/record/tagged-union elements retain exact types and ordinary checked operations. premature leaf reads and incomplete procedure arguments diagnose read-before-initialization. empty shapes, `.len` before initialization, mutability/type/index/bounds/overflow diagnostics, per-cell construction limits and repeatable compilation are verified. runtime undefined storage, uninitialized arrays in ordinary value-returning procedures, partial record construction and storage-bearing constant materialization remain open. - [ ] remaining gate: general type-valued compile-time execution, inline match/loop expansion and metadata reflection (`typeinfo!`, `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. diff --git a/source/compilation/compilation.test.hon b/source/compilation/compilation.test.hon index b1be682..353d7a7 100644 --- a/source/compilation/compilation.test.hon +++ b/source/compilation/compilation.test.hon @@ -206,6 +206,19 @@ recompiles_specialized_calls_without_borrowing_previous_ir test { `UninitializedType proc(types [2]mut type) type { ` return struct { first types[0], second types[1] } `} + `UninitializedValues proc() type { + ` rows [2]mut [2]mut usize := undefined + ` (rows[0])[0] = 1 + ` copied := rows + ` rows[0] = [3, 4] + ` (copied[0])[1] = 2 + ` copied[1] = [1, 1] + ` for (usize(0)..rows[1].len) |index| { (rows[1])[index] = index + 1 } + ` return struct { + ` values [copied[0][1]]i16 + ` bias i16 = i16(copied[1][0] + rows[1][0]) + ` } + `} `read_selected proc($choice Choice, value i16) i16 { ` match choice { ` .payload |detail|: { @@ -252,6 +265,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test { ` } ` same_initialized UninitializedType(UninitializedRows()) :: initialized ` copied_initialized := same_initialized.first + ` initialized_values UninitializedValues() :: { values = [19, 21] } + ` same_initialized_values UninitializedValues() :: initialized_values + ` if (same_initialized_values.values[0] + same_initialized_values.values[1] + same_initialized_values.bias != 42) return 0 ` copied_initialized = same_initialized.second ` payload :: packet.choices[0].payload ` if (payload.values[0] != 40 or payload.values[1] != 2 or payload.text.len != 2) return 0 diff --git a/source/sema/specialize/specialize.hon b/source/sema/specialize/specialize.hon index 50b3c1e..54e564b 100644 --- a/source/sema/specialize/specialize.hon +++ b/source/sema/specialize/specialize.hon @@ -875,6 +875,9 @@ evaluation_dependencies proc( ) bool ! Error { if ((flags[usize(id)] & 4) != 0) return true flags[usize(id)] = flags[usize(id)] | 4 + if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .typed_constant) { + if (!try type_array_initialized(state, id, id)) return false + } resolved :: binding(state, id) if (resolved.kind == .declaration) { target :: resolved.data.declaration @@ -944,6 +947,8 @@ evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Erro } if (!is_type(state, annotation)) { type :: try canonical_type(state, annotation) + if (type != no_node and ast.expr_kind(&state.tree, type) == .array_type + and !try type_array_initialized(state, id, id)) return Value{} if (type != no_node and (contextual_literal(state, id) or ast.expr_kind(&state.tree, type) == .fallible_type)) return try evaluate_ir(state, id, type) } @@ -1477,7 +1482,7 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { } if (ast.expr_kind(&state.tree, base) != .typed_constant) return true array :: ast.typed_constant(&state.tree, base) - if (!try type_array_storage(state, array.type)) return true + if (!try execution_array_storage(state, array.type)) return true selected := no_node if (kind == .index) { index :: try type_array_index(state, ast.index_expr(&state.tree, id).index, @@ -1489,8 +1494,17 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { .read_before_initialization, ast.node_span(&state.tree, id)) return false } + if (!type_array_annotation(&state.tree, state.bindings.items, array.type) + and ast.expr_kind(&state.tree, selected) != .typed_constant) { + descriptor :: ast.array_type(&state.tree, try canonical_type(state, array.type)) + selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id), + descriptor.element, selected) + try refresh(state) + } } else { member :: ast.selection_expr(&state.tree, id).member + if (!type_array_annotation(&state.tree, state.bindings.items, array.type) + and !mem.eql(ast.literal_text(&state.tree, member), "len")) return true if (!mem.eql(ast.literal_text(&state.tree, member), "len")) { state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .unknown_field, ast.node_span(&state.tree, member)) @@ -1517,18 +1531,12 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { return true } -# type arrays are persistent AST values, never runtime storage. keep the same -# typed-constant snapshots as ordinary aggregates, but validate their leaves here. +# type-control arrays are persistent AST snapshots, including uninitialized leaves. +# only fully initialized ordinary values may enter checked runtime IR. @hide:file -type_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Error { - current := annotation - for (0..128) |_| { - if (is_type(state, current)) return current != annotation - current = try canonical_type(state, current) - if (current == no_node or ast.expr_kind(&state.tree, current) != .array_type) return false - current = ast.array_type(&state.tree, current).element - } - return false +execution_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Error { + current :: try canonical_type(state, annotation) + return current != no_node and ast.expr_kind(&state.tree, current) == .array_type } @hide:file @@ -1557,7 +1565,8 @@ type_array_compatible proc(state @mut State, expected, actual ast.NodeId) bool ! left :: try canonical_type(state, expected) right :: try canonical_type(state, actual) if (left == no_node or right == no_node) return false - if (ast.expr_kind(&state.tree, left) != .array_type or ast.expr_kind(&state.tree, right) != .array_type) return false + if (ast.expr_kind(&state.tree, left) != .array_type or ast.expr_kind(&state.tree, right) != .array_type) + return try same_type(state, left, right) a :: ast.array_type(&state.tree, left) b :: ast.array_type(&state.tree, right) if (a.mutable != b.mutable or b.count == null) return false @@ -1593,6 +1602,7 @@ type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { if (is_type(state, annotation)) return expression type :: try canonical_type(state, annotation) + if (type == no_node or ast.expr_kind(&state.tree, type) != .array_type) return expression descriptor :: ast.array_type(&state.tree, type) length := Value{} if (descriptor.count) |count| length = try evaluate(state, count) @@ -1634,6 +1644,7 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N if (ast.expr_kind(&state.tree, expression) == .typed_constant) { actual :: ast.typed_constant(&state.tree, expression) if (try type_array_compatible(state, type, actual.type)) return expression + if (!try type_array_initialized(state, expression, expression)) return no_node } else if (ast.expr_kind(&state.tree, expression) == .array_literal) { count :: ast.array_element_count(&state.tree, expression) if (array.count) |length| { @@ -1665,6 +1676,7 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N child := element_type while (!is_type(state, child)) { child = try canonical_type(state, child) + if (child == no_node or ast.expr_kind(&state.tree, child) != .array_type) break descriptor :: ast.array_type(&state.tree, child) value := Value{} if (descriptor.count) |length| value = try evaluate(state, length) @@ -1685,9 +1697,16 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N try refresh(state) return result } - state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, - .type_mismatch, ast.node_span(&state.tree, expression)) - return no_node + if (type_array_annotation(&state.tree, state.bindings.items, annotation)) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, + .type_mismatch, ast.node_span(&state.tree, expression)) + return no_node + } + value :: try evaluate_ir(state, expression, annotation) + if (value.kind == .invalid) return no_node + result :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, expression), annotation, value.node) + try refresh(state) + return result } # rebuild only the indexed path so previous snapshots retain their owned values. @@ -1758,7 +1777,17 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No state.evaluation_diagnostic = resolved.data.diagnostic return no_node } - if (try type_array_storage(state, annotation)) return try type_array_value(state, expression, annotation) + if (try execution_array_storage(state, annotation)) { + value :: try type_array_value(state, expression, annotation) + if (value != no_node and !type_array_annotation(&state.tree, state.bindings.items, annotation)) { + # erased snapshots still owe ordinary array-element/layout checking. + constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), + ast.typed_constant(&state.tree, value).type, true) + try refresh(state) + try arraylist.append(&state.rewrites.constraints, constraint) + } + return value + } if (is_type(state, annotation)) { if (ast.expr_kind(&state.tree, expression) == .literal_undefined) { state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, @@ -1949,10 +1978,13 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat return try execute_type_statement(state, execution, normalized) } root := assignment.target + indexed := true while (true) { kind :: ast.expr_kind(&state.tree, root) - if (kind == .selection and binding(state, root).kind == .none) + if (kind == .selection and binding(state, root).kind == .none) { + indexed = false root = ast.selection_expr(&state.tree, root).base + } else if (kind == .index) root = ast.index_expr(&state.tree, root).base else break } @@ -1974,7 +2006,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat return .invalid } if (root != assignment.target) { - if (try type_array_storage(state, execution.locals.items[index].annotation)) { + if (indexed and try execution_array_storage(state, execution.locals.items[index].annotation)) { local :: execution.locals.items[index] if ( ast.node_class(&state.tree, local.declaration) != .statement @@ -2167,7 +2199,7 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta collection :: try type_execution_value(state, snapshot, annotation) if (collection == no_node) return .invalid count := Value{} - if (try type_array_storage(state, annotation)) { + 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 { diff --git a/tests/native.py b/tests/native.py index e3232af..11be83a 100644 --- a/tests/native.py +++ b/tests/native.py @@ -1495,15 +1495,208 @@ CASES = { } """, b"", b"generic argument must be a compile-time value", ), - "type_arrays_undefined_does_not_enable_comptime_value_storage": ( + "value_arrays_undefined_loop_initialization_generates_storage": ( + """ + Storage proc() type { + rows [2]mut [2]mut usize := undefined + for (usize(0)..rows.len) |row| { + for (usize(0)..rows[row].len) |column| { (rows[row])[column] = row + column + 1 } + } + return struct { values [rows[0][1]]i16, bias i16 = i16(rows[1][0]) } + } + main proc() i16 { + value Storage() :: { values = [19, 21] } + same Storage() :: value + return same.values[0] + same.values[1] + same.bias + } + """, b"42\n", None, + ), + "value_arrays_undefined_partial_copies_and_whole_replacement": ( + """ + Storage proc() type { + rows [2]mut [2]mut i16 := undefined + (rows[0])[0] = 19 + copied := rows + (rows[0])[1] = 1 + rows[1] = [2, 3] + (copied[0])[1] = 21 + copied[1] = [1, 1] + replacement [2]mut i16 := undefined + replacement = copied[1] + if (rows[0][1] != 1 or rows[1][0] != 2) return bool + return struct { value i16 = copied[0][0] + copied[0][1] + replacement[0] + replacement[1] } + } + main proc() i16 { value Storage() :: {} + return value.value + } + """, b"42\n", None, + ), + "value_arrays_undefined_strings_optionals_records_and_unions": ( + """ + Item :: struct { text string, amount ?i16, values [1]mut i16 } + Choice :: union(enum) { value i16, empty void } + Storage proc() type { + words [2]mut string := undefined + words[0] = "ok" + words[1] = "yes" + options [2]mut ?i16 := undefined + options[0] = null + options[1] = 37 + items [1]mut Item := undefined + items[0] = { text = words[0], amount = options[0], values = [0] } + items[0].amount = options[1] + items[0].values[0] = 37 + choices [1]mut Choice := undefined + choices[0] = .empty + choices[0] = .value{items[0].values[0]} + amount :: match choices[0] { .value |value|: value, .empty: i16(0) } + return struct { value i16 = amount + items[0].amount? - 37 + i16(items[0].text.len + words[1].len) } + } + main proc() i16 { value Storage() :: {} + return value.value + } + """, b"42\n", None, + ), + "value_arrays_undefined_empty_shapes_and_type_array_producer": ( + """ + Rows proc() [1]mut type { + rows [2]mut [0]mut i16 := undefined + outer [0]mut [2]mut usize := undefined + copied :: rows + if (copied.len != 2 or copied[0].len != 0 or outer.len != 0) return [bool] + for rows |row| { for row |_| { return [bool] } } + counts [1]mut usize := undefined + counts[0] = rows.len + return [struct { values [counts[0]]i16, bias i16 = 2 }] + } + main proc() i16 { + value Rows()[0] :: { values = [19, 21] } + return value.values[0] + value.values[1] + value.bias + } + """, b"42\n", None, + ), + "value_arrays_undefined_reject_premature_leaf_read": ( """ Bad proc() type { - values [1]mut usize := undefined - values[0] = 1 + values [2]mut usize := undefined + values[0] = 2 + return [values[1]]i16 + } + main proc() Bad() { return [] } + """, b"", b"local value was read before initialization", + ), + "value_arrays_undefined_reject_partial_argument": ( + """ + Selected proc(values [2]mut usize) type { return [values[0]]i16 } + Bad proc() type { + values [2]mut usize := undefined + values[0] = 2 + return Selected(values) + } + main proc() Bad() { return [19, 23] } + """, b"", b"local value was read before initialization", + ), + "value_arrays_undefined_copy_does_not_observe_later_initialization": ( + """ + Bad proc() type { + values [2]mut usize := undefined + values[0] = 2 + copied :: values + values[1] = 1 + return [copied[1]]i16 + } + main proc() Bad() { return [] } + """, b"", b"local value was read before initialization", + ), + "value_arrays_undefined_reject_readonly_binding": ( + """ + Bad proc() type { + values [1]mut usize :: undefined + values = [2] return i16 } main proc() Bad() { return 42 } - """, b"", b"generic argument must be a compile-time value", + """, b"", b"assignment requires a mutable local binding", + ), + "value_arrays_undefined_reject_readonly_elements": ( + """ + Bad proc() type { + values [1]usize := undefined + values[0] = 2 + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"indexed assignment requires a writable array element", + ), + "value_arrays_undefined_reject_wrong_leaf_type": ( + """ + Bad proc() type { + values [1]mut usize := undefined + wrong u8 :: 2 + values[0] = wrong + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"", + ), + "value_arrays_undefined_reject_wrong_index_type": ( + """ + Bad proc() type { + values [1]mut usize := undefined + index u8 :: 0 + values[index] = 2 + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"array index must have type usize", + ), + "value_arrays_undefined_reject_out_of_bounds_write": ( + """ + Bad proc() type { + values [1]mut usize := undefined + values[1] = 2 + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"index out of bounds", + ), + "value_arrays_undefined_preserve_narrow_overflow": ( + """ + Bad proc() type { + values [1]mut i8 := undefined + values[0] = 127 + values[0] = values[0] + 1 + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"integer arithmetic overflow", + ), + "value_arrays_undefined_construction_is_bounded": ( + """ + Bad proc() type { + values [1000000000]mut usize := undefined + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"compile-time execution exceeds its recursion or step limit", + ), + "value_arrays_undefined_nested_empty_construction_is_bounded": ( + """ + Bad proc() type { + values [100]mut [100]mut [0]mut usize := undefined + return i16 + } + main proc() Bad() { return 42 } + """, b"", b"compile-time execution exceeds its recursion or step limit", + ), + "value_arrays_undefined_reject_invalid_empty_element_annotation": ( + """ + Storage proc() type { + values [0]mut void := undefined + return i16 + } + main proc() Storage() { return 42 } + """, b"42\n", b"array element annotations", ), "type_arrays_only_execute_selected_bodies": ( """