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.
This commit is contained in:
mlugg 2024-03-08 10:01:33 +00:00
parent 778ab767b1
commit 00969062a9
No known key found for this signature in database
GPG Key ID: E60D96B91E86EF9F
3 changed files with 56 additions and 21 deletions

View File

@ -13496,6 +13496,15 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
// We don't have shadowing for test names, so we just track those for duplicate reporting locally.
var named_tests: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.Node.Index) = .{};
var decltests: std.AutoHashMapUnmanaged(Zir.NullTerminatedString, Ast.Node.Index) = .{};
defer {
named_tests.deinit(gpa);
decltests.deinit(gpa);
}
var decl_count: u32 = 0;
for (members) |member_node| {
const name_token = switch (node_tags[member_node]) {
@ -13525,11 +13534,50 @@ fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.
break :blk ident;
},
.@"comptime", .@"usingnamespace", .test_decl => {
.@"comptime", .@"usingnamespace" => {
decl_count += 1;
continue;
},
.test_decl => {
decl_count += 1;
// We don't want shadowing detection here, and test names work a bit differently, so
// we must do the redeclaration detection ourselves.
const test_name_token = main_tokens[member_node] + 1;
switch (token_tags[test_name_token]) {
else => {}, // unnamed test
.string_literal => {
const name = try astgen.strLitAsString(test_name_token);
const gop = try named_tests.getOrPut(gpa, name.index);
if (gop.found_existing) {
const name_slice = astgen.string_bytes.items[@intFromEnum(name.index)..][0..name.len];
const name_duped = try gpa.dupe(u8, name_slice);
defer gpa.free(name_duped);
try astgen.appendErrorNodeNotes(member_node, "duplicate test name '{s}'", .{name_duped}, &.{
try astgen.errNoteNode(gop.value_ptr.*, "other test here", .{}),
});
} else {
gop.value_ptr.* = member_node;
}
},
.identifier => {
const name = try astgen.identAsString(test_name_token);
const gop = try decltests.getOrPut(gpa, name);
if (gop.found_existing) {
const name_slice = mem.span(astgen.nullTerminatedString(name));
const name_duped = try gpa.dupe(u8, name_slice);
defer gpa.free(name_duped);
try astgen.appendErrorNodeNotes(member_node, "duplicate decltest '{s}'", .{name_duped}, &.{
try astgen.errNoteNode(gop.value_ptr.*, "other decltest here", .{}),
});
} else {
gop.value_ptr.* = member_node;
}
},
}
continue;
},
else => continue,
};

View File

@ -4128,6 +4128,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
.@"comptime" => info: {
const i = iter.comptime_index;
iter.comptime_index += 1;
// TODO: avoid collisions with named decls with this name
break :info .{
try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}),
.@"comptime",
@ -4137,6 +4138,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
.@"usingnamespace" => info: {
const i = iter.usingnamespace_index;
iter.usingnamespace_index += 1;
// TODO: avoid collisions with named decls with this name
break :info .{
try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}),
.@"usingnamespace",
@ -4146,6 +4148,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
.unnamed_test => info: {
const i = iter.unnamed_test_index;
iter.unnamed_test_index += 1;
// TODO: avoid collisions with named decls with this name
break :info .{
try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}),
.@"test",
@ -4155,6 +4158,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
.decltest => info: {
assert(declaration.flags.has_doc_comment);
const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));
// TODO: avoid collisions with named decls with this name
break :info .{
try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{name}),
.@"test",
@ -4162,6 +4166,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
};
},
_ => if (declaration.name.isNamedTest(zir)) .{
// TODO: avoid collisions with named decls with this name
try ip.getOrPutStringFmt(gpa, "test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),
.@"test",
true,
@ -4226,24 +4231,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
}
const decl_index = gop.key_ptr.*;
const decl = zcu.declPtr(decl_index);
if (kind == .@"test") {
const src_loc = SrcLoc{
.file_scope = decl.getFileScope(zcu),
.parent_decl_node = decl.src_node,
.lazy = .{ .token_offset = 1 },
};
const msg = try ErrorMsg.create(gpa, src_loc, "duplicate test name: {}", .{
decl_name.fmt(ip),
});
errdefer msg.destroy(gpa);
try zcu.failed_decls.putNoClobber(gpa, decl_index, msg);
const other_src_loc = SrcLoc{
.file_scope = namespace.file_scope,
.parent_decl_node = decl_node,
.lazy = .{ .token_offset = 1 },
};
try zcu.errNoteNonLazy(other_src_loc, msg, "other test here", .{});
}
// Update the AST node of the decl; even if its contents are unchanged, it may
// have been re-ordered.
decl.src_node = decl_node;

View File

@ -6,5 +6,5 @@ test "thingy" {}
// target=native
// is_test=true
//
// :1:6: error: duplicate test name: test.thingy
// :2:6: note: other test here
// :2:1: error: duplicate test name 'thingy'
// :1:1: note: other test here