feat(metatype): comptime-eval generic type-fn body locals
A generic ($T) -> Type type-fn comptime-evaluated only its return
EXPRESSION, so a local declared before the return ('vs := …; return
make_enum(…, vs)') was unresolved. Now a body with a prelude (statements
before the return) has its full body evaluated: createComptimeFunction-
WithPrelude lowers the pre-return statements into the comptime function's
scope before the return expr, so the locals resolve.
- comptime.zig: createComptimeFunctionWithPrelude (prelude stmts +
expr); evalComptimeTypeBody (extract prelude + return expr, scan the
whole body for declare() forward types); runComptimeTypeFunc factored
out of evalComptimeType (shared bail/declare-never-defined handling).
- generic.zig: route a type-fn body WITH a prelude through
evalComptimeTypeBody; no-prelude bodies stay on evalComptimeType (zero
change for RecvResult/TryResult etc.).
Non-generic builders (whole body already evaluated) and the List-growth
path are unaffected. Suite green (684).
This commit is contained in:
@@ -1567,6 +1567,8 @@ pub const Lowering = struct {
|
||||
pub const evalComptimeInt = lower_comptime.evalComptimeInt;
|
||||
pub const evalComptimeString = lower_comptime.evalComptimeString;
|
||||
pub const evalComptimeType = lower_comptime.evalComptimeType;
|
||||
pub const evalComptimeTypeBody = lower_comptime.evalComptimeTypeBody;
|
||||
pub const runComptimeTypeFunc = lower_comptime.runComptimeTypeFunc;
|
||||
pub const renameNominalType = lower_comptime.renameNominalType;
|
||||
pub const lowerComptimeGlobal = lower_comptime.lowerComptimeGlobal;
|
||||
pub const lowerComptimeSideEffect = lower_comptime.lowerComptimeSideEffect;
|
||||
@@ -1578,6 +1580,7 @@ pub const Lowering = struct {
|
||||
pub const substituteComptimeNodes = lower_comptime.substituteComptimeNodes;
|
||||
pub const fnBodyHasReturn = lower_comptime.fnBodyHasReturn;
|
||||
pub const createComptimeFunction = lower_comptime.createComptimeFunction;
|
||||
pub const createComptimeFunctionWithPrelude = lower_comptime.createComptimeFunctionWithPrelude;
|
||||
pub const constExprValue = lower_comptime.constExprValue;
|
||||
pub const constArrayLiteral = lower_comptime.constArrayLiteral;
|
||||
pub const constStructLiteral = lower_comptime.constStructLiteral;
|
||||
|
||||
@@ -448,7 +448,39 @@ pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId {
|
||||
// body. The interp's `declare` returns this same slot; `define` completes it.
|
||||
preregisterForwardTypes(self, expr);
|
||||
const func_id = self.createComptimeFunction("__ctype", expr, .any);
|
||||
return self.runComptimeTypeFunc(func_id, expr.span);
|
||||
}
|
||||
|
||||
/// Comptime-evaluate a type-fn BODY that has local statements before its
|
||||
/// `return` (the plain `evalComptimeType` only sees the return expression, so a
|
||||
/// local declared before it is unresolved). Lowers the pre-return statements as
|
||||
/// a prelude, then the return expression. Only used for bodies that actually
|
||||
/// have a prelude — the no-prelude case stays on `evalComptimeType`.
|
||||
pub fn evalComptimeTypeBody(self: *Lowering, body: *const Node, ret_expr: *const Node) ?TypeId {
|
||||
// Scan the WHOLE body for `declare("Name")` (a local `h := declare(…)` is in
|
||||
// the prelude, not the return) so forward types register before lowering.
|
||||
preregisterForwardTypes(self, body);
|
||||
const prelude = preludeBeforeReturn(body);
|
||||
const func_id = self.createComptimeFunctionWithPrelude("__ctype", prelude, ret_expr, .any);
|
||||
return self.runComptimeTypeFunc(func_id, ret_expr.span);
|
||||
}
|
||||
|
||||
/// The statements of a block body that PRECEDE its first `return` — the locals a
|
||||
/// type-fn binds before minting. Empty for a non-block (arrow) body.
|
||||
fn preludeBeforeReturn(body: *const Node) []const *const Node {
|
||||
if (body.data != .block) return &.{};
|
||||
const stmts = body.data.block.stmts;
|
||||
for (stmts, 0..) |stmt, i| {
|
||||
if (stmt.data == .return_stmt) return stmts[0..i];
|
||||
}
|
||||
return &.{};
|
||||
}
|
||||
|
||||
/// Run a comptime type-construction function and post-process its result: render
|
||||
/// any interp bail as a build-gating diagnostic (issue 0140) and reject a bare
|
||||
/// `declare()` never completed by `define()` (a zero-field nominal slot that
|
||||
/// would otherwise panic at codegen). `span` locates both diagnostics.
|
||||
pub fn runComptimeTypeFunc(self: *Lowering, func_id: FuncId, span: ast.Span) ?TypeId {
|
||||
var interp = interp_mod.Interpreter.init(self.module, self.alloc);
|
||||
defer interp.deinit();
|
||||
if (self.diagnostics) |d| if (d.import_sources) |sm| interp.setSourceMap(sm);
|
||||
@@ -469,7 +501,7 @@ pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId {
|
||||
// unannounced.
|
||||
if (self.diagnostics) |d| {
|
||||
const detail = interp_mod.Interpreter.last_bail_detail orelse @errorName(err);
|
||||
d.addFmt(.err, expr.span, "comptime type construction failed: {s}", .{detail});
|
||||
d.addFmt(.err, span, "comptime type construction failed: {s}", .{detail});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -483,7 +515,7 @@ pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId {
|
||||
const info = self.module.types.get(tid);
|
||||
if (info == .tagged_union and info.tagged_union.fields.len == 0) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, expr.span, "type '{s}' is declared but never defined — complete it with define(handle, info)", .{self.module.types.getString(info.tagged_union.name)});
|
||||
d.addFmt(.err, span, "type '{s}' is declared but never defined — complete it with define(handle, info)", .{self.module.types.getString(info.tagged_union.name)});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -799,6 +831,15 @@ pub fn fnBodyHasReturn(node: *const Node) bool {
|
||||
/// Creates a temporary function marked `is_comptime = true` that wraps
|
||||
/// the given expression as its return value. Returns the FuncId.
|
||||
pub fn createComptimeFunction(self: *Lowering, prefix: []const u8, expr: *const Node, ret_ty: TypeId) FuncId {
|
||||
return self.createComptimeFunctionWithPrelude(prefix, &.{}, expr, ret_ty);
|
||||
}
|
||||
|
||||
/// Like `createComptimeFunction`, but lowers `prelude` statements (e.g. a
|
||||
/// type-fn body's local declarations) into the comptime function's scope BEFORE
|
||||
/// the result `expr`, so the expr can reference names they bind. Used to
|
||||
/// comptime-evaluate a generic type-fn body that has locals before its `return`
|
||||
/// (the non-prelude path only sees the return expression).
|
||||
pub fn createComptimeFunctionWithPrelude(self: *Lowering, prefix: []const u8, prelude: []const *const Node, expr: *const Node, ret_ty: TypeId) FuncId {
|
||||
var buf: [64]u8 = undefined;
|
||||
const name = std.fmt.bufPrint(&buf, "{s}_{d}", .{ prefix, self.comptime_counter }) catch prefix;
|
||||
self.comptime_counter += 1;
|
||||
@@ -883,6 +924,11 @@ pub fn createComptimeFunction(self: *Lowering, prefix: []const u8, expr: *const
|
||||
var ct_scope = Scope.init(self.alloc, saved_scope);
|
||||
self.scope = &ct_scope;
|
||||
|
||||
// Lower any prelude statements (type-fn body locals) so the result
|
||||
// expression can reference the names they bind. Empty for the common
|
||||
// single-expression case.
|
||||
for (prelude) |stmt| self.lowerStmt(stmt);
|
||||
|
||||
// Lower the expression and return it
|
||||
const result = self.lowerExpr(expr);
|
||||
if (ret_ty == .void) {
|
||||
|
||||
@@ -1759,7 +1759,21 @@ pub fn instantiateTypeFunction(self: *Lowering, alias_name: []const u8, template
|
||||
// `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;
|
||||
// A body with LOCALS before its `return` (e.g. `vs := …; return
|
||||
// make_enum(…, vs)`) needs its full body comptime-evaluated so those
|
||||
// locals resolve; the bare return-expr path leaves them unresolved.
|
||||
// A no-prelude body stays on the simpler `evalComptimeType` path.
|
||||
const has_prelude = fd.body.data == .block and blk: {
|
||||
for (fd.body.data.block.stmts) |stmt| {
|
||||
if (stmt.data == .return_stmt) break :blk false;
|
||||
break :blk true; // a non-return statement precedes the return
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
const tid = (if (has_prelude)
|
||||
self.evalComptimeTypeBody(fd.body, ret_node)
|
||||
else
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user