Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9641,6 +9641,21 @@ fn zirAsShiftOperand(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
return sema.analyzeAs(block, src, extra.dest_type, extra.operand, true);
}

fn validateCastDestType(
sema: *Sema,
block: *Block,
dest_ty: Type,
src: LazySrcLoc,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (dest_ty.zigTypeTag(zcu)) {
.@"opaque" => return sema.fail(block, src, "cannot cast to opaque type '{f}'", .{dest_ty.fmt(pt)}),
.noreturn => return sema.fail(block, src, "cannot cast to noreturn", .{}),
else => {},
}
}

fn analyzeAs(
sema: *Sema,
block: *Block,
Expand All @@ -9651,13 +9666,48 @@ fn analyzeAs(
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;

// Optimize nested cast builtins to prevent redundant coercion and circular dependencies.
if (zir_operand.toIndex()) |operand_index| {
const operand_tag = sema.code.instructions.items(.tag)[@intFromEnum(operand_index)];
switch (operand_tag) {
// These builtins perform their own type-directed casting
.int_cast, .float_cast, .ptr_cast, .truncate => {
// Resolve dest_ty first so inner cast can use it as type context
const dest_ty = try sema.resolveTypeOrPoison(block, src, zir_dest_type) orelse {
return sema.resolveInst(zir_operand);
};

try sema.validateCastDestType(block, dest_ty, src);

// Now analyze inner cast with dest_ty already resolved
const operand = try sema.resolveInst(zir_operand);

// If inner cast already produced the correct type, skip redundant coercion
if (sema.typeOf(operand).eql(dest_ty, zcu)) {
return operand;
}

// Otherwise perform outer coercion (handles vectors/arrays and other edge cases)
const is_ret = if (zir_dest_type.toIndex()) |ptr_index|
sema.code.instructions.items(.tag)[@intFromEnum(ptr_index)] == .ret_type
else
false;
return sema.coerceExtra(block, dest_ty, operand, src, .{
.is_ret = is_ret,
.no_cast_to_comptime_int = no_cast_to_comptime_int
}) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
};
},
else => {},
}
}

const operand = try sema.resolveInst(zir_operand);
const dest_ty = try sema.resolveTypeOrPoison(block, src, zir_dest_type) orelse return operand;
switch (dest_ty.zigTypeTag(zcu)) {
.@"opaque" => return sema.fail(block, src, "cannot cast to opaque type '{f}'", .{dest_ty.fmt(pt)}),
.noreturn => return sema.fail(block, src, "cannot cast to noreturn", .{}),
else => {},
}
try sema.validateCastDestType(block, dest_ty, src);

const is_ret = if (zir_dest_type.toIndex()) |ptr_index|
sema.code.instructions.items(.tag)[@intFromEnum(ptr_index)] == .ret_type
Expand Down
18 changes: 18 additions & 0 deletions test/behavior/cast_int.zig
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,21 @@ test "load non byte-sized value in union" {
try expect(pieces[1].type == .PAWN);
try expect(pieces[1].color == .BLACK);
}
test "@as with nested @intCast in loop with optional" {
// Regression test for circular dependency bug in Sema.analyzeAs
// where @as(DestType, @intCast(value)) would cause compilation timeout
// in complex control flow contexts (loop + short-circuit OR + optional unwrap)
const arr = [_]bool{ true, false, true };
var opt: ?[]const bool = &arr;
var pid: u32 = 1;
_ = .{ &opt, &pid };

var i: usize = 0;
while (i < 3) : (i += 1) {
// This pattern previously caused infinite recursion during compilation
if (opt == null or (opt.?)[@as(usize, @intCast(pid))] == false) {
break;
}
}
try expect(i == 0); // Should break immediately since arr[1] == false
}