green(reify): type-fn bodies comptime-evaluated; reify fully removed from the compiler

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).
This commit is contained in:
agra
2026-06-16 21:03:16 +03:00
parent 442a70b8c9
commit 8ae655687a
11 changed files with 112 additions and 194 deletions

View File

@@ -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.