Commit 8344a50e1c

Andrew Kelley <andrew@ziglang.org>
2021-05-14 08:51:22
AstGen: add compile error for decl name conflicts
* Remove the ability for GenZir parent Scope to be null. Now there is a Top Scope at the top. * Introduce Scope.Namespace to contain a table of decl names in order to emit a compile error for name conflicts. * Fix use of invalid memory when reporting compile errors by duplicating decl names into a temporary heap allocated buffer. * Fix memory leak in while and for loops, not cleaning up their labeled_breaks and store_to_block_ptr_list arrays. * Fix stage2 test cases because now the source location of redundant comptime keyword compile errors is improved. * Implement compile error for local variable shadowing declaration.
1 parent 93896ef
Changed files (3)
src/AstGen.zig
@@ -82,9 +82,11 @@ pub fn generate(gpa: *Allocator, tree: ast.Tree) InnerError!Zir {
     try astgen.extra.ensureTotalCapacity(gpa, tree.nodes.len + reserved_count);
     astgen.extra.items.len += reserved_count;
 
+    var top_scope: Scope.Top = .{};
+
     var gen_scope: GenZir = .{
         .force_comptime = true,
-        .parent = null,
+        .parent = &top_scope.base,
         .anon_name_strategy = .parent,
         .decl_node_index = 0,
         .decl_line = 0,
@@ -1514,7 +1516,7 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) Inn
                     } else if (block_gz.break_block != 0) {
                         break :blk block_gz.break_block;
                     }
-                    scope = block_gz.parent orelse break;
+                    scope = block_gz.parent;
                     continue;
                 };
 
@@ -1545,6 +1547,7 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) Inn
             },
             .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
             .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            .namespace => break,
             .defer_normal => {
                 const defer_scope = scope.cast(Scope.Defer).?;
                 scope = defer_scope.parent;
@@ -1552,6 +1555,7 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) Inn
                 try unusedResultExpr(parent_gz, defer_scope.parent, expr_node);
             },
             .defer_error => scope = scope.cast(Scope.Defer).?.parent,
+            .top => unreachable,
         }
     }
     if (break_label != 0) {
@@ -1576,7 +1580,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index)
                 const gen_zir = scope.cast(GenZir).?;
                 const continue_block = gen_zir.continue_block;
                 if (continue_block == 0) {
-                    scope = gen_zir.parent orelse break;
+                    scope = gen_zir.parent;
                     continue;
                 }
                 if (break_label != 0) blk: {
@@ -1587,7 +1591,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index)
                         }
                     }
                     // found continue but either it has a different label, or no label
-                    scope = gen_zir.parent orelse break;
+                    scope = gen_zir.parent;
                     continue;
                 }
 
@@ -1604,6 +1608,8 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index)
                 try unusedResultExpr(parent_gz, defer_scope.parent, expr_node);
             },
             .defer_error => scope = scope.cast(Scope.Defer).?.parent,
+            .namespace => break,
+            .top => unreachable,
         }
     }
     if (break_label != 0) {
@@ -1664,11 +1670,13 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: ast.Toke
                         });
                     }
                 }
-                scope = gen_zir.parent orelse return;
+                scope = gen_zir.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 => break,
+            .top => unreachable,
         }
     }
 }
