Commit c1a5caa454

mlugg <mlugg@mlugg.co.uk>
2025-06-01 08:41:24
compiler: combine `@intCast` safety checks
`castTruncatedData` was a poorly worded error (all shrinking casts "truncate bits", it's just that we assume those bits to be zext/sext of the other bits!), and `negativeToUnsigned` was a pointless distinction which forced the compiler to emit worse code (since two separate safety checks were required for casting e.g. 'i32' to 'u16') and wasn't even implemented correctly. This commit combines those safety panics into one function, `integerOutOfBounds`. The name maybe isn't perfect, but that's not hugely important; what matters is the new default message, which is clearer than the old ones: "integer does not fit in destination type".
1 parent 6daa37d
doc/langref/test_intCast_builtin.zig
@@ -5,4 +5,4 @@ test "integer cast panic" {
     _ = b;
 }
 
-// test_error=cast truncated bits
+// test_error=integer does not fit in destination type
lib/std/debug/no_panic.zig
@@ -65,12 +65,7 @@ pub fn invalidErrorCode() noreturn {
     @trap();
 }
 
-pub fn castTruncatedData() noreturn {
-    @branchHint(.cold);
-    @trap();
-}
-
-pub fn negativeToUnsigned() noreturn {
+pub fn integerOutOfBounds() noreturn {
     @branchHint(.cold);
     @trap();
 }
@@ -127,6 +122,10 @@ pub fn forLenMismatch() noreturn {
 
 /// Delete after next zig1.wasm update
 pub const memcpyLenMismatch = copyLenMismatch;
+/// Delete after next zig1.wasm update
+pub const castTruncatedData = integerOutOfBounds;
+/// Delete after next zig1.wasm update
+pub const negativeToUnsigned = integerOutOfBounds;
 
 pub fn copyLenMismatch() noreturn {
     @branchHint(.cold);
lib/std/debug/simple_panic.zig
@@ -72,12 +72,8 @@ pub fn invalidErrorCode() noreturn {
     call("invalid error code", null);
 }
 
-pub fn castTruncatedData() noreturn {
-    call("integer cast truncated bits", null);
-}
-
-pub fn negativeToUnsigned() noreturn {
-    call("attempt to cast negative value to unsigned integer", null);
+pub fn integerOutOfBounds() noreturn {
+    call("integer does not fit in destination type", null);
 }
 
 pub fn integerOverflow() noreturn {
@@ -122,6 +118,10 @@ pub fn forLenMismatch() noreturn {
 
 /// Delete after next zig1.wasm update
 pub const memcpyLenMismatch = copyLenMismatch;
+/// Delete after next zig1.wasm update
+pub const castTruncatedData = integerOutOfBounds;
+/// Delete after next zig1.wasm update
+pub const negativeToUnsigned = integerOutOfBounds;
 
 pub fn copyLenMismatch() noreturn {
     call("source and destination have non-equal lengths", null);
lib/std/debug.zig
@@ -78,13 +78,9 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
             @branchHint(.cold);
             call("invalid error code", @returnAddress());
         }
-        pub fn castTruncatedData() noreturn {
+        pub fn integerOutOfBounds() noreturn {
             @branchHint(.cold);
-            call("integer cast truncated bits", @returnAddress());
-        }
-        pub fn negativeToUnsigned() noreturn {
-            @branchHint(.cold);
-            call("attempt to cast negative value to unsigned integer", @returnAddress());
+            call("integer does not fit in destination type", @returnAddress());
         }
         pub fn integerOverflow() noreturn {
             @branchHint(.cold);
@@ -128,6 +124,10 @@ pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
         }
         /// Delete after next zig1.wasm update
         pub const memcpyLenMismatch = copyLenMismatch;
+        /// Delete after next zig1.wasm update
+        pub const castTruncatedData = integerOutOfBounds;
+        /// Delete after next zig1.wasm update
+        pub const negativeToUnsigned = integerOutOfBounds;
         pub fn copyLenMismatch() noreturn {
             @branchHint(.cold);
             call("source and destination arguments have non-equal lengths", @returnAddress());
src/Air/Legalize.zig
@@ -1307,7 +1307,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
     var main_block: Block = .init(&inst_buf);
     var cur_block: *Block = &main_block;
 
-    const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .cast_truncated_data;
+    const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds;
 
     if (have_min_check or have_max_check) {
         const dest_int_ty = if (dest_is_enum) dest_ty.intTagType(zcu) else dest_ty;
src/codegen/llvm.zig
@@ -9189,11 +9189,7 @@ pub const FuncGen = struct {
             const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
             assert(is_vector == (dest_ty.zigTypeTag(zcu) == .vector));
 
-            const min_panic_id: Zcu.SimplePanicId, const max_panic_id: Zcu.SimplePanicId = id: {
-                if (dest_is_enum) break :id .{ .invalid_enum_value, .invalid_enum_value };
-                if (dest_info.signedness == .unsigned) break :id .{ .negative_to_unsigned, .cast_truncated_data };
-                break :id .{ .cast_truncated_data, .cast_truncated_data };
-            };
+            const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds;
 
             if (have_min_check) {
                 const min_const_scalar = try minIntConst(&o.builder, dest_scalar, operand_scalar_llvm_ty, zcu);
@@ -9207,7 +9203,7 @@ pub const FuncGen = struct {
                 const ok_block = try fg.wip.block(1, "IntMinOk");
                 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
                 fg.wip.cursor = .{ .block = fail_block };
-                try fg.buildSimplePanic(min_panic_id);
+                try fg.buildSimplePanic(panic_id);
                 fg.wip.cursor = .{ .block = ok_block };
             }
 
@@ -9223,7 +9219,7 @@ pub const FuncGen = struct {
                 const ok_block = try fg.wip.block(1, "IntMaxOk");
                 _ = try fg.wip.brCond(ok, ok_block, fail_block, .none);
                 fg.wip.cursor = .{ .block = fail_block };
-                try fg.buildSimplePanic(max_panic_id);
+                try fg.buildSimplePanic(panic_id);
                 fg.wip.cursor = .{ .block = ok_block };
             }
         }
src/Sema.zig
@@ -10263,7 +10263,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");
     const operand = try sema.resolveInst(extra.rhs);
 
-    return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src, true, false);
+    return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src);
 }
 
 fn intCast(
@@ -10274,8 +10274,6 @@ fn intCast(
     dest_ty_src: LazySrcLoc,
     operand: Air.Inst.Ref,
     operand_src: LazySrcLoc,
-    runtime_safety: bool,
-    safety_panics_are_enum: bool,
 ) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const zcu = pt.zcu;
@@ -10294,7 +10292,7 @@ fn intCast(
 
     if ((try sema.typeHasOnePossibleValue(dest_ty))) |opv| {
         // requirement: intCast(u0, input) iff input == 0
-        if (runtime_safety and block.wantSafety()) {
+        if (block.wantSafety()) {
             try sema.requireRuntimeBlock(block, src, operand_src);
             const wanted_info = dest_scalar_ty.intInfo(zcu);
             const wanted_bits = wanted_info.bits;
@@ -10311,7 +10309,7 @@ fn intCast(
                     const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);
                     break :ok is_in_range;
                 };
-                try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
+                try sema.addSafetyCheck(block, src, ok, .integer_out_of_bounds);
             }
         }
 
@@ -10319,10 +10317,9 @@ fn intCast(
     }
 
     try sema.requireRuntimeBlock(block, src, operand_src);
-    if (runtime_safety and block.wantSafety()) {
+    if (block.wantSafety()) {
         if (zcu.backendSupportsFeature(.panic_fn)) {
-            _ = try sema.preparePanicId(src, .negative_to_unsigned);
-            _ = try sema.preparePanicId(src, .cast_truncated_data);
+            _ = try sema.preparePanicId(src, .integer_out_of_bounds);
         }
         return block.addTyOp(.intcast_safe, dest_ty, operand);
     }
@@ -37984,8 +37981,7 @@ fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Typ
         .@"panic.castToNull",
         .@"panic.incorrectAlignment",
         .@"panic.invalidErrorCode",
-        .@"panic.castTruncatedData",
-        .@"panic.negativeToUnsigned",
+        .@"panic.integerOutOfBounds",
         .@"panic.integerOverflow",
         .@"panic.shlOverflow",
         .@"panic.shrOverflow",
src/Zcu.zig
@@ -441,8 +441,7 @@ pub const BuiltinDecl = enum {
     @"panic.castToNull",
     @"panic.incorrectAlignment",
     @"panic.invalidErrorCode",
-    @"panic.castTruncatedData",
-    @"panic.negativeToUnsigned",
+    @"panic.integerOutOfBounds",
     @"panic.integerOverflow",
     @"panic.shlOverflow",
     @"panic.shrOverflow",
@@ -518,8 +517,7 @@ pub const BuiltinDecl = enum {
             .@"panic.castToNull",
             .@"panic.incorrectAlignment",
             .@"panic.invalidErrorCode",
-            .@"panic.castTruncatedData",
-            .@"panic.negativeToUnsigned",
+            .@"panic.integerOutOfBounds",
             .@"panic.integerOverflow",
             .@"panic.shlOverflow",
             .@"panic.shrOverflow",
@@ -585,8 +583,7 @@ pub const SimplePanicId = enum {
     cast_to_null,
     incorrect_alignment,
     invalid_error_code,
-    cast_truncated_data,
-    negative_to_unsigned,
+    integer_out_of_bounds,
     integer_overflow,
     shl_overflow,
     shr_overflow,
@@ -609,8 +606,7 @@ pub const SimplePanicId = enum {
             .cast_to_null               => .@"panic.castToNull",
             .incorrect_alignment        => .@"panic.incorrectAlignment",
             .invalid_error_code         => .@"panic.invalidErrorCode",
-            .cast_truncated_data        => .@"panic.castTruncatedData",
-            .negative_to_unsigned       => .@"panic.negativeToUnsigned",
+            .integer_out_of_bounds      => .@"panic.integerOutOfBounds",
             .integer_overflow           => .@"panic.integerOverflow",
             .shl_overflow               => .@"panic.shlOverflow",
             .shr_overflow               => .@"panic.shrOverflow",
test/cases/compile_errors/bad_panic_call_signature.zig
@@ -15,8 +15,7 @@ pub const panic = struct {
     pub const castToNull = simple_panic.castToNull;
     pub const incorrectAlignment = simple_panic.incorrectAlignment;
     pub const invalidErrorCode = simple_panic.invalidErrorCode;
-    pub const castTruncatedData = simple_panic.castTruncatedData;
-    pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
+    pub const integerOutOfBounds = simple_panic.integerOutOfBounds;
     pub const integerOverflow = simple_panic.integerOverflow;
     pub const shlOverflow = simple_panic.shlOverflow;
     pub const shrOverflow = simple_panic.shrOverflow;
@@ -27,8 +26,6 @@ pub const panic = struct {
     pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
     pub const invalidEnumValue = simple_panic.invalidEnumValue;
     pub const forLenMismatch = simple_panic.forLenMismatch;
-    /// Delete after next zig1.wasm update
-    pub const memcpyLenMismatch = copyLenMismatch;
     pub const copyLenMismatch = simple_panic.copyLenMismatch;
     pub const memcpyAlias = simple_panic.memcpyAlias;
     pub const noreturnReturned = simple_panic.noreturnReturned;
test/cases/compile_errors/bad_panic_generic_signature.zig
@@ -11,8 +11,7 @@ pub const panic = struct {
     pub const castToNull = simple_panic.castToNull;
     pub const incorrectAlignment = simple_panic.incorrectAlignment;
     pub const invalidErrorCode = simple_panic.invalidErrorCode;
-    pub const castTruncatedData = simple_panic.castTruncatedData;
-    pub const negativeToUnsigned = simple_panic.negativeToUnsigned;
+    pub const integerOutOfBounds = simple_panic.integerOutOfBounds;
     pub const integerOverflow = simple_panic.integerOverflow;
     pub const shlOverflow = simple_panic.shlOverflow;
     pub const shrOverflow = simple_panic.shrOverflow;
@@ -23,8 +22,6 @@ pub const panic = struct {
     pub const shiftRhsTooBig = simple_panic.shiftRhsTooBig;
     pub const invalidEnumValue = simple_panic.invalidEnumValue;
     pub const forLenMismatch = simple_panic.forLenMismatch;
-    /// Delete after next zig1.wasm update
-    pub const memcpyLenMismatch = copyLenMismatch;
     pub const copyLenMismatch = simple_panic.copyLenMismatch;
     pub const memcpyAlias = simple_panic.memcpyAlias;
     pub const noreturnReturned = simple_panic.noreturnReturned;
test/cases/safety/@intCast to u0.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/signed integer not fitting in cast to unsigned integer - widening.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/signed integer not fitting in cast to unsigned integer.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/signed-unsigned vector cast.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "attempt to cast negative value to unsigned integer")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/truncating vector cast.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/unsigned integer not fitting in cast to signed integer - same bit count.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/unsigned-signed vector cast.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/value does not fit in shortening cast - u0.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/cases/safety/value does not fit in shortening cast.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
     _ = stack_trace;
-    if (std.mem.eql(u8, message, "integer cast truncated bits")) {
+    if (std.mem.eql(u8, message, "integer does not fit in destination type")) {
         std.process.exit(0);
     }
     std.process.exit(1);
test/incremental/change_panic_handler_explicit
@@ -26,8 +26,7 @@ pub const panic = struct {
     pub const castToNull = no_panic.castToNull;
     pub const incorrectAlignment = no_panic.incorrectAlignment;
     pub const invalidErrorCode = no_panic.invalidErrorCode;
-    pub const castTruncatedData = no_panic.castTruncatedData;
-    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const integerOutOfBounds = no_panic.integerOutOfBounds;
     pub const shlOverflow = no_panic.shlOverflow;
     pub const shrOverflow = no_panic.shrOverflow;
     pub const divideByZero = no_panic.divideByZero;
@@ -37,8 +36,6 @@ pub const panic = struct {
     pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
     pub const invalidEnumValue = no_panic.invalidEnumValue;
     pub const forLenMismatch = no_panic.forLenMismatch;
-    /// Delete after next zig1.wasm update
-    pub const memcpyLenMismatch = copyLenMismatch;
     pub const copyLenMismatch = no_panic.copyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
     pub const noreturnReturned = no_panic.noreturnReturned;
@@ -75,8 +72,7 @@ pub const panic = struct {
     pub const castToNull = no_panic.castToNull;
     pub const incorrectAlignment = no_panic.incorrectAlignment;
     pub const invalidErrorCode = no_panic.invalidErrorCode;
-    pub const castTruncatedData = no_panic.castTruncatedData;
-    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const integerOutOfBounds = no_panic.integerOutOfBounds;
     pub const shlOverflow = no_panic.shlOverflow;
     pub const shrOverflow = no_panic.shrOverflow;
     pub const divideByZero = no_panic.divideByZero;
@@ -86,8 +82,6 @@ pub const panic = struct {
     pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
     pub const invalidEnumValue = no_panic.invalidEnumValue;
     pub const forLenMismatch = no_panic.forLenMismatch;
-    /// Delete after next zig1.wasm update
-    pub const memcpyLenMismatch = copyLenMismatch;
     pub const copyLenMismatch = no_panic.copyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
     pub const noreturnReturned = no_panic.noreturnReturned;
@@ -124,8 +118,7 @@ pub const panic = struct {
     pub const castToNull = no_panic.castToNull;
     pub const incorrectAlignment = no_panic.incorrectAlignment;
     pub const invalidErrorCode = no_panic.invalidErrorCode;
-    pub const castTruncatedData = no_panic.castTruncatedData;
-    pub const negativeToUnsigned = no_panic.negativeToUnsigned;
+    pub const integerOutOfBounds = no_panic.integerOutOfBounds;
     pub const shlOverflow = no_panic.shlOverflow;
     pub const shrOverflow = no_panic.shrOverflow;
     pub const divideByZero = no_panic.divideByZero;
@@ -135,8 +128,6 @@ pub const panic = struct {
     pub const shiftRhsTooBig = no_panic.shiftRhsTooBig;
     pub const invalidEnumValue = no_panic.invalidEnumValue;
     pub const forLenMismatch = no_panic.forLenMismatch;
-    /// Delete after next zig1.wasm update
-    pub const memcpyLenMismatch = copyLenMismatch;
     pub const copyLenMismatch = no_panic.copyLenMismatch;
     pub const memcpyAlias = no_panic.memcpyAlias;
     pub const noreturnReturned = no_panic.noreturnReturned;