Skip to content

Commit 3e65d86

Browse files
authored
test(semantic): add scope flag unit test (#178)
1 parent 84a33b8 commit 3e65d86

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

src/semantic/SemanticBuilder.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ inline fn visitFnDecl(self: *SemanticBuilder, node_id: NodeIndex) !void {
11041104
});
11051105

11061106
var fn_signature_implies_comptime = false;
1107-
const tags: []Node.Tag = ast.nodes.items(.tag);
1107+
const tags: []const Node.Tag = ast.nodes.items(.tag);
11081108
for (proto.ast.params) |param_id| {
11091109
if (tags[param_id] == .@"comptime") {
11101110
fn_signature_implies_comptime = true;
@@ -1858,6 +1858,7 @@ const t = std.testing;
18581858
test {
18591859
t.refAllDecls(@import("test/symbol_ref_test.zig"));
18601860
t.refAllDecls(@import("test/symbol_decl_test.zig"));
1861+
t.refAllDecls(@import("test/scope_flags_test.zig"));
18611862
}
18621863
test "Struct/enum fields are bound bound to the struct/enums's member table" {
18631864
const alloc = std.testing.allocator;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const std = @import("std");
2+
const semantic = @import("../../semantic.zig");
3+
4+
const t = std.testing;
5+
const panic = std.debug.panic;
6+
const print = std.debug.print;
7+
const build = @import("./util.zig").build;
8+
const Tuple = std.meta.Tuple;
9+
10+
const Scope = semantic.Scope;
11+
const Symbol = semantic.Symbol;
12+
13+
const TestCase = Tuple(&[_]type{ [:0]const u8, Scope.Flags });
14+
15+
/// Check flags of the scope where a variable `x` is declared.
16+
fn testXDeclScope(cases: []const TestCase) !void {
17+
for (cases) |case| {
18+
const source, const expected_flags = case;
19+
var sem = try build(source);
20+
defer sem.deinit();
21+
22+
const x: Symbol.Id = brk: {
23+
if (sem.symbols.getSymbolNamed("x")) |_x| {
24+
break :brk _x;
25+
} else {
26+
print("Symbol 'x' not found in source:\n\n{s}\n\n", .{source});
27+
return error.TestFailed;
28+
}
29+
};
30+
31+
// Flags for the scope `x` is declared in
32+
const scope: Scope.Id = sem.symbols.symbols.items(.scope)[x.int()];
33+
const flags: Scope.Flags = sem.scopes.scopes.items(.flags)[scope.int()];
34+
35+
t.expectEqual(expected_flags, flags) catch |e| {
36+
print("Expected: {any}\nActual: {any}\n\n", .{ expected_flags, flags });
37+
print("Source:\n\n{s}\n\n", .{source});
38+
return e;
39+
};
40+
}
41+
}
42+
43+
/// Check flags of the scope where `x` is first referenced
44+
fn testXRefScope(cases: []const TestCase) !void {
45+
for (cases) |case| {
46+
const source, const expected_flags = case;
47+
var sem = try build(source);
48+
defer sem.deinit();
49+
50+
const x: Symbol.Id = brk: {
51+
if (sem.symbols.getSymbolNamed("x")) |_x| {
52+
break :brk _x;
53+
} else {
54+
print("Symbol 'x' not found in source:\n\n{s}\n\n", .{source});
55+
return error.TestFailed;
56+
}
57+
};
58+
59+
const refs = sem.symbols.getReferences(x);
60+
t.expectEqual(1, refs.len) catch |e| {
61+
print("Expected 1 reference to 'x', got {d}\n\n", .{refs.len});
62+
print("Source:\n\n{s}\n\n", .{source});
63+
return e;
64+
};
65+
66+
const scope: Scope.Id = sem.symbols.getReference(refs[0]).scope;
67+
const flags: Scope.Flags = sem.scopes.scopes.items(.flags)[scope.int()];
68+
69+
t.expectEqual(expected_flags, flags) catch |e| {
70+
print("Expected: {any}\nActual: {any}\n\n", .{ expected_flags, flags });
71+
print("Source:\n\n{s}\n\n", .{source});
72+
return e;
73+
};
74+
}
75+
}
76+
77+
test "top-level" {
78+
// TODO: Should this be considered comptime?
79+
const cases = &[_]TestCase{.{ "const x = 1;", Scope.Flags{ .s_top = true } }};
80+
try testXDeclScope(cases);
81+
}
82+
83+
test "function signatures and body scopes" {
84+
const cases = &[_]TestCase{
85+
// function symbols aren't declared in the scopes they create
86+
.{ "fn x() void {}", Scope.Flags{ .s_top = true } },
87+
// signatures create their own scope (params + return type)
88+
.{ "fn foo(x: u32) void { _ = x; }", Scope.Flags{ .s_function = true } },
89+
// bodies are flagged as blocks
90+
.{ "fn foo() void { const x = 1; _ = x; }", Scope.Flags{ .s_function = true, .s_block = true } },
91+
};
92+
try testXDeclScope(cases);
93+
94+
const ref_cases = &[_]TestCase{
95+
// signatures create their own scope (params + return type)
96+
.{ "fn foo(x: type) x { @panic(\"not implemented\"); }", Scope.Flags{ .s_function = true } },
97+
.{ "fn foo(x: type) Foo(x) { @panic(\"not implemented\"); }", Scope.Flags{ .s_function = true } },
98+
.{ "fn foo(x: type, bar: x) void { _ = bar; }", Scope.Flags{ .s_function = true } },
99+
.{ "fn foo(x: type, bar: Foo(x)) void { _ = bar; }", Scope.Flags{ .s_function = true } },
100+
};
101+
try testXRefScope(ref_cases);
102+
}
103+
104+
// test "function scopes are comptime if any of their parameters are comptime" {
105+
// const cases = &[_]TestCase{
106+
// .{ "fn foo(y: type) void { const x = 1; _ = x; }", Scope.Flags{ .s_function = true, .s_comptime = true } },
107+
// .{ "fn foo(comptime y: u32) void { const x = 1; _ = x; }", Scope.Flags{ .s_function = true, .s_comptime = true } },
108+
// };
109+
// try testXDeclScope(cases);
110+
// }
111+
112+
test "comptime scopes" {
113+
const cases = &[_]TestCase{
114+
.{
115+
"const y = { const x = 1; };",
116+
Scope.Flags{ .s_block = true, .s_comptime = true },
117+
},
118+
};
119+
120+
try testXDeclScope(cases);
121+
}

0 commit comments

Comments
 (0)