Core-1 preview extensionimplementation reference

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.

Projected fromdocs/dynamic-source-matching.md