Skip to content

Commit c98155c

Browse files
committed
Fix self-recursive generic function call with explicit type args
namedFuncDecl registered a top-level generic function in the generic-func registry only AFTER compiling its body. hasGenericFunc(name) is what lets the expression compiler recognize name[T](...) as a generic call rather than an indexing expression, so a self-recursive call using explicit type args inside that same body (a common pattern for generic recursive functions) was misparsed as indexing — evaluating T as a nonexistent runtime variable and panicking with NotDefined. Fixed by registering the generic function before compiling its body, so a self-recursive call sees it already registered.
1 parent 4aaad6c commit c98155c

2 files changed

Lines changed: 35 additions & 5 deletions

File tree

src/compiler_test.zig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3998,6 +3998,26 @@ test "compiler: dunder operators work at runtime inside a type-erased generic fu
39983998
try std.testing.expectEqual(@as(i64, 7), result.int);
39993999
}
40004000

4001+
// addGenericFunc used to run AFTER compiling the function's own body, so a
4002+
// self-recursive call with explicit type args (countdown[T](...)) inside
4003+
// that same body couldn't find hasGenericFunc(name) yet — compiler_expr.zig
4004+
// treated `countdown[T]` as an indexing expression instead of a generic
4005+
// call, evaluating `T` as a (nonexistent) runtime variable and panicking
4006+
// with NotDefined. Fixed by registering a top-level generic function before
4007+
// compiling its body.
4008+
test "compiler: self-recursive generic function call with explicit type args" {
4009+
var rt = try setup();
4010+
defer rt.deinit();
4011+
try runSrc(&rt,
4012+
\\func countdown[T](x T, n int) T {
4013+
\\ if n <= 0 { return x }
4014+
\\ return countdown[T](x, n - 1)
4015+
\\}
4016+
);
4017+
const result = try rt.callGlobal("countdown", &.{ .{ .int = 0 }, .{ .int = 5 } });
4018+
try std.testing.expectEqual(@as(i64, 0), result.int);
4019+
}
4020+
40014021
test "compiler: := infers struct type for dunder dispatch, same as an explicitly var-typed local" {
40024022
// Struct literals previously left no ExprPrimInfo on their own result at
40034023
// all, and := never inferred struct_type for a local either — both

src/lang/compiler_decls.zig

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,20 @@ pub fn namedFuncDecl(c: anytype, is_pub: bool) !void {
402402
}
403403
errdefer c.type_param_count = saved_param_count;
404404

405+
// A top-level generic function must be registered in the generic-func
406+
// registry BEFORE its body is compiled: hasGenericFunc(name) is what
407+
// lets the expression compiler recognize name[T](...) as a generic
408+
// call rather than an indexing expression (compiler_expr.zig), and a
409+
// self-recursive call inside the body needs that recognition already
410+
// in effect — registering afterward (as this used to) left every
411+
// recursive call misparsed as indexing, panicking at runtime.
412+
if (is_generic and !c.inFunc() and !c.skipping_test_body) {
413+
const qname_early = try c.qualifyGlobalName(name.src);
414+
var gi: ct.GenericFuncInfo = .{ .name = try c.copyName(name.src), .param_count = tparam_count, .qname = qname_early };
415+
for (tparams[0..tparam_count], 0..) |tp, i| gi.params[i] = tp;
416+
try c.registry.addGenericFunc(gi);
417+
}
418+
405419
// current token is '('; compile as a named function for return-type enforcement
406420
if (!is_generic) c.pending_func_qname = try c.qualifyGlobalName(name.src);
407421
_ = try c.compileFuncWithPrefix(&[_][]const u8{}, true, null);
@@ -428,11 +442,7 @@ pub fn namedFuncDecl(c: anytype, is_pub: bool) !void {
428442
c.registry.setGlobalFuncReturnCount(qname, fo.function.named_return_count);
429443
}
430444
}
431-
if (is_generic) {
432-
var gi: ct.GenericFuncInfo = .{ .name = try c.copyName(name.src), .param_count = tparam_count, .qname = qname };
433-
for (tparams[0..tparam_count], 0..) |tp, i| gi.params[i] = tp;
434-
try c.registry.addGenericFunc(gi);
435-
}
445+
// (generic registration now happens above, before compiling the body)
436446
}
437447
try c.cs.emitOpStringConst(.def_global, qname, kw.line);
438448
if (is_pub) try c.addExport(name.src, qname);

0 commit comments

Comments
 (0)