feat: support mixed runtime and metadata execution
This commit is contained in:
@@ -134,7 +134,18 @@ procedures returning fixed arrays or records/tuples containing `type`, such as `
|
|||||||
|
|
||||||
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 metadata arguments remain diagnostic; mutable metadata borrowing and general type-valued execution remain open.
|
||||||
|
|
||||||
local metadata storage also makes a procedure compile-time-only, even with ordinary scalar inputs and results. explicit annotations (`T type := i16`) and inferred mutable type bindings (`T := i16`), metadata array/record/tuple literals, helper results, checked projections and yielding blocks use owned snapshots and erased calls. nested storage, partial initialization, checked updates, loops, recursion and scoped cleanup reuse type-control execution and its shared 4096-step bound. inferred arrays retain readonly elements unless their type explicitly permits element updates; whole-value replacement remains available for mutable bindings. every input remains explicitly supplied and constant; this is not partial evaluation of runtime inputs. ordinary local `alias` declarations and scalar projections from metadata helpers keep their runtime-call contract. mixed runtime/type-valued execution and mutable metadata borrowing remain open.
|
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.
|
||||||
|
|
||||||
|
```hon
|
||||||
|
compute proc(value i16) i16 {
|
||||||
|
T := i8
|
||||||
|
T = i16
|
||||||
|
local T :: value
|
||||||
|
return local
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
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) 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.
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ checked immutable-array/slice views, by-value iteration and erased arguments;
|
|||||||
mutable metadata borrowing remains open.
|
mutable metadata borrowing remains open.
|
||||||
ordinary metadata-input procedures also compute owned runtime values, including
|
ordinary metadata-input procedures also compute owned runtime values, including
|
||||||
reflected field names/counts, recursive helpers and fallible cleanup.
|
reflected field names/counts, recursive helpers and fallible cleanup.
|
||||||
explicit and inferred local metadata storage also make ordinary procedures
|
explicit and inferred local metadata storage also coexist with ordinary runtime
|
||||||
constant-input producers, preserving owned updates, loops, recursion and cleanup.
|
parameters and effects; parameter-dependent metadata retains constant-input execution.
|
||||||
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
`field!` selects record/tuple/enum and sequence members using compile-time names,
|
||||||
including runtime places and type-control updates without runtime reflection.
|
including runtime places and type-control updates without runtime reflection.
|
||||||
`struct_type!` generates ordinary/C-layout records from constant name/type/default
|
`struct_type!` generates ordinary/C-layout records from constant name/type/default
|
||||||
@@ -71,7 +71,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
|
`typeinfo!` exposes owned compile-time array/distinct/record/enum metadata, including
|
||||||
readonly field collections, exact nominal types and reflected generated storage/defaults.
|
readonly field collections, exact nominal types and reflected generated storage/defaults.
|
||||||
the existing `meta.EnumFieldStruct` and `enummap.EnumMap`, `init` and `get` work;
|
the existing `meta.EnumFieldStruct` and `enummap.EnumMap`, `init` and `get` work;
|
||||||
general type-valued execution, broader materialization and full library acceptance remain open.
|
mutable metadata borrowing, broader materialization and full library acceptance remain open.
|
||||||
`some!` preserves nested optional presence; ordinary procedures initialize concrete
|
`some!` preserves nested optional presence; ordinary procedures initialize concrete
|
||||||
runtime `undefined` locals and compute fully initialized materializable constants.
|
runtime `undefined` locals and compute fully initialized materializable constants.
|
||||||
the existing `ArrayList` uses nested generic inference, file-local helpers and
|
the existing `ArrayList` uses nested generic inference, file-local helpers and
|
||||||
@@ -180,10 +180,11 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [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.
|
- [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.
|
||||||
- [x] tagged type-storage checkpoint: tagged unions containing type leaves own active payload snapshots through typed/contextual construction, direct and nested field/index updates, immutable by-value matches and whole-value variant replacement. constant-input procedure arguments/results, erased generic inputs and normalized cache keys preserve nominal leaves and saved copies while generating runtime storage/defaults through interpreter/native execution. undefined union locals and nested record/array storage require whole-value initialization; inactive reads/writes, malformed explicit tags, nominal initializer conflicts, readonly updates, incomplete results and narrow overflow retain diagnostics. explicit sparse tag enums may contain unused members without adding match cases. imported private types, source/session disposal and repeat compilation preserve ownership. untagged metadata layouts remain invalid; mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
- [x] tagged type-storage checkpoint: tagged unions containing type leaves own active payload snapshots through typed/contextual construction, direct and nested field/index updates, immutable by-value matches and whole-value variant replacement. constant-input procedure arguments/results, erased generic inputs and normalized cache keys preserve nominal leaves and saved copies while generating runtime storage/defaults through interpreter/native execution. undefined union locals and nested record/array storage require whole-value initialization; inactive reads/writes, malformed explicit tags, nominal initializer conflicts, readonly updates, incomplete results and narrow overflow retain diagnostics. explicit sparse tag enums may contain unused members without adding match cases. imported private types, source/session disposal and repeat compilation preserve ownership. untagged metadata layouts remain invalid; mutable metadata borrowing, runtime metadata storage, general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
||||||
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals execute entirely at compile time without metadata inputs or results. scalar inputs remain explicit constants; nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse existing type-control execution through interpreter/native paths. runtime inputs and uninitialized leaves diagnose; ordinary unannotated local type aliases retain runtime parameters. imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. inferred metadata-only locals are covered below; mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals support constant-input execution without metadata inputs or results. nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse type-control execution through interpreter/native paths. uninitialized leaves and runtime dependencies diagnose; imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. independent runtime parameters are supported by the mixed-execution checkpoint below.
|
||||||
- [x] inferred metadata-local execution checkpoint: inferred mutable type bindings and metadata array/record/tuple locals select fully 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; runtime inputs diagnose. 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. erased compile-time-only procedures cannot become runtime entries. mixed runtime/type-valued execution, mutable metadata borrowing and broader materialization remain open.
|
- [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] 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.
|
||||||
- [ ] 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. broader imported C-binding support may interleave with later gates.
|
- [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.
|
||||||
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
- [ ] 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.
|
- [ ] 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.
|
||||||
|
|||||||
@@ -485,6 +485,13 @@ expand_expected proc(state @mut State, id, expected ast.NodeId) void ! Error {
|
|||||||
if (class == .function) {
|
if (class == .function) {
|
||||||
state.return_type = ast.function_decl(&state.tree, id).return_type
|
state.return_type = ast.function_decl(&state.tree, id).return_type
|
||||||
state.yield_type = no_node
|
state.yield_type = no_node
|
||||||
|
if (!ast.function_external(&state.tree, id)
|
||||||
|
and type_local_storage(&state.tree, state.bindings.items,
|
||||||
|
ast.function_decl(&state.tree, id).body, 0)
|
||||||
|
and !compiletime_call(state, id)) {
|
||||||
|
try expand_runtime_metadata(state, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (class == .statement) {
|
if (class == .statement) {
|
||||||
match ast.stmt_kind(&state.tree, id) {
|
match ast.stmt_kind(&state.tree, id) {
|
||||||
@@ -2318,16 +2325,167 @@ type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId,
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# local metadata does not make unrelated parameters constant. retain constant-input
|
||||||
|
# execution only when a parameter participates in constructing or updating metadata.
|
||||||
|
@hide:file
|
||||||
|
metadata_local proc(state @State, id ast.NodeId) bool {
|
||||||
|
if (ast.node_class(&state.tree, id) != .statement
|
||||||
|
or ast.stmt_kind(&state.tree, id) != .declaration) return false
|
||||||
|
declaration :: ast.statement_decl(&state.tree, id)
|
||||||
|
if (declaration.type) |annotation| return type_record_storage(state, annotation, 0)
|
||||||
|
return type_storage_initializer(&state.tree, state.bindings.items, declaration.initializer,
|
||||||
|
declaration.mutability == .variable, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
assignment_root proc(state @State, id ast.NodeId) ast.NodeId {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
if (kind == .selection) return assignment_root(state, ast.selection_expr(&state.tree, id).base)
|
||||||
|
if (kind == .index) return assignment_root(state, ast.index_expr(&state.tree, id).base)
|
||||||
|
if (kind == .builtin_call
|
||||||
|
and mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "field")
|
||||||
|
and ast.call_arg_count(&state.tree, id) == 2)
|
||||||
|
return assignment_root(state, ast.call_arg(&state.tree, id, 0))
|
||||||
|
resolved :: binding(state, id)
|
||||||
|
return if (resolved.kind == .declaration) resolved.data.declaration else no_node
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
DependencyPath :: struct { declaration ast.NodeId, parent ?@DependencyPath }
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parameter_dependency proc(state @State, id, body ast.NodeId, path ?@DependencyPath, depth usize) bool {
|
||||||
|
if (depth >= 128) return true
|
||||||
|
resolved :: binding(state, id)
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
declaration :: resolved.data.declaration
|
||||||
|
class :: ast.node_class(&state.tree, declaration)
|
||||||
|
if (class == .parameter and ast.param(&state.tree, declaration).type != no_node)
|
||||||
|
return !ast.param(&state.tree, declaration).comptime
|
||||||
|
if (class == .statement or class == .expression or class == .parameter) {
|
||||||
|
current := path
|
||||||
|
while (current) |entry| {
|
||||||
|
if (entry.declaration == declaration) return false
|
||||||
|
current = entry.parent
|
||||||
|
}
|
||||||
|
entry :: DependencyPath{ declaration = declaration, parent = path }
|
||||||
|
if (class == .statement
|
||||||
|
and parameter_dependency(state, ast.statement_decl(&state.tree, declaration).initializer,
|
||||||
|
body, &entry, depth + 1)) return true
|
||||||
|
if (class == .statement and ast.statement_decl(&state.tree, declaration).mutability == .constant)
|
||||||
|
return false
|
||||||
|
return declaration_parameter_dependency(state, body, declaration, body, &entry, false, depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (parameter_dependency(state, ast.child(&state.tree, id, i), body, path, depth + 1)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
subtree_has proc(state @State, root, node ast.NodeId) bool {
|
||||||
|
if (root == node) return true
|
||||||
|
for (0..ast.child_count(&state.tree, root)) |i|
|
||||||
|
if (subtree_has(state, ast.child(&state.tree, root, i), node)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
# follow writes as well as initializers; the declaration path breaks assignment cycles.
|
||||||
|
@hide:file
|
||||||
|
declaration_parameter_dependency proc(
|
||||||
|
state @State, id, declaration, body ast.NodeId, path @DependencyPath, dynamic bool, depth usize,
|
||||||
|
) bool {
|
||||||
|
if (depth >= 128) return true
|
||||||
|
selected := dynamic
|
||||||
|
class :: ast.node_class(&state.tree, id)
|
||||||
|
if (class == .statement) {
|
||||||
|
kind :: ast.stmt_kind(&state.tree, id)
|
||||||
|
if (kind == .assignment_stmt) {
|
||||||
|
assignment :: ast.assignment_stmt(&state.tree, id)
|
||||||
|
if (assignment_root(state, assignment.target) == declaration)
|
||||||
|
return (dynamic or parameter_dependency(state, assignment.target, body, path, depth + 1)
|
||||||
|
or parameter_dependency(state, assignment.value, body, path, depth + 1))
|
||||||
|
}
|
||||||
|
if (kind == .if_stmt or kind == .while_stmt or kind == .for_stmt) {
|
||||||
|
condition := no_node
|
||||||
|
captured := false
|
||||||
|
if (kind == .if_stmt) {
|
||||||
|
view :: ast.if_stmt(&state.tree, id)
|
||||||
|
condition = view.condition
|
||||||
|
captured = view.capture != null and view.capture? == declaration
|
||||||
|
} else if (kind == .while_stmt) {
|
||||||
|
view :: ast.while_stmt(&state.tree, id)
|
||||||
|
condition = view.condition
|
||||||
|
captured = view.capture != null and view.capture? == declaration
|
||||||
|
} else {
|
||||||
|
view :: ast.for_stmt(&state.tree, id)
|
||||||
|
condition = view.iterable
|
||||||
|
captured = ((view.capture != null and view.capture? == declaration)
|
||||||
|
or (view.index != null and view.index? == declaration))
|
||||||
|
}
|
||||||
|
if (captured) return parameter_dependency(state, condition, body, path, depth + 1)
|
||||||
|
if (!subtree_has(state, id, declaration))
|
||||||
|
selected = dynamic or parameter_dependency(state, condition, body, path, depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
|
||||||
|
subject :: ast.match_expr(&state.tree, id).subject
|
||||||
|
for (0..ast.match_arm_count(&state.tree, id)) |i| {
|
||||||
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, id, i))
|
||||||
|
if (arm.capture != null and arm.capture? == declaration)
|
||||||
|
return parameter_dependency(state, subject, body, path, depth + 1)
|
||||||
|
}
|
||||||
|
if (!subtree_has(state, id, declaration))
|
||||||
|
selected = dynamic or parameter_dependency(state, subject, body, path, depth + 1)
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (declaration_parameter_dependency(state, ast.child(&state.tree, id, i),
|
||||||
|
declaration, body, path, selected, depth + 1)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
metadata_parameter_dependency proc(state @State, id, body, dynamic ast.NodeId) bool {
|
||||||
|
class :: ast.node_class(&state.tree, id)
|
||||||
|
if (metadata_local(state, id) and parameter_dependency(state, id, body, null, 0)) return true
|
||||||
|
if (class == .statement) {
|
||||||
|
kind :: ast.stmt_kind(&state.tree, id)
|
||||||
|
if (kind == .assignment_stmt) {
|
||||||
|
assignment :: ast.assignment_stmt(&state.tree, id)
|
||||||
|
root :: assignment_root(state, assignment.target)
|
||||||
|
if (root != no_node and metadata_local(state, root))
|
||||||
|
return ((dynamic != no_node and !subtree_has(state, dynamic, root))
|
||||||
|
or parameter_dependency(state, assignment.target, body, null, 0)
|
||||||
|
or parameter_dependency(state, assignment.value, body, null, 0))
|
||||||
|
}
|
||||||
|
if (kind == .if_stmt or kind == .while_stmt or kind == .for_stmt) {
|
||||||
|
condition :: if (kind == .if_stmt) ast.if_stmt(&state.tree, id).condition
|
||||||
|
else if (kind == .while_stmt) ast.while_stmt(&state.tree, id).condition
|
||||||
|
else ast.for_stmt(&state.tree, id).iterable
|
||||||
|
selected :: if (parameter_dependency(state, condition, body, null, 0)) id else dynamic
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (metadata_parameter_dependency(state, ast.child(&state.tree, id, i), body, selected)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (metadata_parameter_dependency(state, ast.child(&state.tree, id, i), body, dynamic)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
compiletime_call proc(state @State, function ast.NodeId) bool {
|
compiletime_call proc(state @State, function ast.NodeId) bool {
|
||||||
if (type_record_storage(state, ast.function_decl(&state.tree, function).return_type, 0)) return true
|
if (type_record_storage(state, ast.function_decl(&state.tree, function).return_type, 0)) return true
|
||||||
|
runtime_input := false
|
||||||
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
||||||
parameter :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
parameter :: ast.param(&state.tree, ast.function_param(&state.tree, function, i))
|
||||||
if (!parameter.comptime and type_record_storage(state, parameter.type, 0)) return true
|
if (!parameter.comptime and type_record_storage(state, parameter.type, 0)) return true
|
||||||
|
if (!parameter.comptime) runtime_input = true
|
||||||
}
|
}
|
||||||
return (!ast.function_external(&state.tree, function)
|
if (!runtime_input or ast.function_external(&state.tree, function)) return false
|
||||||
and type_local_storage(&state.tree, state.bindings.items,
|
body :: ast.function_decl(&state.tree, function).body
|
||||||
ast.function_decl(&state.tree, function).body, 0))
|
if (!type_local_storage(&state.tree, state.bindings.items, body, 0)) return false
|
||||||
|
return metadata_parameter_dependency(state, body, body, no_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -3495,11 +3653,273 @@ TypeExecution :: struct {
|
|||||||
loop_depth usize = 0
|
loop_depth usize = 0
|
||||||
inline_body ast.NodeId = no_node
|
inline_body ast.NodeId = no_node
|
||||||
inline_blocks ?@mut arraylist.ArrayList(ast.NodeId) = null
|
inline_blocks ?@mut arraylist.ArrayList(ast.NodeId) = null
|
||||||
|
residual ?@mut arraylist.ArrayList(ast.NodeId) = null
|
||||||
|
residual_boundary usize = 0
|
||||||
|
residual_body ast.NodeId = no_node
|
||||||
|
residual_flow TypeFlow = .next
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
|
TypeFlow :: enum { next, returned, yielded, break, continue, invalid }
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
ResidualCleanup :: struct { position usize, statement ast.NodeId }
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
metadata_reads proc(state @State, id, declaration ast.NodeId, depth usize) bool {
|
||||||
|
if (depth >= 128) return false
|
||||||
|
resolved :: binding(state, id)
|
||||||
|
if (resolved.kind == .declaration) {
|
||||||
|
if (resolved.data.declaration == declaration) return true
|
||||||
|
if (ast.node_class(&state.tree, resolved.data.declaration) == .statement)
|
||||||
|
return metadata_reads(state, ast.statement_decl(&state.tree, resolved.data.declaration).initializer,
|
||||||
|
declaration, depth + 1)
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (metadata_reads(state, ast.child(&state.tree, id, i), declaration, depth + 1)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
metadata_updates_outer proc(state @State, id, scope ast.NodeId) bool {
|
||||||
|
if (ast.node_class(&state.tree, id) == .statement and ast.stmt_kind(&state.tree, id) == .assignment_stmt) {
|
||||||
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, id).target)
|
||||||
|
if (root != no_node and metadata_local(state, root) and !subtree_has(state, scope, root)) return true
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (metadata_updates_outer(state, ast.child(&state.tree, id, i), scope)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
metadata_updates proc(state @State, id ast.NodeId) bool {
|
||||||
|
return metadata_updates_outer(state, id, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
metadata_needs proc(state @State, id, declaration ast.NodeId) bool {
|
||||||
|
if (metadata_local(state, id) and metadata_reads(state, id, declaration, 0)) return true
|
||||||
|
if (ast.node_class(&state.tree, id) == .statement) {
|
||||||
|
kind :: ast.stmt_kind(&state.tree, id)
|
||||||
|
if (kind == .assignment_stmt) {
|
||||||
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, id).target)
|
||||||
|
if (root != no_node and metadata_local(state, root)
|
||||||
|
and metadata_reads(state, id, declaration, 0)) return true
|
||||||
|
}
|
||||||
|
if ((kind == .while_stmt or kind == .for_stmt or kind == .if_stmt) and metadata_updates(state, id)) {
|
||||||
|
condition :: if (kind == .while_stmt) ast.while_stmt(&state.tree, id).condition
|
||||||
|
else if (kind == .for_stmt) ast.for_stmt(&state.tree, id).iterable
|
||||||
|
else ast.if_stmt(&state.tree, id).condition
|
||||||
|
if (metadata_reads(state, condition, declaration, 0)) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i|
|
||||||
|
if (metadata_needs(state, ast.child(&state.tree, id, i), declaration)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
# erase metadata storage before ordinary checking. runtime scopes may create their
|
||||||
|
# own metadata, but cannot change a snapshot belonging to an enclosing runtime path.
|
||||||
|
@hide:file
|
||||||
|
residual_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId, boundary usize) ast.NodeId ! Error {
|
||||||
|
saved :: execution.locals.items.len
|
||||||
|
saved_output :: execution.residual
|
||||||
|
saved_boundary :: execution.residual_boundary
|
||||||
|
statements := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&statements)
|
||||||
|
cleanups := arraylist.init(ResidualCleanup, state.allocator)
|
||||||
|
defer arraylist.deinit(&cleanups)
|
||||||
|
execution.residual = &statements
|
||||||
|
execution.residual_boundary = boundary
|
||||||
|
execution.residual_flow = .next
|
||||||
|
defer {
|
||||||
|
execution.locals.items = execution.locals.items[..saved]
|
||||||
|
execution.residual = saved_output
|
||||||
|
execution.residual_boundary = saved_boundary
|
||||||
|
}
|
||||||
|
for (0..ast.block_statement_count(&state.tree, block)) |i| {
|
||||||
|
statement :: ast.block_statement(&state.tree, block, i)
|
||||||
|
if (!try type_step(state, statement)) return no_node
|
||||||
|
kind :: ast.stmt_kind(&state.tree, statement)
|
||||||
|
if ((kind == .break_stmt or kind == .continue_stmt) and execution.loop_depth > 0) {
|
||||||
|
execution.residual_flow = if (kind == .break_stmt) TypeFlow.break else TypeFlow.continue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (kind == .defer_stmt) {
|
||||||
|
try arraylist.append(&cleanups, ResidualCleanup{ position = statements.items.len, statement = statement })
|
||||||
|
try arraylist.append(&statements, statement)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
constant := metadata_local(state, statement)
|
||||||
|
if (kind == .declaration and !constant) {
|
||||||
|
declaration :: ast.statement_decl(&state.tree, statement)
|
||||||
|
constant = (type_storage_initializer(&state.tree, state.bindings.items,
|
||||||
|
declaration.initializer, true, 0)
|
||||||
|
or metadata_needs(state, execution.residual_body, statement))
|
||||||
|
}
|
||||||
|
if (kind == .assignment_stmt) {
|
||||||
|
root :: assignment_root(state, ast.assignment_stmt(&state.tree, statement).target)
|
||||||
|
for (execution.locals.items) |local, index| {
|
||||||
|
if (root != local.declaration) continue
|
||||||
|
if (index < boundary) {
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
|
||||||
|
.comptime_argument_required, ast.node_span(&state.tree, statement))
|
||||||
|
return no_node
|
||||||
|
}
|
||||||
|
constant = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (constant) {
|
||||||
|
saved_values :: execution.evaluate_values
|
||||||
|
execution.evaluate_values = true
|
||||||
|
flow :: execute_type_statement(state, execution, statement) catch |err| {
|
||||||
|
execution.evaluate_values = saved_values
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
execution.evaluate_values = saved_values
|
||||||
|
if (flow != .next) return no_node
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (kind == .block_stmt) {
|
||||||
|
child :: try residual_block(state, execution, ast.statement_block(&state.tree, statement), boundary)
|
||||||
|
if (child == no_node) return no_node
|
||||||
|
try arraylist.append(&statements, try ast.add_block_stmt(state.builder, child))
|
||||||
|
try refresh(state)
|
||||||
|
if (execution.residual_flow != .next) break
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (kind == .expression
|
||||||
|
and ast.expr_kind(&state.tree, ast.statement_expr(&state.tree, statement)) == .match_expr) {
|
||||||
|
expression :: ast.statement_expr(&state.tree, statement)
|
||||||
|
subject :: try type_snapshot(state, execution, ast.match_expr(&state.tree, expression).subject)
|
||||||
|
if (subject == no_node) return no_node
|
||||||
|
try expand(state, subject)
|
||||||
|
if (type_record_storage(state, try expression_type(state, subject, 0), 0)
|
||||||
|
or branch_constant(state, subject, 0)) {
|
||||||
|
arms := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&arms)
|
||||||
|
for (0..ast.match_arm_count(&state.tree, expression)) |position|
|
||||||
|
try arraylist.append(&arms, ast.match_arm(&state.tree, expression, position))
|
||||||
|
dispatch :: try ast.add_match(state.builder, ast.node_span(&state.tree, expression), subject, arms.items)
|
||||||
|
try refresh(state)
|
||||||
|
try expand_inline_match(state, dispatch, try expression_type(state, subject, 0))
|
||||||
|
index :: try type_match_arm(state, dispatch)
|
||||||
|
if (index == no_index) return no_node
|
||||||
|
arm :: ast.match_arm_view(&state.tree, ast.match_arm(&state.tree, dispatch, index))
|
||||||
|
selected :: try type_match_body(state, dispatch, arm)
|
||||||
|
if (selected == no_node) return no_node
|
||||||
|
if (arm.expression) {
|
||||||
|
snapshot :: try type_snapshot(state, execution, selected)
|
||||||
|
if (snapshot == no_node) return no_node
|
||||||
|
try arraylist.append(&statements, try ast.add_expression_stmt(state.builder, snapshot))
|
||||||
|
} else {
|
||||||
|
body :: try residual_block(state, execution, selected, boundary)
|
||||||
|
if (body == no_node) return no_node
|
||||||
|
value :: try ast.add_block_expr(state.builder, ast.node_span(&state.tree, expression), body)
|
||||||
|
try arraylist.append(&statements, try ast.add_expression_stmt(state.builder, value))
|
||||||
|
if (execution.residual_flow == .yielded) execution.residual_flow = .next
|
||||||
|
}
|
||||||
|
try refresh(state)
|
||||||
|
if (execution.residual_flow != .next) break
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kind == .if_stmt and ast.if_stmt(&state.tree, statement).capture != null) {
|
||||||
|
condition :: try type_snapshot(state, execution, ast.if_stmt(&state.tree, statement).condition)
|
||||||
|
if (condition == no_node) return no_node
|
||||||
|
try expand(state, condition)
|
||||||
|
if (type_record_storage(state, try expression_type(state, condition, 0), 0)) {
|
||||||
|
flow :: try execute_type_statement(state, execution, statement)
|
||||||
|
if (flow == .invalid) return no_node
|
||||||
|
execution.residual_flow = flow
|
||||||
|
if (flow != .next) break
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kind == .if_stmt and ast.if_stmt(&state.tree, statement).capture == null) {
|
||||||
|
condition :: try type_snapshot(state, execution, ast.if_stmt(&state.tree, statement).condition)
|
||||||
|
if (condition == no_node) return no_node
|
||||||
|
try expand(state, condition)
|
||||||
|
if (branch_constant(state, condition, 0)) {
|
||||||
|
value :: try evaluate_ir(state, condition, try builtin(state, condition, .bool))
|
||||||
|
if (value.kind != .boolean) return no_node
|
||||||
|
view :: ast.if_stmt(&state.tree, statement)
|
||||||
|
selected := if (value.boolean) view.then_block else no_node
|
||||||
|
if (!value.boolean and view.else_block != null) selected = view.else_block?
|
||||||
|
if (selected != no_node) {
|
||||||
|
child :: try residual_block(state, execution, selected, boundary)
|
||||||
|
if (child == no_node) return no_node
|
||||||
|
try arraylist.append(&statements, try ast.add_block_stmt(state.builder, child))
|
||||||
|
try refresh(state)
|
||||||
|
if (execution.residual_flow != .next) break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((kind == .for_stmt or kind == .while_stmt) and metadata_updates(state, statement)) {
|
||||||
|
flow :: try execute_type_statement(state, execution, statement)
|
||||||
|
if (flow == .invalid) return no_node
|
||||||
|
execution.residual_flow = flow
|
||||||
|
if (flow != .next) break
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
snapshot :: try type_snapshot(state, execution, statement)
|
||||||
|
if (snapshot == no_node) return no_node
|
||||||
|
try arraylist.append(&statements, snapshot)
|
||||||
|
if (kind == .return_stmt or kind == .yield_stmt) {
|
||||||
|
execution.residual_flow = if (kind == .return_stmt) TypeFlow.returned else TypeFlow.yielded
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remaining := cleanups.items.len
|
||||||
|
while (remaining > 0) {
|
||||||
|
remaining -= 1
|
||||||
|
cleanup :: cleanups.items[remaining]
|
||||||
|
saved_flow :: execution.residual_flow
|
||||||
|
body :: try residual_block(state, execution, ast.cleanup_stmt(&state.tree, cleanup.statement).body, boundary)
|
||||||
|
if (body == no_node) return no_node
|
||||||
|
statements.items[cleanup.position] = try ast.add_cleanup_stmt(state.builder,
|
||||||
|
ast.node_span(&state.tree, cleanup.statement), body, null, false)
|
||||||
|
try refresh(state)
|
||||||
|
execution.residual_flow = saved_flow
|
||||||
|
}
|
||||||
|
result :: try ast.add_block(state.builder, ast.node_span(&state.tree, block), statements.items)
|
||||||
|
try refresh(state)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
expand_runtime_metadata proc(state @mut State, function ast.NodeId) void ! Error {
|
||||||
|
view :: ast.function_decl(&state.tree, function)
|
||||||
|
if (state.type_execution_depth == 0) state.type_execution_remaining = 4096
|
||||||
|
state.type_execution_depth += 1
|
||||||
|
defer state.type_execution_depth -= 1
|
||||||
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
|
execution := TypeExecution{
|
||||||
|
locals = arraylist.init(state.allocator),
|
||||||
|
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 mem.free(state.allocator, execution.mapping)
|
||||||
|
mem.set(execution.mapping, no_node)
|
||||||
|
body := try residual_block(state, &execution, view.body, 0)
|
||||||
|
if (body == no_node) {
|
||||||
|
diagnostic := state.evaluation_diagnostic
|
||||||
|
if (diagnostic == diagnostics.no_diagnostic)
|
||||||
|
diagnostic = try diagnostics.add(state.diagnostics, .comptime_argument_required, view.span)
|
||||||
|
invalid :: try ast.add_invalid_stmt(state.builder, view.span, diagnostic)
|
||||||
|
statements [1]ast.NodeId :: [invalid]
|
||||||
|
body = try ast.add_block(state.builder, view.span, statements[..])
|
||||||
|
}
|
||||||
|
try replace(state, view.body, body)
|
||||||
|
try refresh(state)
|
||||||
|
try expand(state, view.return_type)
|
||||||
|
for (0..ast.function_param_count(&state.tree, function)) |i|
|
||||||
|
try expand(state, ast.function_param(&state.tree, function, i))
|
||||||
|
try expand(state, view.body)
|
||||||
|
}
|
||||||
|
|
||||||
# type values cannot enter runtime IR. execute their control flow here, substituting
|
# type values cannot enter runtime IR. execute their control flow here, substituting
|
||||||
# owned constants into snapshots; ordinary value operations still use checked IR.
|
# owned constants into snapshots; ordinary value operations still use checked IR.
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -3599,6 +4019,37 @@ type_step proc(state @mut State, at ast.NodeId) bool ! Error {
|
|||||||
@hide:file
|
@hide:file
|
||||||
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void ! Error {
|
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void ! Error {
|
||||||
if (id == no_node or usize(id) >= execution.mapping.len or execution.pending_flow != .next) return
|
if (id == no_node or usize(id) >= execution.mapping.len or execution.pending_flow != .next) return
|
||||||
|
resolved_capture :: binding(state, id)
|
||||||
|
if (execution.residual != null and !execution.evaluate_values
|
||||||
|
and (ast.node_class(&state.tree, id) == .expression or ast.node_class(&state.tree, id) == .parameter)
|
||||||
|
and resolved_capture.kind == .declaration and resolved_capture.data.declaration == id) return
|
||||||
|
if (execution.residual != null and !execution.evaluate_values) {
|
||||||
|
# captures precede their bodies semantically, regardless of AST child order.
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i| {
|
||||||
|
child :: ast.child(&state.tree, id, i)
|
||||||
|
resolved :: binding(state, child)
|
||||||
|
if ((ast.node_class(&state.tree, child) != .expression and ast.node_class(&state.tree, child) != .parameter)
|
||||||
|
or resolved.kind != .declaration or resolved.data.declaration != child) continue
|
||||||
|
capture :: try ast.copy_node(state.builder, child)
|
||||||
|
try refresh(state)
|
||||||
|
execution.mapping[usize(child)] = capture
|
||||||
|
state.bindings.items[usize(capture)] = sema.Binding{
|
||||||
|
kind = .declaration, data = sema.BindingData{ declaration = capture },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (execution.residual != null and !execution.evaluate_values and ast.node_class(&state.tree, id) == .block) {
|
||||||
|
saved_flow :: execution.residual_flow
|
||||||
|
saved_loop :: execution.loop_depth
|
||||||
|
execution.loop_depth = 0
|
||||||
|
defer {
|
||||||
|
execution.residual_flow = saved_flow
|
||||||
|
execution.loop_depth = saved_loop
|
||||||
|
}
|
||||||
|
execution.mapping[usize(id)] = try residual_block(state, execution, id, execution.locals.items.len)
|
||||||
|
if (execution.mapping[usize(id)] == no_node) execution.pending_flow = .invalid
|
||||||
|
return
|
||||||
|
}
|
||||||
# substituted arguments belong to the caller, including nominal type identity.
|
# substituted arguments belong to the caller, including nominal type identity.
|
||||||
if (id < execution.first_owned) {
|
if (id < execution.first_owned) {
|
||||||
execution.mapping[usize(id)] = id
|
execution.mapping[usize(id)] = id
|
||||||
@@ -5822,6 +6273,14 @@ type_borrow_call proc(state @mut State, execution @mut TypeExecution, call ast.N
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
execute_type_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId) TypeFlow ! Error {
|
execute_type_block proc(state @mut State, execution @mut TypeExecution, block ast.NodeId) TypeFlow ! Error {
|
||||||
|
if (execution.residual != null and !execution.evaluate_values) {
|
||||||
|
block_value :: try residual_block(state, execution, block, execution.residual_boundary)
|
||||||
|
if (block_value == no_node) return .invalid
|
||||||
|
statement :: try ast.add_block_stmt(state.builder, block_value)
|
||||||
|
try refresh(state)
|
||||||
|
try arraylist.append(execution.residual?, statement)
|
||||||
|
return execution.residual_flow
|
||||||
|
}
|
||||||
if (block == execution.inline_body) {
|
if (block == execution.inline_body) {
|
||||||
saved_demand :: state.demand_depth
|
saved_demand :: state.demand_depth
|
||||||
state.demand_depth = 0
|
state.demand_depth = 0
|
||||||
|
|||||||
+214
-5
@@ -170,14 +170,223 @@ CASES = {
|
|||||||
}
|
}
|
||||||
""", b"[65, 233, 128578, 10, 13, 9, 92, 39]\n", None,
|
""", b"[65, 233, 128578, 10, 13, 9, 92, 39]\n", None,
|
||||||
),
|
),
|
||||||
"compiletime_only_main_cannot_select_another_runtime_function": (
|
"mixed_metadata_locals_preserve_snapshots_and_runtime_effects": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
compute proc($T type, effects @mut i16, value T) T {
|
||||||
|
Explicit type := i8
|
||||||
|
Explicit = T
|
||||||
|
Inferred := bool
|
||||||
|
Inferred = Explicit
|
||||||
|
types [2]mut type := [i8, bool]
|
||||||
|
saved_types :: types
|
||||||
|
if (T == i16) { types[0] = Inferred } else { types[0] = i16 }
|
||||||
|
inferred_types := [i8, bool]
|
||||||
|
saved_inferred_types :: inferred_types
|
||||||
|
inferred_types = [types[0], bool]
|
||||||
|
config Config := { child = i8, count = 1 }
|
||||||
|
saved_config :: config
|
||||||
|
config.child = inferred_types[0]
|
||||||
|
config.count += 1
|
||||||
|
inferred_config := Config { child = bool, count = 1 }
|
||||||
|
saved_inferred_config :: inferred_config
|
||||||
|
inferred_config.child = config.child
|
||||||
|
inferred_config.count = config.count
|
||||||
|
if (saved_types[0] != i8 or saved_inferred_types[0] != i8) return T(-1)
|
||||||
|
if (saved_config.child != i8 or saved_config.count != 1) return T(-2)
|
||||||
|
if (saved_inferred_config.child != bool or saved_inferred_config.count != 1) return T(-3)
|
||||||
|
Element :: inferred_config.child
|
||||||
|
values [inferred_config.count]Element :: [value, T(2)]
|
||||||
|
effects^ += 1
|
||||||
|
return values[0] + values[1]
|
||||||
|
}
|
||||||
|
forward proc(effects @mut i16, value i16) i16 { return compute(i16, effects, value) }
|
||||||
|
main proc() [2]i16 {
|
||||||
|
effects := i16(0)
|
||||||
|
first :: forward(&effects, 18)
|
||||||
|
second :: forward(&effects, 20)
|
||||||
|
return [first + second, effects]
|
||||||
|
}
|
||||||
|
""", b"[42, 2]\n", None,
|
||||||
|
),
|
||||||
|
"mixed_runtime_branches_and_loops_keep_metadata_locals_independent": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
compute proc(value i16, repeats usize, take bool) i16 {
|
||||||
|
total := value
|
||||||
|
if (take) {
|
||||||
|
Element := i8
|
||||||
|
Element = i16
|
||||||
|
amount Element :: 2
|
||||||
|
total += amount
|
||||||
|
} else {
|
||||||
|
config Config := { child = i8, count = 1 }
|
||||||
|
config.child = i16
|
||||||
|
amount config.child :: 4
|
||||||
|
total += amount
|
||||||
|
}
|
||||||
|
index := usize(0)
|
||||||
|
while (index < repeats) {
|
||||||
|
config := Config { child = i8, count = 1 }
|
||||||
|
saved :: config
|
||||||
|
config.child = i16
|
||||||
|
config.count += 1
|
||||||
|
if (saved.child != i8 or saved.count != 1) return -1
|
||||||
|
values [config.count]config.child :: [total, 1]
|
||||||
|
total = values[0] + values[1]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
forward proc(value i16, repeats usize, take bool) i16 { return compute(value, repeats, take) }
|
||||||
|
main proc() [2]i16 { return [forward(38, 2, true), forward(36, 2, false)] }
|
||||||
|
""", b"[42, 42]\n", None,
|
||||||
|
),
|
||||||
|
"mixed_metadata_construction_static_exits_and_cleanup": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
compute proc(value i16, effects @mut i16) i16 {
|
||||||
|
config Config := { child = i8, count = 0 }
|
||||||
|
types [2]mut type := undefined
|
||||||
|
for (0..usize(3)) |index| {
|
||||||
|
if (index == 2) break
|
||||||
|
types[index] = i16
|
||||||
|
config.count += 1
|
||||||
|
effects^ += 1
|
||||||
|
}
|
||||||
|
{
|
||||||
|
defer config.child = types[0]
|
||||||
|
config.child = bool
|
||||||
|
}
|
||||||
|
selected ?type :: types[1]
|
||||||
|
if selected |Child| {
|
||||||
|
if (Child != i16) return -1
|
||||||
|
amount Child :: 2
|
||||||
|
values [config.count]config.child :: [value, amount]
|
||||||
|
return values[0] + values[1]
|
||||||
|
}
|
||||||
|
return -2
|
||||||
|
}
|
||||||
|
main proc() [2]i16 {
|
||||||
|
value := i16(40)
|
||||||
|
effects := i16(0)
|
||||||
|
result :: compute(value, &effects)
|
||||||
|
return [result, effects]
|
||||||
|
}
|
||||||
|
""", b"[42, 2]\n", None,
|
||||||
|
),
|
||||||
|
"mixed_runtime_captures_and_tagged_metadata_selection": (
|
||||||
|
"""
|
||||||
|
Choice :: union(enum) { child type, empty void }
|
||||||
|
compute proc(value ?i16, values []i16) i16 {
|
||||||
|
T := i16
|
||||||
|
choice Choice :: { child = T }
|
||||||
|
match choice {
|
||||||
|
.child |Child|: { if (Child != i16) return -1 },
|
||||||
|
.empty: return -2,
|
||||||
|
}
|
||||||
|
total := i16(0)
|
||||||
|
if value |captured| {
|
||||||
|
U := T
|
||||||
|
local U :: captured
|
||||||
|
total += local
|
||||||
|
}
|
||||||
|
for values |captured, index| {
|
||||||
|
U := T
|
||||||
|
local U :: captured
|
||||||
|
total += local + i16(index)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
values [2]i16 :: [10, 10]
|
||||||
|
return compute(21, values[..])
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"metadata_input_dependencies_follow_mutable_scalar_updates": (
|
||||||
|
"""
|
||||||
|
compute proc(count usize) usize {
|
||||||
|
size := usize(0)
|
||||||
|
size = count
|
||||||
|
config struct { child type, count usize } := { child = i16, count = size }
|
||||||
|
return config.count
|
||||||
|
}
|
||||||
|
main proc() usize { return compute(42) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"metadata_scalar_update_dependencies_still_require_constant_inputs": (
|
||||||
|
"""
|
||||||
|
compute proc(count usize) usize {
|
||||||
|
size := usize(0)
|
||||||
|
size = count
|
||||||
|
config struct { child type, count usize } := { child = i16, count = size }
|
||||||
|
return config.count
|
||||||
|
}
|
||||||
|
forward proc(count usize) usize { return compute(count) }
|
||||||
|
main proc() usize { return forward(42) }
|
||||||
|
""", b"", b"compile-time value",
|
||||||
|
),
|
||||||
|
"mixed_metadata_locals_reject_runtime_type_initializers": (
|
||||||
|
"""
|
||||||
|
compute proc(wide bool) i16 {
|
||||||
|
Element := if (wide) i16 else i8
|
||||||
|
value Element :: 42
|
||||||
|
return i16(value)
|
||||||
|
}
|
||||||
|
forward proc(wide bool) i16 { return compute(wide) }
|
||||||
|
main proc() i16 { return forward(true) }
|
||||||
|
""", b"", b"compile-time value",
|
||||||
|
),
|
||||||
|
"mixed_metadata_locals_reject_runtime_record_updates": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
compute proc(wide bool) i16 {
|
||||||
|
config Config := { child = i8, count = 1 }
|
||||||
|
config.child = if (wide) i16 else i8
|
||||||
|
value config.child :: 42
|
||||||
|
return i16(value)
|
||||||
|
}
|
||||||
|
forward proc(wide bool) i16 { return compute(wide) }
|
||||||
|
main proc() i16 { return forward(true) }
|
||||||
|
""", b"", b"compile-time value",
|
||||||
|
),
|
||||||
|
"mixed_runtime_branches_reject_outer_type_mutation": (
|
||||||
|
"""
|
||||||
|
compute proc(wide bool) i16 {
|
||||||
|
Element := i8
|
||||||
|
if (wide) { Element = i16 }
|
||||||
|
value Element :: 42
|
||||||
|
return i16(value)
|
||||||
|
}
|
||||||
|
forward proc(wide bool) i16 { return compute(wide) }
|
||||||
|
main proc() i16 { return forward(true) }
|
||||||
|
""", b"", b"compile-time value",
|
||||||
|
),
|
||||||
|
"mixed_runtime_loops_reject_outer_metadata_mutation": (
|
||||||
|
"""
|
||||||
|
Config :: struct { child type, count usize }
|
||||||
|
compute proc(repeats usize) usize {
|
||||||
|
config := Config { child = i16, count = 0 }
|
||||||
|
index := usize(0)
|
||||||
|
while (index < repeats) {
|
||||||
|
config.count += 1
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return config.count
|
||||||
|
}
|
||||||
|
forward proc(repeats usize) usize { return compute(repeats) }
|
||||||
|
main proc() usize { return forward(42) }
|
||||||
|
""", b"", b"compile-time value",
|
||||||
|
),
|
||||||
|
"metadata_only_main_keeps_runtime_entrypoint": (
|
||||||
"""
|
"""
|
||||||
fallback proc() i16 { return -99 }
|
fallback proc() i16 { return -99 }
|
||||||
main proc() i16 {
|
main proc() i16 {
|
||||||
types := [i16, bool]
|
types := [i16, bool]
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
""", b"", b"main must be a runtime function with no parameters",
|
""", b"42\n", None,
|
||||||
),
|
),
|
||||||
"inferred_metadata_locals_preserve_owned_copies_and_updates": (
|
"inferred_metadata_locals_preserve_owned_copies_and_updates": (
|
||||||
"""
|
"""
|
||||||
@@ -296,7 +505,7 @@ CASES = {
|
|||||||
}
|
}
|
||||||
runtime proc(value usize) usize { return count(value) }
|
runtime proc(value usize) usize { return count(value) }
|
||||||
main proc() usize { return runtime(42) }
|
main proc() usize { return runtime(42) }
|
||||||
""", b"", b"generic argument must be a compile-time value",
|
""", b"", b"compile-time value",
|
||||||
),
|
),
|
||||||
"inferred_metadata_arrays_keep_readonly_elements": (
|
"inferred_metadata_arrays_keep_readonly_elements": (
|
||||||
"""
|
"""
|
||||||
@@ -369,7 +578,7 @@ CASES = {
|
|||||||
}
|
}
|
||||||
""", b"42\n", None,
|
""", b"42\n", None,
|
||||||
),
|
),
|
||||||
"explicit_metadata_locals_require_constant_runtime_inputs": (
|
"explicit_metadata_locals_allow_independent_runtime_inputs": (
|
||||||
"""
|
"""
|
||||||
count proc(value usize) usize {
|
count proc(value usize) usize {
|
||||||
T type :: i16
|
T type :: i16
|
||||||
@@ -377,7 +586,7 @@ CASES = {
|
|||||||
}
|
}
|
||||||
runtime proc(value usize) usize { return count(value) }
|
runtime proc(value usize) usize { return count(value) }
|
||||||
main proc() usize { return runtime(42) }
|
main proc() usize { return runtime(42) }
|
||||||
""", b"", b"generic argument must be a compile-time value",
|
""", b"42\n", None,
|
||||||
),
|
),
|
||||||
"explicit_metadata_locals_reject_incomplete_results": (
|
"explicit_metadata_locals_reject_incomplete_results": (
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user