This commit is contained in:
Filipe Guerreiro 2025-11-25 11:33:36 +01:00 committed by GitHub
commit 3bf2507de2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 5 deletions

View file

@ -9676,6 +9676,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,
@ -9686,13 +9701,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

View file

@ -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
}