feat: execute initialized compile-time type records
Recognize type-bearing storage syntactically so recursive nominal fields expand only after instance caching.
This commit is contained in:
@@ -98,19 +98,21 @@ 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 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 aggregate construction/validation 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 remains 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. 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.
|
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 ordinary record-field operations reuse 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 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.
|
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.
|
||||||
|
|
||||||
|
initialized records/tuples containing `type` fields also live inside type/type-array 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, record-valued compile-time procedure inputs/results, partial record initialization, metadata reflection and inline expansion remain open.
|
||||||
|
|
||||||
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. 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.
|
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 procedure boundaries and reflection remain open.
|
||||||
|
|
||||||
optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null, but nonnull pointer/slice/callback-bearing payloads retain the constant-materialization escape boundary.
|
optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null, but nonnull pointer/slice/callback-bearing payloads retain the constant-materialization escape boundary.
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ 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 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.
|
||||||
|
initialized type-valued records/tuples support local schemas/defaults, nested
|
||||||
|
field/index updates and owned copies/captures inside type-producing bodies.
|
||||||
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
||||||
including runtime places and type-control updates without runtime reflection.
|
including runtime places and type-control updates without runtime reflection.
|
||||||
general type-valued execution, metadata reflection and generic library acceptance remain open.
|
general type-valued execution, metadata reflection and generic library acceptance remain open.
|
||||||
@@ -88,13 +90,14 @@ 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 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] 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 and partial record construction remain open; type-valued records/arrays 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 and local type-valued records are covered below; reflection remains 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 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 is covered below; runtime undefined storage, 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 and local type-valued records are covered below; runtime undefined 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.
|
- [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.
|
||||||
|
- [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. runtime type-valued records, type-record procedure inputs/results, partial record initialization, metadata reflection and inline expansion 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.
|
||||||
|
|||||||
@@ -219,6 +219,15 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` bias i16 = i16(copied[1][0] + rows[1][0])
|
` bias i16 = i16(copied[1][0] + rows[1][0])
|
||||||
` }
|
` }
|
||||||
`}
|
`}
|
||||||
|
`TypeRecord proc() type {
|
||||||
|
` Config :: struct { child type, counts [2]mut usize, bias i16 = 2 }
|
||||||
|
` config Config := { child = i8, counts = [0, 0] }
|
||||||
|
` original :: config
|
||||||
|
` config.child = i16
|
||||||
|
` for config.counts |_, index| { config.counts[index] = index + 1 }
|
||||||
|
` if (original.child != i8 or original.counts[1] != 0) return bool
|
||||||
|
` return struct { values [config.counts[1]]config.child, bias i16 = config.bias }
|
||||||
|
`}
|
||||||
`read_selected proc($choice Choice, value i16) i16 {
|
`read_selected proc($choice Choice, value i16) i16 {
|
||||||
` match choice {
|
` match choice {
|
||||||
` .payload |detail|: {
|
` .payload |detail|: {
|
||||||
@@ -268,6 +277,9 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` initialized_values UninitializedValues() :: { values = [19, 21] }
|
` initialized_values UninitializedValues() :: { values = [19, 21] }
|
||||||
` same_initialized_values UninitializedValues() :: initialized_values
|
` same_initialized_values UninitializedValues() :: initialized_values
|
||||||
` if (same_initialized_values.values[0] + same_initialized_values.values[1] + same_initialized_values.bias != 42) return 0
|
` if (same_initialized_values.values[0] + same_initialized_values.values[1] + same_initialized_values.bias != 42) return 0
|
||||||
|
` type_record TypeRecord() :: { values = [19, 21] }
|
||||||
|
` same_type_record TypeRecord() :: type_record
|
||||||
|
` if (same_type_record.values[0] + same_type_record.values[1] + same_type_record.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
|
||||||
|
|||||||
@@ -1482,9 +1482,21 @@ 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 execution_array_storage(state, array.type)) return true
|
record :: (type_record_storage(state, array.type, 0)
|
||||||
|
and ast.expr_kind(&state.tree, array.type) == .record_type)
|
||||||
|
if (!record and !try execution_array_storage(state, array.type)) return true
|
||||||
selected := no_node
|
selected := no_node
|
||||||
if (kind == .index) {
|
if (record) {
|
||||||
|
if (kind != .selection) return true
|
||||||
|
member :: ast.selection_expr(&state.tree, id).member
|
||||||
|
slot :: type_record_slot(state, array.type, member)
|
||||||
|
if (slot == no_index) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
|
.unknown_field, ast.node_span(&state.tree, member))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
selected = ast.field(&state.tree, ast.record_field(&state.tree, array.value, slot)).value?
|
||||||
|
} 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
|
||||||
@@ -1494,7 +1506,7 @@ 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)
|
if (!type_record_storage(state, array.type, 0)
|
||||||
and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
and ast.expr_kind(&state.tree, selected) != .typed_constant) {
|
||||||
descriptor :: ast.array_type(&state.tree, try canonical_type(state, array.type))
|
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),
|
selected = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
||||||
@@ -1539,6 +1551,154 @@ execution_array_storage proc(state @mut State, annotation ast.NodeId) bool ! Err
|
|||||||
return current != no_node and ast.expr_kind(&state.tree, current) == .array_type
|
return current != no_node and ast.expr_kind(&state.tree, current) == .array_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# inspect storage syntax without elaboration: nominal results must enter the cache
|
||||||
|
# before any recursive field calls expand. pointers retain the materialization boundary.
|
||||||
|
@hide:file
|
||||||
|
type_record_storage proc(state @State, annotation ast.NodeId, depth usize) bool {
|
||||||
|
if (depth >= 128) return false
|
||||||
|
resolved :: binding(state, annotation)
|
||||||
|
if (resolved.kind == .builtin) return resolved.data.builtin == .type
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
declaration :: resolved.data.declaration
|
||||||
|
if (ast.node_class(&state.tree, declaration) != .statement) return false
|
||||||
|
view :: ast.statement_decl(&state.tree, declaration)
|
||||||
|
return (view.mutability == .constant
|
||||||
|
and type_record_storage(state, view.initializer, depth + 1))
|
||||||
|
}
|
||||||
|
kind :: ast.expr_kind(&state.tree, annotation)
|
||||||
|
if (kind == .alias_expr)
|
||||||
|
return type_record_storage(state, ast.type_operand(&state.tree, annotation), depth + 1)
|
||||||
|
if (kind == .array_type)
|
||||||
|
return type_record_storage(state, ast.array_type(&state.tree, annotation).element, depth + 1)
|
||||||
|
if (kind != .record_type) return false
|
||||||
|
for (0..ast.record_field_count(&state.tree, annotation)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, i))
|
||||||
|
if (field.type != null and type_record_storage(state, field.type?, depth + 1)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
type_record_slot proc(state @State, record, member ast.NodeId) usize {
|
||||||
|
if (ast.expr_kind(&state.tree, member) != .literal_int) {
|
||||||
|
for (0..ast.record_field_count(&state.tree, record)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, record, i))
|
||||||
|
if (field.name != null and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, member))
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return no_index
|
||||||
|
}
|
||||||
|
ordinal := usize(0)
|
||||||
|
for (ast.literal_text(&state.tree, member)) |byte| {
|
||||||
|
if (byte < '0' or byte > '9') return no_index
|
||||||
|
digit :: usize(byte - '0')
|
||||||
|
if (ordinal > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
||||||
|
ordinal = ordinal * 10 + digit
|
||||||
|
}
|
||||||
|
if (ordinal >= ast.record_field_count(&state.tree, record)) return no_index
|
||||||
|
if (ast.field(&state.tree, ast.record_field(&state.tree, record, ordinal)).name != null) return no_index
|
||||||
|
return ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep ordinary field/default diagnostics even though the enclosing metadata record
|
||||||
|
# has no runtime layout. type leaves are checked here; value leaves use the checker.
|
||||||
|
@hide:file
|
||||||
|
type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
|
||||||
|
if (is_type(state, annotation)) return true
|
||||||
|
type :: try canonical_type(state, annotation)
|
||||||
|
if (type == no_node) return false
|
||||||
|
if (ast.expr_kind(&state.tree, type) == .array_type)
|
||||||
|
return try type_record_check(state, ast.array_type(&state.tree, type).element)
|
||||||
|
for (0..ast.record_field_count(&state.tree, type)) |i| {
|
||||||
|
field_id :: ast.record_field(&state.tree, type, i)
|
||||||
|
field :: ast.field(&state.tree, field_id)
|
||||||
|
if (!try type_step(state, field_id)) return false
|
||||||
|
if (field.name != null and type_record_slot(state, type, field.name?) != i) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (type_record_storage(state, field.type?, 0)) {
|
||||||
|
if (!try type_record_check(state, field.type?)) return false
|
||||||
|
if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false
|
||||||
|
} else {
|
||||||
|
fields [1]ast.NodeId :: [field_id]
|
||||||
|
record :: try ast.add_record_type(state.builder, field.span, fields[..])
|
||||||
|
constraint :: try ast.add_type_operand(state.builder, field.span, record, true)
|
||||||
|
try refresh(state)
|
||||||
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||||
|
if (field.value != null and try type_execution_value(state, field.value?, field.type?) == no_node) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
# normalize complete record snapshots in declaration order, evaluating every leaf
|
||||||
|
# through its declared context. no record containing type values enters runtime IR.
|
||||||
|
@hide:file
|
||||||
|
type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
|
||||||
|
type :: try canonical_type(state, annotation)
|
||||||
|
if (ast.expr_kind(&state.tree, expression) == .typed_constant) {
|
||||||
|
actual :: ast.typed_constant(&state.tree, expression)
|
||||||
|
if (try same_type(state, type, actual.type)) return expression
|
||||||
|
} else if (ast.expr_kind(&state.tree, expression) == .record_literal) {
|
||||||
|
if (!try type_record_check(state, type)) return no_node
|
||||||
|
literal :: ast.record_literal(&state.tree, expression)
|
||||||
|
if (literal.type != null and !try same_type(state, type, literal.type?)) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
count :: ast.record_field_count(&state.tree, type)
|
||||||
|
values :: try mem.alloc(ast.NodeId, state.allocator, count)
|
||||||
|
defer mem.free(state.allocator, values)
|
||||||
|
mem.set(values, no_node)
|
||||||
|
for (0..ast.record_field_count(&state.tree, expression)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, expression, i))
|
||||||
|
slot := i
|
||||||
|
if (field.name) |name| slot = type_record_slot(state, type, name)
|
||||||
|
if (slot >= count or values[slot] != no_node) {
|
||||||
|
code :: if (slot < count) diagnostics.Code.duplicate_field
|
||||||
|
else if (field.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, field.span)
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
values[slot] = field.value?
|
||||||
|
}
|
||||||
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&fields)
|
||||||
|
for (0..count) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, type, i))
|
||||||
|
if (field.name) |name| {
|
||||||
|
if (type_record_slot(state, type, name) != i) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .duplicate_field, field.span)
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input := values[i]
|
||||||
|
if (input == no_node and field.value != null) input = field.value?
|
||||||
|
if (input == no_node) {
|
||||||
|
code :: if (field.name == null) diagnostics.Code.tuple_arity_mismatch else diagnostics.Code.missing_field
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, expression))
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
if (!try type_step(state, input)) return no_node
|
||||||
|
value :: try type_execution_value(state, input, 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)
|
||||||
|
normalized :: try ast.add_record_literal(state.builder, span, null, fields.items)
|
||||||
|
result :: try ast.add_typed_constant(state.builder, span, type, normalized)
|
||||||
|
try refresh(state)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
|
.type_mismatch, ast.node_span(&state.tree, expression))
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
type_array_index proc(state @mut State, expression ast.NodeId, count usize) usize ! Error {
|
type_array_index proc(state @mut State, expression ast.NodeId, count usize) usize ! Error {
|
||||||
usize_type :: try builtin(state, expression, .usize)
|
usize_type :: try builtin(state, expression, .usize)
|
||||||
@@ -1593,6 +1753,12 @@ type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error
|
|||||||
if (!try type_array_initialized(state, ast.array_element(&state.tree, value, i), at)) return false
|
if (!try type_array_initialized(state, ast.array_element(&state.tree, value, i), at)) return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (kind == .record_literal) {
|
||||||
|
for (0..ast.record_field_count(&state.tree, value)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, value, i))
|
||||||
|
if (field.value != null and !try type_array_initialized(state, field.value?, at)) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1697,7 +1863,7 @@ 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)) {
|
if (type_record_storage(state, annotation, 0)) {
|
||||||
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
|
||||||
@@ -1709,48 +1875,84 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
# rebuild only the indexed path so previous snapshots retain their owned values.
|
# rebuild only the field/index path so previous snapshots retain their owned values.
|
||||||
@hide:file
|
@hide:file
|
||||||
type_array_update proc(
|
type_array_update proc(
|
||||||
state @mut State, execution @mut TypeExecution, target, root, current, input ast.NodeId,
|
state @mut State, execution @mut TypeExecution, target, root, current, input ast.NodeId,
|
||||||
) ast.NodeId ! Error {
|
) ast.NodeId ! Error {
|
||||||
# find the outermost index; rebuild its child before copying this parent.
|
# find the outermost projection; rebuild its child before copying this parent.
|
||||||
first := target
|
first := target
|
||||||
while (true) {
|
while (true) {
|
||||||
if (ast.expr_kind(&state.tree, first) != .index) {
|
kind :: ast.expr_kind(&state.tree, first)
|
||||||
|
if (kind != .index and kind != .selection) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
.invalid_assignment_target, ast.node_span(&state.tree, target))
|
.invalid_assignment_target, ast.node_span(&state.tree, target))
|
||||||
return no_node
|
return no_node
|
||||||
}
|
}
|
||||||
base :: ast.index_expr(&state.tree, first).base
|
base :: if (kind == .index) ast.index_expr(&state.tree, first).base
|
||||||
|
else ast.selection_expr(&state.tree, first).base
|
||||||
if (base == root) break
|
if (base == root) break
|
||||||
first = base
|
first = base
|
||||||
}
|
}
|
||||||
array :: ast.typed_constant(&state.tree, current)
|
array :: ast.typed_constant(&state.tree, current)
|
||||||
descriptor :: ast.array_type(&state.tree, array.type)
|
type :: try canonical_type(state, array.type)
|
||||||
if (!descriptor.mutable) {
|
record :: ast.expr_kind(&state.tree, type) == .record_type
|
||||||
|
index := no_index
|
||||||
|
element_type := no_node
|
||||||
|
count := usize(0)
|
||||||
|
previous := no_node
|
||||||
|
if (record) {
|
||||||
|
if (ast.expr_kind(&state.tree, first) == .selection)
|
||||||
|
index = type_record_slot(state, type, ast.selection_expr(&state.tree, first).member)
|
||||||
|
if (index == no_index) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
|
.unknown_field, ast.node_span(&state.tree, first))
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
element_type = ast.field(&state.tree, ast.record_field(&state.tree, type, index)).type?
|
||||||
|
count = ast.record_field_count(&state.tree, array.value)
|
||||||
|
previous = ast.field(&state.tree, ast.record_field(&state.tree, array.value, index)).value?
|
||||||
|
} else {
|
||||||
|
descriptor :: ast.array_type(&state.tree, type)
|
||||||
|
if (!descriptor.mutable or ast.expr_kind(&state.tree, first) != .index) {
|
||||||
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
.invalid_index_assignment, ast.node_span(&state.tree, target))
|
||||||
return no_node
|
return no_node
|
||||||
}
|
}
|
||||||
expression :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index)
|
expression :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index)
|
||||||
if (!try expand_type_matches(state, expression)) return no_node
|
if (!try expand_type_matches(state, expression)) return no_node
|
||||||
count :: ast.array_element_count(&state.tree, array.value)
|
count = ast.array_element_count(&state.tree, array.value)
|
||||||
index :: try type_array_index(state, expression, count)
|
index = try type_array_index(state, expression, count)
|
||||||
if (index == no_index) return no_node
|
if (index == no_index) return no_node
|
||||||
previous :: ast.array_element(&state.tree, array.value, index)
|
element_type = descriptor.element
|
||||||
|
previous = ast.array_element(&state.tree, array.value, index)
|
||||||
|
}
|
||||||
updated := no_node
|
updated := no_node
|
||||||
if (first == target) updated = try type_execution_value(state, input, descriptor.element)
|
if (first == target) updated = try type_execution_value(state, input, element_type)
|
||||||
else updated = try type_array_update(state, execution, target, first, previous, input)
|
else {
|
||||||
|
if (ast.expr_kind(&state.tree, previous) != .typed_constant) {
|
||||||
|
previous = try type_execution_value(state, previous, element_type)
|
||||||
|
if (previous == no_node) return no_node
|
||||||
|
}
|
||||||
|
updated = try type_array_update(state, execution, target, first, previous, input)
|
||||||
|
}
|
||||||
if (updated == no_node) return no_node
|
if (updated == 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..count) |i| {
|
for (0..count) |i| {
|
||||||
element :: if (i == index) updated else ast.array_element(&state.tree, array.value, i)
|
element := no_node
|
||||||
|
if (record) {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, array.value, i))
|
||||||
|
value :: if (i == index) updated else field.value?
|
||||||
|
element = try ast.add_field(state.builder, field.span, field.name, null, value)
|
||||||
|
try refresh(state)
|
||||||
|
} else element = if (i == index) updated else ast.array_element(&state.tree, array.value, i)
|
||||||
try arraylist.append(&elements, element)
|
try arraylist.append(&elements, element)
|
||||||
}
|
}
|
||||||
span :: ast.node_span(&state.tree, target)
|
span :: ast.node_span(&state.tree, target)
|
||||||
literal :: try ast.add_array_literal(state.builder, span, elements.items)
|
literal := no_node
|
||||||
|
if (record) literal = try ast.add_record_literal(state.builder, span, null, elements.items)
|
||||||
|
else literal = try ast.add_array_literal(state.builder, span, elements.items)
|
||||||
result :: try ast.add_typed_constant(state.builder, span, array.type, literal)
|
result :: try ast.add_typed_constant(state.builder, span, array.type, literal)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
return result
|
return result
|
||||||
@@ -1765,6 +1967,10 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
|||||||
# a recursive record needs its cached declaration before expanding its fields.
|
# a recursive record needs its cached declaration before expanding its fields.
|
||||||
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
if (is_type(state, annotation) and nominal_type_value(state, expression)) {
|
||||||
concrete :: try materialize(state, Value{ kind = .type, node = expression })
|
concrete :: try materialize(state, Value{ kind = .type, node = expression })
|
||||||
|
if (type_record_storage(state, expression, 0)) {
|
||||||
|
if (!try type_record_check(state, expression)) return no_node
|
||||||
|
return concrete.node
|
||||||
|
}
|
||||||
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||||
@@ -1779,7 +1985,7 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
|||||||
}
|
}
|
||||||
if (try execution_array_storage(state, annotation)) {
|
if (try execution_array_storage(state, annotation)) {
|
||||||
value :: try type_array_value(state, expression, annotation)
|
value :: try type_array_value(state, expression, annotation)
|
||||||
if (value != no_node and !type_array_annotation(&state.tree, state.bindings.items, annotation)) {
|
if (value != no_node and !type_record_storage(state, annotation, 0)) {
|
||||||
# erased snapshots still owe ordinary array-element/layout checking.
|
# erased snapshots still owe ordinary array-element/layout checking.
|
||||||
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression),
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression),
|
||||||
ast.typed_constant(&state.tree, value).type, true)
|
ast.typed_constant(&state.tree, value).type, true)
|
||||||
@@ -1788,6 +1994,8 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
|||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
if (!is_type(state, annotation) and type_record_storage(state, annotation, 0))
|
||||||
|
return try type_record_value(state, expression, annotation)
|
||||||
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,
|
||||||
@@ -1802,6 +2010,10 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
|
|||||||
return no_node
|
return no_node
|
||||||
}
|
}
|
||||||
concrete :: try materialize(state, value)
|
concrete :: try materialize(state, value)
|
||||||
|
if (type_record_storage(state, concrete.node, 0)) {
|
||||||
|
if (!try type_record_check(state, concrete.node)) return no_node
|
||||||
|
return concrete.node
|
||||||
|
}
|
||||||
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||||
@@ -2006,7 +2218,8 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
|
|||||||
return .invalid
|
return .invalid
|
||||||
}
|
}
|
||||||
if (root != assignment.target) {
|
if (root != assignment.target) {
|
||||||
if (indexed and try execution_array_storage(state, execution.locals.items[index].annotation)) {
|
if ((indexed and try execution_array_storage(state, execution.locals.items[index].annotation))
|
||||||
|
or type_record_storage(state, execution.locals.items[index].annotation, 0)) {
|
||||||
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
|
||||||
@@ -2442,6 +2655,7 @@ evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
if (left.kind == .type) {
|
if (left.kind == .type) {
|
||||||
operands [2]ast.NodeId :: [view.left, view.right]
|
operands [2]ast.NodeId :: [view.left, view.right]
|
||||||
for (operands) |operand| {
|
for (operands) |operand| {
|
||||||
|
if (type_record_storage(state, operand, 0)) continue
|
||||||
constraint :: try ast.add_type_operand(state.builder,
|
constraint :: try ast.add_type_operand(state.builder,
|
||||||
ast.node_span(&state.tree, operand), operand, true)
|
ast.node_span(&state.tree, operand), operand, true)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
|
|||||||
+198
-1
@@ -11,6 +11,174 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"type_records_local_schema_defaults_and_owned_copies": (
|
||||||
|
"""
|
||||||
|
Storage proc() type {
|
||||||
|
Config :: struct { child type = i8, count usize = 2, text string = "ok" }
|
||||||
|
config Config := {}
|
||||||
|
original :: config
|
||||||
|
field!(config, "child") = i16
|
||||||
|
config.count = config.count + 1
|
||||||
|
config.text = "owned"
|
||||||
|
if (original.child != i8 or original.count != 2 or original.text.len != 2) return bool
|
||||||
|
return struct { values [config.count]config.child, bias original.child = 2 }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Storage() :: { values = [19, 21, 0] }
|
||||||
|
same Storage() :: value
|
||||||
|
return same.values[0] + same.values[1] + i16(same.bias)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"type_records_nested_arrays_borrowed_iteration_and_capture_copies": (
|
||||||
|
"""
|
||||||
|
Storage proc() type {
|
||||||
|
Item :: struct { child type, values [1]mut usize }
|
||||||
|
config struct { items [2]mut Item, bias struct { value i16 } } := {
|
||||||
|
items = [{ child = i8, values = [1] }, { child = bool, values = [1] }],
|
||||||
|
bias = { value = 0 },
|
||||||
|
}
|
||||||
|
original :: config
|
||||||
|
for config.items |item, index| {
|
||||||
|
config.items[1].child = i16
|
||||||
|
config.items[index].values[0] = index + 1
|
||||||
|
config.bias.value = config.bias.value + 1
|
||||||
|
if (index == 0 and item.child != i8) return bool
|
||||||
|
if (index == 1 and item.child != i16) return bool
|
||||||
|
copied := item
|
||||||
|
copied.child = u8
|
||||||
|
if (item.child == u8) return bool
|
||||||
|
}
|
||||||
|
if (original.items[1].child != bool or original.items[1].values[0] != 1) return bool
|
||||||
|
return struct { values [config.items[1].values[0]]config.items[1].child, bias i16 = config.bias.value }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Storage() :: { values = [19, 21] }
|
||||||
|
return value.values[0] + value.values[1] + value.bias
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"type_records_inferred_tuples_nested_replacement_and_nominal_leaves": (
|
||||||
|
"""
|
||||||
|
Storage proc() type {
|
||||||
|
Child :: struct { value i16 }
|
||||||
|
config := { nested = { child = Child, count = usize(1) }, tuple = {i8, usize(2)} }
|
||||||
|
saved :: config
|
||||||
|
config.nested = saved.nested
|
||||||
|
config.tuple.0 = i16
|
||||||
|
config.nested.count = config.tuple.1
|
||||||
|
if (saved.tuple.0 != i8 or config.nested.child != Child) return bool
|
||||||
|
Element :: field!(config.tuple, "0")
|
||||||
|
return struct { first config.nested.child, second Child, bias Element = 2 }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
value Storage() :: { first = { value = 19 }, second = { value = 21 } }
|
||||||
|
copied := value.first
|
||||||
|
copied = value.second
|
||||||
|
return value.first.value + copied.value + value.bias
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"type_records_reject_unknown_initializer_field": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { child type } :: { child = i16, missing = 1 }
|
||||||
|
return config.child
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"no such field",
|
||||||
|
),
|
||||||
|
"type_records_reject_duplicate_initializer_field": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { child type } :: { child = i16, child = i8 }
|
||||||
|
return config.child
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"more than once",
|
||||||
|
),
|
||||||
|
"type_records_reject_missing_initializer_field": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { child type, count usize } :: { child = i16 }
|
||||||
|
return config.child
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"missing a required field",
|
||||||
|
),
|
||||||
|
"type_records_reject_non_type_leaf": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { child type } :: { child = 42 }
|
||||||
|
return config.child
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"type_records_preserve_value_leaf_overflow": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { child type, count i8 } := { child = i16, count = 127 }
|
||||||
|
config.count = config.count + 1
|
||||||
|
return config.child
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"type_records_reject_immutable_capture_updates": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
for [{ child = i16 }] |item| { item.child = i8 }
|
||||||
|
return i16
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"writable array element",
|
||||||
|
),
|
||||||
|
"type_records_reject_readonly_nested_array_updates": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config struct { children [1]type } := { children = [i16] }
|
||||||
|
config.children[0] = i8
|
||||||
|
return i16
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"writable array element",
|
||||||
|
),
|
||||||
|
"type_records_reject_unknown_selected_field": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
config :: { child = i16 }
|
||||||
|
return field!(config, "missing")
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"no such field",
|
||||||
|
),
|
||||||
|
"type_records_reject_unused_invalid_value_field": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
Config :: struct { child type, invalid void }
|
||||||
|
return i16
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"42\n", b"void cannot be stored",
|
||||||
|
),
|
||||||
|
"type_records_reject_unused_invalid_default": (
|
||||||
|
"""
|
||||||
|
Bad proc() type {
|
||||||
|
Config :: struct { child type, count i8 = 128 }
|
||||||
|
return i16
|
||||||
|
}
|
||||||
|
main proc() Bad() { return 42 }
|
||||||
|
""", b"", b"outside its runtime type",
|
||||||
|
),
|
||||||
|
"type_records_do_not_enable_runtime_storage": (
|
||||||
|
"""
|
||||||
|
main proc() i16 {
|
||||||
|
config :: { child = i16, count = 42 }
|
||||||
|
return config.count
|
||||||
|
}
|
||||||
|
""", b"", b"type name cannot be used as a value",
|
||||||
|
),
|
||||||
"field_reflection_runtime_places_callbacks_and_single_evaluation": (
|
"field_reflection_runtime_places_callbacks_and_single_evaluation": (
|
||||||
"""
|
"""
|
||||||
Point :: struct { value i16, callback @proc(value i16) i16 }
|
Point :: struct { value i16, callback @proc(value i16) i16 }
|
||||||
@@ -5454,6 +5622,14 @@ def check_specialization_packages(temporary, executable):
|
|||||||
.empty: bool,
|
.empty: bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TypeRecord proc(T type) type {
|
||||||
|
config struct { child type, count usize } := { child = i8, count = 1 }
|
||||||
|
original :: config
|
||||||
|
config.child = T
|
||||||
|
config.count = 2
|
||||||
|
if (original.child != i8 or original.count != 1) return bool
|
||||||
|
return struct { values [config.count]config.child }
|
||||||
|
}
|
||||||
""").strip() + "\n")
|
""").strip() + "\n")
|
||||||
(library / "functions.hon").write_text(textwrap.dedent("""
|
(library / "functions.hon").write_text(textwrap.dedent("""
|
||||||
identity proc($T type, value T) T { return value }
|
identity proc($T type, value T) T { return value }
|
||||||
@@ -5496,6 +5672,15 @@ def check_specialization_packages(temporary, executable):
|
|||||||
}
|
}
|
||||||
""").strip() + "\n")
|
""").strip() + "\n")
|
||||||
check(app, executable, (b"42\n", None))
|
check(app, executable, (b"42\n", None))
|
||||||
|
source.write_text(textwrap.dedent("""
|
||||||
|
lib :: import "../library"
|
||||||
|
main proc() i16 {
|
||||||
|
value lib.TypeRecord(i16) :: { values = [19, 23] }
|
||||||
|
same lib.TypeRecord(i16) :: value
|
||||||
|
return same.values[0] + same.values[1]
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
check(app, executable, (b"42\n", None))
|
||||||
source.write_text(textwrap.dedent("""
|
source.write_text(textwrap.dedent("""
|
||||||
lib :: import "../library"
|
lib :: import "../library"
|
||||||
Box proc($T type) type { return struct { value T } }
|
Box proc($T type) type { return struct { value T } }
|
||||||
@@ -5705,7 +5890,19 @@ def check_specialization_packages(temporary, executable):
|
|||||||
interpreted = run(str(HONEY), "run", str(app))
|
interpreted = run(str(HONEY), "run", str(app))
|
||||||
assert interpreted.stderr.startswith(f"{sizing.resolve()}:2:".encode()), outcome(interpreted)
|
assert interpreted.stderr.startswith(f"{sizing.resolve()}:2:".encode()), outcome(interpreted)
|
||||||
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
||||||
return 16
|
sizing.write_text(textwrap.dedent("""
|
||||||
|
BadRecord proc() type {
|
||||||
|
config :: { child = i16, count = i8(127) }
|
||||||
|
next :: config.count + 1
|
||||||
|
return [usize(next)]config.child
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
source.write_text('lib :: import "../library"\nmain proc() lib.BadRecord() { return 42 }\n')
|
||||||
|
check(app, executable, (b"", b"integer arithmetic overflow"))
|
||||||
|
interpreted = run(str(HONEY), "run", str(app))
|
||||||
|
assert interpreted.stderr.startswith(f"{sizing.resolve()}:3:".encode()), outcome(interpreted)
|
||||||
|
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
||||||
|
return 18
|
||||||
|
|
||||||
|
|
||||||
def check_dependency_provenance(temporary, executable):
|
def check_dependency_provenance(temporary, executable):
|
||||||
|
|||||||
Reference in New Issue
Block a user