feat: construct partial compile-time type records

This commit is contained in:
2026-10-30 18:04:05 +01:00
parent 32a7a1de24
commit 83b6153e72
5 changed files with 221 additions and 27 deletions
+4 -2
View File
@@ -112,11 +112,13 @@ inline tuples may mix types and values, including nested aggregates, strings, op
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.
initialized records/tuples containing `type` fields live inside compile-time producers. local schemas, typed/contextual/anonymous construction, defaults, nested records/arrays, field/index reads and updates, whole-value replacement and by-value collection captures retain owned snapshots. for example, `config struct { child type, count usize } := { child = i8, count = 2 }`, then `config.child = i16` and `return [config.count]config.child`, generates `[2]i16`. earlier copies and loop captures remain independent, while borrowed array places observe later updates. type leaves retain nominal identity; ordinary value leaves reuse checked constant execution. unknown/duplicate/missing fields, invalid types/defaults, readonly places, narrow overflow and dependency provenance retain diagnostics. record validation/construction shares the type-control bound. runtime type-valued records, partial record initialization, metadata reflection and inline expansion remain open. initialized records/tuples containing `type` fields live inside compile-time producers. local schemas, typed/contextual/anonymous construction, defaults, nested records/arrays, field/index reads and updates, whole-value replacement and by-value collection captures retain owned snapshots. for example, `config struct { child type, count usize } := { child = i8, count = 2 }`, then `config.child = i16` and `return [config.count]config.child`, generates `[2]i16`. earlier copies and loop captures remain independent, while borrowed array places observe later updates. type leaves retain nominal identity; ordinary value leaves reuse checked constant execution. unknown/duplicate/missing fields, invalid types/defaults, readonly places, narrow overflow and dependency provenance retain diagnostics. record validation/construction shares the type-control bound. runtime type-valued records remain unsupported.
type-valued records/tuples may also start with `undefined`: `config struct { child type, count usize } := undefined`, then `config.child = i16` and `config.count = 2`. nested record fields, arrays of records, tuple ordinals and `field!` updates use the same persistent snapshots as initialized storage; partial copies retain independent initialization state. defaults do not initialize `undefined` fields. initialized leaves and array lengths can be read while other leaves remain uninitialized, but premature leaf reads and incomplete procedure arguments/results diagnose read-before-initialization. fully initialized results cross compile-time procedure boundaries and supply erased generic inputs, generated storage and defaults. empty shapes are initialized vacuously; each expanded record field/array cell shares the construction bound.
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. 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. partial type-valued record construction and constant materialization of mutable slices or nonnull data pointers remain unsupported. 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. constant materialization of mutable slices or nonnull data pointers remains unsupported.
ordinary procedures also accept `undefined` as the initializer of a mutable local with a concrete runtime type, including nested records and fixed arrays. field/index writes initialize storage without reading its previous contents; aggregate copies retain independent contents and initialization state in the interpreter. ordinary compile-time calls can fill this storage and return materializable values, but observing or materializing an uninitialized leaf is diagnosed. native storage uses LLVM poison, not zero initialization: assign every leaf before observing it, and do not rely on native read-before-initialization traps for `undefined` storage. immutable bindings, globals, inferred array lengths and other expression uses remain invalid. ordinary procedures also accept `undefined` as the initializer of a mutable local with a concrete runtime type, including nested records and fixed arrays. field/index writes initialize storage without reading its previous contents; aggregate copies retain independent contents and initialization state in the interpreter. ordinary compile-time calls can fill this storage and return materializable values, but observing or materializing an uninitialized leaf is diagnosed. native storage uses LLVM poison, not zero initialization: assign every leaf before observing it, and do not rely on native read-before-initialization traps for `undefined` storage. immutable bindings, globals, inferred array lengths and other expression uses remain invalid.
+6 -4
View File
@@ -40,7 +40,8 @@ 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` type/value arrays track initialization per leaf while loops fill generated types/defaults. `undefined` type/value arrays and type-valued records/tuples track initialization
per leaf while field/index updates fill generated types/defaults.
type-array and type-record parameters/results cross compile-time procedure boundaries type-array and type-record parameters/results cross compile-time procedure boundaries
with owned snapshots, cached nominal identity and erased arguments. with owned snapshots, cached nominal identity and erased arguments.
initialized type-valued records/tuples support schemas/defaults, nested initialized type-valued records/tuples support schemas/defaults, nested
@@ -131,9 +132,9 @@ test discovery and FFI remain outside these completed slices.
- [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 procedure boundaries 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 procedure boundaries 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. ordinary value-array construction, local type-valued records, runtime undefined storage and metadata reflection are covered below. - [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, local type-valued records, runtime undefined storage and metadata reflection are covered below.
- [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 and ordinary value-returning procedures are covered below; partial type-valued record construction and storage-bearing constant materialization 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, ordinary value-returning procedures and partial type-valued records are covered below; storage-bearing constant materialization remains open.
- [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 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. procedure inputs/results, partial record initialization, metadata reflection and inline expansion are covered below; runtime type-valued records 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] 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. partial record initialization, reflection and inline expansion are covered below; runtime metadata storage remains 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] 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 tuples and inline-match syntax are covered below; metadata iteration remains 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 tuples and inline-match syntax are covered below; metadata iteration remains 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. inline-match syntax is covered below; metadata collections 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. inline-match syntax is covered below; metadata collections remain open.
@@ -153,6 +154,7 @@ test discovery and FFI remain outside these completed slices.
- [x] nonzero-sentinel byte-slice checkpoint: readonly `[;sentinel]u8` compile-time results own exact payload bytes and any byte sentinel through interpreter/native execution. canonical string-backed views preserve logical lengths, empty views, embedded NULs, high bytes, computed/direct specialization-cache identity, generated defaults, inline captures, nested aggregates, optionals and both fallible channels. sentinel checks observe final evaluation storage, diagnosing mutation, uninitialized bytes and invalid addresses; imported helpers, source/session disposal and repeated compilation retain ownership. nonbyte/nominal-byte slices are covered below; mutable slices, nonnull data pointers and address-bearing untagged union layouts retain the escape boundary. - [x] nonzero-sentinel byte-slice checkpoint: readonly `[;sentinel]u8` compile-time results own exact payload bytes and any byte sentinel through interpreter/native execution. canonical string-backed views preserve logical lengths, empty views, embedded NULs, high bytes, computed/direct specialization-cache identity, generated defaults, inline captures, nested aggregates, optionals and both fallible channels. sentinel checks observe final evaluation storage, diagnosing mutation, uninitialized bytes and invalid addresses; imported helpers, source/session disposal and repeated compilation retain ownership. nonbyte/nominal-byte slices are covered below; mutable slices, nonnull data pointers and address-bearing untagged union layouts retain the escape boundary.
- [x] untagged-union materialization checkpoint: address-free untagged unions own full native-layout byte snapshots through interpreter/native execution, including scalar/IEEE reinterpretation, nested padded records/arrays, smaller overlapping updates, zero-sized storage, distinct members and generated nominal unions. explicit `$()` values, compile-time arguments, full-byte specialization-cache identity, generated defaults, nested optionals/tagged payloads and both fallible channels preserve exact types and independent copies. uninitialized tail/padding bytes diagnose read-before-initialization; every member layout is checked recursively to reject data pointers, callbacks, strings and slices rather than retain interpreter handles. imported helpers/diagnostics, source/session disposal and repeated compilation retain ownership. address-bearing union layouts and broader constant materialization remain open. - [x] untagged-union materialization checkpoint: address-free untagged unions own full native-layout byte snapshots through interpreter/native execution, including scalar/IEEE reinterpretation, nested padded records/arrays, smaller overlapping updates, zero-sized storage, distinct members and generated nominal unions. explicit `$()` values, compile-time arguments, full-byte specialization-cache identity, generated defaults, nested optionals/tagged payloads and both fallible channels preserve exact types and independent copies. uninitialized tail/padding bytes diagnose read-before-initialization; every member layout is checked recursively to reject data pointers, callbacks, strings and slices rather than retain interpreter handles. imported helpers/diagnostics, source/session disposal and repeated compilation retain ownership. address-bearing union layouts and broader constant materialization remain open.
- [x] readonly typed-slice materialization checkpoint: ordinary compile-time calls snapshot readonly slices of materializable elements into module-lifetime backing through interpreter/native execution. nominal bytes, wider integers, arrays/records, source-bound callbacks, nested readonly slices, empty/zero-sized elements and full-width signed/unsigned sentinels retain exact types and logical lengths. explicit `$()` values, computed/direct content-based specialization identity, generated defaults and nested optional/tagged/fallible channels preserve ownership across helper returns, imported private declarations, source/session disposal and repeat compilation. initialization, address-range, sentinel, mutable-slice and nonnull-pointer escape diagnostics remain checked; 100,000-value/128-level snapshot bounds reject oversized and cyclic storage. mutable slices, nonnull data pointers and address-bearing untagged union layouts remain open. - [x] readonly typed-slice materialization checkpoint: ordinary compile-time calls snapshot readonly slices of materializable elements into module-lifetime backing through interpreter/native execution. nominal bytes, wider integers, arrays/records, source-bound callbacks, nested readonly slices, empty/zero-sized elements and full-width signed/unsigned sentinels retain exact types and logical lengths. explicit `$()` values, computed/direct content-based specialization identity, generated defaults and nested optional/tagged/fallible channels preserve ownership across helper returns, imported private declarations, source/session disposal and repeat compilation. initialization, address-range, sentinel, mutable-slice and nonnull-pointer escape diagnostics remain checked; 100,000-value/128-level snapshot bounds reject oversized and cyclic storage. mutable slices, nonnull data pointers and address-bearing untagged union layouts remain open.
- [x] uninitialized type-record checkpoint: compile-time records/tuples containing type values start with `undefined` and fill through nested field/index updates or whole-value replacement. persistent snapshots preserve independent partial copies; defaults do not initialize undefined fields. initialized leaves and empty shapes work while premature type/value reads and incomplete procedure arguments/results diagnose read-before-initialization. fully initialized results supply erased generic inputs, generated storage/defaults and cached nominal types through interpreter/native execution. ordinary mutability/schema validation and shared per-field/cell construction bounds remain checked; general type-valued execution and broader materialization remain open.
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates. - [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. 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.
+14
View File
@@ -231,6 +231,17 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
`} `}
`BoundaryConfig :: struct { child type, counts [2]mut usize, bias i16 = 2 } `BoundaryConfig :: struct { child type, counts [2]mut usize, bias i16 = 2 }
`record_config proc() BoundaryConfig { return { child = i16, counts = [1, 2] } } `record_config proc() BoundaryConfig { return { child = i16, counts = [1, 2] } }
`partial_record_config proc() BoundaryConfig {
` config BoundaryConfig := undefined
` config.child = i8
` config.counts[0] = 1
` copied := config
` config.child = bool
` copied.child = i16
` copied.counts[1] = 2
` copied.bias = 2
` return copied
`}
`change_record_config proc(input BoundaryConfig) BoundaryConfig { `change_record_config proc(input BoundaryConfig) BoundaryConfig {
` copied := input ` copied := input
` copied.child = u8 ` copied.child = u8
@@ -310,6 +321,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` same_record_boundary RecordBoundary({ child = i16, counts = [1, 2] }) :: record_boundary ` same_record_boundary RecordBoundary({ child = i16, counts = [1, 2] }) :: record_boundary
` if (same_record_boundary.values[0] + same_record_boundary.values[1] + same_record_boundary.bias != 42) return 0 ` if (same_record_boundary.values[0] + same_record_boundary.values[1] + same_record_boundary.bias != 42) return 0
` if (read_record_config(record_config(), 40) != 42) return 0 ` if (read_record_config(record_config(), 40) != 42) return 0
` partial_boundary RecordBoundary(partial_record_config()) :: record_boundary
` if (partial_boundary.values[0] + partial_boundary.values[1] + partial_boundary.bias != 42) return 0
` if (read_record_config(partial_record_config(), 40) != 42) return 0
` if (read_record_pair(record_pair(), 40) != 42 or read_record_pair(record_pair(), 40) != 42) return 0 ` if (read_record_pair(record_pair(), 40) != 42 or read_record_pair(record_pair(), 40) != 42) return 0
` copied_initialized = same_initialized.second ` copied_initialized = same_initialized.second
` payload :: packet.choices[0].payload ` payload :: packet.choices[0].payload
+44 -21
View File
@@ -3679,14 +3679,14 @@ 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)
type :: try canonical_type(state, array.type) type :: try canonical_type(state, array.type)
record :: (type != no_node and type_record_storage(state, type, 0) record :: (type != no_node and (ast.expr_kind(&state.tree, type) == .record_type
and (ast.expr_kind(&state.tree, type) == .record_type or (type_record_storage(state, type, 0) and ast.expr_kind(&state.tree, type) == .union_type)))
or ast.expr_kind(&state.tree, type) == .union_type))
if ( if (
!record and !try execution_array_storage(state, array.type) !record and !try execution_array_storage(state, array.type)
and !try metadata_sequence(state, array.type) and !try metadata_sequence(state, array.type)
) return true ) return true
selected := no_node selected := no_node
selected_type := no_node
if (record) { if (record) {
if (kind != .selection) return true if (kind != .selection) return true
member :: ast.selection_expr(&state.tree, id).member member :: ast.selection_expr(&state.tree, id).member
@@ -3705,23 +3705,15 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
} }
selected = active.value? selected = active.value?
} else selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value? } else selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value?
if (!type_record_storage(state, array.type, 0))
selected_type = ast.field(&state.tree, ast.record_field(&state.tree, type, slot)).type?
} else if (kind == .index) { } else 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,
ast.array_element_count(&state.tree, array.value)) ast.array_element_count(&state.tree, array.value))
if (index == no_index) return false if (index == no_index) return false
selected = ast.array_element(&state.tree, array.value, index) selected = ast.array_element(&state.tree, array.value, index)
if (ast.expr_kind(&state.tree, selected) == .literal_undefined) { if (!type_record_storage(state, array.type, 0))
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, selected_type = ast.array_type(&state.tree, type).element
.read_before_initialization, ast.node_span(&state.tree, id))
return false
}
if (!type_record_storage(state, array.type, 0)
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_record_storage(state, array.type, 0) if (!type_record_storage(state, array.type, 0)
@@ -3737,6 +3729,16 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
try builtin(state, id, .usize), count) try builtin(state, id, .usize), count)
try refresh(state) try refresh(state)
} }
if (ast.expr_kind(&state.tree, selected) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, id))
return false
}
if (selected_type != no_node and ast.expr_kind(&state.tree, selected) != .typed_constant) {
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
selected_type, selected)
try refresh(state)
}
# copying a nominal definition into the projection would create a new # copying a nominal definition into the projection would create a new
# identity on every read. retain its original node behind a type alias. # identity on every read. retain its original node behind a type alias.
if (nominal_type_value(state, selected)) { if (nominal_type_value(state, selected)) {
@@ -3888,13 +3890,15 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
return true return true
} }
# normalize complete record snapshots in declaration order, evaluating every leaf # normalize record snapshots in declaration order through their declared context.
# through its declared context. no record containing type values enters runtime IR. # undefined records retain per-leaf initialization state without applying defaults.
@hide:file @hide:file
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
type :: try canonical_type(state, annotation) type :: try canonical_type(state, annotation)
if (ast.expr_kind(&state.tree, type) == .union_type) if (ast.expr_kind(&state.tree, type) == .union_type)
return try type_union_value(state, expression, type) return try type_union_value(state, expression, type)
if (ast.expr_kind(&state.tree, expression) == .literal_undefined)
return try type_storage_undefined(state, expression, annotation)
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 same_type(state, type, actual.type)) return expression if (try same_type(state, type, actual.type)) return expression
@@ -4023,9 +4027,28 @@ type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error
# bound the expanded snapshot, not just its dimensions, so later reads and # bound the expanded snapshot, not just its dimensions, so later reads and
# initialization checks cannot traverse an exponentially shared empty shape. # initialization checks cannot traverse an exponentially shared empty shape.
@hide:file @hide:file
type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { type_storage_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 and ast.expr_kind(&state.tree, type) == .record_type) {
if (!try type_record_check(state, type)) return no_node
fields := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&fields)
for (0..ast.record_field_count(&state.tree, type)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
if (!try type_step(state, expression)) return no_node
value :: try type_storage_undefined(state, expression, field.type?)
if (value == no_node) return no_node
generated :: try ast.add_field(state.builder, field.span, field.name, null, value)
try refresh(state)
try arraylist.append(&fields, generated)
}
span :: ast.node_span(&state.tree, expression)
literal :: try ast.add_record_literal(state.builder, span, null, fields.items)
result :: try ast.add_typed_constant(state.builder, span, type, literal)
try refresh(state)
return result
}
if (type == no_node or ast.expr_kind(&state.tree, type) != .array_type) return expression 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{}
@@ -4036,14 +4059,14 @@ type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) a
return no_node return no_node
} }
# validate child dimensions even when this dimension is empty. # validate child dimensions even when this dimension is empty.
child :: try type_array_undefined(state, expression, descriptor.element) child :: try type_storage_undefined(state, expression, descriptor.element)
if (child == no_node) return no_node if (child == no_node) return no_node
elements := arraylist.init(ast.NodeId, state.allocator) elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements) defer arraylist.deinit(&elements)
for (0..length.magnitude) |i| { for (0..length.magnitude) |i| {
if (!try type_step(state, expression)) return no_node if (!try type_step(state, expression)) return no_node
element := child element := child
if (i != 0) element = try type_array_undefined(state, expression, descriptor.element) if (i != 0) element = try type_storage_undefined(state, expression, descriptor.element)
if (element == no_node) return no_node if (element == no_node) return no_node
try arraylist.append(&elements, element) try arraylist.append(&elements, element)
} }
@@ -4062,7 +4085,7 @@ type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) a
@hide:file @hide:file
type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) if (ast.expr_kind(&state.tree, expression) == .literal_undefined)
return try type_array_undefined(state, expression, annotation) return try type_storage_undefined(state, expression, annotation)
type :: try canonical_type(state, annotation) type :: try canonical_type(state, annotation)
array :: ast.array_type(&state.tree, type) array :: ast.array_type(&state.tree, type)
if (ast.expr_kind(&state.tree, expression) == .typed_constant) { if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
+153
View File
@@ -2278,6 +2278,159 @@ CASES = {
} }
""", b"", b"initialization", """, b"", b"initialization",
), ),
"type_records_undefined_nested_updates_preserve_partial_copies": (
"""
Config :: struct {
child type = bool
detail struct { count usize, bias i16 }
rows [2]mut struct { child type, count usize }
}
config proc() Config {
value Config := undefined
value.child = i16
value.detail.count = 2
copied := value
value.detail.bias = 99
copied.detail.bias = 2
for (usize(0)..copied.rows.len) |index| {
copied.rows[index].child = copied.child
copied.rows[index].count = index + 1
}
return copied
}
Storage proc(input Config) type {
return struct { values [input.detail.count]input.rows[1].child, bias i16 = input.detail.bias }
}
main proc() i16 {
value Storage(config()) :: { values = [19, 21] }
same Storage(config()) :: value
return same.values[0] + same.values[1] + same.bias
}
""", b"42\n", None,
),
"type_records_undefined_tuples_replacement_and_empty_fields": (
"""
Pair :: struct { type, struct { child type, values [0]mut type } }
pair proc() Pair {
value Pair := undefined
value.0 = bool
value.1.child = i8
copied := value
value = {i16, { child = i16, values = [] }}
copied.0 = value.0
copied.1 = value.1
return copied
}
Element proc(input Pair) type { return field!(input, "0") }
add proc($schema Pair, value Element(schema)) Element(schema) {
if (schema.1.child != i16) return 0
return value + 2
}
main proc() i16 { return add(pair(), 40) }
""", b"42\n", None,
),
"type_records_undefined_reject_type_leaf_read": (
"""
Bad proc() type {
value struct { child type } := undefined
return value.child
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_reject_value_leaf_read": (
"""
Bad proc() type {
value struct { child type, count usize } := undefined
value.child = i16
return [value.count]value.child
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_defaults_do_not_initialize_fields": (
"""
config proc() struct { child type = i16 } {
value struct { child type = i16 } := undefined
return value
}
main proc() config().child { return 42 }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_copy_retains_uninitialized_leaves": (
"""
Bad proc() type {
value struct { child type, count usize } := undefined
value.child = i16
copied :: value
value.count = 2
return [copied.count]copied.child
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_reject_partial_argument": (
"""
Config :: struct { child type, count usize }
Selected proc(value Config) type { return value.child }
Bad proc() type {
value Config := undefined
value.child = i16
return Selected(value)
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_reject_partial_erased_argument": (
"""
Config :: struct { child type, count usize }
selected proc($value Config) i16 { return 42 }
Bad proc() type {
value Config := undefined
value.child = i16
return [usize(selected(value))]i16
}
main proc() Bad() { return [] }
""", b"", b"local value was read before initialization",
),
"type_records_undefined_reject_readonly_field_update": (
"""
Bad proc() type {
value struct { child type } :: undefined
value.child = i16
return i16
}
main proc() Bad() { return 42 }
""", b"", b"indexed assignment requires a writable array element",
),
"type_records_undefined_reject_readonly_nested_array_update": (
"""
Bad proc() type {
value struct { children [1]type } := undefined
value.children[0] = i16
return i16
}
main proc() Bad() { return 42 }
""", b"", b"indexed assignment requires a writable array element",
),
"type_records_undefined_reject_invalid_schema": (
"""
Bad proc() type {
value struct { child type, count i8 = 128 } := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"numeric literal is outside its runtime type's range",
),
"type_records_undefined_construction_is_bounded": (
"""
Bad proc() type {
value struct { child type, rows [1000000000]mut struct { child type } } := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"compile-time execution exceeds its recursion or step limit",
),
"type_records_local_schema_defaults_and_owned_copies": ( "type_records_local_schema_defaults_and_owned_copies": (
""" """
Storage proc() type { Storage proc() type {