AstGen: pre-scan all decls in a namespace

Also:
 * improve the "ambiguous reference" error by swapping the order of
   "declared here" and "also declared here" notes.
 * improve the "not accessible from inner function" error:
   - point out that it has to do with the thing being mutable
   - eliminate the incorrect association with it being a function
   - note where it crosses a namespace boundary
 * struct field types are evaluated in a context that has the struct
   namespace visible. Likewise with align expressions, linksection
   expressions, enum tag values, and union/enum tag argument
   expressions.

Closes #9194
Closes #9622
This commit is contained in:
Andrew Kelley 2021-08-28 15:15:46 -07:00
parent 05cf44933d
commit 4bb5d17edc
6 changed files with 182 additions and 140 deletions

View File

@ -2883,8 +2883,6 @@ fn fnDecl(
};
const fn_name_str_index = try astgen.identAsString(fn_name_token);
try astgen.declareNewName(scope, fn_name_str_index, decl_node, fn_name_token);
// We insert this at the beginning so that its instruction index marks the
// start of the top level declaration.
const block_inst = try gz.addBlock(.block_inline, fn_proto.ast.proto_node);
@ -3153,8 +3151,6 @@ fn globalVarDecl(
const name_token = var_decl.ast.mut_token + 1;
const name_str_index = try astgen.identAsString(name_token);
try astgen.declareNewName(scope, name_str_index, node, name_token);
var block_scope: GenZir = .{
.parent = scope,
.decl_node_index = node,
@ -3509,9 +3505,11 @@ fn structDeclInner(
};
defer block_scope.instructions.deinit(gpa);
var namespace: Scope.Namespace = .{ .parent = scope };
var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
defer namespace.decls.deinit(gpa);
try astgen.scanDecls(&namespace, container_decl.ast.members);
var wip_decls: WipDecls = .{};
defer wip_decls.deinit(gpa);
@ -3671,7 +3669,7 @@ fn structDeclInner(
const field_type: Zir.Inst.Ref = if (node_tags[member.ast.type_expr] == .@"anytype")
.none
else
try typeExpr(&block_scope, &block_scope.base, member.ast.type_expr);
try typeExpr(&block_scope, &namespace.base, member.ast.type_expr);
fields_data.appendAssumeCapacity(@enumToInt(field_type));
known_has_bits = known_has_bits or nodeImpliesRuntimeBits(tree, member.ast.type_expr);
@ -3687,13 +3685,13 @@ fn structDeclInner(
(@as(u32, @boolToInt(unused)) << 31);
if (have_align) {
const align_inst = try expr(&block_scope, &block_scope.base, align_rl, member.ast.align_expr);
const align_inst = try expr(&block_scope, &namespace.base, align_rl, member.ast.align_expr);
fields_data.appendAssumeCapacity(@enumToInt(align_inst));
}
if (have_value) {
const rl: ResultLoc = if (field_type == .none) .none else .{ .ty = field_type };
const default_inst = try expr(&block_scope, &block_scope.base, rl, member.ast.value_expr);
const default_inst = try expr(&block_scope, &namespace.base, rl, member.ast.value_expr);
fields_data.appendAssumeCapacity(@enumToInt(default_inst));
} else if (member.comptime_token) |comptime_token| {
return astgen.failTok(comptime_token, "comptime field without default initialization value", .{});
@ -3756,7 +3754,7 @@ fn unionDeclInner(
node: ast.Node.Index,
members: []const ast.Node.Index,
layout: std.builtin.TypeInfo.ContainerLayout,
arg_inst: Zir.Inst.Ref,
arg_node: ast.Node.Index,
have_auto_enum: bool,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
@ -3778,9 +3776,16 @@ fn unionDeclInner(
};
defer block_scope.instructions.deinit(gpa);
var namespace: Scope.Namespace = .{ .parent = scope };
var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
defer namespace.decls.deinit(gpa);
try astgen.scanDecls(&namespace, members);
const arg_inst: Zir.Inst.Ref = if (arg_node != 0)
try typeExpr(gz, &namespace.base, arg_node)
else
.none;
var wip_decls: WipDecls = .{};
defer wip_decls.deinit(gpa);
@ -3946,7 +3951,7 @@ fn unionDeclInner(
(@as(u32, @boolToInt(unused)) << 31);
if (have_type and node_tags[member.ast.type_expr] != .@"anytype") {
const field_type = try typeExpr(&block_scope, &block_scope.base, member.ast.type_expr);
const field_type = try typeExpr(&block_scope, &namespace.base, member.ast.type_expr);
fields_data.appendAssumeCapacity(@enumToInt(field_type));
}
if (have_align) {
@ -4046,11 +4051,6 @@ fn containerDecl(
// We must not create any types until Sema. Here the goal is only to generate
// ZIR for all the field types, alignments, and default value expressions.
const arg_inst: Zir.Inst.Ref = if (container_decl.ast.arg != 0)
try comptimeExpr(gz, scope, .{ .ty = .type_type }, container_decl.ast.arg)
else
.none;
switch (token_tags[container_decl.ast.main_token]) {
.keyword_struct => {
const layout = if (container_decl.layout_token) |t| switch (token_tags[t]) {
@ -4059,7 +4059,7 @@ fn containerDecl(
else => unreachable,
} else std.builtin.TypeInfo.ContainerLayout.Auto;
assert(arg_inst == .none);
assert(container_decl.ast.arg == 0);
const result = try structDeclInner(gz, scope, node, container_decl, layout);
return rvalue(gz, rl, result, node);
@ -4073,7 +4073,7 @@ fn containerDecl(
const have_auto_enum = container_decl.ast.enum_token != null;
const result = try unionDeclInner(gz, scope, node, container_decl.ast.members, layout, arg_inst, have_auto_enum);
const result = try unionDeclInner(gz, scope, node, container_decl.ast.members, layout, container_decl.ast.arg, have_auto_enum);
return rvalue(gz, rl, result, node);
},
.keyword_enum => {
@ -4140,7 +4140,7 @@ fn containerDecl(
}
total_fields += 1;
if (member.ast.value_expr != 0) {
if (arg_inst == .none) {
if (container_decl.ast.arg == 0) {
return astgen.failNode(member.ast.value_expr, "value assigned to enum tag with inferred tag type", .{});
}
values += 1;
@ -4159,7 +4159,7 @@ fn containerDecl(
// must be at least one tag.
return astgen.failNode(node, "enum declarations must have at least one tag", .{});
}
if (counts.nonexhaustive_node != 0 and arg_inst == .none) {
if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) {
return astgen.failNodeNotes(
node,
"non-exhaustive enum missing integer tag type",
@ -4189,9 +4189,16 @@ fn containerDecl(
};
defer block_scope.instructions.deinit(gpa);
var namespace: Scope.Namespace = .{ .parent = scope };
var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
defer namespace.decls.deinit(gpa);
try astgen.scanDecls(&namespace, container_decl.ast.members);
const arg_inst: Zir.Inst.Ref = if (container_decl.ast.arg != 0)
try comptimeExpr(gz, &namespace.base, .{ .ty = .type_type }, container_decl.ast.arg)
else
.none;
var wip_decls: WipDecls = .{};
defer wip_decls.deinit(gpa);
@ -4364,7 +4371,7 @@ fn containerDecl(
},
);
}
const tag_value_inst = try expr(&block_scope, &block_scope.base, .{ .ty = arg_inst }, member.ast.value_expr);
const tag_value_inst = try expr(&block_scope, &namespace.base, .{ .ty = arg_inst }, member.ast.value_expr);
fields_data.appendAssumeCapacity(@enumToInt(tag_value_inst));
}
@ -4416,9 +4423,13 @@ fn containerDecl(
return rvalue(gz, rl, indexToRef(decl_inst), node);
},
.keyword_opaque => {
var namespace: Scope.Namespace = .{ .parent = scope };
assert(container_decl.ast.arg == 0);
var namespace: Scope.Namespace = .{ .parent = scope, .node = node };
defer namespace.decls.deinit(gpa);
try astgen.scanDecls(&namespace, container_decl.ast.members);
var wip_decls: WipDecls = .{};
defer wip_decls.deinit(gpa);
@ -6352,73 +6363,68 @@ fn identifier(
// Local variables, including function parameters.
const name_str_index = try astgen.identAsString(ident_token);
{
var s = scope;
var found_already: ?ast.Node.Index = null; // we have found a decl with the same name already
var hit_namespace = false;
while (true) switch (s.tag) {
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
var s = scope;
var found_already: ?ast.Node.Index = null; // we have found a decl with the same name already
var hit_namespace: ast.Node.Index = 0;
while (true) switch (s.tag) {
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (local_val.name == name_str_index) {
local_val.used = true;
// Captures of non-locals need to be emitted as decl_val or decl_ref.
// This *might* be capturable depending on if it is comptime known.
if (!hit_namespace) {
return rvalue(gz, rl, local_val.inst, ident);
}
if (local_val.name == name_str_index) {
local_val.used = true;
// Captures of non-locals need to be emitted as decl_val or decl_ref.
// This *might* be capturable depending on if it is comptime known.
if (hit_namespace == 0) {
return rvalue(gz, rl, local_val.inst, ident);
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (local_ptr.name == name_str_index) {
local_ptr.used = true;
if (hit_namespace) {
if (local_ptr.maybe_comptime)
break
else
return astgen.failNodeNotes(ident, "'{s}' not accessible from inner function", .{ident_name}, &.{
try astgen.errNoteTok(local_ptr.token_src, "declared here", .{}),
// TODO add crossed function definition here note.
// Maybe add a note to the error about it being because of the var,
// maybe recommend copying it into a const variable. -SpexGuy
});
}
switch (rl) {
.ref, .none_or_ref => return local_ptr.ptr,
else => {
const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
return rvalue(gz, rl, loaded, ident);
},
}
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(GenZir).?.parent,
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
// look for ambiguous references to decls
.namespace => {
const ns = s.cast(Scope.Namespace).?;
if (ns.decls.get(name_str_index)) |i| {
if (found_already) |f|
return astgen.failNodeNotes(ident, "ambiguous reference", .{}, &.{
try astgen.errNoteNode(i, "declared here", .{}),
try astgen.errNoteNode(f, "also declared here", .{}),
})
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (local_ptr.name == name_str_index) {
local_ptr.used = true;
if (hit_namespace != 0) {
if (local_ptr.maybe_comptime)
break
else
found_already = i;
return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{
try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}),
try astgen.errNoteNode(hit_namespace, "crosses namespace boundary here", .{}),
});
}
hit_namespace = true;
s = ns.parent;
},
.top => break,
};
}
switch (rl) {
.ref, .none_or_ref => return local_ptr.ptr,
else => {
const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
return rvalue(gz, rl, loaded, ident);
},
}
}
s = local_ptr.parent;
},
.gen_zir => s = s.cast(GenZir).?.parent,
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
.namespace => {
const ns = s.cast(Scope.Namespace).?;
if (ns.decls.get(name_str_index)) |i| {
if (found_already) |f| {
return astgen.failNodeNotes(ident, "ambiguous reference", .{}, &.{
try astgen.errNoteNode(f, "declared here", .{}),
try astgen.errNoteNode(i, "also declared here", .{}),
});
}
// We found a match but must continue looking for ambiguous references to decls.
found_already = i;
}
hit_namespace = ns.node;
s = ns.parent;
},
.top => break,
};
// We can't look up Decls until Sema because the same ZIR code is supposed to be
// used for multiple generic instantiations, and this may refer to a different Decl
// depending on the scope, determined by the generic instantiation.
// Decl references happen by name rather than ZIR index so that when unrelated
// decls are modified, ZIR code containing references to them can be unmodified.
switch (rl) {
.ref, .none_or_ref => return gz.addStrTok(.decl_ref, name_str_index, ident_token),
else => {
@ -8941,6 +8947,7 @@ const Scope = struct {
/// Maps string table index to the source location of declaration,
/// for the purposes of reporting name shadowing compile errors.
decls: std.AutoHashMapUnmanaged(u32, ast.Node.Index) = .{},
node: ast.Node.Index,
};
const Top = struct {
@ -10014,53 +10021,6 @@ fn nullTerminatedString(astgen: AstGen, index: usize) [*:0]const u8 {
return @ptrCast([*:0]const u8, astgen.string_bytes.items.ptr) + index;
}
fn declareNewName(
astgen: *AstGen,
start_scope: *Scope,
name_index: u32,
node: ast.Node.Index,
name_token: ast.TokenIndex,
) !void {
const gpa = astgen.gpa;
const token_bytes = astgen.tree.tokenSlice(name_token);
if (token_bytes[0] != '@' and isPrimitive(token_bytes)) {
return astgen.failTokNotes(name_token, "name shadows primitive '{s}'", .{
token_bytes,
}, &[_]u32{
try astgen.errNoteTok(name_token, "consider using @\"{s}\" to disambiguate", .{
token_bytes,
}),
});
}
var scope = start_scope;
while (true) {
switch (scope.tag) {
.gen_zir => scope = scope.cast(GenZir).?.parent,
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
.defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,
.namespace => {
const ns = scope.cast(Scope.Namespace).?;
const gop = try ns.decls.getOrPut(gpa, name_index);
if (gop.found_existing) {
const name = try gpa.dupe(u8, mem.span(astgen.nullTerminatedString(name_index)));
defer gpa.free(name);
return astgen.failNodeNotes(node, "redeclaration of '{s}'", .{
name,
}, &[_]u32{
try astgen.errNoteNode(gop.value_ptr.*, "other declaration here", .{}),
});
}
gop.value_ptr.* = node;
break;
},
.top => break,
}
}
}
fn isPrimitive(name: []const u8) bool {
if (simple_types.get(name) != null) return true;
if (name.len < 2) return false;
@ -10183,3 +10143,56 @@ fn refToIndex(inst: Zir.Inst.Ref) ?Zir.Inst.Index {
return null;
}
}
fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const ast.Node.Index) !void {
const gpa = astgen.gpa;
const tree = astgen.tree;
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
for (members) |member_node| {
const name_token = switch (node_tags[member_node]) {
.fn_decl,
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> main_tokens[member_node] + 1,
else => continue,
};
const token_bytes = astgen.tree.tokenSlice(name_token);
if (token_bytes[0] != '@' and isPrimitive(token_bytes)) {
switch (astgen.failTokNotes(name_token, "name shadows primitive '{s}'", .{
token_bytes,
}, &[_]u32{
try astgen.errNoteTok(name_token, "consider using @\"{s}\" to disambiguate", .{
token_bytes,
}),
})) {
error.AnalysisFail => continue,
error.OutOfMemory => return error.OutOfMemory,
}
}
const name_str_index = try astgen.identAsString(name_token);
const gop = try namespace.decls.getOrPut(gpa, name_str_index);
if (gop.found_existing) {
const name = try gpa.dupe(u8, mem.span(astgen.nullTerminatedString(name_str_index)));
defer gpa.free(name);
switch (astgen.failNodeNotes(member_node, "redeclaration of '{s}'", .{
name,
}, &[_]u32{
try astgen.errNoteNode(gop.value_ptr.*, "other declaration here", .{}),
})) {
error.AnalysisFail => continue,
error.OutOfMemory => return error.OutOfMemory,
}
}
gop.value_ptr.* = member_node;
}
}

View File

@ -2648,7 +2648,10 @@ pub fn astGenFile(mod: *Module, file: *Scope.File) !void {
undefined;
defer if (data_has_safety_tag) gpa.free(safety_buffer);
const data_ptr = if (data_has_safety_tag)
@ptrCast([*]const u8, safety_buffer.ptr)
if (file.zir.instructions.len == 0)
@as([*]const u8, undefined)
else
@ptrCast([*]const u8, safety_buffer.ptr)
else
@ptrCast([*]const u8, file.zir.instructions.items(.data).ptr);
if (data_has_safety_tag) {

View File

@ -91,7 +91,8 @@ test "type constructed by comptime function call" {
}
fn SimpleList(comptime L: usize) type {
var T = u8;
var mutable_T = u8;
const T = mutable_T;
return struct {
array: [L]T,
};

View File

@ -506,7 +506,7 @@ test "lazy typeInfo value as generic parameter" {
S.foo(@typeInfo(@TypeOf(.{})));
}
fn A() type {
fn ZA() type {
return struct {
b: B(),
@ -520,7 +520,7 @@ fn A() type {
};
}
test "non-ambiguous reference of shadowed decls" {
try expect(A().B().Self != A().Self);
try expect(ZA().B().Self != ZA().Self);
}
test "use of declaration with same name as primitive" {

View File

@ -984,8 +984,8 @@ pub fn addCases(ctx: *TestContext) !void {
\\};
, &.{
":4:17: error: ambiguous reference",
":1:1: note: declared here",
":2:5: note: also declared here",
":2:5: note: declared here",
":1:1: note: also declared here",
});
ctx.compileError("inner func accessing outer var", linux_x64,
@ -999,8 +999,9 @@ pub fn addCases(ctx: *TestContext) !void {
\\ _ = S;
\\}
, &.{
":5:20: error: 'bar' not accessible from inner function",
":2:9: note: declared here",
":5:20: error: mutable 'bar' not accessible from here",
":2:9: note: declared mutable here",
":3:15: note: crosses namespace boundary here",
});
ctx.compileError("global variable redeclaration", linux_x64,

View File

@ -5342,6 +5342,17 @@ pub fn addCases(ctx: *TestContext) !void {
"tmp.zig:2:1: note: declared here",
});
ctx.objErrStage1("local shadows global that occurs later",
\\pub fn main() void {
\\ var foo = true;
\\ _ = foo;
\\}
\\fn foo() void {}
, &[_][]const u8{
"tmp.zig:2:9: error: local shadows declaration of 'foo'",
"tmp.zig:5:1: note: declared here",
});
ctx.objErrStage1("switch expression - missing enumeration prong",
\\const Number = enum {
\\ One,
@ -8814,4 +8825,17 @@ pub fn addCases(ctx: *TestContext) !void {
, &[_][]const u8{
"error: Unsupported OS",
});
ctx.objErrStage1("attempt to close over comptime variable from outer scope",
\\fn SimpleList(comptime L: usize) type {
\\ var T = u8;
\\ return struct {
\\ array: [L]T,
\\ };
\\}
, &[_][]const u8{
"tmp.zig:4:19: error: mutable 'T' not accessible from here",
"tmp.zig:2:9: note: declared mutable here",
"tmp.zig:3:12: note: crosses namespace boundary here",
});
}