Detects name conflicts for decls and fields, and populates namespace.decls with all named declarations.
fn scanContainer(
astgen: *AstGen,
namespace: *Scope.Namespace,
members: []const Ast.Node.Index,
container_kind: enum
fn scanContainer(
astgen: *AstGen,
namespace: *Scope.Namespace,
members: []const Ast.Node.Index,
container_kind: enum { @"struct", @"union", @"enum", @"opaque" },
) !ScanContainerResult {
const gpa = astgen.gpa;
const tree = astgen.tree;
var any_invalid_declarations = false;
// This type forms a linked list of source tokens declaring the same name.
const NameEntry = struct {
tok: Ast.TokenIndex,
/// Using a linked list here simplifies memory management, and is acceptable since
///ewntries are only allocated in error situations. The entries are allocated into the
/// AstGen arena.
next: ?*@This(),
};
// The maps below are allocated into this BFA to avoid using the GPA for small namespaces.
var bfa_buf: [512]u8 = undefined;
var bfa_state: std.heap.BufferFirstAllocator = .init(&bfa_buf, astgen.gpa);
const bfa = bfa_state.allocator();
var names: std.array_hash_map.Auto(Zir.NullTerminatedString, NameEntry) = .empty;
var test_names: std.array_hash_map.Auto(Zir.NullTerminatedString, NameEntry) = .empty;
var decltest_names: std.array_hash_map.Auto(Zir.NullTerminatedString, NameEntry) = .empty;
defer {
names.deinit(bfa);
test_names.deinit(bfa);
decltest_names.deinit(bfa);
}
var any_duplicates = false;
var decl_count: u32 = 0;
var any_field_aligns = false;
var any_field_values = false;
var any_comptime_fields = false;
var has_underscore_field = false;
for (members) |member_node| {
const Kind = enum { decl, field };
const kind: Kind, const name_token = switch (tree.nodeTag(member_node)) {
.container_field_init,
.container_field_align,
.container_field,
=> blk: {
var full = tree.fullContainerField(member_node).?;
switch (container_kind) {
.@"struct", .@"opaque" => {},
.@"union", .@"enum" => full.convertToNonTupleLike(astgen.tree),
}
if (full.ast.align_expr != .none) any_field_aligns = true;
if (full.ast.value_expr != .none) any_field_values = true;
if (full.comptime_token != null) any_comptime_fields = true;
if (mem.eql(u8, tree.tokenSlice(full.ast.main_token), "_")) has_underscore_field = true;
if (full.ast.tuple_like) continue;
break :blk .{ .field, full.ast.main_token };
},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> blk: {
decl_count += 1;
break :blk .{ .decl, tree.nodeMainToken(member_node) + 1 };
},
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
.fn_decl,
=> blk: {
decl_count += 1;
const ident = tree.nodeMainToken(member_node) + 1;
if (tree.tokenTag(ident) != .identifier) {
try astgen.appendErrorNode(member_node, "missing function name", .{});
any_invalid_declarations = true;
continue;
}
break :blk .{ .decl, ident };
},
.@"comptime" => {
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 = tree.nodeMainToken(member_node) + 1;
const new_ent: NameEntry = .{
.tok = test_name_token,
.next = null,
};
switch (tree.tokenTag(test_name_token)) {
else => {}, // unnamed test
.string_literal => {
const name = try astgen.strLitAsString(test_name_token);
const gop = try test_names.getOrPut(bfa, name.index);
if (gop.found_existing) {
var e = gop.value_ptr;
while (e.next) |n| e = n;
e.next = try astgen.arena.create(NameEntry);
e.next.?.* = new_ent;
any_duplicates = true;
} else {
gop.value_ptr.* = new_ent;
}
},
.identifier => {
const name = try astgen.identAsString(test_name_token);
const gop = try decltest_names.getOrPut(bfa, name);
if (gop.found_existing) {
var e = gop.value_ptr;
while (e.next) |n| e = n;
e.next = try astgen.arena.create(NameEntry);
e.next.?.* = new_ent;
any_duplicates = true;
} else {
gop.value_ptr.* = new_ent;
}
},
}
continue;
},
else => unreachable,
};
const name_str_index = try astgen.identAsString(name_token);
if (kind == .decl) {
// Put the name straight into `decls`, even if there are compile errors.
// This avoids incorrect "undeclared identifier" errors later on.
try namespace.decls.put(gpa, name_str_index, member_node);
}
{
const gop = try names.getOrPut(bfa, name_str_index);
const new_ent: NameEntry = .{
.tok = name_token,
.next = null,
};
if (gop.found_existing) {
var e = gop.value_ptr;
while (e.next) |n| e = n;
e.next = try astgen.arena.create(NameEntry);
e.next.?.* = new_ent;
any_duplicates = true;
continue;
} else {
gop.value_ptr.* = new_ent;
}
}
// For fields, we only needed the duplicate check! Decls have some more checks to do, though.
switch (kind) {
.decl => {},
.field => continue,
}
const token_bytes = astgen.tree.tokenSlice(name_token);
if (token_bytes[0] != '@' and isPrimitive(token_bytes)) {
try astgen.appendErrorTokNotes(name_token, "name shadows primitive '{s}'", .{
token_bytes,
}, &.{
try astgen.errNoteTok(name_token, "consider using @\"{s}\" to disambiguate", .{
token_bytes,
}),
});
any_invalid_declarations = true;
continue;
}
find_scope: switch (namespace.parent.unwrap()) {
.local_val => |local_val| {
if (local_val.name == name_str_index) {
try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{
token_bytes, @tagName(local_val.id_cat),
}, &.{
try astgen.errNoteTok(
local_val.token_src,
"previous declaration here",
.{},
),
});
any_invalid_declarations = true;
break :find_scope;
}
continue :find_scope local_val.parent.unwrap();
},
.local_ptr => |local_ptr| {
if (local_ptr.name == name_str_index) {
try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{
token_bytes, @tagName(local_ptr.id_cat),
}, &.{
try astgen.errNoteTok(
local_ptr.token_src,
"previous declaration here",
.{},
),
});
any_invalid_declarations = true;
break :find_scope;
}
continue :find_scope local_ptr.parent.unwrap();
},
.namespace => |ns| continue :find_scope ns.parent.unwrap(),
.gen_zir => |gen_zir| continue :find_scope gen_zir.parent.unwrap(),
.defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
.top => break :find_scope,
}
}
if (!any_duplicates) {
if (any_invalid_declarations) return error.AnalysisFail;
return .{
.decls_len = decl_count,
.fields_len = @intCast(members.len - decl_count),
.any_field_aligns = any_field_aligns,
.any_field_values = any_field_values,
.any_comptime_fields = any_comptime_fields,
.has_underscore_field = has_underscore_field,
};
}
for (names.keys(), names.values()) |name, first| {
if (first.next == null) continue;
var notes: std.ArrayList(u32) = .empty;
var prev: NameEntry = first;
while (prev.next) |cur| : (prev = cur.*) {
try notes.append(astgen.arena, try astgen.errNoteTok(cur.tok, "duplicate name here", .{}));
}
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate {s} member name '{s}'", .{ @tagName(container_kind), name_duped }, notes.items);
any_invalid_declarations = true;
}
for (test_names.keys(), test_names.values()) |name, first| {
if (first.next == null) continue;
var notes: std.ArrayList(u32) = .empty;
var prev: NameEntry = first;
while (prev.next) |cur| : (prev = cur.*) {
try notes.append(astgen.arena, try astgen.errNoteTok(cur.tok, "duplicate test here", .{}));
}
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate test name '{s}'", .{name_duped}, notes.items);
any_invalid_declarations = true;
}
for (decltest_names.keys(), decltest_names.values()) |name, first| {
if (first.next == null) continue;
var notes: std.ArrayList(u32) = .empty;
var prev: NameEntry = first;
while (prev.next) |cur| : (prev = cur.*) {
try notes.append(astgen.arena, try astgen.errNoteTok(cur.tok, "duplicate decltest here", .{}));
}
try notes.append(astgen.arena, try astgen.errNoteNode(namespace.node, "{s} declared here", .{@tagName(container_kind)}));
const name_duped = try astgen.arena.dupe(u8, mem.span(astgen.nullTerminatedString(name)));
try astgen.appendErrorTokNotes(first.tok, "duplicate decltest '{s}'", .{name_duped}, notes.items);
any_invalid_declarations = true;
}
assert(any_invalid_declarations);
return error.AnalysisFail;
}