Commit 84c2c47fae

Andrew Kelley <andrew@ziglang.org>
2022-01-18 04:45:55
Sema: implement else capture value
The ZIR instructions `switch_capture_else` and `switch_capture_ref` are removed because they are not needed. Instead, the prong index is set to max int for the special prong. Else prong with error sets is not handled yet. Adds a new behavior test because there was not a prior on to cover only the capture value of else on a switch.
1 parent 2600978
src/AstGen.zig
@@ -2198,8 +2198,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
             .switch_capture_ref,
             .switch_capture_multi,
             .switch_capture_multi_ref,
-            .switch_capture_else,
-            .switch_capture_else_ref,
             .struct_init_empty,
             .struct_init,
             .struct_init_ref,
@@ -5758,16 +5756,19 @@ fn switchExpr(
             }
             if (case_node == special_node) {
                 const capture_tag: Zir.Inst.Tag = if (is_ptr)
-                    .switch_capture_else_ref
+                    .switch_capture_ref
                 else
-                    .switch_capture_else;
+                    .switch_capture;
                 capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
                 try astgen.instructions.append(gpa, .{
                     .tag = capture_tag,
-                    .data = .{ .switch_capture = .{
-                        .switch_inst = switch_block,
-                        .prong_index = undefined,
-                    } },
+                    .data = .{
+                        .switch_capture = .{
+                            .switch_inst = switch_block,
+                            // Max int communicates that this is the else/underscore prong.
+                            .prong_index = std.math.maxInt(u32),
+                        },
+                    },
                 });
             } else {
                 const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
src/print_zir.zig
@@ -425,8 +425,6 @@ const Writer = struct {
             .switch_capture_ref,
             .switch_capture_multi,
             .switch_capture_multi_ref,
-            .switch_capture_else,
-            .switch_capture_else_ref,
             => try self.writeSwitchCapture(stream, inst),
 
             .dbg_stmt => try self.writeDbgStmt(stream, inst),
src/Sema.zig
@@ -669,8 +669,6 @@ fn analyzeBodyInner(
             .switch_capture_ref           => try sema.zirSwitchCapture(block, inst, false, true),
             .switch_capture_multi         => try sema.zirSwitchCapture(block, inst, true, false),
             .switch_capture_multi_ref     => try sema.zirSwitchCapture(block, inst, true, true),
-            .switch_capture_else          => try sema.zirSwitchCaptureElse(block, inst, false),
-            .switch_capture_else_ref      => try sema.zirSwitchCaptureElse(block, inst, true),
             .type_info                    => try sema.zirTypeInfo(block, inst),
             .size_of                      => try sema.zirSizeOf(block, inst),
             .bit_size_of                  => try sema.zirBitSizeOf(block, inst),
@@ -6071,6 +6069,27 @@ fn zirSwitchCapture(
     const operand_ptr_ty = sema.typeOf(operand_ptr);
     const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty;
 
+    if (capture_info.prong_index == std.math.maxInt(@TypeOf(capture_info.prong_index))) {
+        // It is the else/`_` prong.
+        switch (operand_ty.zigTypeTag()) {
+            .ErrorSet => {
+                return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCaptureElse for error sets", .{});
+            },
+            else => {},
+        }
+        if (is_ref) {
+            assert(operand_is_ref);
+            return operand_ptr;
+        }
+
+        const operand = if (operand_is_ref)
+            try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src)
+        else
+            operand_ptr;
+
+        return operand;
+    }
+
     if (is_multi) {
         return sema.fail(block, switch_src, "TODO implement Sema for switch capture multi", .{});
     }
@@ -6137,26 +6156,6 @@ fn zirSwitchCapture(
     }
 }
 
-fn zirSwitchCaptureElse(
-    sema: *Sema,
-    block: *Block,
-    inst: Zir.Inst.Index,
-    is_ref: bool,
-) CompileError!Air.Inst.Ref {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const zir_datas = sema.code.instructions.items(.data);
-    const capture_info = zir_datas[inst].switch_capture;
-    const switch_info = zir_datas[capture_info.switch_inst].pl_node;
-    const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index).data;
-    const src = switch_info.src();
-    const operand_is_ref = switch_extra.bits.is_ref;
-    assert(!is_ref or operand_is_ref);
-
-    return sema.fail(block, src, "TODO implement Sema for zirSwitchCaptureElse", .{});
-}
-
 fn zirSwitchCond(
     sema: *Sema,
     block: *Block,
src/Zir.zig
@@ -625,10 +625,14 @@ pub const Inst = struct {
         switch_cond_ref,
         /// Produces the capture value for a switch prong.
         /// Uses the `switch_capture` field.
+        /// If the `prong_index` field is max int, it means this is the capture
+        /// for the else/`_` prong.
         switch_capture,
         /// Produces the capture value for a switch prong.
         /// Result is a pointer to the value.
         /// Uses the `switch_capture` field.
+        /// If the `prong_index` field is max int, it means this is the capture
+        /// for the else/`_` prong.
         switch_capture_ref,
         /// Produces the capture value for a switch prong.
         /// The prong is one of the multi cases.
@@ -639,13 +643,6 @@ pub const Inst = struct {
         /// Result is a pointer to the value.
         /// Uses the `switch_capture` field.
         switch_capture_multi_ref,
-        /// Produces the capture value for the else/'_' switch prong.
-        /// Uses the `switch_capture` field.
-        switch_capture_else,
-        /// Produces the capture value for the else/'_' switch prong.
-        /// Result is a pointer to the value.
-        /// Uses the `switch_capture` field.
-        switch_capture_else_ref,
         /// Given a set of `field_ptr` instructions, assumes they are all part of a struct
         /// initialization expression, and emits compile errors for duplicate fields
         /// as well as missing fields, if applicable.
@@ -1082,8 +1079,6 @@ pub const Inst = struct {
                 .switch_capture_ref,
                 .switch_capture_multi,
                 .switch_capture_multi_ref,
-                .switch_capture_else,
-                .switch_capture_else_ref,
                 .switch_block,
                 .switch_cond,
                 .switch_cond_ref,
@@ -1340,8 +1335,6 @@ pub const Inst = struct {
                 .switch_capture_ref = .switch_capture,
                 .switch_capture_multi = .switch_capture,
                 .switch_capture_multi_ref = .switch_capture,
-                .switch_capture_else = .switch_capture,
-                .switch_capture_else_ref = .switch_capture,
                 .validate_struct_init = .pl_node,
                 .validate_struct_init_comptime = .pl_node,
                 .validate_array_init = .pl_node,
@@ -1469,6 +1462,11 @@ pub const Inst = struct {
                 .extended = .extended,
             });
         };
+
+        // Uncomment to view how many tag slots are available.
+        //comptime {
+        //    @compileLog("ZIR tags left: ", 256 - @typeInfo(Tag).Enum.fields.len);
+        //}
     };
 
     /// Rarer instructions are here; ones that do not fit in the 8-bit `Tag` enum.
test/behavior/switch.zig
@@ -359,6 +359,21 @@ fn return_a_number() anyerror!i32 {
     return 1;
 }
 
+test "switch on integer with else capturing expr" {
+    const S = struct {
+        fn doTheTest() !void {
+            var x: i32 = 5;
+            switch (x + 10) {
+                14 => @panic("fail"),
+                16 => @panic("fail"),
+                else => |e| try expect(e == 15),
+            }
+        }
+    };
+    try S.doTheTest();
+    comptime try S.doTheTest();
+}
+
 test "else prong of switch on error set excludes other cases" {
     if (@import("builtin").zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
 
test/behavior/while.zig
@@ -266,3 +266,20 @@ test "while optional 2 break statements and an else" {
     try S.entry(true, false);
     comptime try S.entry(true, false);
 }
+
+test "while error 2 break statements and an else" {
+    if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
+
+    const S = struct {
+        fn entry(opt_t: anyerror!bool, f: bool) !void {
+            var ok = false;
+            ok = while (opt_t) |t| {
+                if (f) break false;
+                if (t) break true;
+            } else |_| false;
+            try expect(ok);
+        }
+    };
+    try S.entry(true, false);
+    comptime try S.entry(true, false);
+}
test/behavior/while_stage1.zig
@@ -1,17 +0,0 @@
-const std = @import("std");
-const expect = std.testing.expect;
-
-test "while error 2 break statements and an else" {
-    const S = struct {
-        fn entry(opt_t: anyerror!bool, f: bool) !void {
-            var ok = false;
-            ok = while (opt_t) |t| {
-                if (f) break false;
-                if (t) break true;
-            } else |_| false;
-            try expect(ok);
-        }
-    };
-    try S.entry(true, false);
-    comptime try S.entry(true, false);
-}
test/behavior.zig
@@ -203,7 +203,6 @@ test {
                     if (builtin.target.cpu.arch == .wasm32) {
                         _ = @import("behavior/wasm.zig");
                     }
-                    _ = @import("behavior/while_stage1.zig");
                     _ = @import("behavior/translate_c_macros_stage1.zig");
                 }
             }