Skip to content

Commit 369aa0f

Browse files
committed
Fix constrained generic struct/variant type parameters
looksLikeGenericTypeParams (compiler.zig) — the lookahead that decides whether Name[...] after a type name is a generic-parameter list — only accepted .ident/.comma inside the brackets, so a constrained parameter (type Box[T: numeric] struct {...}) fell through to `else => return false` and the whole declaration was never recognized as generic at all, despite docs/language.md documenting the same constraint syntax generic functions already support (isNamedFuncDecl's sibling scanner already allowed ':'). Fixed by bringing the two scanners in line. Once recognized as generic, the constraint was still never enforced: instantiateGenericType built the concrete type args but never checked them against the declared constraints, unlike generic function calls (which already run checkTypeArgConstraints). Fixed by calling the same check during type instantiation.
1 parent c98155c commit 369aa0f

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

src/compiler_test.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4018,6 +4018,44 @@ test "compiler: self-recursive generic function call with explicit type args" {
40184018
try std.testing.expectEqual(@as(i64, 0), result.int);
40194019
}
40204020

4021+
// looksLikeGenericTypeParams (compiler.zig) — the lookahead that decides
4022+
// whether `Name[...]` after a type name is a generic-parameter list — only
4023+
// accepted .ident/.comma inside the brackets, so a `:` constraint (e.g.
4024+
// `[T: numeric]`) fell through to `else => return false` and the whole
4025+
// declaration was never even recognized as generic, despite the sibling
4026+
// scanner for generic functions (isNamedFuncDecl) already allowing `:`, and
4027+
// despite docs/language.md explicitly documenting the same constraint
4028+
// syntax for generic types. Fixed by allowing .colon in the type-param
4029+
// scan too, and by wiring checkTypeArgConstraints into
4030+
// instantiateGenericType (compiler_decls.zig) — generic functions already
4031+
// enforced constraints at call time, but generic types never did, even
4032+
// once recognized as generic.
4033+
test "compiler: constrained generic struct type parameter parses and instantiates" {
4034+
var rt = try setup();
4035+
defer rt.deinit();
4036+
try runSrc(&rt,
4037+
\\type Box[T: numeric] struct { val T }
4038+
\\func f() int {
4039+
\\ b := Box[int]{ val: 42 }
4040+
\\ return b.val
4041+
\\}
4042+
);
4043+
const result = try rt.callGlobal("f", &.{});
4044+
try std.testing.expectEqual(@as(i64, 42), result.int);
4045+
}
4046+
4047+
test "compiler: constrained generic struct type parameter rejects a non-conforming type arg" {
4048+
var rt = try setup();
4049+
defer rt.deinit();
4050+
try std.testing.expectError(error.ConstraintViolation, compile(&rt,
4051+
\\type Box[T: numeric] struct { val T }
4052+
\\func f() string {
4053+
\\ b := Box[string]{ val: "hi" }
4054+
\\ return b.val
4055+
\\}
4056+
));
4057+
}
4058+
40214059
test "compiler: := infers struct type for dunder dispatch, same as an explicitly var-typed local" {
40224060
// Struct literals previously left no ExprPrimInfo on their own result at
40234061
// all, and := never inferred struct_type for a local either — both

src/lang/compiler.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,13 @@ pub const Compiler = struct {
19211921
.eof => return false,
19221922
.ident => {},
19231923
.comma => {},
1924+
// `[T: numeric]`-style constraint, matching the analogous
1925+
// scan for generic functions (compiler_decls.zig's
1926+
// isNamedFuncDecl) — a bare `.colon` here previously
1927+
// fell through to `else => return false`, so a
1928+
// constrained generic type was never even recognized as
1929+
// generic at all (docs/language.md claimed it worked).
1930+
.colon => {},
19241931
else => return false,
19251932
}
19261933
t = lx.next();

src/lang/compiler_decls.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,16 @@ pub fn instantiateGenericType(c: anytype, tname: []const u8, line: u32) anyerror
12701270
const ginfo = c.registry.getGenericType(tname).?;
12711271
var args: [ct.MaxTypeParams]FieldTypeSpec = undefined;
12721272
const arg_count = try parseInstArgSpecs(c, tname, ginfo.param_count, &args);
1273+
// Generic functions have enforced type-parameter constraints (e.g.
1274+
// `[T: numeric]`) since checkTypeArgConstraints existed; generic
1275+
// struct/variant types parsed and stored the same constraint syntax
1276+
// (namedTypeDecl) but never checked it here — the parser's lookahead
1277+
// (looksLikeGenericTypeParams) didn't even recognize a `:` inside
1278+
// `[...]` as a generic-type declaration at all until this fix, so a
1279+
// constrained generic type silently failed to parse as generic in the
1280+
// first place; now that it does, the same enforcement generic
1281+
// functions already get must apply to instantiation too.
1282+
try checkTypeArgConstraints(c, ginfo.params[0..ginfo.param_count], args[0..arg_count], tname, line);
12731283
return applyGenericInst(c, tname, args[0..arg_count], line);
12741284
}
12751285

0 commit comments

Comments
 (0)