From 8ae655687a0970c147f180172dde1e49e9cebc60 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 16 Jun 2026 21:03:16 +0300 Subject: [PATCH] green(reify): type-fn bodies comptime-evaluated; reify fully removed from the compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- examples/0614-comptime-reify-enum.sx | 2 +- .../0615-comptime-reify-typefn-identity.sx | 2 +- library/modules/std/meta.sx | 9 +- src/ir/inst.zig | 19 ++-- src/ir/interp.zig | 32 ++++--- src/ir/lower.zig | 6 +- src/ir/lower/call.zig | 24 ++--- src/ir/lower/comptime.zig | 32 +++---- src/ir/lower/decl.zig | 18 ++-- src/ir/lower/generic.zig | 68 +++++++------- src/ir/lower/nominal.zig | 94 ------------------- 11 files changed, 112 insertions(+), 194 deletions(-) diff --git a/examples/0614-comptime-reify-enum.sx b/examples/0614-comptime-reify-enum.sx index 327be8b6..3b072ee7 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(.{ variants = .[ +E :: reify(.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 3877ba70..aaf2b8b5 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(.{ variants = .[ + return reify(.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 9c95cf60..476a0df2 100644 --- a/library/modules/std/meta.sx +++ b/library/modules/std/meta.sx @@ -17,8 +17,11 @@ EnumVariant :: struct { payload: Type; } -// The shape of an enum/tagged-union being reflected or constructed. +// The shape of an enum/tagged-union being reflected or constructed. `name` is +// the type's name — it travels WITH the shape (so `define` can name the slot and +// `type_info` round-trips it); the compiler derives nothing from a binding LHS. EnumInfo :: struct { + name: string; variants: []EnumVariant; } @@ -61,7 +64,7 @@ reify :: (info: TypeInfo) -> Type { // A blocking recv: a value, or the channel was closed (drained). RecvResult :: ($T: Type) -> Type { - return reify(.enum(.{ variants = .[ + return reify(.enum(.{ name = "RecvResult", variants = .[ EnumVariant.{ name = "value", payload = T }, EnumVariant.{ name = "closed", payload = void }, ] })); @@ -70,7 +73,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(.{ variants = .[ + return reify(.enum(.{ name = "TryResult", variants = .[ EnumVariant.{ name = "value", payload = T }, EnumVariant.{ name = "empty", payload = void }, EnumVariant.{ name = "closed", payload = void }, diff --git a/src/ir/inst.zig b/src/ir/inst.zig index 52f9dd51..e2586a4e 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -449,15 +449,16 @@ pub const BuiltinId = enum(u16) { type_eq, type_is_unsigned, has_impl, - // Comptime type CONSTRUCTION (REIFY floor). The compiler's ONLY - // type-minting primitives — `reify` / `make_enum` / `RecvResult` etc. - // are sx in `meta.sx`, built over these. Both are comptime-only (the - // interp mutates the type table via its `mint` handle); reaching them - // at runtime / emit is a hard error. - // declare() → mint an EMPTY (undefined) nominal slot, return - // it as a `Type` value. Using the slot before - // `define` is a loud diagnostic (F5). - // define(handle, info) → decode the `TypeInfo` VALUE + complete the slot. + // The compiler's ONLY comptime type-CONSTRUCTION primitives. Higher-level + // constructors (one-shot, channel-result, etc.) are ordinary sx built over + // these — the compiler knows none of them by name. Both are comptime-only + // (the interp mutates the type table via its + // `mint` handle); reaching them at runtime / emit is a hard error. + // declare() → mint an EMPTY (undefined) nominal slot, returned + // as a `Type` value. Using the slot before its + // `define` is a loud diagnostic. + // define(handle, info) → decode the `TypeInfo` VALUE (the name travels in + // it) and complete the slot. declare, define, }; diff --git a/src/ir/interp.zig b/src/ir/interp.zig index 3debb15b..b4d6212c 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -186,12 +186,12 @@ pub const Interpreter = struct { /// Comptime type-MINT target — the SAME `TypeTable` the host (`Lowering`) /// owns (aliases `self.module.types`; the const view here and the host's /// mutable view point at one table). Set by the host before a comptime-eval - /// that may run `declare`/`define` (the REIFY floor). Null elsewhere (unit - /// tests, emit-time `#run`) → those builtins bail loudly. + /// that may run `declare`/`define`. Null elsewhere (unit tests, emit-time + /// `#run`) → those builtins bail loudly. mint: ?*types.TypeTable = null, /// Monotonic suffix for `declare()`'s anonymous slot names, so two - /// undefined slots alive at once don't collide in `findByName` before the - /// binding site renames them to the real (alias / mangled) name. + /// undefined slots alive at once don't collide in `findByName` before + /// `define` names them (or a type-fn renames them to the mangled name). declare_counter: u32 = 0, // Heap: dynamically allocated memory blocks @@ -257,7 +257,7 @@ pub const Interpreter = struct { /// Enable the comptime type-construction builtins (`declare`/`define`) by /// handing the interp the host's mutable `TypeTable`. Called by `Lowering` - /// before a comptime-eval that may mint types (the REIFY floor). + /// before a comptime-eval that may mint types. pub fn setMintTable(self: *Interpreter, tbl: *types.TypeTable) void { self.mint = tbl; } @@ -1976,7 +1976,7 @@ pub const Interpreter = struct { return bailDetail("comptime has_impl: interp-time evaluation not yet wired (use static type args for now — they fold at lower time)"); }, - // ── Comptime type CONSTRUCTION (REIFY floor) ───────── + // ── Comptime type CONSTRUCTION primitives ──────────── .declare => { const tbl = self.mint orelse return bailDetail("comptime declare(): no type-mint target (declare/define are comptime-only — reached at runtime/emit?)"); @@ -2027,8 +2027,11 @@ pub const Interpreter = struct { .aggregate => |f| f, else => return bailDetail("comptime define(): `.enum` payload is not an EnumInfo struct value"), }; - if (einfo_fields.len < 1) return bailDetail("comptime define(): EnumInfo is missing its `variants` field"); - const elems = decodeVariantElements(einfo_fields[0]) orelse + // EnumInfo = `{ name: string, variants: []EnumVariant }`. The name + // travels with the shape — `define` names the slot from it. + if (einfo_fields.len != 2) return bailDetail("comptime define(): EnumInfo must have `name` and `variants`"); + const name = einfo_fields[0].asString(self) orelse return bailDetail("comptime define(): EnumInfo `name` is not a string"); + const elems = decodeVariantElements(einfo_fields[1]) orelse return bailDetail("comptime define(): `variants` is not a slice/array of EnumVariant"); if (elems.len == 0) return bailDetail("comptime define(): enum has no variants"); @@ -2039,23 +2042,26 @@ pub const Interpreter = struct { else => return bailDetail("comptime define(): EnumVariant did not evaluate to a struct value"), }; if (ev.len != 2) return bailDetail("comptime define(): EnumVariant must have `name` and `payload`"); - const name = ev[0].asString(self) orelse return bailDetail("comptime define(): EnumVariant `name` is not a string"); + const vname = ev[0].asString(self) orelse return bailDetail("comptime define(): EnumVariant `name` is not a string"); const payload_tid = ev[1].asTypeId() orelse return bailDetail("comptime define(): EnumVariant `payload` is not a Type value"); - fields.append(self.alloc, .{ .name = tbl.internString(name), .ty = payload_tid }) catch return error.CannotEvalComptime; + fields.append(self.alloc, .{ .name = tbl.internString(vname), .ty = payload_tid }) catch return error.CannotEvalComptime; } - // Preserve the declared slot's intern key (name + nominal id); fill body. + // Complete the declared slot: NAME it from the EnumInfo (the name travels + // with the shape) and fill the body. The name changes the intern key + // (declare minted an anonymous `__reified_N`), so re-key via + // `replaceKeyedInfo`. The nominal id is preserved. const cur = tbl.get(handle); if (cur != .tagged_union) return bailDetail("comptime define(): handle is not a declare()'d enum slot"); const full: types.TypeInfo = .{ .tagged_union = .{ - .name = cur.tagged_union.name, + .name = tbl.internString(name), .fields = fields.items, .tag_type = .i64, .backing_type = null, .explicit_tag_values = null, .nominal_id = cur.tagged_union.nominal_id, } }; - tbl.updatePreservingKey(handle, full); + tbl.replaceKeyedInfo(handle, full); return .{ .value = .void_val }; } }; diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 07c734c1..8575f63f 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1566,8 +1566,8 @@ pub const Lowering = struct { pub const evalComptimeMatch = lower_comptime.evalComptimeMatch; pub const evalComptimeInt = lower_comptime.evalComptimeInt; pub const evalComptimeString = lower_comptime.evalComptimeString; - pub const evalComptimeTypeNamed = lower_comptime.evalComptimeTypeNamed; - pub const renameReifiedType = lower_comptime.renameReifiedType; + pub const evalComptimeType = lower_comptime.evalComptimeType; + pub const renameNominalType = lower_comptime.renameNominalType; pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal; pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect; pub const lowerComptimeCall = lower_comptime.lowerComptimeCall; @@ -1705,7 +1705,6 @@ pub const Lowering = struct { pub const registerErrorSetDecl = lower_nominal.registerErrorSetDecl; pub const registerStructDecl = lower_nominal.registerStructDecl; pub const registerEnumDecl = lower_nominal.registerEnumDecl; - pub const reifyType = lower_nominal.reifyType; pub const registerUnionDecl = lower_nominal.registerUnionDecl; pub const qualifyAnonType = lower_nominal.qualifyAnonType; pub const nominalIdOf = lower_nominal.nominalIdOf; @@ -1869,6 +1868,7 @@ pub const Lowering = struct { pub const findStructInBody = lower_generic.findStructInBody; pub const findUnionInBody = lower_generic.findUnionInBody; pub const findReturnTypeExpr = lower_generic.findReturnTypeExpr; + pub const returnExprMintsType = lower_generic.returnExprMintsType; pub const genericInstanceMethod = lower_generic.genericInstanceMethod; pub const ensureGenericInstanceMethodLowered = lower_generic.ensureGenericInstanceMethodLowered; pub const assertInstanceMapsCoincide = lower_generic.assertInstanceMapsCoincide; diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index 9f0eddce..41539e48 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -1677,10 +1677,10 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C if (self.reflectionTypeArgGuard(name, c)) |sentinel| return sentinel; if (std.mem.eql(u8, name, "declare")) { - // Comptime type-construction primitive (REIFY floor): mint an empty - // nominal slot. Comptime-only — emitted as a builtin_call the interp - // executes against its `mint` table; never reaches codegen (reify and - // friends, which call it, are only ever comptime-evaluated). + // Comptime type-construction primitive: mint an empty nominal slot. + // Comptime-only — emitted as a builtin_call the interp executes against + // its `mint` table; never reaches codegen (its sx callers are only ever + // comptime-evaluated). if (c.args.len != 0) { if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "declare() takes no arguments", .{}); return Ref.none; @@ -1688,8 +1688,8 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C return self.builder.callBuiltin(.declare, &.{}, .any); } if (std.mem.eql(u8, name, "define")) { - // Comptime type-construction primitive (REIFY floor): complete a - // declare()'d slot from a TypeInfo value. `define(handle, info)`. + // Comptime type-construction primitive: complete a declare()'d slot + // from a TypeInfo value. `define(handle, info)`. if (c.args.len != 2) { if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "define(handle, info) takes exactly two arguments", .{}); return Ref.none; @@ -1700,13 +1700,13 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C return self.builder.callBuiltin(.define, args_owned, .void); } if (std.mem.eql(u8, name, "type_info")) { - // Comptime reflection-into-data (REIFY). Until the interpreter-side - // reflection lands (Phase 2), bail loudly rather than fall through to - // the no-body `#builtin` const_decl path (which would mis-lower as a - // zero-arg call). A silent fall-through would hand the caller a - // garbage TypeInfo value. + // Comptime reflection-into-data (reflect a type INTO a `TypeInfo` + // value). Until the interpreter-side reflection lands, bail loudly + // rather than fall through to the no-body `#builtin` const_decl path + // (which would mis-lower as a zero-arg call). A silent fall-through + // would hand the caller a garbage TypeInfo value. if (self.diagnostics) |d| - d.addFmt(.err, c.callee.span, "type_info is not yet implemented (REIFY Phase 2)", .{}); + d.addFmt(.err, c.callee.span, "type_info is not yet implemented", .{}); return Ref.none; } if (std.mem.eql(u8, name, "size_of")) { diff --git a/src/ir/lower/comptime.zig b/src/ir/lower/comptime.zig index d572ef24..d7e136a4 100644 --- a/src/ir/lower/comptime.zig +++ b/src/ir/lower/comptime.zig @@ -384,15 +384,16 @@ pub fn lowerInsertExprValue(self: *Lowering, expr: *const Node) Ref { return last_val; } -/// Evaluate a Type-returning expression at compile time → its `TypeId`. -/// The driver of the REIFY floor: `expr` (e.g. `reify(.enum(...))`, a type-fn -/// call) is wrapped in a throwaway comptime fn and run through the interpreter -/// with the type-MINT table enabled, so `declare`/`define` builtins reached -/// inside it mutate the real type table. The result value is a `.type_tag`. -/// When `name` is given, the minted (anonymous) type is renamed to it so -/// `type_name` / diagnostics read the binding's name. Returns null (caller -/// poisons) if evaluation didn't yield a Type. -pub fn evalComptimeTypeNamed(self: *Lowering, expr: *const Node, name: ?[]const u8) ?TypeId { +/// Evaluate a `Type`-returning expression at compile time → its `TypeId`. +/// `expr` (a call to any bodied `-> Type` fn) is wrapped in a throwaway comptime +/// fn and run through the interpreter with the type-mint table enabled, so the +/// `declare`/`define` builtins reached inside it mutate the real type table. The +/// result value is a `.type_tag`. A type minted via `define` is already named +/// (the name travels in its `TypeInfo`); a caller needing a different identity +/// name (the type-fn mangled-name path) renames afterwards via +/// `renameNominalType`. Returns null (caller poisons) if evaluation didn't yield +/// a Type. +pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId { const func_id = self.createComptimeFunction("__ctype", expr, .any); var interp = interp_mod.Interpreter.init(self.module, self.alloc); @@ -401,15 +402,14 @@ pub fn evalComptimeTypeNamed(self: *Lowering, expr: *const Node, name: ?[]const interp.setMintTable(&self.module.types); const result = interp.call(func_id, &.{}) catch return null; - const tid = result.asTypeId() orelse return null; - if (name) |nm| self.renameReifiedType(tid, nm); - return tid; + return result.asTypeId(); } -/// Rename a freshly-minted (anonymous `__reified_N`) nominal type to its -/// binding's name, re-keying `intern_map` so `findByName(name)` resolves it. -/// A no-op for a non-nominal / already-named-as-requested type. -pub fn renameReifiedType(self: *Lowering, tid: TypeId, name: []const u8) void { +/// Rename a nominal type to a new name, re-keying `intern_map` so +/// `findByName(name)` resolves it. Used by the type-fn instantiation path to +/// give a comptime-minted type its mangled instantiation name (identity / +/// Contract 1). A no-op for a non-nominal / already-named-as-requested type. +pub fn renameNominalType(self: *Lowering, tid: TypeId, name: []const u8) void { const tbl = &self.module.types; const new_name_id = tbl.internString(name); var info = tbl.get(tid); diff --git a/src/ir/lower/decl.zig b/src/ir/lower/decl.zig index 3bd96fa0..e9b345d5 100644 --- a/src/ir/lower/decl.zig +++ b/src/ir/lower/decl.zig @@ -44,8 +44,8 @@ const isPackFn = Lowering.isPackFn; /// Anything starting with `Java_` is a JNI native method that Android's /// runtime resolves by name mangling — same rule. /// True when `fd` declares a `-> Type` return — the signal that a non-generic -/// call to it (`E :: f(...)`) should be comptime-evaluated to mint a type (the -/// REIFY floor). Matches a bare `Type` type-expr return only. +/// call to it (`E :: f(...)`) should be comptime-evaluated to mint a type. +/// Matches a bare `Type` type-expr return only. fn fnReturnsTypeValue(fd: *const ast.FnDecl) bool { const rt = fd.return_type orelse return false; return rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Type"); @@ -660,16 +660,18 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void { else => "", }; // `E :: f(...)` where `f` is a NON-generic fn returning - // `Type` (e.g. the sx `reify` / `make_enum`): comptime- - // evaluate the call — `declare`/`define` reached inside it - // mint the type — and bind `E` as an alias to the result. - // The compiler has ZERO `reify` knowledge: any Type-returning - // value-fn flows here. Generic type-fns (`$T`) are minted by + // `Type` (a comptime type constructor): comptime-evaluate the + // call — `declare`/`define` reached inside it mint the type — + // and bind `E` as an alias to the result. No hardcoded + // constructor names: any Type-returning value-fn flows here. + // Generic type-fns (`$T`) are minted by // `instantiateTypeFunction` below. Poison on failure so // `E.x` gets a clean follow-on, never a silent default. if (self.program_index.fn_ast_map.get(callee_name)) |fd| { if (fd.type_params.len == 0 and fnReturnsTypeValue(fd)) { - const tid = self.evalComptimeTypeNamed(cd.value, cd.name) orelse TypeId.unresolved; + // The minted type's NAME comes from its `TypeInfo` + // (via `define`), not the binding LHS — no rename. + const tid = self.evalComptimeType(cd.value) orelse TypeId.unresolved; self.putTypeAlias(self.current_source_file, cd.name, tid); continue; } diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index bc0fd3ab..084f5421 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -1252,20 +1252,11 @@ pub fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId .field_access => |fa| fa.field, else => return .unresolved, }; - // Comptime type-construction builtins (REIFY). `reify` is minted in a - // `::` type-binding position by `decl.zig` (`E :: reify(...)`); reaching it - // HERE means an inline type position (`x : reify(...)`, a nested type arg), - // which Phase 0 does not support — bail LOUDLY rather than fall through to - // the misleading "unknown type 'reify'" diagnostic below. - if (std.mem.eql(u8, callee_name, "reify")) { - if (self.diagnostics) |d| - d.addFmt(.err, cl.callee.span, "reify is only supported in a `::` type binding (e.g. `E :: reify(...)`) in Phase 0", .{}); - return .unresolved; - } + // field_type($T, i) -> Type — comptime reflection (read a type's i-th + // field / variant-payload / element type). A genuine type-table op, kept as + // a compiler builtin (like type_name); folds at lower time so it composes + // inside type_eq / type_name / any type-arg slot. if (std.mem.eql(u8, callee_name, "field_type")) { - // field_type($T, i) -> Type — the i-th field / variant-payload / - // element type of `T`. Folds at lower time (it's a `$T: Type` builtin), - // so it composes inside `type_eq` / `type_name` / any type-arg slot. if (cl.args.len != 2) { if (self.diagnostics) |d| d.addFmt(.err, cl.callee.span, "field_type takes a type and an index: field_type($T, i)", .{}); @@ -1758,17 +1749,22 @@ pub fn instantiateTypeFunction(self: *Lowering, alias_name: []const u8, template return self.instantiateTypeUnion(if (has_alias) alias_name else mangled_name, mangled_name, &enum_decl); } - // A type-fn body that RETURNS `reify(...)` — mint the enum under THIS - // instantiation's name (mangled for inline use, the alias name for - // `Foo :: Box(i64)`). The type-arg bindings are active here, so the reify - // payloads resolve against the instantiation's args (`payload = T` → the - // bound type). Registering under the mangled name lets the cache check at - // the top of this fn return the SAME TypeId on a second instantiation — - // so `Box(i64)` at two sites is ONE type (Contract 1). Must precede the - // general case below, whose `resolveTypeWithBindings` would route the - // reify call to the inline-position loud bail. - if (findReturnReifyCall(fd.body)) |reify_call| { - return self.reifyType(if (has_alias) alias_name else mangled_name, reify_call); + // A type-fn body that returns a COMPUTED Type — a call to a non-generic, + // bodied, Type-returning fn (a comptime type constructor). Comptime-evaluate + // the return expression with the type bindings active (so a payload `= T` + // resolves to the bound arg) and mint under THIS instantiation's name. The + // rename to the mangled name lets the cache check at the top return the + // SAME TypeId on a second instantiation — `Foo(i64)` at two sites is ONE + // type (nominal identity). Must precede the general static case below, whose + // `resolveTypeWithBindings` can't evaluate a Type-returning call. + if (findReturnTypeExpr(fd.body)) |ret_node| { + if (self.returnExprMintsType(ret_node)) { + const tid = self.evalComptimeType(ret_node) orelse return .unresolved; + // Re-key to the instantiation's mangled (or alias) name so the + // cache check at the top dedups a second instantiation — Contract 1. + self.renameNominalType(tid, if (has_alias) alias_name else mangled_name); + return tid; + } } // General case: the body returns a TYPE EXPRESSION that is not an inline @@ -1801,17 +1797,21 @@ pub fn findReturnTypeExpr(body: *const Node) ?*const Node { return body; } -/// The `reify(...)` call a type-fn body returns (block `return reify(...)` or -/// arrow `=> reify(...)`), or null if the body's return is not a bare `reify` -/// call. Used to route a reify-returning type-fn through `reifyType` under the -/// instantiation name (Phase 1 nominal identity). -pub fn findReturnReifyCall(body: *const Node) ?*const ast.Call { - const ret = findReturnTypeExpr(body) orelse return null; - if (ret.data != .call) return null; +/// 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. +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 null; - if (!std.mem.eql(u8, callee.data.identifier.name, "reify")) return null; - return &ret.data.call; + 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 + 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"); } /// Instantiate a tagged enum from a type function body. diff --git a/src/ir/lower/nominal.zig b/src/ir/lower/nominal.zig index cc8e503c..d527c84e 100644 --- a/src/ir/lower/nominal.zig +++ b/src/ir/lower/nominal.zig @@ -734,100 +734,6 @@ pub fn registerEnumDecl(self: *Lowering, ed: *const ast.EnumDecl) void { _ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id); } -/// REIFY Phase 0: mint a NEW nominal enum type from a `TypeInfo` literal passed -/// to `reify(...)`, registered under `type_name`. The argument shape this phase -/// supports is exactly the flat-enum literal: -/// -/// reify(.enum(.{ variants = .[ EnumVariant.{ name = "value", payload = i64 }, -/// EnumVariant.{ name = "closed", payload = void } ] })) -/// -/// The variant data is read DIRECTLY off the literal AST (Phase 0 reify takes a -/// comptime-known literal; the general interp-evaluated path is a later phase), -/// then handed to the SAME `buildEnumInfo` path source enums use — so the -/// minted type is byte-identical to an equivalent hand-written `enum { value: -/// i64; closed; }` and flows through enum codegen (layout / construct / match) -/// unmodified (Contract 2). Returns the minted `TypeId`, or null after emitting -/// a diagnostic if the argument is not a shape this phase can build (never a -/// silent default — REJECTED PATTERNS). -pub fn reifyType(self: *Lowering, type_name: []const u8, reify_call: *const ast.Call) ?TypeId { - const span = reify_call.callee.span; - if (reify_call.args.len != 1) return reifyBail(self, span, "reify expects exactly one TypeInfo argument"); - - // arg = `.enum(EnumInfo)` — an enum-literal applied as a call. - const arg = reify_call.args[0]; - if (arg.data != .call or arg.data.call.callee.data != .enum_literal) - return reifyBail(self, span, "reify Phase 0 supports only `.enum(...)` TypeInfo"); - const variant_kind = arg.data.call.callee.data.enum_literal.name; - if (!std.mem.eql(u8, variant_kind, "enum")) - return reifyBail(self, span, "reify Phase 0 supports only the `.enum` TypeInfo variant"); - if (arg.data.call.args.len != 1) - return reifyBail(self, span, "reify `.enum(...)` takes one EnumInfo payload"); - - // EnumInfo payload = `.{ variants = .[ ... ] }`. - const einfo = arg.data.call.args[0]; - if (einfo.data != .struct_literal) - return reifyBail(self, span, "reify `.enum(...)` payload must be an EnumInfo struct literal"); - const variants_node = fieldInitValue(&einfo.data.struct_literal, "variants") orelse - return reifyBail(self, span, "reify EnumInfo is missing the `variants` field"); - if (variants_node.data != .array_literal) - return reifyBail(self, span, "reify `variants` must be an array literal of EnumVariant"); - - // Each element = `EnumVariant.{ name = "...", payload = T }`. - var names = std.ArrayList([]const u8).empty; - var payloads = std.ArrayList(?*Node).empty; - for (variants_node.data.array_literal.elements) |elem| { - if (elem.data != .struct_literal) - return reifyBail(self, span, "reify variant must be an EnumVariant struct literal"); - const name_node = fieldInitValue(&elem.data.struct_literal, "name") orelse - return reifyBail(self, span, "reify EnumVariant is missing `name`"); - if (name_node.data != .string_literal) - return reifyBail(self, span, "reify EnumVariant `name` must be a string literal"); - const payload_node = fieldInitValue(&elem.data.struct_literal, "payload") orelse - return reifyBail(self, span, "reify EnumVariant is missing `payload`"); - names.append(self.alloc, name_node.data.string_literal.raw) catch return null; - payloads.append(self.alloc, payload_node) catch return null; - } - if (names.items.len == 0) - return reifyBail(self, span, "reify enum has no variants"); - - // Hand the synthesized decl to the shared enum body-builder (`self` is the - // visibility-aware payload-type resolver, as in registerEnumDecl). A - // payload that resolves to `.void` becomes a tagless variant (`closed`), - // exactly as a source `enum { … ; closed; }` would. - const ed = ast.EnumDecl{ - .name = type_name, - .variant_names = names.items, - .variant_types = payloads.items, - .is_flags = false, - .variant_values = &.{}, - .backing_type = null, - .is_raw = false, - }; - const table = &self.module.types; - const info = type_bridge.buildEnumInfo(&ed, table, self); - const name_id = table.internString(type_name); - const tid = table.findByName(name_id) orelse table.internNominal(info, 0); - table.updatePreservingKey(tid, info); - return tid; -} - -/// Emit a reify diagnostic and return null — the single loud-failure exit for -/// `reifyType` (no silent default ever reaches the type table). -fn reifyBail(self: *Lowering, span: ?ast.Span, comptime msg: []const u8) ?TypeId { - if (self.diagnostics) |d| d.addFmt(.err, span, msg, .{}); - return null; -} - -/// The value node of a named field init in a struct literal, or null if absent. -fn fieldInitValue(lit: *const ast.StructLiteral, name: []const u8) ?*Node { - for (lit.field_inits) |fi| { - if (fi.name) |n| { - if (std.mem.eql(u8, n, name)) return fi.value; - } - } - return null; -} - /// Register a top-level UNION decl under a per-decl nominal identity (E6a) — /// the union twin of `registerEnumDecl` / `registerStructDecl`. pub fn registerUnionDecl(self: *Lowering, ud: *const ast.UnionDecl) void {