Commit 5e37da6ade

Veikka Tuominen <git@vexu.eu>
2022-09-09 20:36:08
Sema: check_comptime_control_flow needs to check runtime_index
1 parent bf4a3df
src/AstGen.zig
@@ -1981,7 +1981,7 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
                 else
                     .@"break";
                 if (break_tag == .break_inline) {
-                    _ = try parent_gz.addNode(.check_comptime_control_flow, node);
+                    _ = try parent_gz.addUnNode(.check_comptime_control_flow, Zir.indexToRef(continue_block), node);
                 }
                 _ = try parent_gz.addBreak(break_tag, continue_block, .void_value);
                 return Zir.Inst.Ref.unreachable_value;
src/print_zir.zig
@@ -232,6 +232,7 @@ const Writer = struct {
             .make_ptr_const,
             .validate_deref,
             .overflow_arithmetic_ptr,
+            .check_comptime_control_flow,
             => try self.writeUnNode(stream, inst),
 
             .ref,
@@ -406,7 +407,6 @@ const Writer = struct {
             .alloc_inferred_comptime_mut,
             .ret_ptr,
             .ret_type,
-            .check_comptime_control_flow,
             => try self.writeNode(stream, inst),
 
             .error_value,
src/Sema.zig
@@ -144,6 +144,7 @@ pub const Block = struct {
     /// Non zero if a non-inline loop or a runtime conditional have been encountered.
     /// Stores to to comptime variables are only allowed when var.runtime_index <= runtime_index.
     runtime_index: Value.RuntimeIndex = .zero,
+    inline_block: Zir.Inst.Index = 0,
 
     is_comptime: bool,
     is_typeof: bool = false,
@@ -1157,9 +1158,20 @@ fn analyzeBodyInner(
             },
             .check_comptime_control_flow => {
                 if (!block.is_comptime) {
-                    if (block.runtime_cond orelse block.runtime_loop) |runtime_src| {
-                        const inst_data = sema.code.instructions.items(.data)[inst].node;
-                        const src = LazySrcLoc.nodeOffset(inst_data);
+                    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+                    const src = inst_data.src();
+                    const inline_block = Zir.refToIndex(inst_data.operand).?;
+
+                    var check_block = block;
+                    const target_runtime_index = while (true) {
+                        if (check_block.inline_block == inline_block) {
+                            break check_block.runtime_index;
+                        }
+                        check_block = check_block.parent.?;
+                    } else unreachable;
+
+                    if (@enumToInt(target_runtime_index) < @enumToInt(block.runtime_index)) {
+                        const runtime_src = block.runtime_cond orelse block.runtime_loop.?;
                         const msg = msg: {
                             const msg = try sema.errMsg(block, src, "comptime control flow inside runtime block", .{});
                             errdefer msg.destroy(sema.gpa);
@@ -1272,10 +1284,15 @@ fn analyzeBodyInner(
                 // current list of parameters and restore it later.
                 // Note: this probably needs to be resolved in a more general manner.
                 const prev_params = block.params;
+                const prev_inline_block = block.inline_block;
+                if (tags[inline_body[inline_body.len - 1]] == .repeat_inline) {
+                    block.inline_block = inline_body[0];
+                }
                 block.params = .{};
                 defer {
                     block.params.deinit(gpa);
                     block.params = prev_params;
+                    block.inline_block = prev_inline_block;
                 }
                 const opt_break_data = try sema.analyzeBodyBreak(block, inline_body);
                 // A runtime conditional branch that needs a post-hoc block to be
src/Zir.zig
@@ -287,7 +287,7 @@ pub const Inst = struct {
         /// Uses the `break` union field.
         break_inline,
         /// Checks that comptime control flow does not happen inside a runtime block.
-        /// Uses the `node` union field.
+        /// Uses the `un_node` union field.
         check_comptime_control_flow,
         /// Function call.
         /// Uses the `pl_node` union field with payload `Call`.
@@ -1600,7 +1600,7 @@ pub const Inst = struct {
                 .bool_br_or = .bool_br,
                 .@"break" = .@"break",
                 .break_inline = .@"break",
-                .check_comptime_control_flow = .node,
+                .check_comptime_control_flow = .un_node,
                 .call = .pl_node,
                 .cmp_lt = .pl_node,
                 .cmp_lte = .pl_node,
test/behavior/eval.zig
@@ -1371,3 +1371,30 @@ test "break from inline loop depends on runtime condition" {
         try expect(blk == 4);
     }
 }
+
+test "inline for inside a runtime condition" {
+    var a = false;
+    if (a) {
+        const arr = .{ 1, 2, 3 };
+        inline for (arr) |val| {
+            if (val < 3) continue;
+            try expect(val == 3);
+        }
+    }
+}
+
+test "continue in inline for inside a comptime switch" {
+    const arr = .{ 1, 2, 3 };
+    var count: u8 = 0;
+    switch (arr[1]) {
+        2 => {
+            inline for (arr) |val| {
+                if (val == 2) continue;
+
+                count += val;
+            }
+        },
+        else => {},
+    }
+    try expect(count == 4);
+}
test/cases/compile_errors/comptime_continue_to_outer_inline_loop.zig
@@ -0,0 +1,21 @@
+pub export fn entry() void {
+    var a = false;
+    const arr1 = .{ 1, 2, 3 };
+    loop: inline for (arr1) |val1| {
+        _ = val1;
+        if (a) {
+            const arr = .{ 1, 2, 3 };
+            inline for (arr) |val| {
+                if (val < 3) continue :loop;
+                if (val != 3) unreachable;
+            }
+        }
+    }
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :9:30: error: comptime control flow inside runtime block
+// :6:13: note: runtime control flow here