diff --git a/src/astgen.zig b/src/astgen.zig index 4631e46b5db4..6c697897aa5f 100644 --- a/src/astgen.zig +++ b/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); } } diff --git a/test/stage2/test.zig b/test/stage2/test.zig index cb5e9bebbb81..8f05c0fd23a3 100644 --- a/test/stage2/test.zig +++ b/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", + }); } {