feat: construct uninitialized compile-time type arrays

This commit is contained in:
2026-10-27 18:01:00 +01:00
parent e00a2abe27
commit ca53dab86b
12 changed files with 307 additions and 13 deletions
+4 -2
View File
@@ -98,7 +98,7 @@ 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 steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. cleanup and type-valued record storage in type-producing bodies remain outside this checkpoint. type-producing bodies support mutable type and value locals, direct binding assignment, scoped blocks, conditional/early returns, integer range and while loops, break/continue and nested/recursive type-producing calls. for example, start with `Result := T`, repeat `Result = [2]Result` in a loop, then `return Result` to generate nested array storage. snapshots retain each local's current value without changing the source AST; scalar operations reuse checking, typed IR and interpreter execution, preserving exact types and overflow diagnostics. type-control execution is limited to 4096 statement, loop-guard and uninitialized type-array construction steps shared with nested producers; individual scalar evaluations retain the interpreter limits below. only executed bodies are elaborated, while every body is still parsed and name-resolved. cleanup and type-valued record storage in type-producing bodies remain outside this checkpoint.
field/index assignments also update initialized, materializable aggregate locals in type-producing bodies, including nested record fields and mutable fixed-array elements. for example, `counts [2]mut usize := [0, 0]` followed by `counts[1] = 2` can generate `[counts[1]]i16` storage. each update uses ordinary checked IR and returns a new owned constant: earlier copies remain unchanged, while later loop iterations, generated lengths and defaults observe the update. ordinary mutability, index-type/bounds, field and exact value-type checks still apply, including immutable payload captures and narrow arithmetic overflow. imported producers and repeated compilation retain source diagnostics and nominal cache identity; pointer/slice-backed mutation and type-valued record storage remain open. field/index assignments also update initialized, materializable aggregate locals in type-producing bodies, including nested record fields and mutable fixed-array elements. for example, `counts [2]mut usize := [0, 0]` followed by `counts[1] = 2` can generate `[counts[1]]i16` storage. each update uses ordinary checked IR and returns a new owned constant: earlier copies remain unchanged, while later loop iterations, generated lengths and defaults observe the update. ordinary mutability, index-type/bounds, field and exact value-type checks still apply, including immutable payload captures and narrow arithmetic overflow. imported producers and repeated compilation retain source diagnostics and nominal cache identity; pointer/slice-backed mutation and type-valued record storage remain open.
@@ -106,7 +106,9 @@ type-producing bodies also iterate materializable fixed arrays and strings with
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.
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, uninitialized arrays, type-valued record storage and reflection 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. `undefined` does not enable runtime storage or ordinary compile-time value-array construction.
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.
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.
+6 -4
View File
@@ -32,8 +32,9 @@ if/while captures and tagged payload matches select nested constructors, lengths
array/slice/string/pointer-to-array collection loops support exact copy/pointer array/slice/string/pointer-to-array collection loops support exact copy/pointer
captures and `usize` indices through both paths; type-producing bodies iterate 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.
initialized type-valued fixed arrays support nested copies, indexed/whole updates, type-valued fixed arrays support nested copies, indexed/whole updates,
checked projections and by-value collection captures in type-producing bodies. checked projections and by-value collection captures in type-producing bodies;
`undefined` arrays track initialization per leaf while loops fill generated types.
type-array parameters and results cross compile-time procedure boundaries with type-array parameters and results cross compile-time procedure boundaries with
owned snapshots, cached nominal identity and erased arguments. owned snapshots, cached nominal identity and erased arguments.
`field!` selects record/tuple/enum and sequence members using compile-time names, `field!` selects record/tuple/enum and sequence members using compile-time names,
@@ -89,9 +90,10 @@ test discovery and FFI remain outside these completed slices.
- [x] optional type-control checkpoint: type-producing bodies execute optional if/while guards with immutable by-value payload captures. ordinary checked IR validates guards; selected scalar/aggregate payloads retain exact types and owned strings across nested captures, generated storage/defaults, loop updates/transfers and early returns. absent optionals skip unselected bodies; invalid guards, capture mutation/shadowing, overflow and execution bounds retain diagnostics. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. null data/function pointers materialize correctly; nonnull storage-bearing payloads retain the constant-materialization escape boundary. - [x] optional type-control checkpoint: type-producing bodies execute optional if/while guards with immutable by-value payload captures. ordinary checked IR validates guards; selected scalar/aggregate payloads retain exact types and owned strings across nested captures, generated storage/defaults, loop updates/transfers and early returns. absent optionals skip unselected bodies; invalid guards, capture mutation/shadowing, overflow and execution bounds retain diagnostics. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. null data/function pointers materialize correctly; nonnull storage-bearing payloads retain the constant-materialization escape boundary.
- [x] aggregate type-update checkpoint: type-producing bodies update initialized, materializable aggregate locals through nested record fields and mutable fixed-array indices. ordinary checked IR validates and executes each assignment, retaining exact value types, mutability (including immutable payload captures), index types/bounds, unknown-field and narrow-overflow diagnostics. new owned constants preserve earlier by-value copies while later loops, generated storage and defaults observe updates. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. pointer/slice-backed mutation, uninitialized aggregate construction and type-valued record storage remain open; type-valued arrays are covered below. - [x] aggregate type-update checkpoint: type-producing bodies update initialized, materializable aggregate locals through nested record fields and mutable fixed-array indices. ordinary checked IR validates and executes each assignment, retaining exact value types, mutability (including immutable payload captures), index types/bounds, unknown-field and narrow-overflow diagnostics. new owned constants preserve earlier by-value copies while later loops, generated storage and defaults observe updates. imported producers, dependency provenance, nominal cache identity and repeatable compilation work through interpreter/native execution. pointer/slice-backed mutation, uninitialized aggregate construction and type-valued record storage remain open; type-valued arrays are covered below.
- [x] 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 arrays, type-valued record storage and reflection remain open. - [x] type-array storage checkpoint: type-producing bodies own initialized fixed arrays of type values, including explicit/inferred nested arrays, `.len`, checked `usize` indexing, whole/indexed updates and immutable by-value collection captures. persistent snapshots preserve copies and exact shape/mutability while borrowed array places observe later updates. selected types generate nested storage, constructors, signatures and defaults through interpreter/native execution, retaining selected-body and index/type/mutability diagnostics, imported provenance, nominal cache identity, shared bounds and repeatable compilation. uninitialized type arrays are covered below; type-valued record storage and reflection remain open.
- [x] type-array procedure checkpoint: fixed arrays of type values cross compile-time procedure inputs/results, including nested/aliased results, dependent `$N` lengths, empty shapes and erased `$types` parameters in runtime generic procedures. all inputs to type/type-array producers are explicit constants; owned snapshots preserve by-value isolation, exact shape/mutability and canonical/nominal cache identity. projections generate signatures, constructors, storage and defaults through interpreter/native execution. malformed inputs/results, runtime dependencies, imported provenance, shared execution bounds and repeatable compilation retain diagnostics and isolation. runtime type-array storage, uninitialized arrays, type-valued record storage and reflection remain open. - [x] type-array procedure checkpoint: fixed arrays of type values cross compile-time procedure inputs/results, including nested/aliased results, dependent `$N` lengths, empty shapes and erased `$types` parameters in runtime generic procedures. all inputs to type/type-array producers are explicit constants; owned snapshots preserve by-value isolation, exact shape/mutability and canonical/nominal cache identity. projections generate signatures, constructors, storage and defaults through interpreter/native execution. malformed inputs/results, runtime dependencies, imported provenance, shared execution bounds and repeatable compilation retain diagnostics and isolation. runtime type-array storage, type-valued record storage and reflection remain open.
- [x] field-selection reflection checkpoint: `field!(base, name)` evaluates/erases a constant string name and reuses ordinary selection through interpreter/native execution. records/tuples, enum/error-set members, tagged payloads, callbacks, pointer-qualified places and sequence `.len`/`.ptr` retain exact types, single base evaluation, address-taking, mutability and active-field checks. type-producing bodies snapshot changing selector names for nested aggregate updates without losing by-value isolation. runtime/nonstring names, malformed calls, missing members and noncanonical/overflowing tuple ordinals retain diagnostics; full metadata reflection and inline expansion remain open. - [x] field-selection reflection checkpoint: `field!(base, name)` evaluates/erases a constant string name and reuses ordinary selection through interpreter/native execution. records/tuples, enum/error-set members, tagged payloads, callbacks, pointer-qualified places and sequence `.len`/`.ptr` retain exact types, single base evaluation, address-taking, mutability and active-field checks. type-producing bodies snapshot changing selector names for nested aggregate updates without losing by-value isolation. runtime/nonstring names, malformed calls, missing members and noncanonical/overflowing tuple ordinals retain diagnostics; full metadata reflection and inline expansion remain open.
- [x] uninitialized type-array checkpoint: fixed type arrays in type/type-array producers start with `undefined` and fill through loop-driven indexed, nested and whole-array updates. persistent local copies retain independent initialization state; `.len` works before leaves initialize. premature leaf reads and incomplete procedure arguments/results diagnose read-before-initialization without caching invalid values. explicit nonnegative dimensions, empty shapes, ordinary mutability/shape checks, nominal identity and repeatable compilation work through interpreter/native execution. construction shares the type-control bound per expanded cell, including nested zero-leaf shapes. runtime undefined storage, ordinary value-array construction, type-valued record storage and metadata reflection remain open.
- [ ] 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.
+6 -1
View File
@@ -39,6 +39,7 @@ LiteralKind :: enum {
bool bool
null null
void void
undefined
} }
ExprKind :: enum { ExprKind :: enum {
@@ -78,6 +79,7 @@ ExprKind :: enum {
typed_constant typed_constant
comptime_expr comptime_expr
literal_void literal_void
literal_undefined
} }
DeclKind :: enum { DeclKind :: enum {
@@ -184,6 +186,7 @@ NodeKind :: enum {
expr_comptime expr_comptime
literal_float_value literal_float_value
literal_void literal_void
literal_undefined
} }
@hide:file @hide:file
@@ -499,6 +502,7 @@ add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem
.bool: .literal_bool .bool: .literal_bool
.null: .literal_null .null: .literal_null
.void: .literal_void .void: .literal_void
.undefined: .literal_undefined
} }
return try add_node(builder, Node{ kind = node_kind }, span) return try add_node(builder, Node{ kind = node_kind }, span)
} }
@@ -1433,6 +1437,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_comptime: .comptime_expr .expr_comptime: .comptime_expr
.literal_float_value: .literal_float .literal_float_value: .literal_float
.literal_void: .literal_void .literal_void: .literal_void
.literal_undefined: .literal_undefined
else: unreachable else: unreachable
} }
} }
@@ -1828,7 +1833,7 @@ node_layout proc(node Node) NodeLayout {
layout := NodeLayout{} layout := NodeLayout{}
match node.kind { match node.kind {
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null, .literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
.literal_void, .expr_identifier, .invalid_expr: {} .literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2 .expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type: .expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
+18 -1
View File
@@ -195,6 +195,17 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
`read_type_rows proc($rows [2]mut [1]mut type, value rows[0][0]) rows[1][0] { `read_type_rows proc($rows [2]mut [1]mut type, value rows[0][0]) rows[1][0] {
` return value + rows[1][0](rows.len) ` return value + rows[1][0](rows.len)
`} `}
`UninitializedRows proc() [2]mut type {
` types [2]mut type := undefined
` for (usize(0)..types.len) |index| {
` if (index == 0) { types[index] = struct { value i16 } }
` else { types[index] = types[0] }
` }
` return types
`}
`UninitializedType proc(types [2]mut type) type {
` return struct { first types[0], second types[1] }
`}
`read_selected proc($choice Choice, value i16) i16 { `read_selected proc($choice Choice, value i16) i16 {
` match choice { ` match choice {
` .payload |detail|: { ` .payload |detail|: {
@@ -236,6 +247,12 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` if (copy_type_rows(TypeRows())[0][0] != u8 or copy_type_rows(TypeRows())[1][0] != i16) return 0 ` if (copy_type_rows(TypeRows())[0][0] != u8 or copy_type_rows(TypeRows())[1][0] != i16) return 0
` if (TypeRows()[0][0] != i16) return 0 ` if (TypeRows()[0][0] != i16) return 0
` if (read_type_rows(TypeRows(), 40) != 42 or read_type_rows(TypeRows(), 40) != 42) return 0 ` if (read_type_rows(TypeRows(), 40) != 42 or read_type_rows(TypeRows(), 40) != 42) return 0
` initialized UninitializedType(UninitializedRows()) :: {
` first = { value = 19 }, second = { value = 21 },
` }
` same_initialized UninitializedType(UninitializedRows()) :: initialized
` copied_initialized := same_initialized.first
` 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
` match packet.choices[1] { .empty: {}, .payload: return 0 } ` match packet.choices[1] { .empty: {}, .payload: return 0 }
@@ -244,7 +261,7 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
` .failed |detail|: detail.code + i16(detail.text.len), ` .failed |detail|: detail.code + i16(detail.text.len),
` } ` }
` if (error_code != 7) return 0 ` if (error_code != 7) return 0
` return read_selected(packet.choices[0], identity(same.values[0]) + same.values[$(usize(1))] + same.bias - 2) ` return read_selected(packet.choices[0], identity(initialized.first.value) + copied_initialized.value + same.values[$(usize(1))] + same.bias - 2)
`} `}
, ,
) )
+1
View File
@@ -23,6 +23,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "proc", .proc }, { "proc", .proc },
{ "c_func", .c_func }, { "c_func", .c_func },
{ "null", .null }, { "null", .null },
{ "undefined", .undefined },
{ "return", .return }, { "return", .return },
{ "break", .break }, { "break", .break },
{ "continue", .continue }, { "continue", .continue },
+1
View File
@@ -33,6 +33,7 @@ TokenKind :: enum {
mut mut
c_func c_func
null null
undefined
# literals # literals
ident, string ident, string
+1 -1
View File
@@ -1893,7 +1893,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span) if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
} }
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id)) .alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
.enum_literal: { .enum_literal, .literal_undefined: {
if (!trap_type(state, id)) unreachable if (!trap_type(state, id)) unreachable
} }
.match_expr: return try lower_match(state, id) .match_expr: return try lower_match(state, id)
+6 -2
View File
@@ -299,7 +299,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.defer, .errdefer: return try parse_cleanup(state, allow_else) .defer, .errdefer: return try parse_cleanup(state, allow_else)
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state)) .open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct, .ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct,
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .try, .dollar: { .enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .undefined, .try, .dollar: {
expression :: try parse_expr(state) expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else) if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(state.builder, expression) return try ast.add_expression_stmt(state.builder, expression)
@@ -852,7 +852,7 @@ member_name proc(kind lexer.TokenKind) bool {
return match kind { return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue, .ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct, .true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test, .c_func, .null, .try, .catch, .defer, .errdefer: true .alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
else: false else: false
} }
} }
@@ -1682,6 +1682,10 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1 state.next_token += 1
return try ast.add_literal(state.builder, .null, token.span) return try ast.add_literal(state.builder, .null, token.span)
} }
.undefined: {
state.next_token += 1
return try ast.add_literal(state.builder, .undefined, token.span)
}
.ident: { .ident: {
state.next_token += 1 state.next_token += 1
return try ast.add_identifier(state.builder, token.span, token.data.str_id) return try ast.add_identifier(state.builder, token.span, token.data.str_id)
+1
View File
@@ -1201,6 +1201,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
.literal_string: state.cells[usize(id)].type = Type{ kind = .string } .literal_string: state.cells[usize(id)].type = Type{ kind = .string }
.literal_null: state.cells[usize(id)].type = Type{ kind = .null } .literal_null: state.cells[usize(id)].type = Type{ kind = .null }
.literal_void: state.cells[usize(id)].type = Type{ kind = .void } .literal_void: state.cells[usize(id)].type = Type{ kind = .void }
.literal_undefined: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
.builtin_call: try collect_builtin(state, id) .builtin_call: try collect_builtin(state, id)
.slice: { .slice: {
view :: ast.slice_expr(tree, id) view :: ast.slice_expr(tree, id)
+2 -1
View File
@@ -522,7 +522,8 @@ resolve_expr proc(
try resolve_expr(state, scope, value.type) try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value) try resolve_expr(state, scope, value.value)
} }
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void: {} .literal_int, .literal_float, .literal_bool, .literal_string, .literal_null, .literal_void,
.literal_undefined: {}
.enum_literal: {} .enum_literal: {}
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id)) .distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
.enum_type: { .enum_type: {
+73 -1
View File
@@ -938,7 +938,7 @@ argument_annotation proc(
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error { evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
if (type_array_annotation(&state.tree, state.bindings.items, annotation)) { if (type_array_annotation(&state.tree, state.bindings.items, annotation)) {
constant :: try type_execution_value(state, id, annotation) constant :: try type_execution_value(state, id, annotation)
if (constant == no_node) return Value{} if (constant == no_node or !try type_array_initialized(state, constant, id)) return Value{}
value :: ast.typed_constant(&state.tree, constant) value :: ast.typed_constant(&state.tree, constant)
return Value{ kind = .aggregate, node = value.value, annotation = value.type } return Value{ kind = .aggregate, node = value.value, annotation = value.type }
} }
@@ -1484,6 +1484,11 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
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) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, id))
return false
}
} else { } else {
member :: ast.selection_expr(&state.tree, id).member member :: ast.selection_expr(&state.tree, id).member
if (!mem.eql(ast.literal_text(&state.tree, member), "len")) { if (!mem.eql(ast.literal_text(&state.tree, member), "len")) {
@@ -1562,8 +1567,68 @@ type_array_compatible proc(state @mut State, expected, actual ast.NodeId) bool !
return try type_array_compatible(state, a.element, b.element) return try type_array_compatible(state, a.element, b.element)
} }
# undefined leaves are initialization state, not type values. snapshots may copy
# that state locally; procedure boundaries require every leaf to be initialized.
@hide:file
type_array_initialized proc(state @mut State, value, at ast.NodeId) bool ! Error {
kind :: ast.expr_kind(&state.tree, value)
if (kind == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, at))
return false
}
if (kind == .typed_constant)
return try type_array_initialized(state, ast.typed_constant(&state.tree, value).value, at)
if (kind == .array_literal) {
for (0..ast.array_element_count(&state.tree, value)) |i| {
if (!try type_array_initialized(state, ast.array_element(&state.tree, value, i), at)) return false
}
}
return true
}
# bound the expanded snapshot, not just its dimensions, so later reads and
# initialization checks cannot traverse an exponentially shared empty shape.
@hide:file
type_array_undefined proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error {
if (is_type(state, annotation)) return expression
type :: try canonical_type(state, annotation)
descriptor :: ast.array_type(&state.tree, type)
length := Value{}
if (descriptor.count) |count| length = try evaluate(state, count)
if (length.kind != .integer or length.negative) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_array_length, ast.node_span(&state.tree, annotation))
return no_node
}
# validate child dimensions even when this dimension is empty.
child :: try type_array_undefined(state, expression, descriptor.element)
if (child == no_node) return no_node
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (0..length.magnitude) |i| {
if (!try type_step(state, expression)) return no_node
element := child
if (i != 0) element = try type_array_undefined(state, expression, descriptor.element)
if (element == no_node) return no_node
try arraylist.append(&elements, element)
}
span :: ast.node_span(&state.tree, expression)
type_span :: ast.node_span(&state.tree, annotation)
element_type :: if (ast.expr_kind(&state.tree, child) == .typed_constant)
ast.typed_constant(&state.tree, child).type else descriptor.element
count :: try ast.add_integer(state.builder, span, length.magnitude)
concrete :: try ast.add_array_type(state.builder, type_span, count, element_type, descriptor.mutable)
literal :: try ast.add_array_literal(state.builder, span, elements.items)
result :: try ast.add_typed_constant(state.builder, span, concrete, literal)
try refresh(state)
return result
}
@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)
return try type_array_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) {
@@ -1695,6 +1760,11 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
} }
if (try type_array_storage(state, annotation)) return try type_array_value(state, expression, annotation) if (try type_array_storage(state, annotation)) return try type_array_value(state, expression, annotation)
if (is_type(state, annotation)) { if (is_type(state, annotation)) {
if (ast.expr_kind(&state.tree, expression) == .literal_undefined) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.read_before_initialization, ast.node_span(&state.tree, expression))
return no_node
}
value :: try evaluate(state, expression) value :: try evaluate(state, expression)
if (value.kind != .type) { if (value.kind != .type) {
if (state.evaluation_diagnostic == diagnostics.no_diagnostic) if (state.evaluation_diagnostic == diagnostics.no_diagnostic)
@@ -1832,6 +1902,8 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
if (ast.return_stmt(&state.tree, statement).value) |value| { if (ast.return_stmt(&state.tree, statement).value) |value| {
expression :: try type_snapshot(state, execution, value) expression :: try type_snapshot(state, execution, value)
execution.returned = try type_execution_value(state, expression, execution.result_type) execution.returned = try type_execution_value(state, expression, execution.result_type)
if (execution.returned != no_node
and !try type_array_initialized(state, execution.returned, value)) return .invalid
if (execution.returned != no_node) return .returned if (execution.returned != no_node) return .returned
} }
return .invalid return .invalid
+188
View File
@@ -1317,6 +1317,194 @@ CASES = {
} }
""", b"42\n", None, """, b"42\n", None,
), ),
"type_arrays_undefined_loop_initialization_generates_storage": (
"""
Storage proc() type {
types [2]mut type := undefined
if (types.len != 2) return bool
for (usize(0)..types.len) |index| { types[index] = i16 }
return struct { values [types.len]types[0], bias types[1] = 2 }
}
main proc() i16 {
value Storage() :: { values = [19, 21] }
same Storage() :: value
return same.values[0] + same.values[1] + same.bias
}
""", b"42\n", None,
),
"type_arrays_undefined_nested_updates_partial_copies_and_replacement": (
"""
Storage proc() type {
rows [2]mut [2]mut type := undefined
if (rows.len != 2 or rows[0].len != 2) return bool
(rows[0])[0] = i8
copied := rows
(rows[0])[1] = u8
rows[1] = [i8, bool]
(copied[0])[1] = i16
copied[1] = [bool, i16]
if (rows[0][0] != i8 or copied[0][0] != i8) return bool
if (rows[0][1] != u8 or rows[1][0] != i8 or rows[1][1] != bool) return bool
replacement [2]mut type := undefined
replacement = [i16, bool]
return struct { first copied[0][1], second copied[1][1], bias replacement[0] = 2 }
}
main proc() i16 {
value Storage() :: { first = 19, second = 21 }
return value.first + value.second + value.bias
}
""", b"42\n", None,
),
"type_arrays_undefined_empty_shapes_and_erased_generic_consumer": (
"""
empty_rows proc() [2]mut [0]mut type {
rows [2]mut [0]mut type := undefined
return rows
}
empty_outer proc() [0]mut [2]mut type {
rows [0]mut [2]mut type := undefined
return rows
}
row proc() [2]mut type {
types [2]mut type := undefined
rows :: empty_rows()
outer :: empty_outer()
if (rows.len != 2 or rows[0].len != 0 or rows[1].len != 0 or outer.len != 0) {
return [bool, bool]
}
for rows |empty| { for empty |_| { return [bool, bool] } }
types[0] = i16
types[1] = bool
return types
}
add proc($types [2]mut type, left types[0], right types[0]) types[0] {
enabled types[1] :: true
if (enabled) return left + right
return 0
}
forward proc(value i16) i16 { return add(row(), value, 2) }
main proc() i16 { return forward(40) }
""", b"42\n", None,
),
"type_arrays_undefined_reject_premature_leaf_read": (
"""
Bad proc() type {
types [2]mut type := undefined
types[0] = i16
return types[1]
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_arrays_undefined_reject_partial_return": (
"""
rows proc() [2]mut [1]mut type {
types [2]mut [1]mut type := undefined
(types[0])[0] = i16
return types
}
main proc() rows()[0][0] { return 42 }
""", b"", b"local value was read before initialization",
),
"type_arrays_undefined_reject_partial_argument": (
"""
Selected proc(types [2]mut [1]mut type) type { return i16 }
Bad proc() type {
types [2]mut [1]mut type := undefined
(types[0])[0] = i16
return Selected(types)
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_arrays_undefined_reject_partial_erased_argument": (
"""
Selected proc($types [2]mut type) type { return i16 }
Bad proc() type {
types [2]mut type := undefined
types[0] = i16
return Selected(types)
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_arrays_undefined_copy_does_not_observe_later_initialization": (
"""
Bad proc() type {
types [2]mut type := undefined
types[0] = i16
copied :: types
types[1] = bool
return copied[1]
}
main proc() Bad() { return 42 }
""", b"", b"local value was read before initialization",
),
"type_arrays_undefined_reject_readonly_element_write": (
"""
Bad proc() type {
types [1]type := undefined
types[0] = i16
return types[0]
}
main proc() Bad() { return 42 }
""", b"", b"indexed assignment requires a writable array element",
),
"type_arrays_undefined_reject_inferred_length": (
"""
Bad proc() type {
types [_]mut type := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"array length must be a nonnegative supported compile-time integer",
),
"type_arrays_undefined_empty_outer_rejects_inferred_child_length": (
"""
Bad proc() type {
types [0]mut [_]mut type := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"array length must be a nonnegative supported compile-time integer",
),
"type_arrays_undefined_construction_is_bounded": (
"""
Bad proc() type {
types [1000000000]mut type := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"compile-time execution exceeds its recursion or step limit",
),
"type_arrays_undefined_nested_empty_construction_is_bounded": (
"""
Bad proc() type {
types [100]mut [100]mut [0]mut type := undefined
return i16
}
main proc() Bad() { return 42 }
""", b"", b"compile-time execution exceeds its recursion or step limit",
),
"type_arrays_undefined_does_not_enable_runtime_storage": (
"""
main proc() i16 {
values [1]mut i16 := undefined
values[0] = 42
return values[0]
}
""", b"", b"generic argument must be a compile-time value",
),
"type_arrays_undefined_does_not_enable_comptime_value_storage": (
"""
Bad proc() type {
values [1]mut usize := undefined
values[0] = 1
return i16
}
main proc() Bad() { return 42 }
""", b"", b"generic argument must be a compile-time value",
),
"type_arrays_only_execute_selected_bodies": ( "type_arrays_only_execute_selected_bodies": (
""" """
Selected proc(ok bool) type { Selected proc(ok bool) type {