green(metatype): declare(name) + self-reference (recursive enums via *Name)

declare now takes the type's NAME — `declare(name) -> Type` — because the
compiler needs it at compile time to register the forward type, which is
what makes self-reference resolve. EnumInfo drops `name` (it lives on
declare now); define completes the handle's body in place (the slot is
already named).

Self-reference mechanism (evalComptimeType): before lowering a comptime
type expression, preregisterForwardTypes scans it (and a called ctor fn's
body) for `declare("Name")` calls and registers each as an empty forward
nominal type AND binds it as a type alias. The alias is essential: a
`Name :: ctor()` decl makes `Name` a const_decl author, so a `*Name`
self-reference resolves through the forward-ALIAS path
(type_aliases_by_source), which a bare findByName registration doesn't
satisfy. With both in place `*Name` resolves to the forward slot at lower
time; the interp's declare returns that same slot; define fills it.

  List :: make_list();
  make_list :: () -> Type {
      h := declare("List");
      return define(h, .enum(.{ variants = .[
          EnumVariant.{ name = "cons", payload = *List },
          EnumVariant.{ name = "nil",  payload = void } ] }));
  }

Verified: cons/nil construct + match (direct and through the pointer),
multi-node list traversal via a recursive `count(*List)`. meta.sx
RecvResult/TryResult + examples 0614/0615/0617 updated to declare(name);
full suite green (673).
This commit is contained in:
agra
2026-06-16 22:02:48 +03:00
parent 12e2ff7ef4
commit 7a9db03bcc
6 changed files with 104 additions and 54 deletions

View File

@@ -1677,15 +1677,20 @@ 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: 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", .{});
// Comptime type-construction primitive: mint an empty nominal slot NAMED
// by its (compile-time string) argument. 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 != 1) {
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "declare(name) takes one string argument", .{});
return Ref.none;
}
return self.builder.callBuiltin(.declare, &.{}, .any);
// The named forward type is pre-registered by `evalComptimeType`'s
// `preregisterForwardTypes` before this body lowers (so a `*Name`
// self-reference resolves); the interp's `declare` returns that slot.
const name_ref = self.lowerExpr(c.args[0]);
const args_owned = self.alloc.dupe(Ref, &.{name_ref}) catch return Ref.none;
return self.builder.callBuiltin(.declare, args_owned, .any);
}
if (std.mem.eql(u8, name, "define")) {
// Comptime type-construction primitive: complete a declare()'d slot