Skip to content

Commit

Permalink
fix(semantic): bind type annotations on container field declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Nov 28, 2024
1 parent 44cb2c4 commit 6d55522
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 62 deletions.
1 change: 1 addition & 0 deletions src/linter/rules/homeless_try.zig
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub fn runOnNode(_: *const HomelessTry, wrapper: NodeWrapper, ctx: *LinterContex
},
);
}

fn checkFnDecl(ctx: *LinterContext, scope: Scope.Id, try_node: Node.Index) void {
const tags: []const Node.Tag = ctx.ast().nodes.items(.tag);
const main_tokens: []const Ast.TokenIndex = ctx.ast().nodes.items(.main_token);
Expand Down
2 changes: 0 additions & 2 deletions src/linter/rules/snapshots/homeless-try.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
╭─[homeless-try.zig:2:11]
1 │ const std = @import("std");
2const x = try std.heap.page_allocator.alloc(u8, 8);
· ───
· ─┬─
· ╰── there is nowhere to propagate errors to.
╰────
Expand All @@ -23,7 +22,6 @@
╭─[homeless-try.zig:4:17]
3 │ const Bar = struct {
4baz: []u8 = try std.heap.page_allocator.alloc(u8, 8),
· ───
· ─┬─
· ╰── there is nowhere to propagate errors to.
5 │ };
Expand Down
4 changes: 4 additions & 0 deletions src/linter/tester.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const RuleTester = @This();
const SNAPSHOT_DIR = "src/linter/rules/snapshots";

const SnapshotError = fs.Dir.OpenError || fs.Dir.MakeError || fs.Dir.StatFileError || Allocator.Error || fs.File.WriteError;

const TestError = error{
/// Expected no violations, but violations were found.
PassFailed,
Expand Down Expand Up @@ -54,13 +55,15 @@ pub fn setFileName(self: *RuleTester, filename: string) void {
panic("Failed to allocate for filename {s}: {s}", .{ filename, @errorName(e) });
};
}

pub fn withPath(self: *RuleTester, source_dir: string) *RuleTester {
const new_name = fs.path.join(self.alloc, &[_]string{ source_dir, self.filename }) catch @panic("OOM");
self.alloc.free(self.filename);
self.filename = new_name;
return self;
}

/// Add test cases that, when linted, should not produce any diagnostics.
pub fn withPass(self: *RuleTester, comptime pass: []const [:0]const u8) *RuleTester {
self.passes.appendSlice(self.alloc, pass) catch |e| {
const name = self.rule.meta.name;
Expand All @@ -69,6 +72,7 @@ pub fn withPass(self: *RuleTester, comptime pass: []const [:0]const u8) *RuleTes
return self;
}

/// Add test cases that, when linted, should produce diagnostics.
pub fn withFail(self: *RuleTester, comptime fail: []const [:0]const u8) *RuleTester {
self.fails.appendSlice(self.alloc, fail) catch |e| {
const name = self.rule.meta.name;
Expand Down
14 changes: 12 additions & 2 deletions src/semantic/SemanticBuilder.zig
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ fn visitErrorSetDecl(self: *SemanticBuilder, node_id: NodeIndex) !void {
/// ```
fn visitContainerField(self: *SemanticBuilder, node_id: NodeIndex, field: full.ContainerField) !void {
const main_token = self.AST().nodes.items(.main_token)[node_id];
const flags: []const Symbol.Flags = self.symbolTable().symbols.items(.flags);
// main_token points to the field name
// NOTE: container fields are always public
// TODO: record type annotations
Expand All @@ -601,9 +602,18 @@ fn visitContainerField(self: *SemanticBuilder, node_id: NodeIndex, field: full.C
.s_comptime = field.comptime_token != null,
},
});
if (field.ast.value_expr != NULL_NODE) {
try self.visit(field.ast.value_expr);
const parent = self.currentContainerSymbolUnwrap().into(usize);

try self.visit(field.ast.align_expr);
if (!flags[parent].s_enum) {
const prev = self.takeReferenceFlags();
defer self._curr_reference_flags = prev;
self._curr_reference_flags.read = false;
self._curr_reference_flags.write = false;
self._curr_reference_flags.type = true;
try self.visit(field.ast.type_expr);
}
try self.visit(field.ast.value_expr);
}

/// Visit a variable declaration. Global declarations are visited
Expand Down
7 changes: 7 additions & 0 deletions src/semantic/Symbol.zig
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ pub const Flags = packed struct(FLAGS_REPR) {
self.* = @bitCast(a & ~b);
}
}

/// Returns `true` if any flags in `other` are also enabled in `self`.
pub fn intersects(self: Flags, other: Flags) bool {
const a: FLAGS_REPR = @bitCast(self);
const b: FLAGS_REPR = @bitCast(other);
return a & b != 0;
}
};

/// Stores symbols created and referenced within a Zig program.
Expand Down
77 changes: 65 additions & 12 deletions src/semantic/test/symbol_ref_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,36 @@ test "references record where and how a symbol is used" {
try t.expectEqual(ref.flags, Reference.Flags{ .read = true });
}

const TestCase = meta.Tuple(&[_]type{ [:0]const u8, Reference.Flags });
fn testRefsOnX(cases: []const TestCase) !void {
// No symbols declared in these snippets are referenced anywhere else in the
// same snippet.
test "symbols that are never referenced" {
const cases = [_][:0]const u8{
"const x = 1;",
"fn foo(x: u32) u32 { return 1; }",
"const Foo = enum { a, b, c };",
"const Foo = union(enum) { a: u32, b: i32, c: bool };",
};

for (cases) |case| {
var sema = try build(case);
defer sema.deinit();
const references = sema.symbols.symbols.items(.references);
const names = sema.symbols.symbols.items(.name);
var it = sema.symbols.iter();

while (it.next()) |symbol| {
const refs: []const Reference.Id = references[symbol.into(usize)].items;
t.expectEqual(0, refs.len) catch |e| {
print("Symbol: {s}\n", .{names[symbol.into(usize)]});
print("Source: {s}\n\n", .{case});
return e;
};
}
}
}

const RefTestCase = meta.Tuple(&[_]type{ [:0]const u8, Reference.Flags });
fn testRefsOnX(cases: []const RefTestCase) !void {
for (cases) |case| {
const source = case[0];
const expected_flags = case[1];
Expand Down Expand Up @@ -87,7 +115,7 @@ fn testRefsOnX(cases: []const TestCase) !void {
}
}
test "Reference flags - `x` - simple references" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
.{
\\const x = 1;
\\const y = x;
Expand Down Expand Up @@ -120,7 +148,7 @@ test "Reference flags - `x` - simple references" {
}

test "Reference flags - `x` - control flow" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
// if
.{
\\const x = 1;
Expand Down Expand Up @@ -208,7 +236,7 @@ test "Reference flags - `x` - control flow" {
}

test "Reference flags - `x` - try/catch" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
.{
\\fn x() !u32 { return 1; }
\\fn y() u32 {
Expand Down Expand Up @@ -239,7 +267,7 @@ test "Reference flags - `x` - try/catch" {
}

test "Reference flags - `x` - function calls and arguments" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
.{
"fn x() void {}\n fn y() void { x(); }",
.{ .call = true },
Expand Down Expand Up @@ -271,7 +299,7 @@ test "Reference flags - `x` - function calls and arguments" {
}

test "Reference flags - `x` - type annotations" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
.{
"const x = u32; const y: x = 1;",
.{ .type = true },
Expand Down Expand Up @@ -323,16 +351,30 @@ test "Reference flags - `x` - type annotations" {
\\}
\\const y: Foo(x(u32)) = .{ .foo = .{ .bar = 1 } };
,
.{
.type = true,
.call = true,
},
.{ .type = true, .call = true },
},
.{
\\const x = u32;
\\const Foo = struct {
\\ y: x = 1,
\\};
,
.{ .type = true },
},
.{
\\const x = u32;
\\const Foo = union(enum) {
\\ a: i32,
\\ b: x,
\\};
,
.{ .type = true },
},
});
}

test "Reference flags - `x` - indexes and slices" {
try testRefsOnX(&[_]TestCase{
try testRefsOnX(&[_]RefTestCase{
.{
\\fn foo() void {
\\ const x = [_]u32{1, 2, 3};
Expand Down Expand Up @@ -389,6 +431,17 @@ test "Reference flags - `x` - indexes and slices" {
});
}

test "Reference flags - `x` - containers" {
try testRefsOnX(&[_]RefTestCase{
.{
\\const x = 1;
\\const Foo = struct { bar: u32 = x };
,
.{ .read = true },
},
});
}

test "symbols referenced before their declaration" {
const sources = [_][:0]const u8{
\\const y = x;
Expand Down
13 changes: 11 additions & 2 deletions src/semantic/test/util.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");
const mem = std.mem;

const _source = @import("../../source.zig");
const SemanticBuilder = @import("../SemanticBuilder.zig");
const Semantic = @import("../Semantic.zig");
const report = @import("../../reporter.zig");
Expand All @@ -18,16 +19,24 @@ const AnalysisError = error{
pub fn build(src: [:0]const u8) !Semantic {
var r = report.GraphicalReporter.init(std.io.getStdErr().writer(), report.GraphicalFormatter.unicode(t.allocator, false));
var builder = SemanticBuilder.init(t.allocator);
var source = try _source.Source.fromString(
t.allocator,
try t.allocator.dupeZ(u8, src),
try t.allocator.dupe(u8, "test.zig"),
);
defer source.deinit();
builder.withSource(&source);
defer builder.deinit();

var result = builder.build(src) catch |e| {
print("Analysis failed on source:\n\n{s}\n\n", .{src});
return e;
};
errdefer result.value.deinit();
r.reportErrors(result.errors.toManaged(t.allocator));
if (result.hasErrors()) {
print("Analysis failed on source:\n\n{s}\n\n", .{src});
print("Analysis failed.\n", .{});
r.reportErrors(result.errors.toManaged(t.allocator));
print("\nSource:\n\n{s}\n\n", .{src});
return error.AnalysisFailed;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"scope": semantic.id.NominalId(u32)(1),
"flags": ["const", "fn_param"],
"references": [
{"symbol":2,"scope":3,"node":"identifier","identifier":"T","flags":["type"]},
{"symbol":2,"scope":4,"node":"identifier","identifier":"T","flags":["type"]},

],
Expand Down Expand Up @@ -123,6 +124,7 @@
"scope": semantic.id.NominalId(u32)(6),
"flags": ["comptime", "const", "fn_param"],
"references": [
{"symbol":8,"scope":11,"node":"identifier","identifier":"size","flags":["type"]},

],
"members": [],
Expand Down
Loading

0 comments on commit 6d55522

Please sign in to comment.