feat: construct uninitialized compile-time value arrays

This commit is contained in:
2026-10-28 00:51:15 +01:00
parent ca53dab86b
commit 443de49d55
5 changed files with 274 additions and 30 deletions
+5 -3
View File
@@ -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. 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. 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. 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. 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.
+4 -3
View File
@@ -34,7 +34,7 @@ captures and `usize` indices through both paths; type-producing bodies iterate
materializable arrays/strings by value without retaining interpreter addresses. materializable arrays/strings by value without retaining interpreter addresses.
type-valued fixed arrays support nested copies, indexed/whole updates, type-valued fixed arrays support nested copies, indexed/whole updates,
checked projections and by-value collection captures in type-producing bodies; 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 type-array parameters and results cross compile-time procedure boundaries with
owned snapshots, cached nominal identity and erased arguments. owned snapshots, cached nominal identity and erased arguments.
`field!` selects record/tuple/enum and sequence members using compile-time names, `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] 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] 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 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] 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 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] 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] 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 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`. - [ ] 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.
+16
View File
@@ -206,6 +206,19 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
`UninitializedType proc(types [2]mut type) type { `UninitializedType proc(types [2]mut type) type {
` return struct { first types[0], second types[1] } ` 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 { `read_selected proc($choice Choice, value i16) i16 {
` match choice { ` match choice {
` .payload |detail|: { ` .payload |detail|: {
@@ -252,6 +265,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` } ` }
` same_initialized UninitializedType(UninitializedRows()) :: initialized ` same_initialized UninitializedType(UninitializedRows()) :: initialized
` copied_initialized := same_initialized.first ` 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 ` copied_initialized = same_initialized.second
` 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
+49 -17
View File
@@ -875,6 +875,9 @@ evaluation_dependencies proc(
) bool ! Error { ) bool ! Error {
if ((flags[usize(id)] & 4) != 0) return true if ((flags[usize(id)] & 4) != 0) return true
flags[usize(id)] = flags[usize(id)] | 4 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) resolved :: binding(state, id)
if (resolved.kind == .declaration) { if (resolved.kind == .declaration) {
target :: resolved.data.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)) { if (!is_type(state, annotation)) {
type :: try canonical_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)) 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) 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 if (ast.expr_kind(&state.tree, base) != .typed_constant) return true
array :: ast.typed_constant(&state.tree, base) 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 selected := no_node
if (kind == .index) { if (kind == .index) {
index :: try type_array_index(state, ast.index_expr(&state.tree, id).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)) .read_before_initialization, ast.node_span(&state.tree, id))
return false 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 { } else {
member :: ast.selection_expr(&state.tree, id).member 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")) { if (!mem.eql(ast.literal_text(&state.tree, member), "len")) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.unknown_field, ast.node_span(&state.tree, member)) .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 return true
} }
# type arrays are persistent AST values, never runtime storage. keep the same # type-control arrays are persistent AST snapshots, including uninitialized leaves.
# typed-constant snapshots as ordinary aggregates, but validate their leaves here. # only fully initialized ordinary values may enter checked runtime IR.
@hide:file @hide:file
type_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Error { execution_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Error {
current := annotation current :: try canonical_type(state, annotation)
for (0..128) |_| { return current != no_node and ast.expr_kind(&state.tree, current) == .array_type
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
} }
@hide:file @hide:file
@@ -1557,7 +1565,8 @@ type_array_compatible proc(state @mut State, expected, actual ast.NodeId) bool !
left :: try canonical_type(state, expected) left :: try canonical_type(state, expected)
right :: try canonical_type(state, actual) right :: try canonical_type(state, actual)
if (left == no_node or right == no_node) return false 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) a :: ast.array_type(&state.tree, left)
b :: ast.array_type(&state.tree, right) b :: ast.array_type(&state.tree, right)
if (a.mutable != b.mutable or b.count == null) return false 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 { type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (is_type(state, annotation)) return expression if (is_type(state, annotation)) return expression
type :: try canonical_type(state, annotation) 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) descriptor :: ast.array_type(&state.tree, type)
length := Value{} length := Value{}
if (descriptor.count) |count| length = try evaluate(state, count) 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) { if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression) actual :: ast.typed_constant(&state.tree, expression)
if (try type_array_compatible(state, type, actual.type)) return 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) { } else if (ast.expr_kind(&state.tree, expression) == .array_literal) {
count :: ast.array_element_count(&state.tree, expression) count :: ast.array_element_count(&state.tree, expression)
if (array.count) |length| { if (array.count) |length| {
@@ -1665,6 +1676,7 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N
child := element_type child := element_type
while (!is_type(state, child)) { while (!is_type(state, child)) {
child = try canonical_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) descriptor :: ast.array_type(&state.tree, child)
value := Value{} value := Value{}
if (descriptor.count) |length| value = try evaluate(state, length) if (descriptor.count) |length| value = try evaluate(state, length)
@@ -1685,10 +1697,17 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N
try refresh(state) try refresh(state)
return result return result
} }
if (type_array_annotation(&state.tree, state.bindings.items, annotation)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, expression)) .type_mismatch, ast.node_span(&state.tree, expression))
return no_node 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. # rebuild only the indexed path so previous snapshots retain their owned values.
@hide:file @hide:file
@@ -1758,7 +1777,17 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
state.evaluation_diagnostic = resolved.data.diagnostic state.evaluation_diagnostic = resolved.data.diagnostic
return no_node 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 (is_type(state, annotation)) {
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) { if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, 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) return try execute_type_statement(state, execution, normalized)
} }
root := assignment.target root := assignment.target
indexed := true
while (true) { while (true) {
kind :: ast.expr_kind(&state.tree, root) 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 root = ast.selection_expr(&state.tree, root).base
}
else if (kind == .index) root = ast.index_expr(&state.tree, root).base else if (kind == .index) root = ast.index_expr(&state.tree, root).base
else break else break
} }
@@ -1974,7 +2006,7 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
return .invalid return .invalid
} }
if (root != assignment.target) { 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] local :: execution.locals.items[index]
if ( if (
ast.node_class(&state.tree, local.declaration) != .statement 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) collection :: try type_execution_value(state, snapshot, annotation)
if (collection == no_node) return .invalid if (collection == no_node) return .invalid
count := Value{} count := Value{}
if (try type_array_storage(state, annotation)) { if (try execution_array_storage(state, annotation)) {
count = Value{ kind = .integer, count = Value{ kind = .integer,
magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) } magnitude = u64(ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, collection).value)) }
} else { } else {
+197 -4
View File
@@ -1495,15 +1495,208 @@ CASES = {
} }
""", b"", b"generic argument must be a compile-time value", """, 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 { Bad proc() type {
values [1]mut usize := undefined values [2]mut usize := undefined
values[0] = 1 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 return i16
} }
main proc() Bad() { return 42 } 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": ( "type_arrays_only_execute_selected_bodies": (
""" """