Commit 68db9d5074

Andrew Kelley <superjoe30@gmail.com>
2018-09-04 21:28:13
add compile error for comptime control flow inside runtime block
closes #834
1 parent 36828a2
src/all_types.hpp
@@ -1836,6 +1836,7 @@ enum ScopeId {
     ScopeIdFnDef,
     ScopeIdCompTime,
     ScopeIdCoroPrelude,
+    ScopeIdRuntime,
 };
 
 struct Scope {
@@ -1928,6 +1929,15 @@ struct ScopeLoop {
     ZigList<IrBasicBlock *> *incoming_blocks;
 };
 
+// This scope blocks certain things from working such as comptime continue
+// inside a runtime if expression.
+// NodeTypeIfBoolExpr, NodeTypeWhileExpr, NodeTypeForExpr
+struct ScopeRuntime {
+    Scope base;
+
+    IrInstruction *is_comptime;
+};
+
 // This scope is created for a suspend block in order to have labeled
 // suspend for breaking out of a suspend and for detecting if a suspend
 // block is inside a suspend block.
@@ -2147,6 +2157,7 @@ enum IrInstructionId {
     IrInstructionIdErrSetCast,
     IrInstructionIdToBytes,
     IrInstructionIdFromBytes,
+    IrInstructionIdCheckRuntimeScope,
 };
 
 struct IrInstruction {
@@ -3236,6 +3247,13 @@ struct IrInstructionSqrt {
     IrInstruction *op;
 };
 
+struct IrInstructionCheckRuntimeScope {
+    IrInstruction base;
+
+    IrInstruction *scope_is_comptime;
+    IrInstruction *is_comptime;
+};
+
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/analyze.cpp
@@ -157,6 +157,13 @@ ScopeLoop *create_loop_scope(AstNode *node, Scope *parent) {
     return scope;
 }
 
+Scope *create_runtime_scope(AstNode *node, Scope *parent, IrInstruction *is_comptime) {
+    ScopeRuntime *scope = allocate<ScopeRuntime>(1);
+    scope->is_comptime = is_comptime;
+    init_scope(&scope->base, ScopeIdRuntime, node, parent);
+    return &scope->base;
+}
+
 ScopeSuspend *create_suspend_scope(AstNode *node, Scope *parent) {
     assert(node->type == NodeTypeSuspend);
     ScopeSuspend *scope = allocate<ScopeSuspend>(1);
@@ -3770,6 +3777,7 @@ FnTableEntry *scope_get_fn_if_root(Scope *scope) {
             case ScopeIdSuspend:
             case ScopeIdCompTime:
             case ScopeIdCoroPrelude:
+            case ScopeIdRuntime:
                 scope = scope->parent;
                 continue;
             case ScopeIdFnDef:
src/analyze.hpp
@@ -111,6 +111,7 @@ ScopeFnDef *create_fndef_scope(AstNode *node, Scope *parent, FnTableEntry *fn_en
 ScopeDecls *create_decls_scope(AstNode *node, Scope *parent, TypeTableEntry *container_type, ImportTableEntry *import);
 Scope *create_comptime_scope(AstNode *node, Scope *parent);
 Scope *create_coro_prelude_scope(AstNode *node, Scope *parent);
+Scope *create_runtime_scope(AstNode *node, Scope *parent, IrInstruction *is_comptime);
 
 void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str);
 ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str);
src/codegen.cpp
@@ -678,6 +678,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) {
         case ScopeIdSuspend:
         case ScopeIdCompTime:
         case ScopeIdCoroPrelude:
+        case ScopeIdRuntime:
             return get_di_scope(g, scope->parent);
     }
     zig_unreachable();
@@ -4869,6 +4870,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdFromBytes:
         case IrInstructionIdToBytes:
         case IrInstructionIdEnumToInt:
+        case IrInstructionIdCheckRuntimeScope:
             zig_unreachable();
 
         case IrInstructionIdReturn:
src/ir.cpp
@@ -832,6 +832,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionSqrt *) {
     return IrInstructionIdSqrt;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckRuntimeScope *) {
+    return IrInstructionIdCheckRuntimeScope;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -2974,6 +2978,17 @@ static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *sourc
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) {
+    IrInstructionCheckRuntimeScope *instruction = ir_build_instruction<IrInstructionCheckRuntimeScope>(irb, scope, source_node);
+    instruction->scope_is_comptime = scope_is_comptime;
+    instruction->is_comptime = is_comptime;
+
+    ir_ref_instruction(scope_is_comptime, irb->current_basic_block);
+    ir_ref_instruction(is_comptime, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope, size_t *results) {
     results[ReturnKindUnconditional] = 0;
     results[ReturnKindError] = 0;
@@ -2999,6 +3014,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco
             case ScopeIdLoop:
             case ScopeIdSuspend:
             case ScopeIdCompTime:
+            case ScopeIdRuntime:
                 scope = scope->parent;
                 continue;
             case ScopeIdDeferExpr:
@@ -3051,6 +3067,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o
             case ScopeIdLoop:
             case ScopeIdSuspend:
             case ScopeIdCompTime:
+            case ScopeIdRuntime:
                 scope = scope->parent;
                 continue;
             case ScopeIdDeferExpr:
@@ -4980,7 +4997,8 @@ static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode
 
     ir_set_cursor_at_end_and_append_block(irb, then_block);
 
-    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, scope);
+    Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
+    IrInstruction *then_expr_result = ir_gen_node(irb, then_node, subexpr_scope);
     if (then_expr_result == irb->codegen->invalid_instruction)
         return then_expr_result;
     IrBasicBlock *after_then_block = irb->current_basic_block;
@@ -4990,7 +5008,7 @@ static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode
     ir_set_cursor_at_end_and_append_block(irb, else_block);
     IrInstruction *else_expr_result;
     if (else_node) {
-        else_expr_result = ir_gen_node(irb, else_node, scope);
+        else_expr_result = ir_gen_node(irb, else_node, subexpr_scope);
         if (else_expr_result == irb->codegen->invalid_instruction)
             return else_expr_result;
     } else {
@@ -5271,6 +5289,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
         ir_should_inline(irb->exec, scope) || node->data.while_expr.is_inline);
     ir_build_br(irb, scope, node, cond_block, is_comptime);
 
+    Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
     Buf *var_symbol = node->data.while_expr.var_symbol;
     Buf *err_symbol = node->data.while_expr.err_symbol;
     if (err_symbol != nullptr) {
@@ -5281,13 +5300,13 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
         VariableTableEntry *payload_var;
         if (var_symbol) {
             // TODO make it an error to write to payload variable
-            payload_var = ir_create_var(irb, symbol_node, scope, var_symbol,
+            payload_var = ir_create_var(irb, symbol_node, subexpr_scope, var_symbol,
                     true, false, false, is_comptime);
             payload_scope = payload_var->child_scope;
         } else {
-            payload_scope = scope;
+            payload_scope = subexpr_scope;
         }
-        IrInstruction *err_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LValPtr);
+        IrInstruction *err_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, subexpr_scope, LValPtr);
         if (err_val_ptr == irb->codegen->invalid_instruction)
             return err_val_ptr;
         IrInstruction *err_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, err_val_ptr);
@@ -5367,12 +5386,14 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
         return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
     } else if (var_symbol != nullptr) {
         ir_set_cursor_at_end_and_append_block(irb, cond_block);
+        Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
         // TODO make it an error to write to payload variable
         AstNode *symbol_node = node; // TODO make more accurate
-        VariableTableEntry *payload_var = ir_create_var(irb, symbol_node, scope, var_symbol,
+
+        VariableTableEntry *payload_var = ir_create_var(irb, symbol_node, subexpr_scope, var_symbol,
                 true, false, false, is_comptime);
         Scope *child_scope = payload_var->child_scope;
-        IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, scope, LValPtr);
+        IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, node->data.while_expr.condition, subexpr_scope, LValPtr);
         if (maybe_val_ptr == irb->codegen->invalid_instruction)
             return maybe_val_ptr;
         IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, maybe_val_ptr);
