diff --git a/src/linter/rules/homeless_try.zig b/src/linter/rules/homeless_try.zig index d6cffdb..5cc8ba3 100644 --- a/src/linter/rules/homeless_try.zig +++ b/src/linter/rules/homeless_try.zig @@ -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); diff --git a/src/linter/rules/snapshots/homeless-try.snap b/src/linter/rules/snapshots/homeless-try.snap index 788d1c7..f5a971c 100644 --- a/src/linter/rules/snapshots/homeless-try.snap +++ b/src/linter/rules/snapshots/homeless-try.snap @@ -14,7 +14,6 @@ ╭─[homeless-try.zig:2:11] 1 │ const std = @import("std"); 2 │ const x = try std.heap.page_allocator.alloc(u8, 8); - · ─── · ─┬─ · ╰── there is nowhere to propagate errors to. ╰──── @@ -23,7 +22,6 @@ ╭─[homeless-try.zig:4:17] 3 │ const Bar = struct { 4 │ baz: []u8 = try std.heap.page_allocator.alloc(u8, 8), - · ─── · ─┬─ · ╰── there is nowhere to propagate errors to. 5 │ }; diff --git a/src/linter/tester.zig b/src/linter/tester.zig index c4e35b6..129673b 100644 --- a/src/linter/tester.zig +++ b/src/linter/tester.zig @@ -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, @@ -54,6 +55,7 @@ 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); @@ -61,6 +63,7 @@ pub fn withPath(self: *RuleTester, source_dir: string) *RuleTester { 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; @@ -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; diff --git a/src/semantic/SemanticBuilder.zig b/src/semantic/SemanticBuilder.zig index 0e41c71..3bcae48 100644 --- a/src/semantic/SemanticBuilder.zig +++ b/src/semantic/SemanticBuilder.zig @@ -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 @@ -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 diff --git a/src/semantic/Symbol.zig b/src/semantic/Symbol.zig index 406f4b2..1f4a591 100644 --- a/src/semantic/Symbol.zig +++ b/src/semantic/Symbol.zig @@ -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. diff --git a/src/semantic/test/symbol_ref_test.zig b/src/semantic/test/symbol_ref_test.zig index 94651b4..f63df56 100644 --- a/src/semantic/test/symbol_ref_test.zig +++ b/src/semantic/test/symbol_ref_test.zig @@ -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]; @@ -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; @@ -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; @@ -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 { @@ -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 }, @@ -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 }, @@ -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}; @@ -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; diff --git a/src/semantic/test/util.zig b/src/semantic/test/util.zig index 2d2d834..7298702 100644 --- a/src/semantic/test/util.zig +++ b/src/semantic/test/util.zig @@ -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"); @@ -18,6 +19,13 @@ 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| { @@ -25,9 +33,10 @@ pub fn build(src: [:0]const u8) !Semantic { 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; } diff --git a/test/snapshots/snapshot-coverage/simple/pass/fn_comptime.zig.snap b/test/snapshots/snapshot-coverage/simple/pass/fn_comptime.zig.snap index 8c5092e..8032445 100644 --- a/test/snapshots/snapshot-coverage/simple/pass/fn_comptime.zig.snap +++ b/test/snapshots/snapshot-coverage/simple/pass/fn_comptime.zig.snap @@ -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"]}, ], @@ -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": [], diff --git a/test/snapshots/snapshot-coverage/simple/pass/writer_interface.zig.snap b/test/snapshots/snapshot-coverage/simple/pass/writer_interface.zig.snap index f54b083..78118ba 100644 --- a/test/snapshots/snapshot-coverage/simple/pass/writer_interface.zig.snap +++ b/test/snapshots/snapshot-coverage/simple/pass/writer_interface.zig.snap @@ -22,11 +22,11 @@ "scope": semantic.id.NominalId(u32)(0), "flags": ["variable", "const", "struct"], "references": [ - {"symbol":1,"scope":2,"node":"identifier","identifier":"Writer","flags":["type"]}, - {"symbol":1,"scope":7,"node":"identifier","identifier":"Writer","flags":["read"]}, + {"symbol":1,"scope":3,"node":"identifier","identifier":"Writer","flags":["type"]}, + {"symbol":1,"scope":8,"node":"identifier","identifier":"Writer","flags":["read"]}, ], - "members": [2,3,4,6,7,8,13], + "members": [2,3,6,8,9,10,15], "exports": [], }, @@ -57,6 +57,34 @@ "members": [], "exports": [], + }, + { + "name": "ptr", + "debugName": "", + "token": 16, + "declNode": "ptr_type_aligned", + "scope": semantic.id.NominalId(u32)(2), + "flags": ["const", "fn_param", "struct"], + "references": [ + + ], + "members": [], + "exports": [], + + }, + { + "name": "data", + "debugName": "", + "token": 21, + "declNode": "ptr_type_aligned", + "scope": semantic.id.NominalId(u32)(2), + "flags": ["const", "fn_param", "struct"], + "references": [ + + ], + "members": [], + "exports": [], + }, { "name": "init", @@ -77,11 +105,11 @@ "debugName": "", "token": 35, "declNode": "root", - "scope": semantic.id.NominalId(u32)(2), + "scope": semantic.id.NominalId(u32)(3), "flags": ["const", "fn_param"], "references": [ - {"symbol":5,"scope":3,"node":"identifier","identifier":"ptr","flags":["read"]}, - {"symbol":5,"scope":3,"node":"identifier","identifier":"ptr","flags":["read"]}, + {"symbol":7,"scope":4,"node":"identifier","identifier":"ptr","flags":["read"]}, + {"symbol":7,"scope":4,"node":"identifier","identifier":"ptr","flags":["read"]}, ], "members": [], @@ -93,11 +121,11 @@ "debugName": "", "token": 42, "declNode": "simple_var_decl", - "scope": semantic.id.NominalId(u32)(3), + "scope": semantic.id.NominalId(u32)(4), "flags": ["variable", "const"], "references": [ - {"symbol":6,"scope":3,"node":"identifier","identifier":"T","flags":["read"]}, - {"symbol":6,"scope":6,"node":"identifier","identifier":"T","flags":["type"]}, + {"symbol":8,"scope":4,"node":"identifier","identifier":"T","flags":["read"]}, + {"symbol":8,"scope":7,"node":"identifier","identifier":"T","flags":["type"]}, ], "members": [], @@ -109,10 +137,10 @@ "debugName": "", "token": 50, "declNode": "simple_var_decl", - "scope": semantic.id.NominalId(u32)(3), + "scope": semantic.id.NominalId(u32)(4), "flags": ["variable", "const"], "references": [ - {"symbol":7,"scope":6,"node":"identifier","identifier":"ptr_info","flags":["call"]}, + {"symbol":9,"scope":7,"node":"identifier","identifier":"ptr_info","flags":["call"]}, ], "members": [], @@ -124,13 +152,13 @@ "debugName": "", "token": 58, "declNode": "simple_var_decl", - "scope": semantic.id.NominalId(u32)(3), + "scope": semantic.id.NominalId(u32)(4), "flags": ["variable", "const", "struct"], "references": [ - {"symbol":8,"scope":3,"node":"identifier","identifier":"gen","flags":["read"]}, + {"symbol":10,"scope":4,"node":"identifier","identifier":"gen","flags":["read"]}, ], - "members": [9,12], + "members": [11,14], "exports": [], }, @@ -139,7 +167,7 @@ "debugName": "", "token": 64, "declNode": "fn_decl", - "scope": semantic.id.NominalId(u32)(4), + "scope": semantic.id.NominalId(u32)(5), "flags": ["fn"], "references": [ @@ -153,10 +181,10 @@ "debugName": "", "token": 66, "declNode": "ptr_type_aligned", - "scope": semantic.id.NominalId(u32)(5), + "scope": semantic.id.NominalId(u32)(6), "flags": ["const", "fn_param"], "references": [ - {"symbol":10,"scope":6,"node":"identifier","identifier":"pointer","flags":["read"]}, + {"symbol":12,"scope":7,"node":"identifier","identifier":"pointer","flags":["read"]}, ], "members": [], @@ -168,10 +196,10 @@ "debugName": "", "token": 71, "declNode": "ptr_type_aligned", - "scope": semantic.id.NominalId(u32)(5), + "scope": semantic.id.NominalId(u32)(6), "flags": ["const", "fn_param"], "references": [ - {"symbol":11,"scope":6,"node":"identifier","identifier":"data","flags":["read"]}, + {"symbol":13,"scope":7,"node":"identifier","identifier":"data","flags":["read"]}, ], "members": [], @@ -183,10 +211,10 @@ "debugName": "", "token": 83, "declNode": "simple_var_decl", - "scope": semantic.id.NominalId(u32)(6), + "scope": semantic.id.NominalId(u32)(7), "flags": ["variable", "const"], "references": [ - {"symbol":12,"scope":6,"node":"identifier","identifier":"self","flags":["read"]}, + {"symbol":14,"scope":7,"node":"identifier","identifier":"self","flags":["read"]}, ], "members": [], @@ -212,11 +240,11 @@ "debugName": "", "token": 134, "declNode": "identifier", - "scope": semantic.id.NominalId(u32)(7), + "scope": semantic.id.NominalId(u32)(8), "flags": ["const", "fn_param"], "references": [ - {"symbol":14,"scope":8,"node":"identifier","identifier":"self","flags":["call"]}, - {"symbol":14,"scope":8,"node":"identifier","identifier":"self","flags":["read"]}, + {"symbol":16,"scope":9,"node":"identifier","identifier":"self","flags":["call"]}, + {"symbol":16,"scope":9,"node":"identifier","identifier":"self","flags":["read"]}, ], "members": [], @@ -228,10 +256,10 @@ "debugName": "", "token": 138, "declNode": "ptr_type_aligned", - "scope": semantic.id.NominalId(u32)(7), + "scope": semantic.id.NominalId(u32)(8), "flags": ["const", "fn_param"], "references": [ - {"symbol":15,"scope":8,"node":"identifier","identifier":"data","flags":["read"]}, + {"symbol":17,"scope":9,"node":"identifier","identifier":"data","flags":["read"]}, ], "members": [], @@ -255,8 +283,8 @@ "bindings": { "ptr": 2, "writeAllFn": 3, - "init": 4, - "writeAll": 13, + "init": 6, + "writeAll": 15, }, "children": [ @@ -264,42 +292,52 @@ "id": semantic.id.NominalId(u32)(2), "flags": ["function"], "bindings": { - "ptr": 5, + "ptr": 4, + "data": 5, + + }, + "children": [], + + }, { + "id": semantic.id.NominalId(u32)(3), + "flags": ["function"], + "bindings": { + "ptr": 7, }, "children": [ { - "id": semantic.id.NominalId(u32)(3), + "id": semantic.id.NominalId(u32)(4), "flags": ["function", "block"], "bindings": { - "T": 6, - "ptr_info": 7, - "gen": 8, + "T": 8, + "ptr_info": 9, + "gen": 10, }, "children": [ { - "id": semantic.id.NominalId(u32)(4), + "id": semantic.id.NominalId(u32)(5), "flags": ["struct", "block"], "bindings": { - "writeAll": 9, + "writeAll": 11, }, "children": [ { - "id": semantic.id.NominalId(u32)(5), + "id": semantic.id.NominalId(u32)(6), "flags": ["function"], "bindings": { - "pointer": 10, - "data": 11, + "pointer": 12, + "data": 13, }, "children": [ { - "id": semantic.id.NominalId(u32)(6), + "id": semantic.id.NominalId(u32)(7), "flags": ["function", "block"], "bindings": { - "self": 12, + "self": 14, }, "children": [], @@ -317,16 +355,16 @@ ], }, { - "id": semantic.id.NominalId(u32)(7), + "id": semantic.id.NominalId(u32)(8), "flags": ["function"], "bindings": { - "self": 14, - "data": 15, + "self": 16, + "data": 17, }, "children": [ { - "id": semantic.id.NominalId(u32)(8), + "id": semantic.id.NominalId(u32)(9), "flags": ["function", "block"], "bindings": {