diff --git a/README.md b/README.md index 43629b5..a62d58f 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ 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. +explicit local annotations containing `type`, such as `T type := i16`, also make a procedure compile-time-only, even with ordinary scalar inputs and results. calls evaluate with owned local metadata and are erased. nested metadata records/arrays, partial initialization, checked updates, loops, recursion and scoped cleanup reuse type-control execution and its shared 4096-step bound. every input remains explicitly supplied and constant; this is not partial evaluation of runtime inputs. ordinary local type aliases without metadata storage annotations keep their runtime-call contract. inferred metadata-only locals and mutable metadata borrowing remain open. + readonly type-valued slices (`[]type` and slices of metadata records/arrays) also cross compile-time procedure boundaries as owned snapshots. contextual array literals, including `[]`, construct them; explicit views of immutable readonly arrays and existing metadata slices support checked `usize` bounds, empty end views, indexing and `.len`. nested slices, ordinary/inline by-value iteration, erased `$` inputs and optional/fallible channels retain exact nominal leaves and content-based specialization identity. snapshots generate runtime types/defaults, but metadata itself has no runtime storage. mutable slice types, sentinels and views of mutable array places remain rejected rather than silently losing borrowing semantics; copy an array into an immutable binding before taking an owned view. construction shares the type-control step bound, and uninitialized leaves cannot cross procedure boundaries. optional `if`/`while` captures also execute in type-producing bodies: `if count |length| { return [length]i16 }` generates storage from an optional `usize`. ordinary checked IR validates each executed guard; present payloads become immutable owned constants with exact scalar/aggregate types and strings, and absent optionals skip the body. while guards observe updated locals on each iteration; captures stay body-local across break/continue and early return. generated lengths/defaults, imported producers, cache identity and repeated compilation preserve these values. null data/function pointers materialize as null; callbacks retain source declaration identity, while nonnull data pointers and unsupported slice types retain the constant-materialization escape boundary. diff --git a/TODO.md b/TODO.md index caf550d..039f194 100644 --- a/TODO.md +++ b/TODO.md @@ -58,6 +58,8 @@ checked immutable-array/slice views, by-value iteration and erased arguments; mutable metadata borrowing remains open. ordinary metadata-input procedures also compute owned runtime values, including reflected field names/counts, recursive helpers and fallible cleanup. +explicitly annotated local metadata storage also makes ordinary procedures +constant-input producers, preserving owned updates, loops, recursion and cleanup. `field!` selects record/tuple/enum and sequence members using compile-time names, including runtime places and type-control updates without runtime reflection. `struct_type!` generates ordinary/C-layout records from constant name/type/default @@ -172,6 +174,7 @@ 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] 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] 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, mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open. - [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates. - [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`. - [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library. diff --git a/source/compilation/compilation.test.hon b/source/compilation/compilation.test.hon index 3beff5d..ab5aefd 100644 --- a/source/compilation/compilation.test.hon +++ b/source/compilation/compilation.test.hon @@ -1406,6 +1406,7 @@ tagged_type_metadata_recompiles_owned_private_nominal_payloads test { ` value Selected :: { value = 40 } ` if (lib.read(lib.make(), value) != 42) return -4 ` if (lib.score(lib.make(), 40) != 42) return -6 + ` if (lib.score_local(40) != 42) return -7 ` snapshot Storage() :: { ` original = { values = [{ value = 19 }, { value = 21 }] }, ` nested = { values = [true] }, @@ -1468,6 +1469,13 @@ tagged_type_metadata_recompiles_owned_private_nominal_payloads test { ` if (saved.payload.children[0]? != Child) return -1 ` return i16(saved.payload.count) + bias `} + `score_local proc(bias i16) i16 { + ` config Config := { children = [Child, null], count = 2 } + ` saved :: config + ` config.children[0] = bool + ` if (saved.children[0]? != Child) return -1 + ` return i16(saved.count) + bias + `} , ) defer arraylist.deinit(&imported) diff --git a/source/sema/specialize/specialize.hon b/source/sema/specialize/specialize.hon index 1e82023..cd4f061 100644 --- a/source/sema/specialize/specialize.hon +++ b/source/sema/specialize/specialize.hon @@ -148,6 +148,9 @@ elaborate proc( if (!parameter.comptime and type_storage_annotation(&original, resolution.storage.bindings, parameter.type, 0)) needed = true } + if (!ast.function_external(&original, id) + and type_local_storage(&original, resolution.storage.bindings, + ast.function_decl(&original, id).body, 0)) needed = true } if (class == .statement and ast.stmt_kind(&original, id) == .declaration and type_storage_annotation(&original, resolution.storage.bindings, @@ -1857,6 +1860,23 @@ type_storage_annotation proc(tree @ast.Tree, bindings []sema.Binding, input ast. return false } +# explicit metadata locals have no runtime storage, just like metadata parameters. +# do not classify ordinary local type aliases as constant-input procedures. +@hide:file +type_local_storage proc(tree @ast.Tree, bindings []sema.Binding, id ast.NodeId, depth usize) bool { + if (depth >= 128) return false + if (ast.node_class(tree, id) == .statement and ast.stmt_kind(tree, id) == .declaration) { + declaration :: ast.statement_decl(tree, id) + if (declaration.type) |annotation| { + if (type_storage_annotation(tree, bindings, annotation, 0)) return true + } + } + for (0..ast.child_count(tree, id)) |i| { + if (type_local_storage(tree, bindings, ast.child(tree, id, i), depth + 1)) return true + } + return false +} + @hide:file 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 @@ -1864,7 +1884,9 @@ compiletime_call proc(state @State, function ast.NodeId) bool { 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 } - return false + return (!ast.function_external(&state.tree, function) + and type_local_storage(&state.tree, state.bindings.items, + ast.function_decl(&state.tree, function).body, 0)) } @hide:file @@ -1917,7 +1939,7 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void state.depth += 1 defer state.depth -= 1 count :: ast.function_param_count(&state.tree, function) - # ordinary metadata inputs and metadata results require a fully constant call. + # metadata inputs, results and explicit locals require a fully constant call. type_result :: compiletime_call(state, function) runtime := usize(0) for (0..count) |i| { diff --git a/tests/native.py b/tests/native.py index f83d471..d6100c6 100644 --- a/tests/native.py +++ b/tests/native.py @@ -11,6 +11,93 @@ HONEY = ROOT / "build/honey" # Explicit expectations guard against frontend bugs shared by both execution paths. CASES = { + "explicit_metadata_locals_compute_without_metadata_inputs": ( + """ + Config :: struct { children [2]mut ?type, count usize } + compute proc(count usize) i16 { + config Config := undefined + config.children = [i8, null] + config.count = 0 + saved :: config + while (config.count < count) { + config.children[0] = i16 + config.children[1] = config.children[0] + config.count = config.count + 1 + } + if (saved.children[0]? != i8 or saved.count != 0) return -1 + for config.children |child| { + if child |T| { if (T != i16) return -2 } + } + Element :: config.children[0]? + values [config.count]Element :: [19, 23] + return values[0] + values[1] + } + main proc() i16 { return $(compute(2)) } + """, b"42\n", None, + ), + "explicit_type_locals_recurse_and_preserve_cleanup": ( + """ + Error :: enum { failed } + count proc(depth usize) i16 { + T type := i8 + T = i16 + defer if (T != i16) unreachable + if (depth == 0) return 21 + return count(depth - 1) + 21 + } + fail proc() i16 ! Error { + T ?type := i16 + errdefer if (T? != i16) unreachable + return .failed + } + main proc() i16 { + return fail() catch { yield count(1) } + } + """, b"42\n", None, + ), + "explicit_metadata_locals_require_constant_runtime_inputs": ( + """ + count proc(value usize) usize { + T type :: i16 + return value + } + runtime proc(value usize) usize { return count(value) } + main proc() usize { return runtime(42) } + """, b"", b"generic argument must be a compile-time value", + ), + "explicit_metadata_locals_reject_incomplete_results": ( + """ + Config :: struct { child type, count usize } + compute proc() usize { + config Config := undefined + config.child = i16 + return config.count + } + main proc() usize { return compute() } + """, b"", b"read before initialization", + ), + "explicit_metadata_locals_run_scoped_error_cleanup": ( + """ + Error :: enum { failed } + fail proc() i16 ! Error { + T type :: i16 + errdefer unreachable + return .failed + } + main proc() i16 { return fail() catch 0 } + """, b"", b"reached unreachable statement", + ), + "ordinary_local_type_aliases_keep_runtime_parameters": ( + """ + add proc(value i16) i16 { + Local :: alias i16 + result Local :: value + 2 + return result + } + forward proc(value i16) i16 { return add(value) } + main proc() i16 { return forward(40) } + """, b"42\n", None, + ), "ordinary_metadata_inputs_compute_owned_runtime_results": ( """ Config :: struct { child type, count usize }