| author | |
| committer | |
| log | aa2b178029fc466f847c0cb663c3b32c7809a357 |
| tree | e8f6a3b8b6b2416aef0476ce67fdceb998ad8f00 |
| parent | c4345991340af5ff2e0155a9832f4eed9ec677fc |
| signature |
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.5 files changed, 56 insertions(+), 5 deletions(-)
lib/std/testing.zig+1-1| ... | @@ -813,7 +813,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe | ... | @@ -813,7 +813,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe |
| 813 | } | 813 | } |
| 814 | }, | 814 | }, |
| 815 | 815 | ||
| 816 | .array => |_| { | 816 | .array => { |
| 817 | if (expected.len != actual.len) { | 817 | if (expected.len != actual.len) { |
| 818 | print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len }); | 818 | print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len }); |
| 819 | return error.TestExpectedEqual; | 819 | return error.TestExpectedEqual; |
lib/std/zig/AstGen.zig+9-3| ... | @@ -7882,8 +7882,10 @@ fn switchExpr( | ... | @@ -7882,8 +7882,10 @@ fn switchExpr( |
| 7882 | var payload_sub_scope: *Scope = undefined; | 7882 | var payload_sub_scope: *Scope = undefined; |
| 7883 | if (mem.eql(u8, ident_slice, "_")) { | 7883 | if (mem.eql(u8, ident_slice, "_")) { |
| 7884 | if (capture_is_ref) { | 7884 | if (capture_is_ref) { |
| 7885 | // |*_, tag| is invalid, so we can fail early | ||
| 7885 | return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); | 7886 | return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); |
| 7886 | } | 7887 | } |
| 7888 | capture = .none; | ||
| 7887 | payload_sub_scope = &case_scope.base; | 7889 | payload_sub_scope = &case_scope.base; |
| 7888 | } else { | 7890 | } else { |
| 7889 | const capture_name = try astgen.identAsString(ident); | 7891 | const capture_name = try astgen.identAsString(ident); |
| ... | @@ -7903,11 +7905,15 @@ fn switchExpr( | ... | @@ -7903,11 +7905,15 @@ fn switchExpr( |
| 7903 | 7905 | ||
| 7904 | const tag_token = if (tree.tokenTag(ident + 1) == .comma) | 7906 | const tag_token = if (tree.tokenTag(ident + 1) == .comma) |
| 7905 | ident + 2 | 7907 | ident + 2 |
| 7906 | else | 7908 | else if (capture == .none) { |
| 7907 | break :blk payload_sub_scope; | 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 | |||
| 7908 | const tag_slice = tree.tokenSlice(tag_token); | 7914 | const tag_slice = tree.tokenSlice(tag_token); |
| 7909 | if (mem.eql(u8, tag_slice, "_")) { | 7915 | 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", .{}); |
| 7911 | } else if (case.inline_token == null) { | 7917 | } else if (case.inline_token == null) { |
| 7912 | return astgen.failTok(tag_token, "tag capture on non-inline prong", .{}); | 7918 | return astgen.failTok(tag_token, "tag capture on non-inline prong", .{}); |
| 7913 | } | 7919 | } |
src/Sema.zig+3-1| ... | @@ -11546,7 +11546,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r | ... | @@ -11546,7 +11546,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11546 | }); | 11546 | }); |
| 11547 | } | 11547 | } |
| 11548 | try sema.validateRuntimeValue(block, operand_src, maybe_ptr); | 11548 | try sema.validateRuntimeValue(block, operand_src, maybe_ptr); |
| 11549 | const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: { | 11549 | const operand_alloc = if (extra.data.bits.any_non_inline_capture or |
| 11550 | extra.data.bits.any_has_tag_capture) | ||
| 11551 | a: { | ||
| 11550 | const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr)); | 11552 | const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr)); |
| 11551 | const operand_alloc = try block.addTy(.alloc, operand_ptr_ty); | 11553 | const operand_alloc = try block.addTy(.alloc, operand_ptr_ty); |
| 11552 | _ = try block.addBinOp(.store, operand_alloc, maybe_ptr); | 11554 | _ = try block.addBinOp(.store, operand_alloc, maybe_ptr); |
test/behavior/switch_loop.zig+27| ... | @@ -270,3 +270,30 @@ test "switch loop on non-exhaustive enum" { | ... | @@ -270,3 +270,30 @@ test "switch loop on non-exhaustive enum" { |
| 270 | try S.doTheTest(); | 270 | try S.doTheTest(); |
| 271 | try comptime S.doTheTest(); | 271 | try comptime S.doTheTest(); |
| 272 | } | 272 | } |
| 273 | |||
| 274 | test "switch loop with discarded tag capture" { | ||
| 275 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | ||
| 276 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; | ||
| 277 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | ||
| 278 | |||
| 279 | const S = struct { | ||
| 280 | const U = union(enum) { | ||
| 281 | a: u32, | ||
| 282 | b: u32, | ||
| 283 | c: u32, | ||
| 284 | }; | ||
| 285 | |||
| 286 | fn doTheTest() void { | ||
| 287 | const a: U = .{ .a = 10 }; | ||
| 288 | blk: switch (a) { | ||
| 289 | inline .b => |_, tag| { | ||
| 290 | _ = tag; | ||
| 291 | continue :blk .{ .c = 20 }; | ||
| 292 | }, | ||
| 293 | else => {}, | ||
| 294 | } | ||
| 295 | } | ||
| 296 | }; | ||
| 297 | S.doTheTest(); | ||
| 298 | comptime S.doTheTest(); | ||
| 299 | } |
test/cases/compile_errors/switch_loop_discarded_capture.zig created+16| ... | @@ -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 | ||