@@ -5456,7 +5477,9 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
         ZigList<IrInstruction *> incoming_values = {0};
         ZigList<IrBasicBlock *> incoming_blocks = {0};
 
-        ScopeLoop *loop_scope = create_loop_scope(node, scope);
+        Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
+
+        ScopeLoop *loop_scope = create_loop_scope(node, subexpr_scope);
         loop_scope->break_block = end_block;
         loop_scope->continue_block = continue_block;
         loop_scope->is_comptime = is_comptime;
@@ -5474,7 +5497,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
 
         if (continue_expr_node) {
             ir_set_cursor_at_end_and_append_block(irb, continue_block);
-            IrInstruction *expr_result = ir_gen_node(irb, continue_expr_node, scope);
+            IrInstruction *expr_result = ir_gen_node(irb, continue_expr_node, subexpr_scope);
             if (expr_result == irb->codegen->invalid_instruction)
                 return expr_result;
             if (!instr_is_unreachable(expr_result))
@@ -5485,7 +5508,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
         if (else_node) {
             ir_set_cursor_at_end_and_append_block(irb, else_block);
 
-            else_result = ir_gen_node(irb, else_node, scope);
+            else_result = ir_gen_node(irb, else_node, subexpr_scope);
             if (else_result == irb->codegen->invalid_instruction)
                 return else_result;
             if (!instr_is_unreachable(else_result))
@@ -5828,20 +5851,21 @@ static IrInstruction *ir_gen_test_expr(IrBuilder *irb, Scope *scope, AstNode *no
 
     ir_set_cursor_at_end_and_append_block(irb, then_block);
 
+    Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
     Scope *var_scope;
     if (var_symbol) {
         IrInstruction *var_type = nullptr;
         bool is_shadowable = false;
         bool is_const = true;
-        VariableTableEntry *var = ir_create_var(irb, node, scope,
+        VariableTableEntry *var = ir_create_var(irb, node, subexpr_scope,
                 var_symbol, is_const, is_const, is_shadowable, is_comptime);
 
-        IrInstruction *var_ptr_value = ir_build_unwrap_maybe(irb, scope, node, maybe_val_ptr, false);
-        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, scope, node, var_ptr_value);
-        ir_build_var_decl(irb, scope, node, var, var_type, nullptr, var_value);
+        IrInstruction *var_ptr_value = ir_build_unwrap_maybe(irb, subexpr_scope, node, maybe_val_ptr, false);
+        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, subexpr_scope, node, var_ptr_value);
+        ir_build_var_decl(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
         var_scope = var->child_scope;
     } else {
-        var_scope = scope;
+        var_scope = subexpr_scope;
     }
     IrInstruction *then_expr_result = ir_gen_node(irb, then_node, var_scope);
     if (then_expr_result == irb->codegen->invalid_instruction)
@@ -5853,7 +5877,7 @@ static IrInstruction *ir_gen_test_expr(IrBuilder *irb, Scope *scope, AstNode *no
     ir_set_cursor_at_end_and_append_block(irb, else_block);
     IrInstruction *else_expr_result;
     if (else_node) {
-        else_expr_result = ir_gen_node(irb, else_node, scope);
+        else_expr_result = ir_gen_node(irb, else_node, subexpr_scope);
         if (else_expr_result == irb->codegen->invalid_instruction)
             return else_expr_result;
     } else {
@@ -5902,20 +5926,21 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode *
 
     ir_set_cursor_at_end_and_append_block(irb, ok_block);
 
+    Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
     Scope *var_scope;
     if (var_symbol) {
         IrInstruction *var_type = nullptr;
         bool is_shadowable = false;
-        IrInstruction *var_is_comptime = force_comptime ? ir_build_const_bool(irb, scope, node, true) : ir_build_test_comptime(irb, scope, node, err_val);
-        VariableTableEntry *var = ir_create_var(irb, node, scope,
+        IrInstruction *var_is_comptime = force_comptime ? ir_build_const_bool(irb, subexpr_scope, node, true) : ir_build_test_comptime(irb, subexpr_scope, node, err_val);
+        VariableTableEntry *var = ir_create_var(irb, node, subexpr_scope,
                 var_symbol, var_is_const, var_is_const, is_shadowable, var_is_comptime);
 
-        IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, scope, node, err_val_ptr, false);
-        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, scope, node, var_ptr_value);
-        ir_build_var_decl(irb, scope, node, var, var_type, nullptr, var_value);
+        IrInstruction *var_ptr_value = ir_build_unwrap_err_payload(irb, subexpr_scope, node, err_val_ptr, false);
+        IrInstruction *var_value = var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, subexpr_scope, node, var_ptr_value);
+        ir_build_var_decl(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
         var_scope = var->child_scope;
     } else {
-        var_scope = scope;
+        var_scope = subexpr_scope;
     }
     IrInstruction *then_expr_result = ir_gen_node(irb, then_node, var_scope);
     if (then_expr_result == irb->codegen->invalid_instruction)
@@ -5933,14 +5958,14 @@ static IrInstruction *ir_gen_if_err_expr(IrBuilder *irb, Scope *scope, AstNode *
             IrInstruction *var_type = nullptr;
             bool is_shadowable = false;
             bool is_const = true;
-            VariableTableEntry *var = ir_create_var(irb, node, scope,
+            VariableTableEntry *var = ir_create_var(irb, node, subexpr_scope,
                     err_symbol, is_const, is_const, is_shadowable, is_comptime);
 
-            IrInstruction *var_value = ir_build_unwrap_err_code(irb, scope, node, err_val_ptr);
-            ir_build_var_decl(irb, scope, node, var, var_type, nullptr, var_value);
+            IrInstruction *var_value = ir_build_unwrap_err_code(irb, subexpr_scope, node, err_val_ptr);
+            ir_build_var_decl(irb, subexpr_scope, node, var, var_type, nullptr, var_value);
             err_var_scope = var->child_scope;
         } else {
-            err_var_scope = scope;
+            err_var_scope = subexpr_scope;
         }
         else_expr_result = ir_gen_node(irb, else_node, err_var_scope);
         if (else_expr_result == irb->codegen->invalid_instruction)
@@ -6037,6 +6062,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
     ZigList<IrInstructionCheckSwitchProngsRange> check_ranges = {0};
 
     // First do the else and the ranges
+    Scope *subexpr_scope = create_runtime_scope(node, scope, is_comptime);
     Scope *comptime_scope = create_comptime_scope(node, scope);
     AstNode *else_prong = nullptr;
     for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
@@ -6054,7 +6080,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
 
             IrBasicBlock *prev_block = irb->current_basic_block;
             ir_set_cursor_at_end_and_append_block(irb, else_block);
-            if (!ir_gen_switch_prong_expr(irb, scope, node, prong_node, end_block,
+            if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
                 is_comptime, var_is_comptime, target_value_ptr, nullptr, &incoming_blocks, &incoming_values))
             {
                 return irb->codegen->invalid_instruction;
@@ -6121,7 +6147,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
                         range_block_no, is_comptime));
 
             ir_set_cursor_at_end_and_append_block(irb, range_block_yes);
-            if (!ir_gen_switch_prong_expr(irb, scope, node, prong_node, end_block,
+            if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
                 is_comptime, var_is_comptime, target_value_ptr, nullptr, &incoming_blocks, &incoming_values))
             {
                 return irb->codegen->invalid_instruction;
@@ -6165,7 +6191,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
 
         IrBasicBlock *prev_block = irb->current_basic_block;
         ir_set_cursor_at_end_and_append_block(irb, prong_block);
-        if (!ir_gen_switch_prong_expr(irb, scope, node, prong_node, end_block,
+        if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
             is_comptime, var_is_comptime, target_value_ptr, only_item_value, &incoming_blocks, &incoming_values))
         {
             return irb->codegen->invalid_instruction;
@@ -6308,6 +6334,8 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
     // * defer expression scope => error, cannot break out of defer expression
     // * loop scope => OK
 
+    ZigList<ScopeRuntime *> runtime_scopes = {};
+
     Scope *search_scope = continue_scope;
     ScopeLoop *loop_scope;
     for (;;) {
@@ -6330,6 +6358,9 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
                 loop_scope = this_loop_scope;
                 break;
             }
+        } else if (search_scope->id == ScopeIdRuntime) {
+            ScopeRuntime *scope_runtime = (ScopeRuntime *)search_scope;
+            runtime_scopes.append(scope_runtime);
         }
         search_scope = search_scope->parent;
     }
@@ -6341,6 +6372,11 @@ static IrInstruction *ir_gen_continue(IrBuilder *irb, Scope *continue_scope, Ast
         is_comptime = loop_scope->is_comptime;
     }
 
+    for (size_t i = 0; i < runtime_scopes.length; i += 1) {
+        ScopeRuntime *scope_runtime = runtime_scopes.at(i);
+        ir_mark_gen(ir_build_check_runtime_scope(irb, continue_scope, node, scope_runtime->is_comptime, is_comptime));
+    }
+
     IrBasicBlock *dest_block = loop_scope->continue_block;
     ir_gen_defers_for_block(irb, continue_scope, dest_block->scope, false);
     return ir_mark_gen(ir_build_br(irb, continue_scope, node, dest_block, is_comptime));
@@ -20910,6 +20946,29 @@ static TypeTableEntry *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, IrInst
     return result->value.type;
 }
 
+static TypeTableEntry *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira, IrInstructionCheckRuntimeScope *instruction) {
+    IrInstruction *block_comptime_inst = instruction->scope_is_comptime->other;
+    bool scope_is_comptime;
+    if (!ir_resolve_bool(ira, block_comptime_inst, &scope_is_comptime))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    IrInstruction *is_comptime_inst = instruction->is_comptime->other;
+    bool is_comptime;
+    if (!ir_resolve_bool(ira, is_comptime_inst, &is_comptime))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    if (!scope_is_comptime && is_comptime) {
+        ErrorMsg *msg = ir_add_error(ira, &instruction->base,
+            buf_sprintf("comptime control flow inside runtime block"));
+        add_error_note(ira->codegen, msg, block_comptime_inst->source_node,
+                buf_sprintf("runtime block created here"));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+
+    ir_build_const_from(ira, &instruction->base);
+    return ira->codegen->builtin_types.entry_void;
+}
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -21186,6 +21245,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_int_to_enum(ira, (IrInstructionIntToEnum *)instruction);
         case IrInstructionIdEnumToInt:
             return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction);
+        case IrInstructionIdCheckRuntimeScope:
+            return ir_analyze_instruction_check_runtime_scope(ira, (IrInstructionCheckRuntimeScope *)instruction);
     }
     zig_unreachable();
 }
