Commit 9a70eeeac5

mlugg <mlugg@mlugg.co.uk>
2024-12-29 22:48:09
compiler: ensure local `const`s in comptime scope are comptime-known
This fixes a bug which exposed a compiler implementation detail (ZIR alloc elision). Previously, `const` declarations with a runtime-known value in a comptime scope were permitted only if AstGen was able to elide the alloc in ZIR, since the error was reported by storing to the comptime alloc. This just adds a new instruction to also emit this error when the alloc is elided.
1 parent 6026a5f
Changed files (4)
lib/std/zig/AstGen.zig
@@ -2963,6 +2963,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
             .validate_array_init_result_ty,
             .validate_ptr_array_init,
             .validate_ref_ty,
+            .validate_const,
             .try_operand_ty,
             .try_ref_operand_ty,
             => break :b true,
@@ -3280,6 +3281,7 @@ fn varDecl(
                 const init_inst = try reachableExprComptime(gz, scope, result_info, var_decl.ast.init_node, node, if (force_comptime) .comptime_keyword else null);
                 gz.anon_name_strategy = prev_anon_name_strategy;
 
+                _ = try gz.addUnNode(.validate_const, init_inst, var_decl.ast.init_node);
                 try gz.addDbgVar(.dbg_var_val, ident_name, init_inst);
 
                 // The const init expression may have modified the error return trace, so signal
lib/std/zig/Zir.zig
@@ -711,6 +711,12 @@ pub const Inst = struct {
         /// operator. Emit a compile error if not.
         /// Uses the `un_tok` union field. Token is the `&` operator. Operand is the type.
         validate_ref_ty,
+        /// Given a value, check whether it is a valid local constant in this scope.
+        /// In a runtime scope, this is always a nop.
+        /// In a comptime scope, raises a compile error if the value is runtime-known.
+        /// Result is always void.
+        /// Uses the `un_node` union field. Node is the initializer. Operand is the initializer value.
+        validate_const,
         /// Given a type `T`, construct the type `E!T`, where `E` is this function's error set, to be used
         /// as the result type of a `try` operand. Generic poison is propagated.
         /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `T`.
@@ -1293,6 +1299,7 @@ pub const Inst = struct {
                 .array_init_elem_type,
                 .array_init_elem_ptr,
                 .validate_ref_ty,
+                .validate_const,
                 .try_operand_ty,
                 .try_ref_operand_ty,
                 .restore_err_ret_index_unconditional,
@@ -1353,6 +1360,7 @@ pub const Inst = struct {
                 .validate_array_init_result_ty,
                 .validate_ptr_array_init,
                 .validate_ref_ty,
+                .validate_const,
                 .try_operand_ty,
                 .try_ref_operand_ty,
                 => true,
@@ -1736,6 +1744,7 @@ pub const Inst = struct {
                 .opt_eu_base_ptr_init = .un_node,
                 .coerce_ptr_elem_ty = .pl_node,
                 .validate_ref_ty = .un_tok,
+                .validate_const = .un_node,
                 .try_operand_ty = .un_node,
                 .try_ref_operand_ty = .un_node,
 
@@ -4143,6 +4152,7 @@ fn findTrackableInner(
         .opt_eu_base_ptr_init,
         .coerce_ptr_elem_ty,
         .validate_ref_ty,
+        .validate_const,
         .try_operand_ty,
         .try_ref_operand_ty,
         .struct_init_empty,
src/print_zir.zig
@@ -273,6 +273,7 @@ const Writer = struct {
             .@"await",
             .make_ptr_const,
             .validate_deref,
+            .validate_const,
             .check_comptime_control_flow,
             .opt_eu_base_ptr_init,
             .restore_err_ret_index_unconditional,
src/Sema.zig
@@ -1502,6 +1502,11 @@ fn analyzeBodyInner(
                 i += 1;
                 continue;
             },
+            .validate_const => {
+                try sema.zirValidateConst(block, inst);
+                i += 1;
+                continue;
+            },
             .@"export" => {
                 try sema.zirExport(block, inst);
                 i += 1;
@@ -4614,6 +4619,17 @@ fn zirValidateRefTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
     }
 }
 
+fn zirValidateConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
+    if (!block.isComptime()) return;
+
+    const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
+    const src = block.nodeOffset(un_node.src_node);
+    const init_ref = try sema.resolveInst(un_node.operand);
+    if (!try sema.isComptimeKnown(init_ref)) {
+        return sema.failWithNeededComptime(block, src, null);
+    }
+}
+
 fn zirValidateArrayInitRefTy(
     sema: *Sema,
     block: *Block,