From 4a1b30d9d50861a69c5e01a4b3976c9a9dc432a5 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Fri, 30 Oct 2026 21:30:36 +0100 Subject: [PATCH] feat: execute readonly type-valued compile-time slices --- README.md | 2 + TODO.md | 4 + source/compilation/compilation.test.hon | 84 ++++++ source/sema/specialize/specialize.hon | 177 ++++++++++-- tests/native.py | 363 ++++++++++++++++++++++++ 5 files changed, 610 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b6c2f8c..345fad1 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ ordinary procedures also accept `undefined` as the initializer of a mutable loca procedures returning fixed arrays or records/tuples containing `type`, such as `types proc() [2]type { return [i16, bool] }` or `config proc() struct { child type, count usize } { return { child = i16, count = 2 } }`, 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. compile-time producers accept ordinary type-array/type-record inputs, and runtime generic procedures accept erased `$` parameters with these shapes. direct projections generate signatures, constructors, storage and defaults; explicit `$()` calls inside producers retain owned 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/type-record storage and reflection remain open. +readonly type-valued slices (`[]type` and slices of metadata records/arrays) also cross compile-time procedure boundaries as owned snapshots. contextual array literals, including `[]`, construct them; explicit views of immutable readonly arrays and existing metadata slices support checked `usize` bounds, empty end views, indexing and `.len`. nested slices, ordinary/inline by-value iteration, erased `$` inputs and optional/fallible channels retain exact nominal leaves and content-based specialization identity. snapshots generate runtime types/defaults, but metadata itself has no runtime storage. mutable slice types, sentinels and views of mutable array places remain rejected rather than silently losing borrowing semantics; copy an array into an immutable binding before taking an owned view. construction shares the type-control step bound, and uninitialized leaves cannot cross procedure boundaries. + 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; callbacks retain source declaration identity, while nonnull data pointers and unsupported slice types retain the constant-materialization escape boundary. type-producing bodies also execute scalar/enum `match` statements and type-valued match expressions in initializers, assignments and returns, such as `return match wide { true: i16, false: i8 }`. selected expression arms may themselves be matches. an ordinary checked IR match returns the selected arm index, preserving subject/pattern typing, range boundaries, overlap, unreachable-arm and exhaustiveness diagnostics without checking unexecuted bodies. statement arms retain scoped locals, assignments, early returns and enclosing-loop break/continue; expression selection shares the type-control step bound. diff --git a/TODO.md b/TODO.md index 5c9d9c2..9f9ca94 100644 --- a/TODO.md +++ b/TODO.md @@ -50,6 +50,9 @@ optional type-valued storage preserves nested presence, owned metadata payloads, checked unwraps and immutable captures across compile-time procedure boundaries. fallible type-valued channels own their active payloads, propagate checked errors, and select generated storage through `try`/`catch` and scoped cleanup. +readonly type-valued slices own nested metadata leaves across compile-time calls, +checked immutable-array/slice views, by-value iteration and erased arguments; +mutable metadata borrowing remains open. `field!` selects record/tuple/enum and sequence members using compile-time names, including runtime places and type-control updates without runtime reflection. `struct_type!` generates ordinary/C-layout records from constant name/type/default @@ -161,6 +164,7 @@ test discovery and FFI remain outside these completed slices. - [x] uninitialized type-record checkpoint: compile-time records/tuples containing type values start with `undefined` and fill through nested field/index updates or whole-value replacement. persistent snapshots preserve independent partial copies; defaults do not initialize undefined fields. initialized leaves and empty shapes work while premature type/value reads and incomplete procedure arguments/results diagnose read-before-initialization. fully initialized results supply erased generic inputs, generated storage/defaults and cached nominal types through interpreter/native execution. ordinary mutability/schema validation and shared per-field/cell construction bounds remain checked; general type-valued execution and broader materialization remain open. - [x] optional type-storage checkpoint: `?type` and optional type-valued records/arrays use owned compile-time snapshots, including nested `some!(null)`, contextual payload construction, mutable whole-value/field/index replacement, immutable if/while/conditional captures, null comparisons and checked unwraps. constant-input procedures and erased generic arguments preserve specialization-cache identity and nominal leaves while generating runtime storage through interpreter/native execution. undefined presence, incomplete payloads, non-type values, capture mutation and runtime dependencies retain diagnostics. imported private nominal types, session/source disposal and repeat compilation preserve ownership. fallible type-valued channels are covered below; general type-valued execution and broader constant materialization remain open. - [x] fallible type-storage checkpoint: explicit success/error channels containing types use owned compile-time snapshots, including nested optional/record/array payloads, whole-value updates, independent copies, constant-input procedures and erased generic inputs. `try` validates error subsets and propagates through enclosing producers; `catch` binds immutable exact error payloads and selects generated types or runtime handlers. composed metadata errors preserve named variants, nominal leaves and cache identity. returned snapshots precede scoped LIFO `defer`/`errdefer` cleanup; fallible `void` supports bare returns and successful fallthrough. incomplete/uninitialized channels, invalid payloads/compositions, capture mutation and runtime dependencies retain diagnostics. imported private types, source/session disposal and repeat compilation preserve ownership through interpreter/native execution. runtime metadata storage, general type-valued execution and broader materialization remain open. + - [x] readonly type-slice checkpoint: `[]type` and slices of type-valued records/arrays use owned compile-time snapshots. contextual literals, immutable readonly array views, nested slice views, checked `usize` bounds/indexing, `.len`, empty shapes and ordinary/inline by-value iteration preserve exact nominal leaves. constant-input procedure parameters/results, erased generic inputs, optional/fallible nesting and normalized cache keys generate runtime storage/defaults through interpreter/native execution. mutable annotations/backing, metadata sentinels, invalid ranges/indices, readonly writes, incomplete leaves and runtime dependencies retain diagnostics. imported private types, source/session disposal, shared construction bounds and repeat compilation preserve ownership. mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open. - [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates. - [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library. diff --git a/source/compilation/compilation.test.hon b/source/compilation/compilation.test.hon index 792926e..5f8447a 100644 --- a/source/compilation/compilation.test.hon +++ b/source/compilation/compilation.test.hon @@ -1296,3 +1296,87 @@ fallible_type_metadata_recompiles_owned_private_nominal_payloads test { try testing.expect_equal(i64(42), outcome.value.data.integer) } } + +readonly_type_slices_recompile_owned_private_nominal_payloads test { + diagnostic_store := diagnostics.init(mem.c_allocator) + defer diagnostics.deinit(&diagnostic_store) + modules [2]mut ir.Module := undefined + { + session := init(mem.c_allocator) + defer deinit(&session) + root :: try add_package(&session) + dependency :: try add_package(&session) + bytes := try owned_text( + `lib :: import "../library" + `Child :: struct { value bool } + `Storage proc() type { + ` saved :: $(lib.make()) + ` if (lib.Box(saved) != lib.Box(lib.make())) return bool + ` if (lib.Box(saved) != lib.Box(lib.literal())) return bool + ` Selected :: lib.types()[0] + ` if (Selected == Child or saved[0].child != Selected) return bool + ` return lib.Box(saved) + `} + `main proc() i16 { + ` value lib.types()[0] :: { value = 40 } + ` if (lib.read(lib.make(), value) != 42) return -1 + ` box Storage() :: { values = [{ value = 19 }, { value = 21 }] } + ` same lib.Box(lib.literal()) :: box + ` return same.values[0].value + same.values[1].value + same.bias + `} + , + ) + defer arraylist.deinit(&bytes) + root_source :: try add_source(&session, root, "/readonly_type_slices/app/main.hon", &bytes, &diagnostic_store) + imported := try owned_text( + `@hide:file + `Child :: struct { value i16 } + `@hide:file + `Config :: struct { child type, count usize } + `types proc() []type { return [Child] } + `literal proc() []Config { + ` return [{ child = Child, count = 2 }, { child = Child, count = 2 }] + `} + `make proc() []Config { + ` rows [3]Config := [ + ` { child = Child, count = 99 }, + ` { child = Child, count = 2 }, + ` { child = Child, count = 2 }, + ` ] + ` frozen :: rows + ` saved :: frozen[1..][..2] + ` rows = [ + ` { child = bool, count = 7 }, + ` { child = bool, count = 7 }, + ` { child = bool, count = 7 }, + ` ] + ` return saved + `} + `Box proc(input []Config) type { + ` return struct { values [input[0].count]input[0].child, bias i16 = i16(input[1].count) } + `} + `read proc($input []Config, value input[0].child) i16 { + ` return value.value + i16(input[1].count) + `} + , + ) + defer arraylist.deinit(&imported) + _ = try add_source(&session, dependency, "/readonly_type_slices/library/types.hon", &imported, &diagnostic_store) + try arraylist.append(&session.imports, Import{ + node = declaration(&session, root_source, 0).node, + target = dependency, + }) + modules[0] = try compile(&session, root, &diagnostic_store) + errdefer ir.deinit(&modules[0]) + modules[1] = try compile(&session, root, &diagnostic_store) + } + defer ir.deinit(&modules[0]) + defer ir.deinit(&modules[1]) + try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) + for modules |module| { + outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store) + defer interpreter.deinit(&outcome) + try testing.expect(outcome.trap == null) + try testing.expect_equal(i64(42), outcome.value.data.integer) + } +} diff --git a/source/sema/specialize/specialize.hon b/source/sema/specialize/specialize.hon index c702cc4..0545f61 100644 --- a/source/sema/specialize/specialize.hon +++ b/source/sema/specialize/specialize.hon @@ -3122,6 +3122,35 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as } if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) { kind :: ast.expr_kind(&state.tree, id) + if (kind == .slice) { + base :: ast.slice_expr(&state.tree, id).base + root := base + while (true) { + root_kind :: ast.expr_kind(&state.tree, root) + if (root_kind == .selection) root = ast.selection_expr(&state.tree, root).base + else if (root_kind == .index) root = ast.index_expr(&state.tree, root).base + else if (root_kind == .unary and ast.unary_expr(&state.tree, root).operator == .unwrap) + root = ast.unary_expr(&state.tree, root).operand + else if (root_kind == .builtin_call + and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, root).name), "field") + and ast.call_arg_count(&state.tree, root) == 2) + root = ast.call_arg(&state.tree, root, 0) + else break + } + place :: binding(state, root) + if (place.kind == .declaration + and ast.node_class(&state.tree, place.data.declaration) == .statement + and ast.statement_decl(&state.tree, place.data.declaration).mutability == .variable) { + annotation :: try expression_type(state, base, 0) + if (type_record_storage(state, annotation, 0) + and ast.expr_kind(&state.tree, annotation) == .array_type) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, + .comptime_value_escape, ast.node_span(&state.tree, id)) + execution.pending_flow = .invalid + return + } + } + } if (kind == .block_expr) { execution.mapping[usize(id)] = try execute_type_value_block(state, execution, ast.block_expr(&state.tree, id), no_node) @@ -3874,6 +3903,15 @@ expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error { type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { if (ast.node_class(&state.tree, id) != .expression) return true kind :: ast.expr_kind(&state.tree, id) + if (kind == .slice) { + annotation :: try expression_type(state, id, 0) + if (!try metadata_sequence(state, annotation)) return true + value :: try type_slice_value(state, id, annotation) + if (value == no_node) return false + try replace(state, id, value) + try refresh(state) + return true + } if (kind == .unary and ast.unary_expr(&state.tree, id).operator == .unwrap) { operand :: ast.unary_expr(&state.tree, id).operand annotation :: try expression_type(state, operand, 0) @@ -3912,6 +3950,12 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { !record and !try execution_array_storage(state, array.type) and !try metadata_sequence(state, array.type) ) return true + if (try metadata_sequence(state, array.type) + and ast.expr_kind(&state.tree, array.value) == .literal_undefined) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, + .read_before_initialization, ast.node_span(&state.tree, id)) + return false + } selected := no_node selected_type := no_node if (record) { @@ -3936,7 +3980,7 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error { selected_type = ast.field(&state.tree, ast.record_field(&state.tree, type, slot)).type? } else if (kind == .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), false) if (index == no_index) return false selected = ast.array_element(&state.tree, array.value, index) if (!type_record_storage(state, array.type, 0)) @@ -4114,8 +4158,15 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error { .invalid_sum_type, ast.node_span(&state.tree, type)) return false } - if (ast.expr_kind(&state.tree, type) == .slice_type) - return try type_record_check(state, ast.slice_type(&state.tree, type).element) + if (ast.expr_kind(&state.tree, type) == .slice_type) { + descriptor :: ast.slice_type(&state.tree, type) + if (descriptor.mutable or descriptor.sentinel != null) { + code diagnostics.Code :: if (descriptor.mutable) .comptime_value_escape else .invalid_operand + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, type)) + return false + } + return try type_record_check(state, descriptor.element) + } if (ast.expr_kind(&state.tree, type) == .array_type) { if (!try type_step(state, type)) return false descriptor :: ast.array_type(&state.tree, type) @@ -4231,7 +4282,7 @@ type_record_value proc(state @mut State, expression, annotation ast.NodeId) ast. } @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, boundary bool) usize ! Error { usize_type :: try builtin(state, expression, .usize) actual :: try expression_type(state, expression, 0) if (!flexible(state, expression) and (actual == no_node or !try same_type(state, actual, usize_type))) { @@ -4241,9 +4292,9 @@ type_array_index proc(state @mut State, expression ast.NodeId, count usize) usiz } value :: try evaluate_ir(state, expression, usize_type) if (value.kind != .integer) return no_index - if (value.magnitude >= u64(count)) { - state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, - .index_out_of_bounds, ast.node_span(&state.tree, expression)) + if (value.magnitude > u64(count) or (!boundary and value.magnitude == u64(count))) { + code diagnostics.Code :: if (boundary) .invalid_slice_bounds else .index_out_of_bounds + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, expression)) return no_index } return usize(value.magnitude) @@ -4428,6 +4479,100 @@ type_array_value proc(state @mut State, expression, annotation ast.NodeId) ast.N return result } +# readonly metadata views own their leaves. mutable backing needs borrowing semantics, +# not a snapshot that silently stops observing later element writes. +@hide:file +type_slice_value proc(state @mut State, expression, annotation ast.NodeId) ast.NodeId ! Error { + if (state.type_execution_depth == 0) state.type_execution_remaining = 4096 + state.type_execution_depth += 1 + defer state.type_execution_depth -= 1 + type :: try canonical_type(state, annotation) + if (!try type_record_check(state, type)) return no_node + descriptor :: ast.slice_type(&state.tree, type) + span :: ast.node_span(&state.tree, expression) + kind :: ast.expr_kind(&state.tree, expression) + if (kind == .literal_undefined) { + result :: try ast.add_typed_constant(state.builder, span, type, expression) + try refresh(state) + return result + } + if (kind == .typed_constant) { + actual :: ast.typed_constant(&state.tree, expression) + if (try same_type(state, type, actual.type)) return expression + } + source := expression + start := usize(0) + end := no_index + if (kind == .slice) { + view :: ast.slice_expr(&state.tree, expression) + if (view.sentinel != null) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_operand, span) + return no_node + } + source = view.base + } + if (ast.expr_kind(&state.tree, source) == .array_literal) { + for (0..ast.array_element_count(&state.tree, source)) |_| { + if (!try type_step(state, expression)) return no_node + } + length :: try ast.add_integer(state.builder, span, u64(ast.array_element_count(&state.tree, source))) + array :: try ast.add_array_type(state.builder, span, length, descriptor.element, false) + try refresh(state) + source = try type_array_value(state, source, array) + } else { + actual :: try expression_type(state, source, 0) + if (actual == no_node) return no_node + actual_kind :: ast.expr_kind(&state.tree, actual) + if ((actual_kind != .array_type and actual_kind != .slice_type) + or (actual_kind == .array_type and kind != .slice)) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span) + return no_node + } + mutable :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).mutable + else ast.slice_type(&state.tree, actual).mutable + element :: if (actual_kind == .array_type) ast.array_type(&state.tree, actual).element + else ast.slice_type(&state.tree, actual).element + if (mutable) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span) + return no_node + } + if (!try same_type(state, descriptor.element, element)) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span) + return no_node + } + source = try type_execution_value(state, source, actual) + } + if (source == no_node) return no_node + values :: ast.typed_constant(&state.tree, source).value + if (ast.expr_kind(&state.tree, values) == .literal_undefined) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .read_before_initialization, span) + return no_node + } + count :: ast.array_element_count(&state.tree, values) + end = count + if (kind == .slice) { + view :: ast.slice_expr(&state.tree, expression) + if (view.start) |bound| start = try type_array_index(state, bound, count, true) + if (start == no_index) return no_node + if (view.end) |bound| end = try type_array_index(state, bound, count, true) + if (end == no_index) return no_node + if (start > end) { + state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .invalid_slice_bounds, span) + return no_node + } + } + elements := arraylist.init(ast.NodeId, state.allocator) + defer arraylist.deinit(&elements) + for (start..end) |i| { + if (!try type_step(state, expression)) return no_node + try arraylist.append(&elements, ast.array_element(&state.tree, values, i)) + } + literal :: try ast.add_array_literal(state.builder, span, elements.items) + result :: try ast.add_typed_constant(state.builder, span, type, literal) + try refresh(state) + return result +} + # rebuild only the field/index path so previous snapshots retain their owned values. @hide:file type_array_update proc( @@ -4483,7 +4628,7 @@ type_array_update proc( expression :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index) if (!try expand_type_matches(state, expression)) return no_node 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, false) if (index == no_index) return no_node element_type = descriptor.element previous = ast.array_element(&state.tree, array.value, index) @@ -4691,6 +4836,8 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No declaration :: ast.statement_decl(&state.tree, resolved.data.declaration) if (declaration.mutability != .constant or !try type_step(state, expression)) return no_node if (declaration.type != null and !try same_type(state, annotation, declaration.type?)) { + if (try metadata_sequence(state, annotation)) + return try type_slice_value(state, expression, annotation) state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, ast.node_span(&state.tree, expression)) return no_node @@ -4708,18 +4855,8 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No } return value } - if ( - !is_type(state, annotation) and type_record_storage(state, annotation, 0) - and ast.expr_kind(&state.tree, try canonical_type(state, annotation)) == .slice_type - ) { - if (ast.expr_kind(&state.tree, expression) == .typed_constant) { - actual :: ast.typed_constant(&state.tree, expression) - if (try same_type(state, annotation, actual.type)) return expression - } - state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, - .type_mismatch, ast.node_span(&state.tree, expression)) - return no_node - } + if (try metadata_sequence(state, annotation)) + return try type_slice_value(state, expression, annotation) if (!is_type(state, annotation) and type_record_storage(state, annotation, 0)) { type :: try canonical_type(state, annotation) if (type != no_node and ast.expr_kind(&state.tree, type) == .optional_type) diff --git a/tests/native.py b/tests/native.py index 10834a7..eb2a41d 100644 --- a/tests/native.py +++ b/tests/native.py @@ -2278,6 +2278,369 @@ CASES = { } """, b"", b"initialization", ), + "metadata_slices_direct_literal_views_start_bounded_evaluation": ( + """ + Selected proc($items []type) type { return items[0] } + main proc() [2]usize { + value Selected(([i16, bool])[..1]) :: 42 + return [usize(value), $(([i16, bool])[1..].len)] + } + """, b"[42, 1]\n", None, + ), + "metadata_slices_contextual_literals_inputs_results_and_erased_inputs": ( + """ + Id :: distinct i16 + types proc() []type { return [Id, i16] } + pass proc(values []type) []type { return values } + window proc(values []type, start usize, end usize) []type { return values[start..end] } + Selected proc(values []type) type { return values[0] } + exact proc($values []type, value Selected(values)) Selected(values) { return value } + Storage proc() type { + empty []type :: [] + if (pass(empty).len != 0 or types().len != 2) return bool + return Selected(window(pass([bool, Id]), 1, 2)) + } + main proc() i16 { + value Storage() :: 40 + return i16(exact(types(), value)) + 2 + } + """, b"42\n", None, + ), + "metadata_slices_cache_keys_preserve_nominal_leaves_order_and_length": ( + """ + Id :: distinct i16 + Other :: distinct i16 + Token proc($values []type) type { return struct {} } + types proc() []type { + backing [4]type :: [bool, Id, i16, u8] + return backing[1..3] + } + Storage proc() type { + if (Token(types()) != Token([Id, i16])) return bool + if (Token(types()) == Token([Other, i16])) return bool + if (Token(types()) == Token([i16, Id])) return bool + if (Token(types()) == Token([Id])) return bool + if (Token([]) != Token([])) return bool + return types()[0] + } + main proc() i16 { + value Storage() :: 42 + return i16(value) + } + """, b"42\n", None, + ), + "metadata_record_slices_nested_leaves_and_exact_cache_identity": ( + """ + Id :: distinct i16 + Other :: distinct i16 + Info :: struct { child type, count usize, name string } + Row :: struct { types []type, fields []Info } + Token proc($values []Info) type { return struct {} } + fields proc() []Info { return [{ child = Id, count = 2, name = "value" }] } + rows proc() []Row { + return [{ types = [Id, i16], fields = fields() }] + } + Storage proc() type { + empty []Info :: [] + if (empty.len != 0 or rows()[0].types[1] != i16) return bool + source :: rows()[0].fields + copied := source[0] + copied.child = bool + copied.count = 3 + if (source[0].child != Id or source[0].count != 2) return bool + if (Token(source) != Token(fields())) return bool + if (Token(source) == Token([{ child = Other, count = 2, name = "value" }])) return bool + if (Token(source) == Token([{ child = Id, count = 3, name = "value" }])) return bool + if (Token(source) == Token([{ child = Id, count = 2, name = "other" }])) return bool + return [source[0].count]source[0].child + } + main proc() i16 { + values Storage() :: [40, 2] + return i16(values[0]) + i16(values[1]) + } + """, b"42\n", None, + ), + "metadata_slices_nested_slices_by_value_and_inline_iteration": ( + """ + Info :: struct { child type, count usize } + rows proc() [][]type { return [[i8, i16], [], [bool]] } + fields proc() []Info { return [{ child = i16, count = 2 }] } + Storage proc() type { + total usize := 0 + for rows() |row, index| { + total += row.len + index + for row |T| { + if (T == i16) total += 1 + } + } + inline for rows() |row| { + inline for row |T| { + if (T == bool) total += 1 + } + } + for fields() |field| { + copied := field + copied.child = bool + copied.count = 99 + } + inline for fields() |field| { + if (field.child != i16 or field.count != 2) return bool + } + if (total != 8) return bool + return [fields()[0].count]fields()[0].child + } + main proc() Storage() { return [40, 2] } + """, b"[40, 2]\n", None, + ), + "metadata_slices_subviews_empty_boundaries_and_readonly_backing_replacement": ( + """ + types proc() []type { + backing [3]type := [bool, i16, u8] + frozen :: backing + saved :: frozen[1..2] + backing = [u8, bool, bool] + if (saved[0] != i16 or backing[1] != bool) return [] + return saved + } + Storage proc() type { + source :: types() + full :: source[..] + nested :: full[0..full.len][..1] + if (nested.len != 1 or nested[0] != i16) return bool + if (source[0..0].len != 0 or source[1..1].len != 0) return bool + empty []type :: [] + if (empty[..].len != 0 or empty[0..0].len != 0) return bool + return nested[0] + } + main proc() Storage() { return 42 } + """, b"42\n", None, + ), + "metadata_slices_optional_fallible_parameters_results_and_captures": ( + """ + Failure :: struct { types []type } + Info :: struct { child type, count usize } + choose proc(present bool, failed bool) ?[]Info ! Failure { + if (failed) return Failure { types = [i16] } + if (present) return some!([{ child = i16, count = 2 }]) + return null + } + pass proc(value ?[]Info ! Failure) ?[]Info ! Failure { return value } + Token proc($value ?[]Info ! Failure) type { return struct {} } + Storage proc() type { + original :: pass(choose(true, false)) + if (Token(original) != Token(choose(true, false))) return bool + if (Token(original) == Token(choose(false, false))) return bool + if (Token(original) == Token(choose(false, true))) return bool + missing :: pass(choose(false, false)) catch null + if (missing != null) return bool + pass(choose(false, true)) catch |err| { + if (err.types[0] != i16) return bool + yield null + } + selected :: original catch null + if selected |fields| { + return [fields[0].count]fields[0].child + } + return bool + } + main proc() Storage() { return [40, 2] } + """, b"[40, 2]\n", None, + ), + "metadata_slices_reject_mutable_slice_annotation": ( + """ + types proc() []mut type { return [i16] } + main proc() types()[0] { return 42 } + """, b"", b"compile-time result cannot escape evaluation as a source value", + ), + "metadata_slices_reject_mutable_array_backing": ( + """ + types proc() []type { + backing [1]mut type := [i16] + return backing[..] + } + main proc() types()[0] { return 42 } + """, b"", b"compile-time result cannot escape evaluation as a source value", + ), + "metadata_slices_reject_mutable_binding_with_readonly_array_elements": ( + """ + types proc() []type { + backing [1]type := [i16] + return backing[..] + } + main proc() types()[0] { return 42 } + """, b"", b"compile-time result cannot escape evaluation as a source value", + ), + "metadata_record_slices_reject_mutable_array_backing": ( + """ + Info :: struct { child type, count usize } + fields proc() []Info { + backing [1]mut Info := [{ child = i16, count = 2 }] + return backing[..] + } + main proc() fields()[0].child { return 42 } + """, b"", b"compile-time result cannot escape evaluation as a source value", + ), + "metadata_slices_reject_sentinel_annotation": ( + """ + types proc() [;0]type { return [i16] } + main proc() types()[0] { return 42 } + """, b"", b"operator requires compatible operand types", + ), + "metadata_slices_reject_sentinel_subview": ( + """ + types proc() []type { + backing [2]type :: [i16, bool] + return backing[..1;0] + } + main proc() types()[0] { return 42 } + """, b"", b"operator requires compatible operand types", + ), + "metadata_slices_reject_reversed_bounds": ( + """ + types proc() []type { + backing [2]type :: [i16, bool] + return backing[2..1] + } + main proc() types()[0] { return 42 } + """, b"", b"slice bounds are invalid", + ), + "metadata_slices_reject_excessive_array_bounds": ( + """ + types proc() []type { + backing [1]type :: [i16] + return backing[..2] + } + main proc() types()[0] { return 42 } + """, b"", b"slice bounds are invalid", + ), + "metadata_slices_reject_excessive_subview_bounds": ( + """ + types proc() []type { return [i16, bool] } + bad proc() []type { return types()[1..2][..2] } + main proc() bad()[0] { return 42 } + """, b"", b"slice bounds are invalid", + ), + "metadata_slices_reject_start_beyond_empty_boundary": ( + """ + types proc() []type { return [] } + bad proc() []type { return types()[1..1] } + main proc() bad()[0] { return 42 } + """, b"", b"slice bounds are invalid", + ), + "metadata_slices_reject_non_usize_start": ( + """ + types proc() []type { return [i16] } + bad proc() []type { return types()[u8(0)..] } + main proc() bad()[0] { return 42 } + """, b"", b"array index must have type usize", + ), + "metadata_slices_reject_non_usize_end": ( + """ + types proc() []type { return [i16] } + bad proc() []type { return types()[..i16(1)] } + main proc() bad()[0] { return 42 } + """, b"", b"array index must have type usize", + ), + "metadata_slices_reject_non_usize_element_index": ( + """ + types proc() []type { return [i16] } + main proc() types()[u8(0)] { return 42 } + """, b"", b"array index must have type usize", + ), + "metadata_slices_reject_element_index_at_length": ( + """ + types proc() []type { return [i16] } + main proc() types()[types().len] { return 42 } + """, b"", b"index out of bounds", + ), + "metadata_slices_reject_empty_element_index": ( + """ + types proc() []type { return [] } + main proc() types()[0] { return 42 } + """, b"", b"index out of bounds", + ), + "metadata_slices_reject_non_type_literal_leaf": ( + """ + types proc() []type { return [42] } + main proc() types()[0] { return 42 } + """, b"", b"incompatible value type", + ), + "metadata_record_slices_reject_incompatible_literal_field": ( + """ + Info :: struct { child type, count usize } + fields proc() []Info { return [{ child = i16, count = false }] } + main proc() fields()[0].child { return 42 } + """, b"", b"incompatible value type", + ), + "metadata_slices_reject_readonly_element_assignment": ( + """ + types proc() []type { + values []type := [i16] + values[0] = bool + return values + } + main proc() types()[0] { return 42 } + """, b"", b"writable", + ), + "metadata_record_slices_reject_readonly_field_assignment": ( + """ + Info :: struct { child type, count usize } + fields proc() []Info { + values []Info := [{ child = i16, count = 2 }] + values[0].count = 3 + return values + } + main proc() fields()[0].child { return 42 } + """, b"", b"writable", + ), + "metadata_slices_reject_uninitialized_type_leaf": ( + """ + types proc() []type { + backing [1]type := undefined + frozen :: backing + return frozen[..] + } + main proc() types()[0] { return 42 } + """, b"", b"read before initialization", + ), + "metadata_record_slices_reject_uninitialized_value_leaf": ( + """ + Info :: struct { child type, count usize } + fields proc() []Info { + partial Info := undefined + partial.child = i16 + return [partial] + } + main proc() fields()[0].child { return 42 } + """, b"", b"read before initialization", + ), + "metadata_slices_reject_runtime_producer_dependencies": ( + """ + types proc(wide bool) []type { + if (wide) return [i16] + return [i8] + } + read proc(wide bool) i16 { + value types(wide)[0] :: 42 + return i16(value) + } + main proc() i16 { return read(true) } + """, b"", b"compile-time value", + ), + "metadata_slices_inline_captures_specialize_runtime_bodies": ( + """ + types proc() []type { return [i16, u8] } + add proc(value i16) i16 { return value + 1 } + main proc() i16 { + total i16 := 0 + inline for types() |T, index| { + value T :: 19 + total += add(i16(value)) + i16(index + 1) + } + return total - 1 + } + """, b"42\n", None, + ), "optional_types_procedure_boundaries_copies_and_static_captures": ( """ Token :: distinct i16