Commit 8deb21c58a

Andrew Kelley <andrew@ziglang.org>
2021-01-17 08:14:24
stage2: add compile error for label redefinition
Also fix incorrectly destroying notes. This work is based on Vexu's patch in #7555.
1 parent 629d3be
Changed files (2)
src
test
stage2
src/astgen.zig
@@ -442,6 +442,49 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
     try blockExprStmts(mod, parent_scope, &block_node.base, block_node.statements());
 }
 
+fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void {
+    // Look for the label in the scope.
+    var scope = parent_scope;
+    while (true) {
+        switch (scope.tag) {
+            .gen_zir => {
+                const gen_zir = scope.cast(Scope.GenZIR).?;
+                if (gen_zir.label) |prev_label| {
+                    if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) {
+                        const tree = parent_scope.tree();
+                        const label_src = tree.token_locs[label].start;
+                        const prev_label_src = tree.token_locs[prev_label.token].start;
+
+                        const label_name = try mod.identifierTokenString(parent_scope, label);
+                        const msg = msg: {
+                            const msg = try mod.errMsg(
+                                parent_scope,
+                                label_src,
+                                "redefinition of label '{s}'",
+                                .{label_name},
+                            );
+                            errdefer msg.destroy(mod.gpa);
+                            try mod.errNote(
+                                parent_scope,
+                                prev_label_src,
+                                msg,
+                                "previous definition is here",
+                                .{},
+                            );
+                            break :msg msg;
+                        };
+                        return mod.failWithOwnedErrorMsg(parent_scope, msg);
+                    }
+                }
+                scope = gen_zir.parent;
+            },
+            .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
+            .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+            else => return,
+        }
+    }
+}
+
 fn labeledBlockExpr(
     mod: *Module,
     parent_scope: *Scope,
@@ -457,6 +500,8 @@ fn labeledBlockExpr(
     const tree = parent_scope.tree();
     const src = tree.token_locs[block_node.lbrace].start;
 
+    try checkLabelRedefinition(mod, parent_scope, block_node.label);
+
     // Create the Block ZIR instruction so that we can put it into the GenZIR struct
     // so that break statements can reference it.
     const gen_zir = parent_scope.getGenZIR();
@@ -560,14 +605,30 @@ fn varDecl(
             .local_val => {
                 const local_val = s.cast(Scope.LocalVal).?;
                 if (mem.eql(u8, local_val.name, ident_name)) {
-                    return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
+                    const msg = msg: {
+                        const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
+                            ident_name,
+                        });
+                        errdefer msg.destroy(mod.gpa);
+                        try mod.errNote(scope, local_val.inst.src, msg, "previous definition is here", .{});
+                        break :msg msg;
+                    };
+                    return mod.failWithOwnedErrorMsg(scope, msg);
                 }
                 s = local_val.parent;
             },
             .local_ptr => {
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (mem.eql(u8, local_ptr.name, ident_name)) {
-                    return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
+                    const msg = msg: {
+                        const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
+                            ident_name,
+                        });
+                        errdefer msg.destroy(mod.gpa);
+                        try mod.errNote(scope, local_ptr.ptr.src, msg, "previous definition is here", .{});
+                        break :msg msg;
+                    };
+                    return mod.failWithOwnedErrorMsg(scope, msg);
                 }
                 s = local_ptr.parent;
             },
@@ -1166,8 +1227,10 @@ fn orelseCatchExpr(
     return rlWrapPtr(mod, scope, rl, &block.base);
 }
 
-/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
-/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used.
+/// Return whether the identifier names of two tokens are equal. Resolves @""
+/// tokens without allocating.
+/// OK in theory it could do it without allocating. This implementation
+/// allocates when the @"" form is used.
 fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
     const ident_name_1 = try mod.identifierTokenString(scope, token1);
     const ident_name_2 = try mod.identifierTokenString(scope, token2);
@@ -1514,6 +1577,10 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
         }
     }
 
+    if (while_node.label) |label| {
+        try checkLabelRedefinition(mod, scope, label);
+    }
+
     if (while_node.inline_token) |tok|
         return mod.failTok(scope, tok, "TODO inline while", .{});
 
@@ -1649,7 +1716,16 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
     return &while_block.base;
 }
 
-fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) InnerError!*zir.Inst {
+fn forExpr(
+    mod: *Module,
+    scope: *Scope,
+    rl: ResultLoc,
+    for_node: *ast.Node.For,
+) InnerError!*zir.Inst {
+    if (for_node.label) |label| {
+        try checkLabelRedefinition(mod, scope, label);
+    }
+
     if (for_node.inline_token) |tok|
         return mod.failTok(scope, tok, "TODO inline for", .{});
 
@@ -1928,14 +2004,17 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
         // Check for else/_ prong, those are handled last.
         if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
             if (else_src) |src| {
-                const msg = try mod.errMsg(
-                    scope,
-                    case_src,
-                    "multiple else prongs in switch expression",
-                    .{},
-                );
-                errdefer msg.destroy(mod.gpa);
-                try mod.errNote(scope, src, msg, "previous else prong is here", .{});
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        case_src,
+                        "multiple else prongs in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(mod.gpa);
+                    try mod.errNote(scope, src, msg, "previous else prong is here", .{});
+                    break :msg msg;
+                };
                 return mod.failWithOwnedErrorMsg(scope, msg);
             }
             else_src = case_src;
@@ -1945,14 +2024,17 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
             mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
         {
             if (underscore_src) |src| {
-                const msg = try mod.errMsg(
-                    scope,
-                    case_src,
-                    "multiple '_' prongs in switch expression",
-                    .{},
-                );
-                errdefer msg.destroy(mod.gpa);
-                try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        case_src,
+                        "multiple '_' prongs in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(mod.gpa);
+                    try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
+                    break :msg msg;
+                };
                 return mod.failWithOwnedErrorMsg(scope, msg);
             }
             underscore_src = case_src;
@@ -1962,15 +2044,18 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
 
         if (else_src) |some_else| {
             if (underscore_src) |some_underscore| {
-                const msg = try mod.errMsg(
-                    scope,
-                    switch_src,
-                    "else and '_' prong in switch expression",
-                    .{},
-                );
-                errdefer msg.destroy(mod.gpa);
-                try mod.errNote(scope, some_else, msg, "else prong is here", .{});
-                try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
+                const msg = msg: {
+                    const msg = try mod.errMsg(
+                        scope,
+                        switch_src,
+                        "else and '_' prong in switch expression",
+                        .{},
+                    );
+                    errdefer msg.destroy(mod.gpa);
+                    try mod.errNote(scope, some_else, msg, "else prong is here", .{});
+                    try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
+                    break :msg msg;
+                };
                 return mod.failWithOwnedErrorMsg(scope, msg);
             }
         }
test/stage2/test.zig
@@ -1234,7 +1234,10 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    var i: u32 = 10;
             \\    unreachable;
             \\}
-        , &[_][]const u8{":3:9: error: redefinition of 'i'"});
+        , &[_][]const u8{
+            ":3:9: error: redefinition of 'i'",
+            ":2:9: note: previous definition is here",
+        });
         case.addError(
             \\var testing: i64 = 10;
             \\export fn _start() noreturn {
@@ -1409,6 +1412,14 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    foo: for ("foo") |_| {}
             \\}
         , &[_][]const u8{":2:5: error: unused for label"});
+        case.addError(
+            \\comptime {
+            \\    blk: {blk: {}}
+            \\}
+        , &[_][]const u8{
+            ":2:11: error: redefinition of label 'blk'",
+            ":2:5: note: previous definition is here",
+        });
     }
 
     {