comptime VM: Phase 3 — register_type write side + payloadless-enum fixes
The mutating compiler-API, minting types LAZILY at lowering time (single pass,
the existing runComptimeTypeFunc path — so the write side is legacy-only; the
VM isn't wired at lowering time, and the read-side readers stay dual-path):
declare_type(name) -> Type forward nominal handle (≈ declare)
pointer_to(t) -> Type build *T references
register_type(handle, kind, members) ONE kind-branching fill (≈ unified define)
register_type branches on kind IN THE COMPILER (subsuming define's per-kind
dispatch); codes match type_kind: 1 struct, 2 actual .@"enum", 3 tagged_union,
4 tuple. Members are {name: string, ty: Type}. A non-generic `-> Type` builder is
now flagged is_comptime (decl.zig) so its dead body permits the welded calls.
Graph support: forward declare_type handles + pointer_to express a mutually-
recursive A<->B graph (*A, *B, B-by-value) before bodies are filled. register_type
is idempotent — re-filling a nominal slot (a minting module reached via two import
edges) re-mints identically rather than erroring (nominalIdent reads identity from
any nominal kind).
Fixes (issue 0142):
- A fully payloadless comptime-minted enum was minted as an all-void tagged_union,
whose IR size disagrees with its LLVM size -> verifySizes panic. Now mints a real
.@"enum" (register_type kind 2 AND the metatype defineEnum).
- Bare `EnumType.variant` qualified construction of a payloadless variant wasn't
supported (failed for hand-written enums too — the type name lowered to a Type
value). Added in lowerFieldAccess via isPayloadlessVariant; payload-carrying
variants keep their call form.
Examples: 0631 (graph + actual enum + reflection), 0632 (make_enum all-void),
0633/0634/0635 (namespaced / bare / multi-edge import of a minted type), 0187
(qualified variant construction). Unit tests added.
Parity 697/697 (gate OFF and -Dcomptime-flat).
This commit is contained in:
@@ -2298,6 +2298,14 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
|
||||
// A non-generic `-> Type` builder is a comptime type constructor — only ever
|
||||
// evaluated at lowering time (`runComptimeTypeFunc`) to mint a type, never
|
||||
// called at runtime. Flag it `is_comptime` so its emitted body is dead: the
|
||||
// comptime-only `compiler`-library gate then permits welded calls inside it
|
||||
// (`register_type`/`declare_type`/`pointer_to`), exactly as in a #run/`::`
|
||||
// wrapper. Without this, a builder that calls a welded fn would be rejected
|
||||
// as "comptime-only fn called at runtime" even though it never runs at runtime.
|
||||
if (fnReturnsTypeValue(fd)) func.is_comptime = true;
|
||||
self.fn_decl_fids.put(fd, fid) catch {};
|
||||
}
|
||||
|
||||
|
||||
@@ -490,6 +490,33 @@ pub fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.S
|
||||
}
|
||||
}
|
||||
|
||||
// Bare `Enum.variant` — a qualified enum literal. When the object is a type
|
||||
// NAME resolving to an enum / tagged-union (not shadowed by a value binding /
|
||||
// global value) and `field` is a PAYLOADLESS variant, construct it like the
|
||||
// leading-dot `.variant` in a typed context. Mirrors the `alias.Enum.variant`
|
||||
// namespace path above. Restricted to payloadless variants so a payload-
|
||||
// carrying `Ev.a(5)` still flows through the call path (which supplies the
|
||||
// payload) rather than being hijacked into a zero-arg `.a` here.
|
||||
if (fa.object.data == .identifier) {
|
||||
const oname = fa.object.data.identifier.name;
|
||||
const shadowed = if (self.scope) |s| s.lookup(oname) != null else false;
|
||||
if (!shadowed and !self.program_index.global_names.contains(oname)) {
|
||||
if (self.module.types.findByName(self.module.types.internString(oname))) |ty| {
|
||||
if (!ty.isBuiltin() and self.isPayloadlessVariant(ty, fa.field)) {
|
||||
const synth = self.alloc.create(Node) catch null;
|
||||
if (synth) |n| {
|
||||
n.* = .{ .span = span, .data = .{ .enum_literal = .{ .name = fa.field } } };
|
||||
const saved_tt = self.target_type;
|
||||
self.target_type = ty;
|
||||
const ref = self.lowerExpr(n);
|
||||
self.target_type = saved_tt;
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pack-arity intercept: `<pack_name>.len` in a pack-fn mono's
|
||||
// body resolves to the comptime-known N. The mono doesn't
|
||||
// materialise the `[]Any` slice that the inline path used, so
|
||||
@@ -965,6 +992,25 @@ pub fn lowerEnumLiteral(self: *Lowering, el: *const ast.EnumLiteral) Ref {
|
||||
return self.builder.enumInit(tag, Ref.none, target);
|
||||
}
|
||||
|
||||
/// Is `field` a PAYLOADLESS variant of enum/tagged-union `ty`? A plain `.@"enum"`
|
||||
/// variant is always payloadless; a `tagged_union` variant is payloadless iff its
|
||||
/// payload is `void`. Used by `lowerFieldAccess` to recognise a bare
|
||||
/// `Enum.variant` qualified literal (payload-carrying variants stay on the call
|
||||
/// path, which supplies the payload). False for any non-enum type / unknown field.
|
||||
pub fn isPayloadlessVariant(self: *Lowering, ty: TypeId, field: []const u8) bool {
|
||||
return switch (self.module.types.get(ty)) {
|
||||
.@"enum" => |e| blk: {
|
||||
for (e.variants) |v| if (std.mem.eql(u8, self.module.types.getString(v), field)) break :blk true;
|
||||
break :blk false;
|
||||
},
|
||||
.tagged_union => |u| blk: {
|
||||
for (u.fields) |f| if (std.mem.eql(u8, self.module.types.getString(f.name), field)) break :blk (f.ty == .void);
|
||||
break :blk false;
|
||||
},
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// The enum twin of `emitBadVariant`: an unknown variant of a plain enum,
|
||||
/// with the legal variants listed.
|
||||
pub fn emitBadEnumVariant(
|
||||
|
||||
Reference in New Issue
Block a user