@@ -2103,7 +2111,7 @@ fn genDefers(
     var scope = inner_scope;
     while (scope != outer_scope) {
         switch (scope.tag) {
-            .gen_zir => scope = scope.cast(GenZir).?.parent.?,
+            .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 => {
@@ -2119,6 +2127,8 @@ fn genDefers(
                 const expr_node = node_datas[defer_scope.defer_node].rhs;
                 try unusedResultExpr(gz, defer_scope.parent, expr_node);
             },
+            .namespace => unreachable,
+            .top => unreachable,
         }
     }
 }
@@ -2162,12 +2172,14 @@ fn varDecl(
             .local_val => {
                 const local_val = s.cast(Scope.LocalVal).?;
                 if (local_val.name == ident_name) {
+                    const name = try gpa.dupe(u8, mem.spanZ(astgen.nullTerminatedString(ident_name)));
+                    defer gpa.free(name);
                     return astgen.failTokNotes(name_token, "redeclaration of '{s}'", .{
-                        astgen.nullTerminatedString(ident_name),
+                        name,
                     }, &[_]u32{
                         try astgen.errNoteTok(
                             local_val.token_src,
-                            "previous declaration is here",
+                            "previously declared here",
                             .{},
                         ),
                     });
@@ -2177,20 +2189,37 @@ fn varDecl(
             .local_ptr => {
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (local_ptr.name == ident_name) {
+                    const name = try gpa.dupe(u8, mem.spanZ(astgen.nullTerminatedString(ident_name)));
+                    defer gpa.free(name);
                     return astgen.failTokNotes(name_token, "redeclaration of '{s}'", .{
-                        astgen.nullTerminatedString(ident_name),
+                        name,
                     }, &[_]u32{
                         try astgen.errNoteTok(
                             local_ptr.token_src,
-                            "previous declaration is here",
+                            "previously declared here",
                             .{},
                         ),
                     });
                 }
                 s = local_ptr.parent;
             },
-            .gen_zir => s = s.cast(GenZir).?.parent orelse break,
+            .namespace => {
+                const ns = s.cast(Scope.Namespace).?;
+                const decl_node = ns.decls.get(ident_name) orelse {
+                    s = ns.parent;
+                    continue;
+                };
+                const name = try gpa.dupe(u8, mem.spanZ(astgen.nullTerminatedString(ident_name)));
+                defer gpa.free(name);
+                return astgen.failTokNotes(name_token, "local shadows declaration of '{s}'", .{
+                    name,
+                }, &[_]u32{
+                    try astgen.errNoteNode(decl_node, "declared here", .{}),
+                });
+            },
+            .gen_zir => s = s.cast(GenZir).?.parent,
             .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
+            .top => break,
         };
     }
 
@@ -2676,6 +2705,7 @@ const WipDecls = struct {
 fn fnDecl(
     astgen: *AstGen,
     gz: *GenZir,
+    scope: *Scope,
     wip_decls: *WipDecls,
     decl_node: ast.Node.Index,
     body_node: ast.Node.Index,
@@ -2685,6 +2715,13 @@ fn fnDecl(
     const tree = astgen.tree;
     const token_tags = tree.tokens.items(.tag);
 
+    const fn_name_token = fn_proto.name_token orelse {
+        return astgen.failTok(fn_proto.ast.fn_token, "missing function name", .{});
+    };
+    const fn_name_str_index = try astgen.identAsString(fn_name_token);
+
+    try astgen.declareNewName(scope, fn_name_str_index, decl_node);
+
     // 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);
@@ -2693,7 +2730,7 @@ fn fnDecl(
         .force_comptime = true,
         .decl_node_index = fn_proto.ast.proto_node,
         .decl_line = gz.calcLine(decl_node),
-        .parent = &gz.base,
+        .parent = scope,
         .astgen = astgen,
     };
     defer decl_gz.instructions.deinit(gpa);
@@ -2891,11 +2928,6 @@ fn fnDecl(
         });
     };
 
-    const fn_name_token = fn_proto.name_token orelse {
-        return astgen.failTok(fn_proto.ast.fn_token, "missing function name", .{});
-    };
-    const fn_name_str_index = try astgen.identAsString(fn_name_token);
-
     // We add this at the end so that its instruction index marks the end range
     // of the top level declaration.
     _ = try decl_gz.addBreak(.break_inline, block_inst, func_inst);
@@ -2941,6 +2973,8 @@ 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);
+
     var block_scope: GenZir = .{
         .parent = scope,
         .decl_node_index = node,
@@ -3289,6 +3323,9 @@ fn structDeclInner(
     };
     defer block_scope.instructions.deinit(gpa);
 
+    var namespace: Scope.Namespace = .{ .parent = &gz.base };
+    defer namespace.decls.deinit(gpa);
+
     var wip_decls: WipDecls = .{};
     defer wip_decls.deinit(gpa);
 
@@ -3317,14 +3354,14 @@ fn structDeclInner(
                 switch (node_tags[fn_proto]) {
                     .fn_proto_simple => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto_multi => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -3332,14 +3369,14 @@ fn structDeclInner(
                     },
                     .fn_proto_one => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -3350,14 +3387,14 @@ fn structDeclInner(
             },
             .fn_proto_simple => {
                 var params: [1]ast.Node.Index = undefined;
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .fn_proto_multi => {
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3365,14 +3402,14 @@ fn structDeclInner(
             },
             .fn_proto_one => {
                 var params: [1]ast.Node.Index = undefined;
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .fn_proto => {
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3380,28 +3417,28 @@ fn structDeclInner(
             },
 
             .global_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .local_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .simple_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .aligned_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3409,21 +3446,21 @@ fn structDeclInner(
             },
 
             .@"comptime" => {
-                astgen.comptimeDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.comptimeDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .@"usingnamespace" => {
-                astgen.usingnamespaceDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.usingnamespaceDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .test_decl => {
-                astgen.testDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.testDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3547,6 +3584,9 @@ fn unionDeclInner(
     };
     defer block_scope.instructions.deinit(gpa);
 
+    var namespace: Scope.Namespace = .{ .parent = &gz.base };
+    defer namespace.decls.deinit(gpa);
+
     var wip_decls: WipDecls = .{};
     defer wip_decls.deinit(gpa);
 
@@ -3575,14 +3615,14 @@ fn unionDeclInner(
                 switch (node_tags[fn_proto]) {
                     .fn_proto_simple => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto_multi => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -3590,14 +3630,14 @@ fn unionDeclInner(
                     },
                     .fn_proto_one => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -3608,14 +3648,14 @@ fn unionDeclInner(
             },
             .fn_proto_simple => {
                 var params: [1]ast.Node.Index = undefined;
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .fn_proto_multi => {
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3623,14 +3663,14 @@ fn unionDeclInner(
             },
             .fn_proto_one => {
                 var params: [1]ast.Node.Index = undefined;
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .fn_proto => {
-                astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
+                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3638,28 +3678,28 @@ fn unionDeclInner(
             },
 
             .global_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .local_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .simple_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .aligned_var_decl => {
-                astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
+                astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3667,21 +3707,21 @@ fn unionDeclInner(
             },
 
             .@"comptime" => {
-                astgen.comptimeDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.comptimeDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .@"usingnamespace" => {
-                astgen.usingnamespaceDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.usingnamespaceDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
                 continue;
             },
             .test_decl => {
-                astgen.testDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                astgen.testDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                     error.OutOfMemory => return error.OutOfMemory,
                     error.AnalysisFail => {},
                 };
@@ -3940,6 +3980,9 @@ fn containerDecl(
             };
             defer block_scope.instructions.deinit(gpa);
 
+            var namespace: Scope.Namespace = .{ .parent = &gz.base };
+            defer namespace.decls.deinit(gpa);
+
             var wip_decls: WipDecls = .{};
             defer wip_decls.deinit(gpa);
 
@@ -3968,14 +4011,14 @@ fn containerDecl(
                         switch (node_tags[fn_proto]) {
                             .fn_proto_simple => {
                                 var params: [1]ast.Node.Index = undefined;
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
                                 continue;
                             },
                             .fn_proto_multi => {
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
@@ -3983,14 +4026,14 @@ fn containerDecl(
                             },
                             .fn_proto_one => {
                                 var params: [1]ast.Node.Index = undefined;
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
                                 continue;
                             },
                             .fn_proto => {
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
@@ -4001,14 +4044,14 @@ fn containerDecl(
                     },
                     .fn_proto_simple => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto_multi => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4016,14 +4059,14 @@ fn containerDecl(
                     },
                     .fn_proto_one => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4031,28 +4074,28 @@ fn containerDecl(
                     },
 
                     .global_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .local_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .simple_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .aligned_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4060,21 +4103,21 @@ fn containerDecl(
                     },
 
                     .@"comptime" => {
-                        astgen.comptimeDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.comptimeDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .@"usingnamespace" => {
-                        astgen.usingnamespaceDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.usingnamespaceDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .test_decl => {
-                        astgen.testDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.testDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4164,6 +4207,9 @@ fn containerDecl(
             return rvalue(gz, scope, rl, gz.indexToRef(decl_inst), node);
         },
         .keyword_opaque => {
+            var namespace: Scope.Namespace = .{ .parent = &gz.base };
+            defer namespace.decls.deinit(gpa);
+
             var wip_decls: WipDecls = .{};
             defer wip_decls.deinit(gpa);
 
@@ -4179,14 +4225,14 @@ fn containerDecl(
                         switch (node_tags[fn_proto]) {
                             .fn_proto_simple => {
                                 var params: [1]ast.Node.Index = undefined;
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoSimple(&params, fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
                                 continue;
                             },
                             .fn_proto_multi => {
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoMulti(fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
@@ -4194,14 +4240,14 @@ fn containerDecl(
                             },
                             .fn_proto_one => {
                                 var params: [1]ast.Node.Index = undefined;
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProtoOne(&params, fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
                                 continue;
                             },
                             .fn_proto => {
-                                astgen.fnDecl(gz, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
+                                astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, body, tree.fnProto(fn_proto)) catch |err| switch (err) {
                                     error.OutOfMemory => return error.OutOfMemory,
                                     error.AnalysisFail => {},
                                 };
@@ -4212,14 +4258,14 @@ fn containerDecl(
                     },
                     .fn_proto_simple => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoSimple(&params, member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto_multi => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoMulti(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4227,14 +4273,14 @@ fn containerDecl(
                     },
                     .fn_proto_one => {
                         var params: [1]ast.Node.Index = undefined;
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProtoOne(&params, member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .fn_proto => {
-                        astgen.fnDecl(gz, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
+                        astgen.fnDecl(gz, &namespace.base, &wip_decls, member_node, 0, tree.fnProto(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4242,28 +4288,28 @@ fn containerDecl(
                     },
 
                     .global_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.globalVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .local_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.localVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .simple_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.simpleVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .aligned_var_decl => {
-                        astgen.globalVarDecl(gz, scope, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
+                        astgen.globalVarDecl(gz, &namespace.base, &wip_decls, member_node, tree.alignedVarDecl(member_node)) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4271,21 +4317,21 @@ fn containerDecl(
                     },
 
                     .@"comptime" => {
-                        astgen.comptimeDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.comptimeDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .@"usingnamespace" => {
-                        astgen.usingnamespaceDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.usingnamespaceDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
                         continue;
                     },
                     .test_decl => {
-                        astgen.testDecl(gz, scope, &wip_decls, member_node) catch |err| switch (err) {
+                        astgen.testDecl(gz, &namespace.base, &wip_decls, member_node) catch |err| switch (err) {
                             error.OutOfMemory => return error.OutOfMemory,
                             error.AnalysisFail => {},
                         };
@@ -4971,6 +5017,8 @@ fn whileExpr(
     var loop_scope = parent_gz.makeSubBlock(scope);
     loop_scope.setBreakResultLoc(rl);
     defer loop_scope.instructions.deinit(astgen.gpa);
+    defer loop_scope.labeled_breaks.deinit(astgen.gpa);
+    defer loop_scope.labeled_store_to_block_ptr_list.deinit(astgen.gpa);
 
     var continue_scope = parent_gz.makeSubBlock(&loop_scope.base);
     defer continue_scope.instructions.deinit(astgen.gpa);
@@ -5178,6 +5226,8 @@ fn forExpr(
     var loop_scope = parent_gz.makeSubBlock(scope);
     loop_scope.setBreakResultLoc(rl);
     defer loop_scope.instructions.deinit(astgen.gpa);
+    defer loop_scope.labeled_breaks.deinit(astgen.gpa);
+    defer loop_scope.labeled_store_to_block_ptr_list.deinit(astgen.gpa);
 
     var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
     defer cond_scope.instructions.deinit(astgen.gpa);
@@ -6006,8 +6056,9 @@ fn identifier(
                 }
                 s = local_ptr.parent;
             },
-            .gen_zir => s = s.cast(GenZir).?.parent orelse break,
+            .gen_zir => s = s.cast(GenZir).?.parent,
             .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
+            .namespace, .top => break, // TODO look for ambiguous references to decls
         };
     }
 
@@ -7922,6 +7973,8 @@ const Scope = struct {
         local_ptr,
         defer_normal,
         defer_error,
+        namespace,
+        top,
     };
 
     /// This is always a `const` local and importantly the `inst` is a value type, not a pointer.
@@ -7962,6 +8015,23 @@ const Scope = struct {
         parent: *Scope,
         defer_node: ast.Node.Index,
     };
+
+    /// Represents a global scope that has any number of declarations in it.
+    /// Each declaration has this as the parent scope.
+    const Namespace = struct {
+        const base_tag: Tag = .namespace;
+        base: Scope = Scope{ .tag = base_tag },
+
+        parent: *Scope,
+        /// 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) = .{},
+    };
+
+    const Top = struct {
+        const base_tag: Scope.Tag = .top;
+        base: Scope = Scope{ .tag = base_tag },
+    };
 };
 
 /// This is a temporary structure; references to it are valid only
@@ -7979,7 +8049,7 @@ const GenZir = struct {
     decl_node_index: ast.Node.Index,
     /// The containing decl line index, absolute.
     decl_line: u32,
-    parent: ?*Scope,
+    parent: *Scope,
     /// All `GenZir` scopes for the same ZIR share this.
     astgen: *AstGen,
     /// Keeps track of the list of instructions in this scope only. Indexes
@@ -8989,3 +9059,37 @@ const GenZir = struct {
 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,
+) !void {
+    const gpa = astgen.gpa;
+    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.spanZ(astgen.nullTerminatedString(name_index)));
+                    defer gpa.free(name);
+                    return astgen.failNodeNotes(node, "redeclaration of '{s}'", .{
+                        name,
+                    }, &[_]u32{
+                        try astgen.errNoteNode(gop.entry.value, "other declaration here", .{}),
+                    });
+                }
+                gop.entry.value = node;
+                break;
+            },
+            .top => break,
+        }
+    }
+}
test/stage2/test.zig
@@ -1137,7 +1137,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    var a: comptime u32 = 0;
             \\}
         ,
-            &.{":2:21: error: redundant comptime keyword in already comptime scope"},
+            &.{":2:12: error: redundant comptime keyword in already comptime scope"},
         );
         case.addError(
             \\pub export fn _start() void {
@@ -1146,7 +1146,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    }
             \\}
         ,
-            &.{":3:31: error: redundant comptime keyword in already comptime scope"},
+            &.{":3:22: error: redundant comptime keyword in already comptime scope"},
         );
     }
     {
@@ -1195,9 +1195,15 @@ pub fn addCases(ctx: *TestContext) !void {
         \\// dummy comment
         \\fn entry() void {}
         \\fn entry() void {}
+        \\
+        \\fn foo() void {
+        \\    var foo = 1234;
+        \\}
     , &[_][]const u8{
-        ":3:4: error: redeclaration of 'entry'",
+        ":3:1: error: redeclaration of 'entry'",
         ":2:1: note: previously declared here",
+        ":6:9: error: local shadows declaration of 'foo'",
+        ":5:1: note: declared here",
     });
 
     ctx.compileError("global variable redeclaration", linux_x64,
BRANCH_TODO
@@ -1,8 +1,6 @@
  * get stage2 tests passing
    - after the error from an empty file, "has no member main" is invalidated
      but the comptime block incorrectly does not get re-run
-   - redundant comptime wrong source loc
-   - redeclaration of 'foo' not showing 'previously declared here'
    - segfault in one of the tests
    - memory leaks
  * modify stage2 tests so that only 1 uses _start and the rest use