diff --git a/src/Module.zig b/src/Module.zig index c64b6cb2ef08fc6459967575a061b22251beb457..01aa6702e039529a4c65ad1b90293c5dc3ca92d9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4083,6 +4083,9 @@ pub fn scanNamespace( const gpa = zcu.gpa; const namespace = zcu.namespacePtr(namespace_index); + var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; + defer seen_decls.deinit(gpa); + try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); try namespace.decls.ensureTotalCapacity(gpa, decls.len); @@ -4090,19 +4093,44 @@ pub fn scanNamespace( .zcu = zcu, .namespace_index = namespace_index, .parent_decl = parent_decl, + .seen_decls = &seen_decls, + .pass = .named, }; for (decls) |decl_inst| { try scanDecl(&scan_decl_iter, decl_inst); } + scan_decl_iter.pass = .unnamed; + for (decls) |decl_inst| { + try scanDecl(&scan_decl_iter, decl_inst); + } } const ScanDeclIter = struct { zcu: *Zcu, namespace_index: Namespace.Index, parent_decl: *Decl, + seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), + /// Decl scanning is run in two passes, so that we can detect when a generated + /// name would clash with an explicit name and use a different one. + pass: enum { named, unnamed }, usingnamespace_index: usize = 0, comptime_index: usize = 0, unnamed_test_index: usize = 0, + + fn avoidNameConflict(iter: *ScanDeclIter, comptime fmt: []const u8, args: anytype) !InternPool.NullTerminatedString { + const zcu = iter.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + var name = try ip.getOrPutStringFmt(gpa, fmt, args); + var gop = try iter.seen_decls.getOrPut(gpa, name); + var next_suffix: u32 = 0; + while (gop.found_existing) { + name = try ip.getOrPutStringFmt(gpa, fmt ++ "_{d}", args ++ .{next_suffix}); + gop = try iter.seen_decls.getOrPut(gpa, name); + next_suffix += 1; + } + return name; + } }; fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void { @@ -4126,54 +4154,63 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void // Every Decl needs a name. const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) { .@"comptime" => info: { + if (iter.pass != .unnamed) return; 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}), + try iter.avoidNameConflict("comptime_{d}", .{i}), .@"comptime", false, }; }, .@"usingnamespace" => info: { + if (iter.pass != .unnamed) return; 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}), + try iter.avoidNameConflict("usingnamespace_{d}", .{i}), .@"usingnamespace", false, }; }, .unnamed_test => info: { + if (iter.pass != .unnamed) return; 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}), + try iter.avoidNameConflict("test_{d}", .{i}), .@"test", false, }; }, .decltest => info: { + // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. + if (iter.pass != .unnamed) return; 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}), + try iter.avoidNameConflict("decltest.{s}", .{name}), .@"test", true, }; }, - _ => 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, - } else .{ - try ip.getOrPutString(gpa, zir.nullTerminatedString(declaration.name.toString(zir).?)), - .named, - false, + _ => if (declaration.name.isNamedTest(zir)) info: { + // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. + if (iter.pass != .unnamed) return; + break :info .{ + try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}), + .@"test", + true, + }; + } else info: { + if (iter.pass != .named) return; + const name = try ip.getOrPutString(gpa, zir.nullTerminatedString(declaration.name.toString(zir).?)); + try iter.seen_decls.putNoClobber(gpa, name, {}); + break :info .{ + name, + .named, + false, + }; }, }; diff --git a/test/cases/compile_errors/comptime_decl_name_conflict_resolved.zig b/test/cases/compile_errors/comptime_decl_name_conflict_resolved.zig new file mode 100644 index 0000000000000000000000000000000000000000..c55b4a275cbcb9df9ab4caf9a30491c0be81d559 --- /dev/null +++ b/test/cases/compile_errors/comptime_decl_name_conflict_resolved.zig @@ -0,0 +1,8 @@ +comptime { + @compileError("should be reached"); +} +const comptime_0 = {}; + +// error +// +// :2:5: error: should be reached