@@ -21301,6 +21362,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdOverflowOp: // TODO when we support multiple returns this can be side effect free
         case IrInstructionIdCheckSwitchProngs:
         case IrInstructionIdCheckStatementIsVoid:
+        case IrInstructionIdCheckRuntimeScope:
         case IrInstructionIdPanic:
         case IrInstructionIdSetEvalBranchQuota:
         case IrInstructionIdPtrType:
src/ir_print.cpp
@@ -957,6 +957,14 @@ static void ir_print_enum_to_int(IrPrint *irp, IrInstructionEnumToInt *instructi
     fprintf(irp->f, ")");
 }
 
+static void ir_print_check_runtime_scope(IrPrint *irp, IrInstructionCheckRuntimeScope *instruction) {
+    fprintf(irp->f, "@checkRuntimeScope(");
+    ir_print_other_instruction(irp, instruction->scope_is_comptime);
+    fprintf(irp->f, ",");
+    ir_print_other_instruction(irp, instruction->is_comptime);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_int_to_err(IrPrint *irp, IrInstructionIntToErr *instruction) {
     fprintf(irp->f, "inttoerr ");
     ir_print_other_instruction(irp, instruction->target);
@@ -1749,6 +1757,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdEnumToInt:
             ir_print_enum_to_int(irp, (IrInstructionEnumToInt *)instruction);
             break;
+        case IrInstructionIdCheckRuntimeScope:
+            ir_print_check_runtime_scope(irp, (IrInstructionCheckRuntimeScope *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }
test/compile_errors.zig
@@ -1,6 +1,116 @@
 const tests = @import("tests.zig");
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
+    cases.add(
+        "comptime continue inside runtime switch",
+        \\export fn entry() void {
+        \\    var p: i32 = undefined;
+        \\    comptime var q = true;
+        \\    inline while (q) {
+        \\        switch (p) {
+        \\            11 => continue,
+        \\            else => {},
+        \\        }
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:6:19: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime while error",
+        \\export fn entry() void {
+        \\    var p: error!usize = undefined;
+        \\    comptime var q = true;
+        \\    outer: inline while (q) {
+        \\        while (p) |_| {
+        \\            continue :outer;
+        \\        } else |_| {}
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:6:13: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime while optional",
+        \\export fn entry() void {
+        \\    var p: ?usize = undefined;
+        \\    comptime var q = true;
+        \\    outer: inline while (q) {
+        \\        while (p) |_| continue :outer;
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:5:23: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime while bool",
+        \\export fn entry() void {
+        \\    var p: usize = undefined;
+        \\    comptime var q = true;
+        \\    outer: inline while (q) {
+        \\        while (p == 11) continue :outer;
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:5:25: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime if error",
+        \\export fn entry() void {
+        \\    var p: error!i32 = undefined;
+        \\    comptime var q = true;
+        \\    inline while (q) {
+        \\        if (p) |_| continue else |_| {}
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:5:20: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime if optional",
+        \\export fn entry() void {
+        \\    var p: ?i32 = undefined;
+        \\    comptime var q = true;
+        \\    inline while (q) {
+        \\        if (p) |_| continue;
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:5:20: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
+    cases.add(
+        "comptime continue inside runtime if bool",
+        \\export fn entry() void {
+        \\    var p: usize = undefined;
+        \\    comptime var q = true;
+        \\    inline while (q) {
+        \\        if (p == 11) continue;
+        \\        q = false;
+        \\    }
+        \\}
+    ,
+        ".tmp_source.zig:5:22: error: comptime control flow inside runtime block",
+        ".tmp_source.zig:5:9: note: runtime block created here",
+    );
+
     cases.add(
         "switch with invalid expression parameter",
         \\export fn entry() void {