The JNI/runtime-class path (Decision 5, Runtime* family). Coordinated across the
hook boundary so the BuildOptions accessor + its registered hook string stay in sync:
- src/: RuntimeClassDecl.foreign_path→runtime_path, splitForeignPath→splitRuntimePath,
foreignPathToJavaName→runtimePathToJavaName, jni_main_foreign_paths→
jni_main_runtime_paths, hookJniMainForeignPathAt→hookJniMainRuntimePathAt, and the
hook string 'BuildOptions.jni_main_foreign_path_at'→'…runtime_path_at'.
- library/: build.sx accessor jni_main_foreign_path_at→jni_main_runtime_path_at +
bundle.sx call sites + the local var → runtime_path + a comment.
- specs.md: the accessor name + <foreign_path_with_dots> doc refs.
- Regenerated 37 .ir snapshots: every program importing build declares the renamed
@BuildOptions.jni_main_runtime_path_at hook stub — symbol-name change only (verified
the .ir diff is ONLY this rename; reverted orthogonal empty-file normalization).
Suite green (646 corpus / 444 unit, 0 failed).
The runtime-class object-model identifiers (Decision 5): parse/lower/find/resolve/
register/stamp fns Foreign→Runtime (parseRuntimeClassDecl, lowerRuntimeMethodCall,
findRuntimeMethodInChain, resolveRuntimeMethodReturnType, registerRuntimeClassDecl,
runtimeClassStructType, runtimeKindForOffset, …); state foreign_class_map→
runtime_class_map, current_foreign_class/_method→current_runtime_*, the
foreign_class_decl union variant→runtime_class_decl, foreign_method/static/instance/
class→runtime_*; and the reference-vs-define flag is_foreign→is_reference (+
is_foreign_eff→is_reference_eff) now that it only lives on RuntimeClassDecl.
Snapshot-neutral; suite green (646/444).
Remaining 9.2: the foreign_path family (coupled .sx hooks: jni_main_foreign_path_at
spans build.sx/bundle.sx/compiler_hooks.zig/specs.md) + the extern-ref validators
(checkForeignRefs etc. → Extern, linkage not runtime) + bare 'foreign' comments.
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
BoxAlias :: Box; / Box :: r.Box; now resolve instantiation, methods,
annotations, and chains through the aliased template, and re-export one
flat-import level as ordinary own decls (the facade shape the std.sx
restructure needs). selectGenericStructHead consults aliasedStructTemplate
(nominal.zig) before the global template map — own-wins/single-flat alias
author, each hop pinned to the alias author's source, ns.X RHS through
namespaceAliasVerdictFrom, depth-capped. resolveTypeCallWithBindings'
silent .unresolved tail (panicked in LLVM emission) now diagnoses
"unknown type". Also aligns the stale pre-existing calls.test.zig UFCS
plan test with the opt-in model (a47ea14). Regression: examples/0211
(+rich/+facade). Gates: zig build test 426/426, suite 587/587.
CallPlan collapsed two different field-access dispatches onto namespace_fn:
a true namespace call (`pkg.fn()`, no receiver) and free-function UFCS
(`c.bump()`, receiver prepended + `*T` fixup). Return typing was preserved
either way, but sub-step 3 could not consume the plan — it would have had to
re-classify the AST to decide whether to prepend the receiver.
Add a distinct `free_fn_ufcs` kind and a plan(c) branch, inserted after the
struct-method block and gated on `objectIsValue` (the negation of lowerCall's
`is_namespace`: a non-identifier receiver is always a value; an
identifier/type_expr is a value iff it names a local or a global). The branch
sets prepends_receiver = true and reads prepends_ctx from the resolved FuncId
(best-effort, like direct_fn). namespace_fn now means strictly "receiver is a
namespace/type prefix".
New test `plan: free-function UFCS prepends receiver, distinct from
namespace_fn` covers a scope-bound `c.bump()` against a lowered free fn:
asserts free_fn_ufcs kind, func target, prepends_receiver, prepends_ctx, and
preserved s32 return type.
zig build, zig build test, tests/run_examples.sh (357/0) all green; return
typing unchanged.
Introduce CallPlan — the single classification record for a call: kind (14
variants), return_type, a Target union (builtin/func/named/protocol_method/
foreign_method/constructed/none), variant tag, and the prepends_receiver /
prepends_ctx / expands_defaults properties the selected dispatch implies.
Move call recognition into CallResolver.plan(c) (branch order preserved
exactly) and reimplement resultType(c) as plan(c).return_type — the typing
consumer converges onto the plan first. lowerCall is untouched; routing it
through plan(c) is sub-step 3.
10 plan-object tests assert kind/target/variant + receiver/ctx/default
properties for every pinned call form: builtin/reflection, lazy + resolved
direct fn (incl. default-arg expansion + __sx_ctx prepend), closure /
default-conv vs C-conv fn-pointer, protocol dispatch, struct/UFCS #compiler
method, foreign instance vs static, qualified + dot-shorthand enum
construction, namespace fn, and the unresolved fallthrough.
Widen for the new collaborator only: resolveVariantIndex -> pub (plan resolves
the variant tag); Scope/Binding + init/deinit/put -> pub (so unit tests can
stand up a lexical scope for closure/fn-ptr callees without a full lowering).
zig build, zig build test, and tests/run_examples.sh (357/0) all green; no
behavior change.
Move call-result-type discovery out of Lowering into a new src/ir/calls.zig
(CallResolver): the A3.1 Lowering.inferCallType body moves verbatim into
CallResolver.resultType. inferExprType's `.call` arm now delegates via
callResolver(); Lowering.inferCallType is gone.
CallResolver is a *Lowering facade (Principle 5, like ExprTyper/PackResolver):
call typing reads live lexical-scope / target-type state and the function /
foreign-class / protocol resolver helpers, so it borrows *Lowering. Transform
was `self.` -> `self.l.` plus the file-local static `resolveBuiltin(` ->
`Lowering.resolveBuiltin(`.
Widened to pub only what the facade actually consumes: resolveTypeArg,
inferGenericReturnType, resolveFuncByName, getProtocolInfo,
resolveForeignMethodReturnType, the static resolveBuiltin, and Scope.lookupFn.
resolveTypeArg widening is genuinely required here — the `cast` builtin's
result type calls it.
calls.test.zig adds focused tests (builtin/reflection classification, unknown
callee -> unresolved) for the scope-free paths. Barrel-wired in ir.zig.
This is the relocation half of PLAN-ARCH A3.2; call LOWERING (lowerCall) still
owns its own dispatch, and the CallPlan convergence (one plan shared by typing
and lowering, deleting the duplicated qualified/bare/lazy logic) remains.
Behavior-preserving. Gate: zig build, zig build test (incl. new CallResolver
tests), bash tests/run_examples.sh -> 356/0. lower.zig 18598 -> 18413.