authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-03-08 10:01:33+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-03-14 07:40:05+00:00
log00969062a9d5e8229737f0ec38a8af91822caf31
tree0ffc03a74a69ad3ea53b48fb72491c453bf62119
parent778ab767b1e367233a1a3284e2c24d2c27b44602
signaturelock-open Commit is signed but in an unrecognized format.

compiler: detect duplicate test names in AstGen

There is no reason to perform this detection during semantic analysis. In fact, doing so is problematic, because we wish to utilize detection of existing decls in a namespace in incremental compilation.

3 files changed, 56 insertions(+), 21 deletions(-)

lib/std/zig/AstGen.zig+49-1
...@@ -13496,6 +13496,15 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast....@@ -13496,6 +13496,15 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.
13496 const node_tags = tree.nodes.items(.tag);13496 const node_tags = tree.nodes.items(.tag);
13497 const main_tokens = tree.nodes.items(.main_token);13497 const main_tokens = tree.nodes.items(.main_token);
13498 const token_tags = tree.tokens.items(.tag);13498 const token_tags = tree.tokens.items(.tag);
13499
13500 // We don't have shadowing for test names, so we just track those for duplicate reporting locally.
13501 var named_tests: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.Node.Index) = .{};
13502 var decltests: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.Node.Index) = .{};
13503 defer {
13504 named_tests.deinit(gpa);
13505 decltests.deinit(gpa);
13506 }
13507
13499 var decl_count: u32 = 0;13508 var decl_count: u32 = 0;
13500 for (members) |member_node| {13509 for (members) |member_node| {
13501 const name_token = switch (node_tags[member_node]) {13510 const name_token = switch (node_tags[member_node]) {
...@@ -13525,11 +13534,50 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast....@@ -13525,11 +13534,50 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.
13525 break :blk ident;13534 break :blk ident;
13526 },13535 },
1352713536
13528 .@"comptime", .@"usingnamespace", .test_decl => {13537 .@"comptime", .@"usingnamespace" => {
13529 decl_count += 1;13538 decl_count += 1;
13530 continue;13539 continue;
13531 },13540 },
1353213541
13542 .test_decl => {
13543 decl_count += 1;
13544 // We don't want shadowing detection here, and test names work a bit differently, so
13545 // we must do the redeclaration detection ourselves.
13546 const test_name_token = main_tokens[member_node] + 1;
13547 switch (token_tags[test_name_token]) {
13548 else => {}, // unnamed test
13549 .string_literal => {
13550 const name = try astgen.strLitAsString(test_name_token);
13551 const gop = try named_tests.getOrPut(gpa, name.index);
13552 if (gop.found_existing) {
13553 const name_slice = astgen.string_bytes.items[@intFromEnum(name.index)..][0..name.len];
13554 const name_duped = try gpa.dupe(u8, name_slice);
13555 defer gpa.free(name_duped);
13556 try astgen.appendErrorNodeNotes(member_node, "duplicate test name '{s}'", .{name_duped}, &.{
13557 try astgen.errNoteNode(gop.value_ptr.*, "other test here", .{}),
13558 });
13559 } else {
13560 gop.value_ptr.* = member_node;
13561 }
13562 },
13563 .identifier => {
13564 const name = try astgen.identAsString(test_name_token);
13565 const gop = try decltests.getOrPut(gpa, name);
13566 if (gop.found_existing) {
13567 const name_slice = mem.span(astgen.nullTerminatedString(name));
13568 const name_duped = try gpa.dupe(u8, name_slice);
13569 defer gpa.free(name_duped);
13570 try astgen.appendErrorNodeNotes(member_node, "duplicate decltest '{s}'", .{name_duped}, &.{
13571 try astgen.errNoteNode(gop.value_ptr.*, "other decltest here", .{}),
13572 });
13573 } else {
13574 gop.value_ptr.* = member_node;
13575 }
13576 },
13577 }
13578 continue;
13579 },
13580
13533 else => continue,13581 else => continue,
13534 };13582 };
1353513583
src/Module.zig+5-18
...@@ -4128,6 +4128,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4128,6 +4128,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4128 .@"comptime" => info: {4128 .@"comptime" => info: {
4129 const i = iter.comptime_index;4129 const i = iter.comptime_index;
4130 iter.comptime_index += 1;4130 iter.comptime_index += 1;
4131 // TODO: avoid collisions with named decls with this name
4131 break :info .{4132 break :info .{
4132 try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}),4133 try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}),
4133 .@"comptime",4134 .@"comptime",
...@@ -4137,6 +4138,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4137,6 +4138,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4137 .@"usingnamespace" => info: {4138 .@"usingnamespace" => info: {
4138 const i = iter.usingnamespace_index;4139 const i = iter.usingnamespace_index;
4139 iter.usingnamespace_index += 1;4140 iter.usingnamespace_index += 1;
4141 // TODO: avoid collisions with named decls with this name
4140 break :info .{4142 break :info .{
4141 try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}),4143 try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}),
4142 .@"usingnamespace",4144 .@"usingnamespace",
...@@ -4146,6 +4148,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4146,6 +4148,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4146 .unnamed_test => info: {4148 .unnamed_test => info: {
4147 const i = iter.unnamed_test_index;4149 const i = iter.unnamed_test_index;
4148 iter.unnamed_test_index += 1;4150 iter.unnamed_test_index += 1;
4151 // TODO: avoid collisions with named decls with this name
4149 break :info .{4152 break :info .{
4150 try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}),4153 try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}),
4151 .@"test",4154 .@"test",
...@@ -4155,6 +4158,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4155,6 +4158,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4155 .decltest => info: {4158 .decltest => info: {
4156 assert(declaration.flags.has_doc_comment);4159 assert(declaration.flags.has_doc_comment);
4157 const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));4160 const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));
4161 // TODO: avoid collisions with named decls with this name
4158 break :info .{4162 break :info .{
4159 try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{name}),4163 try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{name}),
4160 .@"test",4164 .@"test",
...@@ -4162,6 +4166,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4162,6 +4166,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4162 };4166 };
4163 },4167 },
4164 _ => if (declaration.name.isNamedTest(zir)) .{4168 _ => if (declaration.name.isNamedTest(zir)) .{
4169 // TODO: avoid collisions with named decls with this name
4165 try ip.getOrPutStringFmt(gpa, "test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),4170 try ip.getOrPutStringFmt(gpa, "test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),
4166 .@"test",4171 .@"test",
4167 true,4172 true,
...@@ -4226,24 +4231,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4226,24 +4231,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4226 }4231 }
4227 const decl_index = gop.key_ptr.*;4232 const decl_index = gop.key_ptr.*;
4228 const decl = zcu.declPtr(decl_index);4233 const decl = zcu.declPtr(decl_index);
4229 if (kind == .@"test") {
4230 const src_loc = SrcLoc{
4231 .file_scope = decl.getFileScope(zcu),
4232 .parent_decl_node = decl.src_node,
4233 .lazy = .{ .token_offset = 1 },
4234 };
4235 const msg = try ErrorMsg.create(gpa, src_loc, "duplicate test name: {}", .{
4236 decl_name.fmt(ip),
4237 });
4238 errdefer msg.destroy(gpa);
4239 try zcu.failed_decls.putNoClobber(gpa, decl_index, msg);
4240 const other_src_loc = SrcLoc{
4241 .file_scope = namespace.file_scope,
4242 .parent_decl_node = decl_node,
4243 .lazy = .{ .token_offset = 1 },
4244 };
4245 try zcu.errNoteNonLazy(other_src_loc, msg, "other test here", .{});
4246 }
4247 // Update the AST node of the decl; even if its contents are unchanged, it may4234 // Update the AST node of the decl; even if its contents are unchanged, it may
4248 // have been re-ordered.4235 // have been re-ordered.
4249 decl.src_node = decl_node;4236 decl.src_node = decl_node;
test/cases/compile_errors/invalid_duplicate_test_decl_name.zig+2-2
...@@ -6,5 +6,5 @@ test "thingy" {}...@@ -6,5 +6,5 @@ test "thingy" {}
6// target=native6// target=native
7// is_test=true7// is_test=true
8//8//
9// :1:6: error: duplicate test name: test.thingy9// :2:1: error: duplicate test name 'thingy'
10// :2:6: note: other test here10// :1:1: note: other test here