A generic ($T) -> Type type-fn comptime-evaluated only its return
EXPRESSION, so a local declared before the return ('vs := …; return
make_enum(…, vs)') was unresolved. Now a body with a prelude (statements
before the return) has its full body evaluated: createComptimeFunction-
WithPrelude lowers the pre-return statements into the comptime function's
scope before the return expr, so the locals resolve.
- comptime.zig: createComptimeFunctionWithPrelude (prelude stmts +
expr); evalComptimeTypeBody (extract prelude + return expr, scan the
whole body for declare() forward types); runComptimeTypeFunc factored
out of evalComptimeType (shared bail/declare-never-defined handling).
- generic.zig: route a type-fn body WITH a prelude through
evalComptimeTypeBody; no-prelude bodies stay on evalComptimeType (zero
change for RecvResult/TryResult etc.).
Non-generic builders (whole body already evaluated) and the List-growth
path are unaffected. Suite green (684).
Per the directive to strip reify entirely: the sx `reify(info)` one-shot is
removed. `define(handle, info)` now RETURNS the (completed) handle, so the
one-shot constructor chains as a single expression:
T :: define(declare(), .enum(.{ name = "T", variants = ... }));
- meta.sx: drop reify; RecvResult/TryResult use `define(declare(), …)`.
- interp .define returns the handle type_tag (was void); call.zig lowers it
with `Type` result and sets the info arg's target type to TypeInfo so the
intercepted call still infers the `.enum(…)` literal.
- returnExprMintsType: a type-fn body that returns `define(…)` (or a bodied
non-generic Type-returning sx helper) is comptime-evaluated.
- examples 0614 (direct) + 0615 (type-fn) use `define(declare(), …)`.
Full suite green (673). Files/docs still carry the old reify naming — the
rename sweep is the next commit.
Second slice of the re-architecture — the compiler now has ZERO type-
construction code beyond declare/define.
- instantiateTypeFunction: a type-fn body returning a computed Type (a call
to a non-generic, bodied, Type-returning fn) is comptime-evaluated with the
type bindings active, then renamed to the mangled instantiation name for
identity (renameNominalType). Replaces the old reify-call pattern-matching.
- DELETED: reifyType (lower/nominal.zig), findReturnReifyCall (lower/generic.zig),
and the stale inline-position reify gate in resolveTypeCallWithBindings.
- evalComptimeType (was evalComptimeTypeNamed): pure eval, no rename; the
type-fn caller renames explicitly. renameReifiedType → renameNominalType.
- The TYPE NAME now travels in the data: EnumInfo gains `name`, and define()
names the slot from it (the compiler derives no name from a binding LHS).
examples/0614/0615 carry `name = "..."`; RecvResult/TryResult set it too.
- field_type stays a reflection #builtin (reads a type); only construction
moved out. All reify mentions stripped from compiler source.
examples 0614/0615/0617 run on the floor. Full suite green (673).
REIFY Phase 2.1. fieldTypeOf (lower/generic.zig, re-exported on Lowering)
returns the i-th member type of T: struct field / tagged-union + union
variant payload (.void for a tagless variant) / tuple element / array +
vector element. Out-of-range and memberless types poison to .unresolved
with a loud diagnostic (never a silent default). Wired into
resolveTypeCallWithBindings (replacing the Phase-2 bail); since it folds
to a TypeId at lower time it composes inside type_eq / type_name / any
type-arg slot.
examples/0616 green: struct fields (name via field_name + type via
field_type), type_eq fold, tagged-union payloads incl. quit -> void.
Suite green (672 examples, 447 unit).
type_info($T) -> TypeInfo (reflect into a value, inverse of reify) is
NOT done — still bails loudly; it's the larger Phase 2.2 step (widen the
TypeInfo data model + comptime value construction). Plan/checkpoint updated.
REIFY Phase 1.1 (Phase 1 complete). instantiateTypeFunction detects a
type-fn body that returns reify(...) (findReturnReifyCall) and routes it
to reifyType under the instantiation's name — mangled for inline use,
the alias name for `Foo :: Box(i64)` — with the type-arg bindings active
so reify payloads (`payload = T`) resolve against the instantiation args.
Placed before the general case, whose resolveTypeWithBindings would
route the reify call to the inline-position loud bail.
Registering under the mangled name lets the top-of-instantiation cache
return the SAME TypeId on a second instantiation, so Box(i64) resolved
at two independent sites is ONE type (Contract 1). examples/0615 green
(build()->consume() cross-site + `b : Box(i64) = .none`). Suite green
(671 examples, 447 unit).
REIFY Phase 0.2 (Phase 0 complete). Lowering.reifyType (lower/nominal.zig)
reads the flat-enum TypeInfo literal off the AST, synthesizes an
ast.EnumDecl, and feeds it through the SAME type_bridge.buildEnumInfo
path source enums use — so the minted type is byte-identical to a
hand-written `enum { value: i64; closed; }` and flows through enum
codegen (layout / construct / match) UNMODIFIED (Contract 2).
Wired at the `E :: reify(...)` const-decl hook in lower/decl.zig
(replacing the Phase-0.0 loud bail). Unsupported argument shapes bail
loudly via reifyBail — never a silent default. The generic.zig inline
reify path now reports it's only supported in a `::` binding (Phase 0).
examples/0614 green: reify a {value: i64, closed} enum, construct
.value(3) and .closed, match both -> "value 3" / "closed". Full suite
green (670 examples, 447 unit).
REIFY Phase 0.0. Add the comptime type-metaprogramming surface as the
on-demand module modules/std/meta.sx (NOT the prelude — declaring its
data types in always-loaded core.sx interns them into every module's
type table and shifts every .ir snapshot):
- EnumVariant / EnumInfo / TypeInfo data types. TypeInfo's variant uses
the backtick raw escape `enum so it reads as the keyword.
- reify / type_info / field_type as bodyless #builtin decls.
Each builtin bails LOUDLY when reached unimplemented (no silent default):
- reify(...) in a :: type-alias position -> decl.zig .call branch
(also the Phase 0.2 construction hook); poisons the alias .unresolved.
- reify / field_type in any other type position ->
generic.zig resolveTypeCallWithBindings.
- type_info(...) in expression position -> call.zig tryLowerReflectionCall.
Unit test src/parser.test.zig (registered in root.zig) locks that the
decls parse. zig build test green (447 unit, 669 examples).
Reword every 'foreign' comment to the extern/runtime-class vocabulary matching the
renamed identifiers (foreign call→extern call, foreign class→runtime class, foreign
path→runtime path, the #foreign-literal comment mentions → extern, etc.). Also fixes
two USER-FACING issues: the 'expected … #foreign … after type annotation' parse error
no longer advertises the removed keyword, and the Android 'no #jni_main' help
diagnostic now shows '#jni_class(…) extern' instead of the rejected '#foreign
#jni_class'. Removed the now-dead prefix-#foreign-vs-postfix conflict branch in
parseRuntimeClassDecl (the caller rejects #foreign before it runs).
src/ now contains 'foreign' ONLY in the hash_foreign token machinery + its 4
rejection messages — the deprecation mechanism (kept per the 9.0 recommendation; the
message MUST name #foreign to guide migration). Snapshot-neutral; suite green
(646 corpus / 444 unit, 0 failed).
The last linkage-family 'foreign' carrier. Migrated c_import.zig auto-synthesis
(#import c {#include}) to build the extern shape (empty-block body + extern_export
= .extern_) instead of a foreign_expr body — the Phase 5.0 fn-body flip applied to
auto-synth. With nothing left building it, deleted the foreign_expr union variant +
ForeignExpr struct (ast.zig) and every reader: the dead-arm switch cases (sema,
resolver, generic, call, semantic_diagnostics, lsp), the coalescing reads in
decl.zig (is_foreign local, cc/rename/dedup/variadic/visibility gates) + pack.zig,
and checkForeignRefs (now reads extern_lib only). 9.1 LINKAGE PURGE COMPLETE — all
that remains in src/ is the runtime-class family (9.2) + comments. Snapshot-neutral
(the #import c examples 1215/1216/1217 + sqlite 1624 exercise the synth path); suite
green (646 corpus / 444 unit, 0 failed).
isPlainFreeFn / isPlainFreeFnDecl excluded a #foreign body but classified
an empty-block extern fn as a plain free function, so existing extern fns
were wrongly counted in the bare-call ambiguity verdict (and eligible for
the out-of-line-slot / shadow-author pass). Both predicates now also
exclude extern_export == .extern_ (an external C symbol with no
sx-lowerable body, name-keyed first-wins dispatch like #foreign); export
keeps a real body and stays plain-free. Greens example 1230 — same-name
extern authors compile like their #foreign twins (0729).
646 corpus / 444 unit, 0 failed.
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.
extractTypeParam's slice arm only extracted from slice-typed args, so
first(a) with a : [3]s64 at first :: (xs: []$T) -> T left T unbound
and the mono body reached LLVM emission carrying the .unresolved
sentinel (panic). The arm now also extracts from array args via the
array's element type — mirroring the array→slice promotion concrete
slice params already perform; the existing arg coercion handles the
rest.
lowerGenericCall additionally diagnoses any still-uninferrable TYPE
param at the call site instead of monomorphizing unbound — the
deliberate string-at-[]$T gap used to hit the same sentinel panic and
now errors with a source-located message. Comptime value params
($N: u32) and ..$Ts packs bind through their own dispatch and stay
exempt.
Regressions: examples/0212-generics-array-arg-slice-param.sx (scalar /
u8 / struct elements + the slice spelling) and
examples/1168-diagnostics-generic-param-uninferrable.sx (string arg
diagnostic) — both failed pre-fix.
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.
Sweep all src/**.zig comments that cite resolved issues (issue NNNN /
fix-NNNN / KB-N): the invariant or mechanism each comment states is
kept; the historical citation is dropped, per the no-conclusion-comments
rule. Pure-history parentheticals are removed outright. References to
the 16 still-open issues (0030, 0041-0056) are untouched, as are test
NAMES carrying regression provenance (matching the sanctioned
"Regression (issue NNNN)" example-header convention).
Also removes the issues/0019-import-non-transitive-c-scope/ fixture dir
— the issue is superseded and its behavior is covered by
examples/0706-modules-import-non-transitive.sx (the .md writeup stays).
issues/0030's repro .sx stays: that issue is an open feature request.
Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
Review follow-up to the ARCH-B split (comment/import hygiene only, no
code changes):
- Section banners that travelled to the wrong file with the B1-B8 cuts
are reworded to describe the section that actually follows (e.g.
stmt.zig's trailing "Expression lowering", expr.zig's "Control flow"
before lowerChainedComparison) or deleted where nothing follows
(4 trailing-at-EOF banners). ffi.zig's facade note no longer claims
the IMP builders "stay here" (they live in lower/objc_class.zig);
protocol.zig's namespace-lookup banner now points at
pack.zig:resolvePackProjection for the orchestrator.
- lower.zig's two lower/expr.zig alias blocks (B8.1 + B8.2 appends)
merged into one.
- 448 unused header decls pruned from the 15 lower/*.zig files (each
had inherited lower.zig's full import block; pruned to fixpoint so
cascading type-extraction consts went too).
Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.