Explicit Dynamic tags, checked scalar bindings, ownership and the separate 2.21 extension.
Explicit Dynamic source matching
Core-1 shared-preview Algorithm bodies can inspect a Dynamic local through
match. The closed tags are null, bool, i64, f64, text, list, and
record. Cover all seven tags or end with _; duplicate, unknown, unreachable,
and nonexhaustive patterns reject with ZL-TYPE-0003.
module example.matching;
public value App {
public interpreted algorithm message invoke(type: Class<Self>,
item: Dynamic, yes: Dynamic, no: Dynamic) -> Dynamic effects {} {
return App.select(item, yes, no);
}
public compiled algorithm message select(type: Class<Self>,
item: Dynamic, yes: Dynamic, no: Dynamic) -> Dynamic effects {} {
match (item) {
Dynamic.i64(let number) => {
if (number < 0) { return no; }
return yes;
}
Dynamic.bool(let flag) => {
if (flag) { return yes; }
return no;
}
_ => { return no; }
}
}
}
Only bool and i64 admit a single optional let binding, typed as Bool
and Int64 respectively and scoped to the arm. All tags admit tag-only patterns.
Matching observes the value without consuming it. There is no implicit numeric
conversion, truthiness, unboxing, or boxing at a function boundary. Other payload
types remain opaque to source matching, including binary64; their owned values
can still be transferred and returned intact. Scalar bindings support existing
checked local expressions and control flow; scalar arguments/results in helper
calls are outside the complete-Dynamic invocation interface.
Execution and ownership
zl_module_invoke_dynamic_v2 and benchmark request revision 4
execute an interpreted public root with complete Dynamic parameters/results.
Reachable compiled or interpreted callees with that same signature can perform
explicit matching. Modality is not a native execution tier: this does not add
compiled root entry points, portable-C emission, AOT, or JIT Dynamic support.
Imported calls use the checked linked closure, including transitive packages.
The caller retains its arguments. Each invocation owns its local copies and
returns a separately owned value; every failure releases partial frames. A
binding checks its runtime tag independently of preceding branches, so a legal
artifact with inconsistent branch logic produces a checked type mismatch, not
an unchecked payload read. Dynamic.get still requires both a declared reflect
effect and a separate runtime-issued capability; tag matching grants no authority.
Modules containing matching operations share a 1,000,000-instruction budget
across the entire call tree, with the existing 256-frame call-depth bound.
Budget exhaustion returns ZL_STATUS_RUNTIME_ERROR / ZL-DYNAMIC-0003 and no
result. Checked integer overflow and division faults return
ZL_STATUS_RUNTIME_ERROR / ZL-FAULT-0001; neither creates a new Dynamic error
enum value. Other scalar semantics use the module’s checked semantic profile.
Versioned representation
Schema 2.21 uses ZLM2 2.21 / ZLA2 1.13 for new matching operations.
project operation 41
tests a closed tag and produces Bool; operation 42 binds Bool or Int64. Each
retains exactly one ordered Dynamic operand. Checked-AST/4, semantic/4,
interface/4, and ZLD2 2.0 retain their structures. Source and decoded artifacts
expose the same checked-AST projection and message identities.
Sources without either operation retain their existing artifact versions and bytes (including wildcard-only matches). Old minors do not admit the new operations; mismatched versions, scalar annotations, unknown tags/operations, and unrelated byte-kernel operations reject before output. Canonical nested linked names and the exact reserved lookup intrinsic are admitted in the new payload; malformed length framing and suffix lookalikes are not.
spec/extensions/core1-dynamic-match.json pins the unchanged Core-1 layout
registry by SHA-256. It generates src/zlm2_dynamic_layout.inc using:
python -m tools.zlm2_dynamic_layout --check
build/zlc zlm2-schema --minor=21
The default CLI descriptor remains 2.20. Public descriptor API
zl_zlm2_wire_schema_v3 adds 2.21 and preserves earlier descriptor bytes;
zl_zlm2_wire_schema_v2 remains limited to 2.13–2.20. No frozen edition or corpus
revision changes in place. Benchmark promotion is a separate revisioned change.
Verification includes runtime.core1_dynamic_match, cli.dynamic_match,
benchmark.dynamic_match, the Hypothesis matching properties, and the
dynamic-match mutation gate. They exercise all tags, owned recursive outputs,
malformed artifacts, scalar faults, bounded recursion/loops, imported lookup
authority, and source/artifact identity agreement.
Ticket 242 dependency audit (2026-09-09)
The remaining all-payload source tranche is not implemented. Ticket 242 depends on the ordinary numeric carriers in ticket 225 and the relevant owned aggregate/carrier tranche in ticket 221. Both prerequisites remain undelivered. The existing native ZLD2 algebra is not evidence that ordinary checked source can bind, read, and construct all of those payloads.
The audit used a clean local git archive of
3cf6d107e4b899500b0c79cd2461a87a866fa8b8, Apple Clang 17.0.0, and LLVM disabled.
The compiler reported that exact source SHA via zlc version --json.
Public checks used zlc check --edition=core-1 --preview=shared --diagnostics=json on otherwise identical compiled Algorithm match bodies:
| Source probe | Current result |
|---|---|
Dynamic.bool(let payload) / Dynamic.i64(let payload) |
Compile successfully |
Dynamic.f64(let payload) and corresponding text, list, record patterns |
ZL-TYPE-0003: only Bool/Int64 scalar bindings admitted |
Ordinary Algorithm parameter/result Float64 or Text |
ZL-TYPE-0001: type does not resolve to a supported scalar or enum |
Interpreted return Dynamic.null(); |
ZL-DYNAMIC-0001: unknown interpreted Dynamic operation |
For example, this currently fails at the f64 binding, not because of an
undeclared variable or a missing native symbol:
module audit.dynamic_f64;
public value App {
public compiled algorithm message select(type: Class<Self>, item: Dynamic)
-> Dynamic effects {} {
match (item) {
Dynamic.f64(let payload) => { return item; }
_ => { return item; }
}
}
}
All seven tag-only patterns already work. Null has no payload to bind: its remaining source-construction work must not introduce an implicit conversion from Unit or a nullable safe reference. The unsupported constructor spelling in the probe is illustrative, not a newly approved source API.
The limits are enforced below the parser too. src/scalar.c and
zl_ir_scalar_kind contain the integer/Bool carriers, not ordinary floating
values. src/ir.c validates project operation 42 as Bool/Int64 only;
src/core1_dynamic.c executes that checked operation with scalar tag reads.
spec/extensions/core1-dynamic-match.json pins those meanings in ZLM2 2.21 /
ZLA2 1.13. A parser-only binding change or treating every payload as Int64 would
not satisfy the ticket and would violate the existing artifact contract.
Resumption requires an explicitly staged typed representation for the missing numeric/owned payloads, with matched reads and constructors, exact null/text semantics, checked child transfers and cleanup, and additive artifact admission. Keep ordinary floating policy work in 225 and owned carrier work in 221; do not silently implement different meanings inside Dynamic. Then prove actual source/package/serialized execution for every tag, independent payload identity, UTF-8/duplicate-key/depth/count failures, deterministic records, no implicit conversion, and missing/stale reflect authority. Tag matching itself remains authority-free; name-based lookup remains separately capability checked.
The two existing native Dynamic/matching tests and all seven CLI matching tests
passed on this archive; the CLI suite used CTest’s Python 3.14.6. An initial CLI test attempt with the
host Python environment could not import the archive’s namespace test package;
that setup error is not a pass or a language regression. The Float64 probe first
used the reserved parameter name value; after correcting it to item, the
qualifying failure was the unsupported-type diagnostic above. These results
protect the current profile; they do not meet the all-payload acceptance
criteria. This is a documentation-only dependency audit, not a runtime change,
fixture promotion, frozen-format update, or stable activation. Ticket 242 is
deferred to Icebox pending its prerequisites, not closed as delivered.