commit fe870418b1d83ad4e08a8fba9785289735d29f4f (tree)
parent 7116b02210bcae7bafac9017a7e47a14b52eafc2
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 16 Jan 2024 02:42:06 -0800
Merge pull request #18584 from Techatrix/fix-switch-on-err
fix ast gen failure to catch incorrect by ref error captures
Diffstat:
5 files changed, 88 insertions(+), 19 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
@@ -841,13 +841,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
.@"if",
=> {
const if_full = tree.fullIf(node).?;
- if (if_full.error_token) |error_token| {
- const tag = node_tags[if_full.ast.else_expr];
- if ((tag == .@"switch" or tag == .switch_comma) and
- std.mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(error_token + 4)))
- {
- return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
+ no_switch_on_err: {
+ const error_token = if_full.error_token orelse break :no_switch_on_err;
+ switch (node_tags[if_full.ast.else_expr]) {
+ .@"switch", .switch_comma => {},
+ else => break :no_switch_on_err,
}
+ const switch_operand = node_datas[if_full.ast.else_expr].lhs;
+ if (node_tags[switch_operand] != .identifier) break :no_switch_on_err;
+ if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(main_tokens[switch_operand]))) break :no_switch_on_err;
+ return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
}
return ifExpr(gz, scope, ri.br(), node, if_full);
},
@@ -1026,16 +1029,21 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
},
.@"catch" => {
const catch_token = main_tokens[node];
- const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) blk: {
- if (token_tags.len > catch_token + 6 and
- token_tags[catch_token + 4] == .keyword_switch)
- {
- if (std.mem.eql(u8, tree.tokenSlice(catch_token + 2), tree.tokenSlice(catch_token + 6))) {
- return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
- }
+ const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe)
+ catch_token + 2
+ else
+ null;
+ no_switch_on_err: {
+ const capture_token = payload_token orelse break :no_switch_on_err;
+ switch (node_tags[node_datas[node].rhs]) {
+ .@"switch", .switch_comma => {},
+ else => break :no_switch_on_err,
}
- break :blk catch_token + 2;
- } else null;
+ const switch_operand = node_datas[node_datas[node].rhs].lhs;
+ if (node_tags[switch_operand] != .identifier) break :no_switch_on_err;
+ if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(main_tokens[switch_operand]))) break :no_switch_on_err;
+ return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
+ }
switch (ri.rl) {
.ref, .ref_coerced_ty => return orelseCatchExpr(
gz,
@@ -7219,7 +7227,9 @@ fn switchExprErrUnion(
};
const capture_token = case.payload_token orelse break :blk &err_scope.base;
- assert(token_tags[capture_token] == .identifier);
+ if (token_tags[capture_token] != .identifier) {
+ return astgen.failTok(capture_token + 1, "error set cannot be captured by reference", .{});
+ }
const capture_slice = tree.tokenSlice(capture_token);
if (mem.eql(u8, capture_slice, "_")) {
diff --git a/test/cases/compile_errors/error_set_cannot_capture_by_reference.zig b/test/cases/compile_errors/error_set_cannot_capture_by_reference.zig
@@ -0,0 +1,15 @@
+export fn entry() void {
+ const err: error{Foo} = error.Foo;
+
+ switch (err) {
+ error.Foo => |*foo| {
+ foo catch {};
+ },
+ }
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :5:23: error: error set cannot be captured by reference
diff --git a/test/cases/compile_errors/switch_on_error_union_discard.zig b/test/cases/compile_errors/switch_on_error_union_discard.zig
@@ -1,10 +1,8 @@
export fn entry() void {
const x: error{}!u32 = 0;
- if (x) |v| v else |_| switch (_) {
- }
+ if (x) |v| v else |_| switch (_) {}
}
-
// error
// backend=stage2
// target=native
diff --git a/test/cases/compile_errors/switch_on_error_with_capture_by_reference.zig b/test/cases/compile_errors/switch_on_error_with_capture_by_reference.zig
@@ -0,0 +1,24 @@
+comptime {
+ const e: error{Foo}!u32 = error.Foo;
+ e catch |err| switch (err) {
+ error.Foo => |*foo| {
+ foo catch {};
+ },
+ };
+}
+
+comptime {
+ const e: error{Foo}!u32 = error.Foo;
+ if (e) {} else |err| switch (err) {
+ error.Foo => |*foo| {
+ foo catch {};
+ },
+ }
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :4:24: error: error set cannot be captured by reference
+// :13:24: error: error set cannot be captured by reference
diff --git a/test/cases/compile_errors/switch_on_error_with_non_trivial_switch_operand.zig b/test/cases/compile_errors/switch_on_error_with_non_trivial_switch_operand.zig
@@ -0,0 +1,22 @@
+export fn entry1() void {
+ var x: error{Foo}!u32 = 0;
+ _ = &x;
+ if (x) |_| {} else |err| switch (err + 1) {
+ else => {},
+ }
+}
+
+export fn entry2() void {
+ var x: error{Foo}!u32 = 0;
+ _ = &x;
+ _ = x catch |err| switch (err + 1) {
+ else => {},
+ };
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :4:42: error: invalid operands to binary expression: 'ErrorSet' and 'ComptimeInt'
+// :12:35: error: invalid operands to binary expression: 'ErrorSet' and 'ComptimeInt'