feat: support mutable metadata slice borrowing

This commit is contained in:
2026-11-01 23:26:34 +01:00
parent 2a4efd0b7f
commit a74dd2e960
6 changed files with 939 additions and 59 deletions
+5 -3
View File
@@ -132,7 +132,7 @@ 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.
ordinary parameters containing `type` also make a procedure compile-time-only, even when its result is a runtime value. for example, `count proc(types []type) usize { return types.len }` computes `count([i16, bool])` without runtime metadata. every input must be explicitly supplied and constant; use `$` parameters when later signature annotations depend on them. helpers can return owned scalar, aggregate and string values, recurse over metadata and use checked `try`/`catch` with scoped cleanup. plain and fallible `void` support bare returns and successful fallthrough. ordinary runtime dependencies and incomplete metadata arguments remain diagnostic; mutable metadata borrowing and general type-valued execution remain open.
ordinary parameters containing `type` also make a procedure compile-time-only, even when its result is a runtime value. for example, `count proc(types []type) usize { return types.len }` computes `count([i16, bool])` without runtime metadata. every input must be explicitly supplied and constant; use `$` parameters when later signature annotations depend on them. helpers can return owned scalar, aggregate and string values, recurse over metadata and use checked `try`/`catch` with scoped cleanup. plain and fallible `void` support bare returns and successful fallthrough. ordinary runtime dependencies and incomplete owned metadata arguments remain diagnostic; mutable slice parameters may borrow partially initialized backing to fill it.
local metadata storage uses owned compile-time snapshots alongside ordinary runtime parameters and effects. explicit annotations (`T type := i16`), inferred mutable type bindings (`T := i16`), metadata arrays/records/tuples, helper results and checked projections are erased before runtime checking. sequential updates, saved copies, static branches, bounded construction loops and scoped metadata cleanup select concrete runtime storage without emitting type values. runtime branches and loops may create independent local metadata; they cannot conditionally update an enclosing compile-time snapshot. runtime optional/collection captures and ordinary effects keep their execution semantics.
@@ -145,9 +145,11 @@ compute proc(value i16) i16 {
}
```
procedures whose metadata depends on ordinary parameters retain fully constant-input execution; every input to those calls remains explicitly supplied and constant. metadata parameters/results also retain their existing compile-time-only contract. nested storage, partial initialization, checked updates, recursion and fallible cleanup reuse type-control execution and its shared 4096-step bound. inferred arrays retain readonly elements unless their annotation permits updates; mutable bindings still permit whole-value replacement. ordinary local aliases and scalar metadata projections keep their runtime-call contract. mutable metadata borrowing remains open.
procedures whose metadata depends on ordinary parameters retain fully constant-input execution; every input to those calls remains explicitly supplied and constant. metadata parameters/results also retain their existing compile-time-only contract. nested storage, partial initialization, checked updates, recursion and fallible cleanup reuse type-control execution and its shared 4096-step bound. inferred arrays retain readonly elements unless their annotation permits updates; mutable bindings still permit whole-value replacement. ordinary local aliases and scalar metadata projections keep their runtime-call contract.
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.
readonly type-valued slices (`[]type` and slices of metadata records/arrays) cross compile-time procedure boundaries as owned snapshots. contextual array literals, including `[]`, construct them. local array/slice views retain live aliases with checked `usize` bounds, frozen subview offsets, indexing and `.len`; later backing writes or whole-array replacement remain visible, while by-value array/record copies stay independent. nested slices, ordinary/inline by-value iteration, erased `$` inputs and optional/fallible channels retain exact nominal leaves and content-based specialization identity.
mutable metadata slice views (`[]mut type` and slices of metadata records/arrays) borrow writable array places, including nested fields/indices. immutable slice bindings may update their mutable elements; readonly backing and enclosing places never gain write access. compile-time helpers share aliases and commit writes to caller storage, including initialization and scoped `defer`/`errdefer` cleanup. calls with mutable borrowed arguments execute afresh rather than reusing cached effects; readonly-only inputs snapshot at call entry and retain content-based nominal caching. readonly results snapshot before return cleanup, and owned arguments/results require initialized leaves. expired local views, bounds errors and runtime-controlled metadata mutation diagnose. mutable results, metadata sentinels, data-pointer borrowing and pointer captures remain unsupported; metadata has no runtime storage.
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.
+6 -4
View File
@@ -54,8 +54,9 @@ tagged type-valued unions own active payloads across compile-time calls and eras
arguments; checked payload updates preserve saved copies, and whole-value assignments
initialize undefined storage or select another variant.
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.
by-value iteration and erased arguments. local metadata slice views borrow live
array places; mutable views and helper parameters update shared backing, while
readonly results snapshot before cleanup. metadata data-pointer borrowing remains open.
ordinary metadata-input procedures also compute owned runtime values, including
reflected field names/counts, recursive helpers and fallible cleanup.
explicit and inferred local metadata storage also coexist with ordinary runtime
@@ -71,7 +72,7 @@ with static tags and exact by-value payloads, preserving runtime dispatch and sc
`typeinfo!` exposes owned compile-time array/distinct/record/enum metadata, including
readonly field collections, exact nominal types and reflected generated storage/defaults.
the existing `meta.EnumFieldStruct` and `enummap.EnumMap`, `init` and `get` work;
mutable metadata borrowing, broader materialization and full library acceptance remain open.
metadata data-pointer borrowing, broader materialization and full library acceptance remain open.
`some!` preserves nested optional presence; ordinary procedures initialize concrete
runtime `undefined` locals and compute fully initialized materializable constants.
the existing `ArrayList` uses nested generic inference, file-local helpers and
@@ -184,7 +185,8 @@ test discovery and FFI remain outside these completed slices.
- [x] inferred metadata-local execution checkpoint: inferred mutable type bindings and metadata array/record/tuple locals support constant-input execution without metadata inputs, results or local annotations. literal/helper results, checked field/index/optional projections, yielding blocks and conditional/fallible selection preserve owned copies, nominal leaves, whole-value replacement, loops, recursion and scoped cleanup through interpreter/native execution. inferred array elements remain readonly; ordinary local aliases and scalar metadata projections retain runtime parameters. ordinary fallible success/error snapshots use checked channel discrimination rather than assuming every payload carries a nominal type. imported private types, source/session disposal and repeat compilation preserve ownership. independent runtime parameters and metadata-local entry points are supported below.
- [x] formatted-IO acceptance checkpoint: existing `std/io.print` parses constant formats and expands tuple fields through interpreter/native execution without replacement or adapted library code. escaped braces, all integer bases/boundaries, booleans, strings/arrays, sparse/aliased enums, nested scalar distinct values, runtime arguments and computed formats retain exact output; short writes, zero progress, invalid callback counts and write errors retain checked propagation. malformed formats, wrong tuple/count/type inputs and unsupported specifiers diagnose. native `f64` default/decimal/scientific formatting uses imported variadic `snprintf` at `-O0`/`-O2`, while interpreter FFI remains a demanded trap. character literals preserve Unicode code points and byte escapes; newline-separated conditional branches and package-local visibility match Bro. partial compile-time prefixes infer remaining arguments without overriding explicit values; pure type builtins and effect-free bound type helpers specialize guards. `rem!` preserves exact numeric types, signed-minimum remainder and checked zero divisors. immutable C byte-pointer coercions, borrowed pointer-to-array slices and optional presence retain mutability/nominal checks. inline iterables propagate constant demand through original parser calls, metadata indexing and borrowed initialization; guarded errors follow reached control flow, owned snapshots commit local mutation, contextual constant numeric locals preserve rigid boundaries, and runtime bodies/effects remain runtime. general type-valued execution, broader materialization and broader `meta` acceptance remain open.
- [x] mixed metadata-local execution checkpoint: ordinary and generic procedures retain runtime scalar/pointer inputs and effects alongside erased explicit/inferred type, array and record locals. owned snapshots preserve sequential field/index/whole updates, saved copies, generated runtime storage, constant branches, bounded construction loops, static exits and scoped metadata cleanup. runtime branches/loops create independent metadata locals; optional/collection captures keep their runtime bindings. constant tagged/optional metadata selection specializes only the selected body. runtime-dependent metadata still requires fully constant-input execution, and runtime paths cannot mutate enclosing metadata snapshots. interpreter/native `-O0`/`-O2`, imported private nominal leaves, source/session disposal and repeat compilation retain ownership and diagnostics. mutable metadata borrowing and broader constant materialization remain open.
- [ ] remaining gate: mutable metadata borrowing, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations. broader imported C-binding support may interleave with later gates.
- [x] mutable metadata slice-borrowing checkpoint: local type-valued array/slice views retain live aliases, frozen subview bounds and exact nominal leaves. mutable views update nested metadata fields/indices through immutable slice bindings, share helper parameters, initialize undefined backing and run scoped `defer`/`errdefer`; ordinary array/record copies remain independent. readonly results snapshot before return cleanup, readonly-only calls retain content-based specialization identity, and mutable borrowed calls never reuse cached effects. mixed runtime parameters/effects remain runtime; runtime-controlled metadata mutation, readonly reversal, expired places, incomplete owned snapshots and bounds/index failures diagnose. imported private leaves and repeat compilation/source disposal retain ownership through interpreter/native execution. metadata data pointers, pointer captures, mutable result materialization and sentinels remain open.
- [ ] remaining gate: metadata data-pointer borrowing and pointer captures, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations. 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.
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
+11
View File
@@ -1318,6 +1318,7 @@ readonly_type_slices_recompile_owned_private_nominal_payloads test {
` saved :: $(lib.make())
` if (lib.Box(saved) != lib.Box(lib.make())) return bool
` if (lib.Box(saved) != lib.Box(lib.literal())) return bool
` if (lib.Box(saved) != lib.Box(lib.borrowed())) return bool
` Selected :: lib.types()[0]
` if (Selected == Child or saved[0].child != Selected) return bool
` return lib.Box(saved)
@@ -1357,6 +1358,16 @@ readonly_type_slices_recompile_owned_private_nominal_payloads test {
` ]
` return saved
`}
`fill proc(values []mut Config) void {
` for values |_, index| { values[index] = { child = Child, count = 2 } }
`}
`borrowed proc() []Config {
` rows [2]mut Config := undefined
` view :: rows[..]
` fill(view)
` defer view[0].child = bool
` return view
`}
`Box proc(input []Config) type {
` return struct { values [input[0].count]input[0].child, bias i16 = i16(input[1].count) }
`}
+622 -41
View File
@@ -108,6 +108,9 @@ State :: struct {
demanded_functions []mut u8
execution_functions []mut u8
execution_visited arraylist.ArrayList(ast.NodeId)
metadata_views arraylist.ArrayList(MetadataView)
metadata_view_indices hashmap.AutoHashMap(ast.NodeId, usize)
metadata_runtime_depth usize = 0
}
#! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
@@ -185,6 +188,8 @@ elaborate proc(
demanded_functions = demanded_functions,
execution_functions = execution_functions,
execution_visited = arraylist.init(allocator),
metadata_views = arraylist.init(allocator),
metadata_view_indices = hashmap.init(allocator),
}
defer arraylist.deinit(&state.bindings)
defer arraylist.deinit(&state.declarations)
@@ -192,6 +197,8 @@ elaborate proc(
defer hashmap.deinit(&state.cache)
defer hashmap.deinit(&state.capture_types)
defer arraylist.deinit(&state.execution_visited)
defer arraylist.deinit(&state.metadata_views)
defer hashmap.deinit(&state.metadata_view_indices)
defer {
for (state.instances.items) |instance| mem.free(allocator, instance.arguments)
arraylist.deinit(&state.instances)
@@ -1759,7 +1766,7 @@ expand_inline_match proc(state @mut State, id, subject_type ast.NodeId) void ! E
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
first_owned = ast.NodeId(0), result_type = no_node,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
span :: ast.node_span(&state.tree, inline_arm)
static_capture :: if (tagged) arm.tag_capture else arm.capture
@@ -2583,6 +2590,18 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
if (contextual or type_record_storage(state, annotation, 0))
annotation = try argument_annotation(state, function, view.type, arguments)
arguments[i] = try evaluate_expected(state, argument, annotation)
if ((view.comptime or !metadata_result) and arguments[i].kind == .aggregate
and metadata_has_view(state, arguments[i].node)) {
wrapped :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, argument),
arguments[i].annotation, arguments[i].node)
try refresh(state)
owned :: try metadata_own(state, wrapped, argument)
if (owned == no_node or !try type_array_initialized(state, owned, argument)) {
try reject_evaluation(state, call)
return
}
arguments[i].node = ast.typed_constant(&state.tree, owned).value
}
if (arguments[i].kind == .invalid) {
try reject_evaluation(state, call)
return
@@ -2648,13 +2667,41 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
annotation :: try argument_annotation(state, function, view.type, arguments)
try expand_expected(state, argument, annotation)
}
borrowed := false
for arguments |argument| {
if (argument.kind != .invalid and metadata_has_view(state, argument.node)) borrowed = true
}
if (borrowed) {
readonly := true
for arguments |argument| {
if (argument.kind != .invalid and !try metadata_readonly_views(state, argument.node, argument.annotation))
readonly = false
}
if (readonly) {
for arguments |@argument| {
if (argument.kind == .invalid or !metadata_has_view(state, argument.node)) continue
wrapped :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, argument.node),
argument.annotation, argument.node)
try refresh(state)
owned :: try metadata_own(state, wrapped, call)
if (owned == no_node or !try type_array_initialized(state, owned, call)) {
try reject_evaluation(state, call)
return
}
argument.node = ast.typed_constant(&state.tree, owned).value
}
borrowed = false
}
}
demand_key :: if (demanded) usize(1) else usize(0)
key := mix(usize(function), demand_key)
for arguments |argument| {
if (argument.kind != .invalid) key = mix(key, try value_hash(state, argument))
}
head := no_index
if (!borrowed) {
if (hashmap.get(&state.cache, key)) |found| head = found
}
current := head
instance := no_index
while (current != no_index) {
@@ -2688,7 +2735,9 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
return err
}
# cache heads are replaced without reallocating a second entry for the same hash.
if (head != no_index) {
if (borrowed) {
# live places carry effects and must never reuse a cached execution.
} else if (head != no_index) {
for (state.cache.entries) |@entry| {
if (entry.hash != 0 and entry.key == key) {
entry.value = instance
@@ -3640,6 +3689,7 @@ TypeExecution :: struct {
mapping []mut ast.NodeId
first_owned ast.NodeId
returned ast.NodeId = no_node
view_identity ast.NodeId = no_node
result_type ast.NodeId
failed bool = false
yielded ast.NodeId = no_node
@@ -3649,6 +3699,8 @@ TypeExecution :: struct {
cleanup_depth usize = 0
cleanup_loop_depth usize = 0
evaluate_values bool = false
# dispatch snapshots must retain match shape until ordinary arm validation.
resolve_views bool = true
pending_flow TypeFlow = .next
loop_depth usize = 0
inline_body ast.NodeId = no_node
@@ -3659,6 +3711,442 @@ TypeExecution :: struct {
residual_flow TypeFlow = .next
}
# unique empty AST literals identify views in metadata_view_indices. owner teardown
# invalidates handles before its stack frame disappears; only owned leaves reach IR.
@hide:file
MetadataView :: struct {
owner @mut TypeExecution
owner_identity ast.NodeId = no_node
place ast.NodeId
start usize
count usize
active bool = true
}
@hide:file
metadata_view_index proc(state @State, id ast.NodeId) usize {
if (id == no_node) return no_index
if (ast.node_class(&state.tree, id) != .expression) return no_index
marker :: if (ast.expr_kind(&state.tree, id) == .typed_constant)
ast.typed_constant(&state.tree, id).value else id
if (hashmap.get(&state.metadata_view_indices, marker)) |index| return index
return no_index
}
@hide:file
metadata_has_view proc(state @State, id ast.NodeId) bool {
if (id == no_node or state.metadata_views.items.len == 0) return false
if (metadata_view_index(state, id) != no_index) return true
for (0..ast.child_count(&state.tree, id)) |i| {
if (metadata_has_view(state, ast.child(&state.tree, id, i))) return true
}
return false
}
@hide:file
metadata_uses_views proc(state @State, execution @TypeExecution, id ast.NodeId) bool {
if (type_call_metadata_borrows(state, execution, id)) return true
for (execution.locals.items) |local| {
if (metadata_has_view(state, local.value) and metadata_reads(state, id, local.declaration, 0))
return true
}
return false
}
@hide:file
type_execution_deinit proc(state @mut State, execution @mut TypeExecution) void {
if (execution.view_identity != no_node) {
for (state.metadata_views.items) |@view| {
if (view.owner_identity == execution.view_identity) view.active = false
}
}
arraylist.deinit(&execution.locals)
}
# handles refer to live type-control places, never to temporary interpreter storage.
@hide:file
metadata_view_read proc(state @mut State, index usize, at ast.NodeId) ast.NodeId ! Error {
view :: state.metadata_views.items[index]
if (view.active) {
root :: assignment_root(state, view.place)
for (view.owner.locals.items) |local| {
if (local.declaration != root) continue
result :: try type_collection_read(state, view.owner, view.place)
if (!try expand_type_matches(state, result)) return no_node
return result
}
}
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_value_escape, ast.node_span(&state.tree, at))
return no_node
}
@hide:file
metadata_view_snapshot proc(state @mut State, value, at ast.NodeId) ast.NodeId ! Error {
index :: metadata_view_index(state, value)
if (index == no_index) return value
view :: state.metadata_views.items[index]
annotation :: ast.typed_constant(&state.tree, value).type
span :: ast.node_span(&state.tree, at)
source :: try metadata_view_read(state, index, at)
if (source == no_node) return no_node
array :: ast.typed_constant(&state.tree, source).value
count :: ast.array_element_count(&state.tree, array)
if (view.start > count or view.count > count - view.start) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_slice_bounds, ast.node_span(&state.tree, at))
return no_node
}
elements := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&elements)
for (view.start..(view.start + view.count)) |i| {
if (!try type_step(state, at)) return no_node
try arraylist.append(&elements, ast.array_element(&state.tree, array, i))
}
literal :: try ast.add_array_literal(state.builder, span, elements.items)
result :: try ast.add_typed_constant(state.builder, span, annotation, literal)
try refresh(state)
return result
}
# readonly boundary values own their leaves before cleanup invalidates local places.
@hide:file
metadata_own proc(state @mut State, input, at ast.NodeId) ast.NodeId ! Error {
value := input
if (!metadata_has_view(state, value)) return value
if (metadata_view_index(state, value) != no_index) {
if (ast.expr_kind(&state.tree, value) != .typed_constant) return no_node
type :: try canonical_type(state, ast.typed_constant(&state.tree, value).type)
if (ast.slice_type(&state.tree, type).mutable) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_value_escape, ast.node_span(&state.tree, at))
return no_node
}
value = try metadata_view_snapshot(state, value, at)
if (value == no_node) return no_node
if (!metadata_has_view(state, value)) return value
}
mapping :: try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree))
defer mem.free(state.allocator, mapping)
mem.set(mapping, no_node)
for (0..ast.child_count(&state.tree, value)) |i| {
child :: ast.child(&state.tree, value, i)
owned :: if (metadata_has_view(state, child)) try metadata_own(state, child, at) else child
if (owned == no_node) return no_node
mapping[usize(child)] = owned
}
result :: try ast.clone_subtree(state.builder, value, mapping)
try refresh(state)
return result
}
@hide:file
metadata_readonly_views proc(state @mut State, id, annotation ast.NodeId) bool ! Error {
if (!metadata_has_view(state, id)) return true
if (metadata_view_index(state, id) != no_index) {
type := annotation
if (ast.node_class(&state.tree, id) == .expression
and ast.expr_kind(&state.tree, id) == .typed_constant)
type = ast.typed_constant(&state.tree, id).type
type = try canonical_type(state, type)
return (type != no_node and ast.expr_kind(&state.tree, type) == .slice_type
and !ast.slice_type(&state.tree, type).mutable)
}
for (0..ast.child_count(&state.tree, id)) |i| {
if (!try metadata_readonly_views(state, ast.child(&state.tree, id, i), no_node)) return false
}
return true
}
@hide:file
MetadataPlace :: struct {
owner @mut TypeExecution
place ast.NodeId
mutable bool
}
@hide:file
metadata_place proc(
state @mut State, execution @mut TypeExecution, place ast.NodeId,
) MetadataPlace ! Error {
kind :: ast.expr_kind(&state.tree, place)
root :: assignment_root(state, place)
if (kind != .selection and kind != .index) {
return MetadataPlace{ owner = execution, place = place,
mutable = (root != no_node and ast.node_class(&state.tree, root) == .statement
and ast.statement_decl(&state.tree, root).mutability == .variable) }
}
parent :: if (kind == .index) ast.index_expr(&state.tree, place).base
else ast.selection_expr(&state.tree, place).base
base :: try type_collection_read(state, execution, parent)
if (!try expand_type_matches(state, base))
return MetadataPlace{ owner = execution, place = no_node, mutable = false }
borrowed :: metadata_view_index(state, base)
if (kind == .index and borrowed != no_index) {
view :: state.metadata_views.items[borrowed]
if (try metadata_view_read(state, borrowed, place) == no_node)
return MetadataPlace{ owner = execution, place = no_node, mutable = false }
offset :: try type_array_index(state, ast.index_expr(&state.tree, place).index, view.count, false)
if (offset == no_index)
return MetadataPlace{ owner = execution, place = no_node, mutable = false }
span :: ast.node_span(&state.tree, place)
type :: try canonical_type(state, ast.typed_constant(&state.tree, base).type)
mutable :: ast.slice_type(&state.tree, type).mutable
index :: try ast.add_integer(state.builder, span, u64(view.start + offset))
result :: try ast.add_index(state.builder, span, view.place, index)
try refresh(state)
return MetadataPlace{ owner = view.owner, place = result, mutable = mutable }
}
result := try metadata_place(state, execution, parent)
if (result.place == no_node) return result
if (kind == .index) {
type :: try canonical_type(state, try expression_type(state, base, 0))
if (type == no_node or ast.expr_kind(&state.tree, type) != .array_type) {
result.mutable = false
} else result.mutable = result.mutable and ast.array_type(&state.tree, type).mutable
result.place = try ast.add_index(state.builder, ast.node_span(&state.tree, place),
result.place, ast.index_expr(&state.tree, place).index)
} else result.place = try ast.add_selection(state.builder, ast.node_span(&state.tree, place),
result.place, ast.selection_expr(&state.tree, place).member)
try refresh(state)
return result
}
@hide:file
metadata_view_create proc(
state @mut State, execution @mut TypeExecution, id ast.NodeId,
) ast.NodeId ! Error {
view :: ast.slice_expr(&state.tree, id)
normalized :: try type_assignment_target(state, execution, view.base)
if (normalized == no_node) return no_node
place :: try type_collection_place(state, execution, normalized)
source := no_node
if (place != no_node) source = try type_collection_read(state, execution, place)
else source = try type_snapshot(state, execution, normalized)
if (source == no_node or !try expand_type_matches(state, source)) return no_node
annotation :: try expression_type(state, source, 0)
if (!type_record_storage(state, annotation, 0)) return no_node
if (view.sentinel != null) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_operand, ast.node_span(&state.tree, id))
return no_node
}
type :: try canonical_type(state, annotation)
kind :: ast.expr_kind(&state.tree, type)
if (kind != .array_type and kind != .slice_type) return no_node
previous :: metadata_view_index(state, source)
if (place == no_node and previous == no_index) return no_node
base := MetadataView{ owner = execution, place = place, start = 0, count = 0 }
mutable := false
element := no_node
if (previous != no_index) {
base = state.metadata_views.items[previous]
mutable = ast.slice_type(&state.tree, type).mutable
element = ast.slice_type(&state.tree, type).element
} else if (kind == .array_type) {
descriptor :: ast.array_type(&state.tree, type)
storage :: try metadata_place(state, execution, place)
if (storage.place == no_node) return no_node
base.owner = storage.owner
base.place = storage.place
mutable = descriptor.mutable and storage.mutable
element = descriptor.element
base.count = ast.array_element_count(&state.tree, ast.typed_constant(&state.tree, source).value)
} else return no_node
start := usize(0)
end := base.count
if (view.start) |bound| {
snapshot :: try type_snapshot(state, execution, bound)
if (snapshot == no_node) return no_node
start = try type_array_index(state, snapshot, base.count, true)
}
if (start == no_index) return no_node
if (view.end) |bound| {
snapshot :: try type_snapshot(state, execution, bound)
if (snapshot == no_node) return no_node
end = try type_array_index(state, snapshot, base.count, true)
}
if (end == no_index) return no_node
if (start > end) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_slice_bounds, ast.node_span(&state.tree, id))
return no_node
}
span :: ast.node_span(&state.tree, id)
slice :: try ast.add_slice_type(state.builder, span, element, mutable, null)
empty [0]ast.NodeId :: []
marker :: try ast.add_array_literal(state.builder, span, empty[..])
result :: try ast.add_typed_constant(state.builder, span, slice, marker)
try refresh(state)
if (base.owner.view_identity == no_node) base.owner.view_identity = marker
if (previous == no_index) base.owner_identity = base.owner.view_identity
base.start += start
base.count = end - start
index :: state.metadata_views.items.len
try arraylist.append(&state.metadata_views, base)
hashmap.put(&state.metadata_view_indices, marker, index) catch |err| {
match err {
.key_exists: unreachable
else: return err
}
}
return result
}
@hide:file
metadata_resolve_views proc(state @mut State, id ast.NodeId) bool ! Error {
if (!metadata_has_view(state, id)) return true
if (ast.node_class(&state.tree, id) == .expression
and ast.expr_kind(&state.tree, id) == .typed_constant) return true
if (ast.node_class(&state.tree, id) == .expression
and ast.expr_kind(&state.tree, id) == .binary) {
binary :: ast.binary_expr(&state.tree, id)
if (binary.operator == .logical_and or binary.operator == .logical_or) {
if (!try metadata_resolve_views(state, binary.left)) return false
if (branch_constant(state, binary.left, 0)) {
value :: try evaluate_ir(state, binary.left, try builtin(state, id, .bool))
if (value.kind != .boolean) return false
if (value.boolean == (binary.operator == .logical_or)) {
# inspect the skipped call's signature without executing its body.
right :: storage_expression_annotation(&state.tree, state.bindings.items, binary.right, 0)
if (!try same_type(state, right, try builtin(state, id, .bool))) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_operand, ast.node_span(&state.tree, binary.right))
return false
}
result :: try ast.add_boolean(state.builder, ast.node_span(&state.tree, id), value.boolean)
try replace(state, id, result)
try refresh(state)
return true
}
return try metadata_resolve_views(state, binary.right)
}
state.metadata_runtime_depth += 1
defer state.metadata_runtime_depth -= 1
return try metadata_resolve_views(state, binary.right)
}
}
if (ast.node_class(&state.tree, id) == .expression
and ast.expr_kind(&state.tree, id) == .match_expr) {
subject :: ast.match_expr(&state.tree, id).subject
if (!try metadata_resolve_views(state, subject)) return false
if (try specialize_match(state, id, no_node, no_node)) {
if (binding(state, id).kind == .invalid) return false
return try metadata_resolve_views(state, id)
}
state.metadata_runtime_depth += 1
defer state.metadata_runtime_depth -= 1
for (0..ast.match_arm_count(&state.tree, id)) |i| {
if (!try metadata_resolve_views(state, ast.match_arm(&state.tree, id, i))) return false
}
return true
}
for (0..ast.child_count(&state.tree, id)) |i| {
if (!try metadata_resolve_views(state, ast.child(&state.tree, id, i))) return false
}
if (ast.node_class(&state.tree, id) != .expression) return true
if (ast.expr_kind(&state.tree, id) == .call) {
try expand(state, id)
if (binding(state, id).kind == .invalid) {
state.evaluation_diagnostic = binding(state, id).data.diagnostic
return false
}
}
return try type_array_projection(state, id)
}
@hide:file
metadata_rebase_place proc(
state @mut State, execution @mut TypeExecution, target, first, replacement ast.NodeId,
) ast.NodeId ! Error {
if (target == first) return replacement
kind :: ast.expr_kind(&state.tree, target)
base :: if (kind == .index) ast.index_expr(&state.tree, target).base
else ast.selection_expr(&state.tree, target).base
rebased :: try metadata_rebase_place(state, execution, base, first, replacement)
result := no_node
span :: ast.node_span(&state.tree, target)
if (kind == .selection)
result = try ast.add_selection(state.builder, span, rebased, ast.selection_expr(&state.tree, target).member)
else {
index :: try type_snapshot(state, execution, ast.index_expr(&state.tree, target).index)
if (index == no_node) return no_node
frozen :: try type_execution_value(state, index, try builtin(state, index, .usize))
if (frozen == no_node) return no_node
result = try ast.add_index(state.builder, span, rebased, frozen)
}
try refresh(state)
return result
}
@hide:file
type_place_root proc(state @State, id ast.NodeId) ast.NodeId {
kind :: ast.expr_kind(&state.tree, id)
if (kind == .selection) return type_place_root(state, ast.selection_expr(&state.tree, id).base)
if (kind == .index) return type_place_root(state, ast.index_expr(&state.tree, id).base)
return id
}
@hide:file
metadata_assignment proc(
state @mut State, execution @mut TypeExecution, target, input ast.NodeId,
) bool ! Error {
first := target
while (true) {
kind :: ast.expr_kind(&state.tree, first)
if (kind != .selection and kind != .index) return false
base :: if (kind == .index) ast.index_expr(&state.tree, first).base
else ast.selection_expr(&state.tree, first).base
if (kind == .index) {
value :: try type_snapshot(state, execution, base)
if (value == no_node or !try expand_type_matches(state, value)) return true
index :: metadata_view_index(state, value)
if (index != no_index) {
view :: state.metadata_views.items[index]
type :: try canonical_type(state, ast.typed_constant(&state.tree, value).type)
if (!ast.slice_type(&state.tree, type).mutable) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_index_assignment, ast.node_span(&state.tree, target))
return true
}
if (try metadata_view_read(state, index, target) == no_node) return true
if (state.metadata_runtime_depth > 0) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_argument_required, ast.node_span(&state.tree, target))
return true
}
snapshot :: try type_snapshot(state, execution, ast.index_expr(&state.tree, first).index)
if (snapshot == no_node) return true
offset :: try type_array_index(state, snapshot, view.count, false)
if (offset == no_index) return true
span :: ast.node_span(&state.tree, first)
frozen :: try ast.add_integer(state.builder, span, u64(view.start + offset))
place :: try ast.add_index(state.builder, span, view.place, frozen)
try refresh(state)
rebased :: try metadata_rebase_place(state, execution, target, first, place)
if (rebased == no_node) return true
expression :: try type_snapshot(state, execution, input)
if (expression == no_node or !try expand_type_matches(state, expression)) return true
root :: assignment_root(state, view.place)
for (view.owner.locals.items) |local, i| {
if (local.declaration != root) continue
if (view.owner.residual != null and i < view.owner.residual_boundary) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_argument_required, ast.node_span(&state.tree, target))
return true
}
updated :: try type_array_update(state, view.owner, rebased,
type_place_root(state, view.place), local.value, expression)
if (updated != no_node) view.owner.locals.items[i].value = updated
return true
}
return true
}
}
first = base
}
}
@hide:file
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
@@ -3900,7 +4388,7 @@ expand_runtime_metadata proc(state @mut State, function ast.NodeId) void ! Error
mapping = try mem.alloc(ast.NodeId, state.allocator, ast.node_count(&state.tree)),
first_owned = ast.NodeId(0), result_type = view.return_type, residual_body = view.body,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
body := try residual_block(state, &execution, view.body, 0)
@@ -3933,7 +4421,7 @@ return_type_value proc(state @mut State, block, first_owned, result_type ast.Nod
result_type = result_type,
evaluate_values = true,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
flow :: try execute_type_block(state, &execution, block)
@@ -3967,7 +4455,7 @@ evaluate_type_value_block proc(state @mut State, expression, annotation ast.Node
result_type = no_node,
evaluate_values = true,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
return try execute_type_value_block(state, &execution, ast.block_expr(&state.tree, expression), annotation)
@@ -4079,41 +4567,75 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
return
}
}
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) {
if (execution.residual != null and !execution.evaluate_values
and ast.node_class(&state.tree, id) == .expression and metadata_uses_views(state, execution, id)) {
kind :: ast.expr_kind(&state.tree, id)
if (kind == .call and type_call_borrows(state, execution, id)) {
execution.mapping[usize(id)] = try type_borrow_call(state, execution, id)
if (execution.mapping[usize(id)] == no_node) execution.pending_flow = .invalid
if (kind == .binary) {
binary :: ast.binary_expr(&state.tree, id)
if (binary.operator == .logical_and or binary.operator == .logical_or) {
left :: try type_snapshot(state, execution, binary.left)
if (left == no_node) return
known :: branch_constant(state, left, 0)
if (known) {
value :: try evaluate_ir(state, left, try builtin(state, id, .bool))
if (value.kind != .boolean) {
execution.pending_flow = .invalid
return
}
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) {
if (value.boolean == (binary.operator == .logical_or)) {
right :: storage_expression_annotation(&state.tree, state.bindings.items, binary.right, 0)
if (!try same_type(state, right, try builtin(state, id, .bool))) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.comptime_value_escape, ast.node_span(&state.tree, id))
.invalid_operand, ast.node_span(&state.tree, binary.right))
execution.pending_flow = .invalid
return
}
execution.mapping[usize(id)] = try ast.add_boolean(state.builder,
ast.node_span(&state.tree, id), value.boolean)
try refresh(state)
return
}
}
if (!known) state.metadata_runtime_depth += 1
defer if (!known) state.metadata_runtime_depth -= 1
right :: try type_snapshot(state, execution, binary.right)
execution.mapping[usize(binary.left)] = left
execution.mapping[usize(binary.right)] = right
if (right == no_node) execution.pending_flow = .invalid
return
}
}
if (kind == .match_expr) {
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, id).subject)
if (subject == no_node) return
if (!branch_constant(state, subject, 0)) {
state.metadata_runtime_depth += 1
defer state.metadata_runtime_depth -= 1
for (0..ast.child_count(&state.tree, id)) |i|
try prepare_type_snapshot(state, execution, ast.child(&state.tree, id, i))
return
}
}
}
if ((execution.evaluate_values or execution.residual != null)
and ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .slice) {
result :: try metadata_view_create(state, execution, id)
if (result != no_node) {
execution.mapping[usize(id)] = result
return
}
if (state.evaluation_diagnostic != diagnostics.no_diagnostic) {
execution.pending_flow = .invalid
return
}
}
if (execution.evaluate_values and ast.node_class(&state.tree, id) == .expression) {
kind :: ast.expr_kind(&state.tree, id)
if (kind == .call and type_call_borrows(state, execution, id)
and !type_call_metadata_borrows(state, execution, id)) {
execution.mapping[usize(id)] = try type_borrow_call(state, execution, id)
if (execution.mapping[usize(id)] == no_node) execution.pending_flow = .invalid
return
}
if (kind == .block_expr) {
execution.mapping[usize(id)] = try execute_type_value_block(state, execution,
@@ -4141,6 +4663,19 @@ prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id as
}
right := no_node
if (value.boolean == (view.operator == .logical_or)) {
if (metadata_uses_views(state, execution, view.right)) {
annotation :: storage_expression_annotation(&state.tree, state.bindings.items, view.right, 0)
if (!try same_type(state, annotation, boolean)) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_operand, ast.node_span(&state.tree, view.right))
execution.pending_flow = .invalid
return
}
execution.mapping[usize(id)] = try ast.add_boolean(state.builder,
ast.node_span(&state.tree, id), value.boolean)
try refresh(state)
return
}
saved :: execution.evaluate_values
execution.evaluate_values = false
right = try type_snapshot(state, execution, view.right)
@@ -4239,6 +4774,7 @@ type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId
result :: try ast.clone_subtree(state.builder, id, execution.mapping)
try refresh(state)
try bind_type_snapshot(state, execution, id, start)
if (execution.resolve_views and !try metadata_resolve_views(state, result)) return no_node
return result
}
@@ -4354,7 +4890,7 @@ specialize_type_catch proc(state @mut State, id, annotation, expected ast.NodeId
first_owned = ast.NodeId(0),
result_type = no_node,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
if (view.capture) |capture| {
@@ -4461,6 +4997,9 @@ type_dispatch_snapshot proc(state @mut State, execution @mut TypeExecution, id a
try expand_inline_match(state, id, subject_type)
if (ast.expr_kind(&state.tree, id) != .match_expr) return no_node
saved :: execution.evaluate_values
saved_views :: execution.resolve_views
execution.resolve_views = false
defer execution.resolve_views = saved_views
execution.evaluate_values = false
defer execution.evaluate_values = saved
snapshot :: try type_snapshot(state, execution, id)
@@ -4634,7 +5173,7 @@ expand_inline_for proc(state @mut State, id ast.NodeId) void ! Error {
first_owned = ast.NodeId(0), result_type = no_node,
inline_body = loop.body, inline_blocks = &blocks,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
for (0..usize(2)) |i| {
@@ -4825,7 +5364,7 @@ type_match_body proc(state @mut State, id ast.NodeId, arm ast.MatchArmView) ast.
first_owned = ast.NodeId(0),
result_type = no_node,
}
defer arraylist.deinit(&execution.locals)
defer type_execution_deinit(state, &execution)
defer mem.free(state.allocator, execution.mapping)
mem.set(execution.mapping, no_node)
captured :: if (is_type(state, snapshot.type)) snapshot.value else value
@@ -4923,7 +5462,12 @@ type_array_projection proc(state @mut State, id ast.NodeId) bool ! Error {
return false
}
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 (metadata_view_index(state, base) != no_index) {
snapshot :: try metadata_view_snapshot(state, base, id)
if (snapshot == no_node) return false
array = ast.typed_constant(&state.tree, snapshot)
}
type :: try canonical_type(state, array.type)
record :: (type != no_node and (ast.expr_kind(&state.tree, type) == .record_type
or (type_record_storage(state, type, 0) and ast.expr_kind(&state.tree, type) == .union_type)))
@@ -5184,8 +5728,8 @@ type_record_check proc(state @mut State, annotation ast.NodeId) bool ! Error {
}
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
if (descriptor.sentinel != null) {
code diagnostics.Code :: .invalid_operand
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, code, ast.node_span(&state.tree, type))
return false
}
@@ -5555,8 +6099,7 @@ 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.
# local handles retain borrowing; owned readonly literals cross execution boundaries.
@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
@@ -5575,8 +6118,27 @@ type_slice_value proc(state @mut State, expression, annotation ast.NodeId) ast.N
if (kind == .typed_constant) {
actual :: ast.typed_constant(&state.tree, expression)
if (try same_type(state, type, actual.type)) return expression
index :: metadata_view_index(state, expression)
if (index != no_index) {
actual_type :: try canonical_type(state, actual.type)
if (ast.expr_kind(&state.tree, actual_type) == .slice_type) {
source :: ast.slice_type(&state.tree, actual_type)
if ((!descriptor.mutable or source.mutable)
and try same_type(state, descriptor.element, source.element)) {
result :: try ast.add_typed_constant(state.builder, span, type, actual.value)
try refresh(state)
return result
}
}
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .type_mismatch, span)
return no_node
}
}
source := expression
if (descriptor.mutable) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
return no_node
}
start := usize(0)
end := no_index
if (kind == .slice) {
@@ -6114,6 +6676,15 @@ type_call_borrows proc(state @State, execution @TypeExecution, call ast.NodeId)
return false
}
@hide:file
type_call_metadata_borrows proc(state @State, execution @TypeExecution, call ast.NodeId) bool {
for (execution.locals.items) |local| {
if (type_record_storage(state, local.annotation, 0)
and type_borrowed_place(state, call, local.declaration, false)) return true
}
return false
}
@hide:file
TypeBorrow :: struct { index usize, previous ast.NodeId, temporary ast.NodeId }
@@ -6431,6 +7002,8 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
expression :: try type_snapshot_expected(state, execution, value, execution.result_type)
if (expression == no_node) return .invalid
execution.returned = try type_execution_value(state, expression, execution.result_type)
if (execution.returned != no_node)
execution.returned = try metadata_own(state, execution.returned, value)
if (execution.returned != no_node
and !try type_array_initialized(state, execution.returned, value)) return .invalid
if (execution.returned != no_node) {
@@ -6491,6 +7064,11 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
mem.set(execution.mapping[previous..], no_node)
return try execute_type_statement(state, execution, normalized)
}
if (try metadata_assignment(state, execution, target, assignment.value)) {
if (state.evaluation_diagnostic != diagnostics.no_diagnostic or execution.pending_flow != .next)
return .invalid
return .next
}
root := assignment.target
indexed := true
while (true) {
@@ -6768,7 +7346,10 @@ execute_type_collection proc(state @mut State, execution @mut TypeExecution, sta
collection :: try type_execution_value(state, snapshot, annotation)
if (collection == no_node) return .invalid
count := Value{}
if tuple {
borrowed :: metadata_view_index(state, collection)
if (borrowed != no_index) {
count = Value{ kind = .integer, magnitude = u64(state.metadata_views.items[borrowed].count) }
} else if tuple {
count = Value{ kind = .integer, magnitude = u64(ast.record_field_count(&state.tree, annotation)) }
} else if (try execution_array_storage(state, annotation) or try metadata_sequence(state, annotation)) {
count = Value{ kind = .integer,
Binary file not shown.
+291 -7
View File
@@ -3375,31 +3375,301 @@ CASES = {
main proc() Storage() { return [40, 2] }
""", b"[40, 2]\n", None,
),
"metadata_slices_live_aliases_saved_bounds_rebinding_and_array_copies": (
"""
Storage proc() type {
backing [3]mut type := [i8, bool, u8]
copied :: backing
start := usize(1)
finish := usize(2)
view []mut type := backing[..]
saved :: view[start..finish]
shared :: saved
start = 0
finish = 3
saved[0] = i16
if (backing[1] != i16 or copied[1] != bool) return bool
backing = [bool, i8, bool]
if (shared.len != 1 or shared[0] != i8) return bool
other [1]mut type := [u8]
view = other[..]
view[0] = i16
shared[0] = i16
if (other[0] != i16 or backing[1] != i16) return bool
return shared[0]
}
main proc() Storage() { return 42 }
""", b"42\n", None,
),
"metadata_record_slices_borrow_helpers_nested_writes_and_uncached_effects": (
"""
Info :: struct { child type, nested struct { children [2]mut type, count usize } }
update proc(items []mut Info) void {
items[0].child = i16
items[0].nested.children[1] = i16
items[0].nested.count += 1
}
read proc(items []Info) usize { return items[0].nested.count }
Storage proc() type {
backing [1]mut Info := [{ child = bool, nested = { children = [i8, bool], count = 0 } }]
copied :: backing
view :: backing[..]
update(view)
if (read(view) != 1) return bool
backing[0] = copied[0]
update(view)
if (read(view) != 1) return bool
update(view)
if (read(view) != 2 or copied[0].nested.count != 0) return bool
if (view[0].child != i16 or view[0].nested.children[1] != i16) return bool
if (copied[0].child != bool or copied[0].nested.children[1] != bool) return bool
return [read(view)]view[0].child
}
main proc() Storage() { return [40, 2] }
""", b"[40, 2]\n", None,
),
"metadata_slices_borrow_cleanup_initialization_and_return_snapshot": (
"""
Error :: enum { failed }
initialize proc(items []mut type) void {
defer items[1] = i16
items[0] = bool
}
fail proc(items []mut type) void ! Error {
errdefer items[0] = i16
return .failed
}
types proc() []type {
backing [2]mut type := undefined
view :: backing[..]
initialize(view)
fail(view) catch {}
defer view[0] = bool
return view
}
main proc() [2]types()[0] { return [40, 2] }
""", b"[40, 2]\n", None,
),
"metadata_slices_reject_immutable_backing_write": (
"""
Storage proc() type {
backing [1]mut type :: [i16]
view :: backing[..]
view[0] = bool
return view[0]
}
main proc() Storage() { return 42 }
""", b"", b"writable",
),
"metadata_slices_reject_readonly_array_to_mutable_view": (
"""
Storage proc() type {
backing [1]type := [i16]
view []mut type :: backing[..]
view[0] = bool
return view[0]
}
main proc() Storage() { return 42 }
""", b"", b"incompatible",
),
"metadata_slices_reject_incomplete_borrow_snapshot": (
"""
types proc() []type {
backing [2]mut type := undefined
view :: backing[..]
view[0] = i16
return view
}
main proc() types()[0] { return 42 }
""", b"", b"read before initialization",
),
"metadata_slices_reject_runtime_branch_borrow_mutation": (
"""
update proc(items []mut type) void { items[0] = i16 }
compute proc(take bool) usize {
backing [1]mut type := [i8]
view :: backing[..]
if (take) update(view)
return sizeof!(view[0])
}
main proc() usize { return compute(true) }
""", b"", b"compile-time value",
),
"metadata_slices_reject_mutable_view_index_type": (
"""
Storage proc() type {
backing [1]mut type := [i16]
view :: backing[..]
view[u8(0)] = bool
return i16
}
main proc() Storage() { return 42 }
""", b"", b"usize",
),
"metadata_slices_reject_mutable_subview_write_at_length": (
"""
Storage proc() type {
backing [2]mut type := [i16, bool]
view :: backing[1..2]
view[1] = i8
return i16
}
main proc() Storage() { return 42 }
""", b"", b"index out of bounds",
),
"metadata_slices_nested_places_iteration_optional_aliases_and_empty_shapes": (
"""
Info :: struct { children [2]mut type }
Choice :: union(enum) { types []mut type, empty void }
Storage proc() type {
first [1]mut Info := [{ children = [i8, bool] }]
second [1]mut Info := [{ children = [bool, u8] }]
rows := first[..]
children :: field!(rows[0], "children")[..]
rows = second[..]
selected ?[]mut type :: some!(children)
if selected |chosen| { chosen[0] = i16 }
choice Choice :: .types {children}
match choice { .types |captured|: captured[1] = i16, .empty: {} }
if (children[1] != i16) return bool
for children |T, index| {
if (index == 0) children[1] = T
if (index == 1 and T != i16) return bool
}
if (second[0].children[0] != bool) return bool
empty [0]mut type := []
view :: empty[..]
for view |T| { return T }
if (view[..].len != 0) return bool
return first[0].children[1]
}
main proc() Storage() { return 42 }
""", b"42\n", None,
),
"metadata_slices_local_fallible_captures_preserve_aliases": (
"""
Failure :: struct { types []mut type }
Storage proc() type {
backing [1]mut type := [i8]
value void ! Failure :: Failure { types = backing[..] }
value catch |failure| { failure.types[0] = i16 }
return backing[0]
}
main proc() Storage() { return 42 }
""", b"42\n", None,
),
"metadata_slices_reject_readonly_enclosing_array_place": (
"""
Info :: struct { children [1]mut type }
Storage proc() type {
rows [1]Info := [{ children = [i8] }]
children :: rows[0].children[..]
children[0] = i16
return rows[0].children[0]
}
main proc() Storage() { return 42 }
""", b"", b"writable",
),
"metadata_slices_mixed_execution_and_readonly_generator_cache": (
"""
Token proc(types []type) type { return struct {} }
update proc(items []mut type) void { items[0] = i16 }
compute proc(value i16) i16 {
backing [1]mut type := [i8]
update(backing[..])
view :: backing[..]
if (Token(view) != Token([i16])) return -1
local view[0] :: value
return local
}
main proc() i16 { return compute(42) }
""", b"42\n", None,
),
"metadata_slices_reject_dangling_nested_scope": (
"""
Storage proc() type {
backing [1]mut type := [i16]
view []type := backing[..]
{
inner [1]mut type := [bool]
view = inner[..]
}
return view[0]
}
main proc() Storage() { return 42 }
""", b"", b"compile-time result cannot escape evaluation as a source value",
),
"metadata_slices_reject_runtime_short_circuit_mutation": (
"""
update proc(items []mut type) bool { items[0] = i16
return true }
compute proc(take bool) usize {
backing [1]mut type := [i8]
view :: backing[..]
if (take and update(view)) return sizeof!(view[0])
return sizeof!(view[0])
}
main proc() usize { return compute(false) }
""", b"", b"compile-time value",
),
"metadata_slices_reject_runtime_conditional_expression_mutation": (
"""
update proc(items []mut type) bool { items[0] = i16
return true }
compute proc(take bool) usize {
backing [1]mut type := [i8]
view :: backing[..]
selected :: if (take) update(view) else false
if (selected) return sizeof!(view[0])
return sizeof!(view[0])
}
main proc() usize { return compute(false) }
""", b"", b"compile-time value",
),
"metadata_slices_skip_short_circuited_borrowed_effects": (
"""
update proc(items []mut type) bool { items[0] = i16
return true }
Storage proc() type {
backing [1]mut type := [i8]
view :: backing[..]
if (false and update(view)) return bool
return view[0]
}
compute proc(value Storage()) i16 {
backing [1]mut type := [i8]
view :: backing[..]
if (true or update(view)) return i16(value)
return -1
}
main proc() i16 { return compute(42) }
""", b"42\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": (
"metadata_slices_snapshot_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",
""", b"42\n", None,
),
"metadata_slices_reject_mutable_binding_with_readonly_array_elements": (
"metadata_slices_snapshot_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",
""", b"42\n", None,
),
"metadata_record_slices_reject_mutable_array_backing": (
"metadata_record_slices_snapshot_mutable_array_backing": (
"""
Info :: struct { child type, count usize }
fields proc() []Info {
@@ -3407,7 +3677,7 @@ CASES = {
return backing[..]
}
main proc() fields()[0].child { return 42 }
""", b"", b"compile-time result cannot escape evaluation as a source value",
""", b"42\n", None,
),
"metadata_slices_reject_sentinel_annotation": (
"""
@@ -11412,6 +11682,16 @@ def check_specialization_packages(temporary, executable):
return struct { values [input.counts[1]]input.child, bias i16 = input.bias }
}
add proc($input Config, value input.child) input.child { return value + input.bias }
@hide:file
Private :: distinct i16
@hide:file
replace proc(items []mut type) void { items[0] = Private }
borrowed_types proc() []type {
backing [1]mut type := [bool]
replace(backing[..])
return backing[..]
}
FromBorrow proc() type { return struct { value borrowed_types()[0] } }
NominalConfig :: struct { child type }
nominal_config proc() NominalConfig {
return { child = struct { value i16 } }
@@ -11426,11 +11706,15 @@ def check_specialization_packages(temporary, executable):
same lib.FromRecord({ child = i16, counts = [1, 2] }) :: value
nominal lib.FromNominal(lib.nominal_config()) :: { value = { value = 40 } }
same_nominal lib.FromNominal(lib.nominal_config()) :: nominal
borrowed lib.FromBorrow() :: { value = 40 }
same_borrowed lib.FromBorrow() :: borrowed
if (i16(same_borrowed.value) != 40) return 0
if (same_nominal.value.value != 40) return 0
return lib.add(lib.record_config(), same.values[0] + same.values[1])
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
check(app, executable, (b"42\n", None))
sizing.write_text(textwrap.dedent("""
Config :: struct { child type, rows [1]mut [1]mut type }
record_config proc() Config { return { child = i16, rows = [[i16]] } }
@@ -11564,7 +11848,7 @@ def check_specialization_packages(temporary, executable):
interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:4:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 26
return 27
def check_dependency_provenance(temporary, executable):