Skip to content

Commit aa2ff25

Browse files
committed
disallow switch case capture discards
Previously Zig allowed you to write something like, ```zig switch (x) { .y => |_| { ``` This seems a bit strange because in other cases, such as when capturing the tag in a switch case, ```zig switch (x) { .y => |_, _| { ``` this produces an error. The only usecase I can think of for the previous behaviour is if you wanted to assert that all union payloads are able to coerce, ```zig const X = union(enum) { y: u8, z: f32 }; switch (x) { .y, .z => |_| { ``` This will compile-error with the `|_|` and pass without it. I don't believe this usecase is strong enough to keep the current behaviour; it was never used in the Zig codebase and I cannot find a single usage of this behaviour in the real world, searching through Sourcegraph.
1 parent c4b7caa commit aa2ff25

File tree

5 files changed

+52
-5
lines changed

5 files changed

+52
-5
lines changed

lib/std/testing.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe
801801
}
802802
},
803803

804-
.array => |_| {
804+
.array => {
805805
if (expected.len != actual.len) {
806806
print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
807807
return error.TestExpectedEqual;

lib/std/zig/AstGen.zig

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7882,8 +7882,10 @@ fn switchExpr(
78827882
var payload_sub_scope: *Scope = undefined;
78837883
if (mem.eql(u8, ident_slice, "_")) {
78847884
if (capture_is_ref) {
7885+
// |*_, tag| is invalid, so we can fail early
78857886
return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
78867887
}
7888+
capture = .none;
78877889
payload_sub_scope = &case_scope.base;
78887890
} else {
78897891
const capture_name = try astgen.identAsString(ident);
@@ -7903,11 +7905,15 @@ fn switchExpr(
79037905

79047906
const tag_token = if (tree.tokenTag(ident + 1) == .comma)
79057907
ident + 2
7906-
else
7907-
break :blk payload_sub_scope;
7908+
else if (capture == .none) {
7909+
// discarding the capture is only valid iff the tag is captured
7910+
// whether the tag capture is discarded is handled below
7911+
return astgen.failTok(payload_token, "discard of capture; omit it instead", .{});
7912+
} else break :blk payload_sub_scope;
7913+
79087914
const tag_slice = tree.tokenSlice(tag_token);
79097915
if (mem.eql(u8, tag_slice, "_")) {
7910-
try astgen.appendErrorTok(tag_token, "discard of tag capture; omit it instead", .{});
7916+
return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
79117917
} else if (case.inline_token == null) {
79127918
return astgen.failTok(tag_token, "tag capture on non-inline prong", .{});
79137919
}

src/Sema.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11338,7 +11338,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1133811338
});
1133911339
}
1134011340
try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
11341-
const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {
11341+
const operand_alloc = if (extra.data.bits.any_non_inline_capture or
11342+
extra.data.bits.any_has_tag_capture)
11343+
a: {
1134211344
const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
1134311345
const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
1134411346
_ = try block.addBinOp(.store, operand_alloc, maybe_ptr);

test/behavior/switch_loop.zig

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,26 @@ test "switch loop on non-exhaustive enum" {
273273
try S.doTheTest();
274274
try comptime S.doTheTest();
275275
}
276+
277+
test "switch loop with discarded tag capture" {
278+
const S = struct {
279+
const U = union(enum) {
280+
a: u32,
281+
b: u32,
282+
c: u32,
283+
};
284+
285+
fn doTheTest() void {
286+
const a: U = .{ .a = 10 };
287+
blk: switch (a) {
288+
inline .b => |_, tag| {
289+
_ = tag;
290+
continue :blk .{ .c = 20 };
291+
},
292+
else => {},
293+
}
294+
}
295+
};
296+
S.doTheTest();
297+
comptime S.doTheTest();
298+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export fn foo() void {
2+
const S = struct {
3+
fn doTheTest() void {
4+
blk: switch (@as(u8, 'a')) {
5+
'1' => |_| continue :blk '1',
6+
else => {},
7+
}
8+
}
9+
};
10+
S.doTheTest();
11+
comptime S.doTheTest();
12+
}
13+
14+
// error
15+
//
16+
// :5:25: error: discard of capture; omit it instead

0 commit comments

Comments
 (0)