From 5f2419854e1c662e730d3fe3fb26f62d5821ac8c Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 16 Jun 2026 21:12:32 +0300 Subject: [PATCH] =?UTF-8?q?green:=20erase=20the=20sx=20reify=20sugar=20?= =?UTF-8?q?=E2=80=94=20declare/define=20are=20the=20only=20constructors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- examples/0614-comptime-reify-enum.sx | 2 +- .../0615-comptime-reify-typefn-identity.sx | 2 +- library/modules/std/meta.sx | 61 +++++++++---------- src/ir/interp.zig | 3 +- src/ir/lower/call.zig | 11 +++- src/ir/lower/generic.zig | 23 ++++--- 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/examples/0614-comptime-reify-enum.sx b/examples/0614-comptime-reify-enum.sx index 3b072ee7..e15d3302 100644 --- a/examples/0614-comptime-reify-enum.sx +++ b/examples/0614-comptime-reify-enum.sx @@ -8,7 +8,7 @@ #import "modules/std.sx"; #import "modules/std/meta.sx"; -E :: reify(.enum(.{ name = "E", variants = .[ +E :: define(declare(), .enum(.{ name = "E", variants = .[ EnumVariant.{ name = "value", payload = i64 }, EnumVariant.{ name = "closed", payload = void }, ] })); diff --git a/examples/0615-comptime-reify-typefn-identity.sx b/examples/0615-comptime-reify-typefn-identity.sx index aaf2b8b5..c2b34879 100644 --- a/examples/0615-comptime-reify-typefn-identity.sx +++ b/examples/0615-comptime-reify-typefn-identity.sx @@ -9,7 +9,7 @@ #import "modules/std/meta.sx"; Box :: ($T: Type) -> Type { - return reify(.enum(.{ name = "Box", variants = .[ + return define(declare(), .enum(.{ name = "Box", variants = .[ EnumVariant.{ name = "some", payload = T }, EnumVariant.{ name = "none", payload = void }, ] })); diff --git a/library/modules/std/meta.sx b/library/modules/std/meta.sx index 476a0df2..c89f0cfd 100644 --- a/library/modules/std/meta.sx +++ b/library/modules/std/meta.sx @@ -1,16 +1,15 @@ -// Comptime type metaprogramming (REIFY) — `type_info` / `reify` / `field_type` -// plus the data model they reflect INTO and construct FROM. Mirrors the Zig -// `@typeInfo` / `@Type` split: reflect a type → data, construct a NEW nominal -// type from data. +// Comptime type metaprogramming — `declare` / `define` (construct a NEW nominal +// type from data), plus `type_info` / `field_type` (reflect a type → data) and +// the data model they reflect INTO and construct FROM. // // This is a SEPARATE on-demand module rather than part of the prelude: its data // types would otherwise intern into every module's type table and shift every // `.ir` snapshot. Import it explicitly: #import "modules/std/meta.sx"; // -// `reify` / `type_info` / `field_type` are comptime-only builtins — a `reify` -// reached at runtime is a hard error (the type must be minted at compile time). +// All four are comptime-only builtins — reaching one at runtime is a hard error +// (the type must be minted / reflected at compile time). -// One variant of a reify'd enum: a name plus an optional payload type. +// One variant of a constructed enum: a name plus an optional payload type. // `payload = void` means a tagless variant (e.g. `closed`). EnumVariant :: struct { name: string; @@ -26,45 +25,43 @@ EnumInfo :: struct { } // The reflected/constructed type shape. A tagged union over the kinds of type -// `reify` can mint. Phase 0 ships only `` .`enum ``; struct/tuple land later. +// that can be minted. Only `` .`enum `` ships today; struct/tuple land later. // The variant uses the backtick raw-identifier escape so it reads as the -// keyword `enum` (`` reify(.`enum(...)) ``) rather than a mangled `enum_`. +// keyword `enum` (`` .`enum(...) ``) rather than a mangled `enum_`. TypeInfo :: enum { `enum: EnumInfo; } // The compiler's ONLY type-construction primitives (comptime-only #builtins): // declare() — mint a NEW empty (undefined) nominal type, returned -// as a `Type` handle. Using it before `define` is a -// loud error. References to it (`*Self`) are fine. -// define(handle, info) — fill a declared handle's body from a `TypeInfo`. -// `reify` and every other constructor below are PLAIN sx built over these — the -// compiler has no `reify` knowledge. +// as a `Type` handle. Using it before its `define` is a +// loud error; references to it (`*Self`) are fine. +// define(handle, info) — fill a declared handle's body from a `TypeInfo` +// (which carries the type's name), and RETURN the +// handle so the one-shot form chains: +// T :: define(declare(), info); +// The recursive / mutually-recursive form keeps them apart so the handle can be +// referenced inside its own definition: +// List :: declare(); +// define(List, .enum(.{ name = "List", variants = .[ +// EnumVariant.{ name = "cons", payload = *List }, +// EnumVariant.{ name = "nil", payload = void } ] })); declare :: () -> Type #builtin; -define :: (handle: Type, info: TypeInfo) #builtin; +define :: (handle: Type, info: TypeInfo) -> Type #builtin; type_info :: ($T: Type) -> TypeInfo #builtin; field_type :: ($T: Type, idx: i64) -> Type #builtin; -// reify(info) — the one-shot, non-recursive sugar: declare + define + return. -// (Recursive / mutually-recursive types use the explicit declare/define split -// so the handle can be referenced inside its own definition.) -reify :: (info: TypeInfo) -> Type { - h := declare(); - define(h, info); - return h; -} - -// --- Reify'd shapes built in sx library code (no new compiler machinery) --- +// --- Type constructors built in sx library code (no compiler machinery) --- // -// The channel result types, expressed as type-fns over `reify`. They are the -// canonical demonstration that `reify` carries a full enum through codegen: -// `RecvResult(i64)` constructs and matches like any hand-written enum, and is -// one nominal type across sites (the type-fn mangled-name identity path). The -// channel library (N3) consumes these once it lands. +// The channel result types, expressed as type-fns over declare/define. They +// demonstrate that a programmatically-built enum carries a full enum through +// codegen: `RecvResult(i64)` constructs and matches like any hand-written enum, +// and is one nominal type across sites (the type-fn identity path). The channel +// library (N3) consumes these once it lands. // A blocking recv: a value, or the channel was closed (drained). RecvResult :: ($T: Type) -> Type { - return reify(.enum(.{ name = "RecvResult", variants = .[ + return define(declare(), .enum(.{ name = "RecvResult", variants = .[ EnumVariant.{ name = "value", payload = T }, EnumVariant.{ name = "closed", payload = void }, ] })); @@ -73,7 +70,7 @@ RecvResult :: ($T: Type) -> Type { // A non-blocking try-recv: a value, currently empty, or closed — three states // a bool can't express. TryResult :: ($T: Type) -> Type { - return reify(.enum(.{ name = "TryResult", variants = .[ + return define(declare(), .enum(.{ name = "TryResult", variants = .[ EnumVariant.{ name = "value", payload = T }, EnumVariant.{ name = "empty", payload = void }, EnumVariant.{ name = "closed", payload = void }, diff --git a/src/ir/interp.zig b/src/ir/interp.zig index b4d6212c..4031747a 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -2062,7 +2062,8 @@ pub const Interpreter = struct { .nominal_id = cur.tagged_union.nominal_id, } }; tbl.replaceKeyedInfo(handle, full); - return .{ .value = .void_val }; + // Return the handle so the one-shot form chains: `T :: define(declare(), info)`. + return .{ .value = .{ .type_tag = handle } }; } }; diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index 41539e48..832eb4c8 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -1695,9 +1695,18 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C return Ref.none; } const handle_ref = self.lowerExpr(c.args[0]); + // Lower the info arg with `TypeInfo` as the target type so its `.enum(…)` + // enum-literal infers correctly (we intercept the call, bypassing the + // normal param-type-threading the regular call path does). + const saved_tt = self.target_type; + if (self.module.types.findByName(self.module.types.internString("TypeInfo"))) |ti| + self.target_type = ti; const info_ref = self.lowerExpr(c.args[1]); + self.target_type = saved_tt; const args_owned = self.alloc.dupe(Ref, &.{ handle_ref, info_ref }) catch return Ref.none; - return self.builder.callBuiltin(.define, args_owned, .void); + // define returns the (now-completed) handle as a `Type` value, so the + // one-shot constructor form chains: `T :: define(declare(), info)`. + return self.builder.callBuiltin(.define, args_owned, .any); } if (std.mem.eql(u8, name, "type_info")) { // Comptime reflection-into-data (reflect a type INTO a `TypeInfo` diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index 084f5421..d7644bc8 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -1797,18 +1797,25 @@ pub fn findReturnTypeExpr(body: *const Node) ?*const Node { return body; } -/// True when a type-fn's return expression mints a type at comptime — a call to -/// a NON-generic, bodied, `Type`-returning fn (a comptime type constructor). -/// Such a body is comptime-evaluated (its `declare`/`define` mint the type) -/// rather than statically resolved. Excludes generic / `#builtin` type -/// constructors (`Vector(N,T)`, `Make($T)`), which the static path handles. No -/// hardcoded constructor names — any qualifying Type-returning fn flows here. +/// True when a type-fn's return expression mints a type at comptime and must be +/// run through the interpreter rather than statically resolved. Two shapes: +/// - a call to the `define` construction primitive — `return define(declare(), +/// info)`, the one-shot constructor form; or +/// - a call to a NON-generic, bodied, `Type`-returning sx fn (a constructor +/// helper that itself ends in `define`). +/// Excludes generic / static type constructors (`Vector(N,T)`, `Make($T)`, +/// `return [K]T`, `return T`), which the static `resolveTypeWithBindings` path +/// handles. pub fn returnExprMintsType(self: *Lowering, ret: *const Node) bool { if (ret.data != .call) return false; const callee = ret.data.call.callee; if (callee.data != .identifier) return false; - const fd = self.program_index.fn_ast_map.get(callee.data.identifier.name) orelse return false; - if (fd.type_params.len != 0) return false; // generic constructors stay static + const name = callee.data.identifier.name; + // The construction terminator builtin — a constructor's final act. + if (std.mem.eql(u8, name, "define")) return true; + // A bodied, non-generic, Type-returning sx helper. + const fd = self.program_index.fn_ast_map.get(name) orelse return false; + if (fd.type_params.len != 0) return false; if (fd.body.data == .block and fd.body.data.block.stmts.len == 0) return false; // bodyless #builtin const rt = fd.return_type orelse return false; return rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Type");