commit 43e52ec5c5a2267791cb2aaaf37f3f84dbe29d25 (tree)
parent b0ed602d5d9358128471588f00a073f2545809fa
Author: Matthew Lugg <mlugg@mlugg.co.uk>
Date: Thu, 6 Feb 2025 22:19:25 +0000
Merge pull request #22777 from mlugg/some-bugs
Fix a bunch of frontend bugs
Diffstat:
19 files changed, 395 insertions(+), 74 deletions(-)
diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig
@@ -920,7 +920,10 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
const start = try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, full.ast.start);
const end = if (full.ast.end != 0) try expr(gz, scope, .{ .rl = .{ .coerced_ty = .usize_type } }, full.ast.end) else .none;
- const sentinel = if (full.ast.sentinel != 0) try expr(gz, scope, .{ .rl = .none }, full.ast.sentinel) else .none;
+ const sentinel = if (full.ast.sentinel != 0) s: {
+ const sentinel_ty = try gz.addUnNode(.slice_sentinel_ty, lhs, node);
+ break :s try expr(gz, scope, .{ .rl = .{ .coerced_ty = sentinel_ty } }, full.ast.sentinel);
+ } else .none;
try emitDbgStmt(gz, cursor);
if (sentinel != .none) {
const result = try gz.addPlNode(.slice_sentinel, node, Zir.Inst.SliceSentinel{
@@ -2855,6 +2858,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.slice_end,
.slice_sentinel,
.slice_length,
+ .slice_sentinel_ty,
.import,
.switch_block,
.switch_block_ref,
diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig
@@ -599,6 +599,10 @@ pub const Inst = struct {
/// Returns a pointer to the subslice.
/// Uses the `pl_node` field. AST node is the slice syntax. Payload is `SliceLength`.
slice_length,
+ /// Given a value which is a pointer to the LHS of a slice operation, return the sentinel
+ /// type, used as the result type of the slice sentinel (i.e. `s` in `lhs[a..b :s]`).
+ /// Uses the `un_node` field. AST node is the slice syntax. Operand is `lhs`.
+ slice_sentinel_ty,
/// Same as `store` except provides a source location.
/// Uses the `pl_node` union field. Payload is `Bin`.
store_node,
@@ -1185,6 +1189,7 @@ pub const Inst = struct {
.slice_end,
.slice_sentinel,
.slice_length,
+ .slice_sentinel_ty,
.import,
.typeof_log2_int_type,
.resolve_inferred_alloc,
@@ -1472,6 +1477,7 @@ pub const Inst = struct {
.slice_end,
.slice_sentinel,
.slice_length,
+ .slice_sentinel_ty,
.import,
.typeof_log2_int_type,
.switch_block,
@@ -1702,6 +1708,7 @@ pub const Inst = struct {
.slice_end = .pl_node,
.slice_sentinel = .pl_node,
.slice_length = .pl_node,
+ .slice_sentinel_ty = .un_node,
.store_node = .pl_node,
.store_to_inferred_ptr = .pl_node,
.str = .str,
@@ -4162,6 +4169,7 @@ fn findTrackableInner(
.slice_end,
.slice_sentinel,
.slice_length,
+ .slice_sentinel_ty,
.store_node,
.store_to_inferred_ptr,
.str,
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -2196,6 +2196,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
zcu.compile_log_text.shrinkAndFree(gpa, 0);
+ zcu.skip_analysis_errors = false;
+
// Make sure std.zig is inside the import_table. We unconditionally need
// it for start.zig.
const std_mod = zcu.std_mod;
@@ -3208,7 +3210,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
});
}
- if (comp.zcu) |zcu| {
+ if (comp.zcu) |zcu| zcu_errors: {
for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| {
if (error_msg) |msg| {
try addModuleErrorMsg(zcu, &bundle, msg.*);
@@ -3225,6 +3227,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}
}
}
+ if (zcu.skip_analysis_errors) break :zcu_errors;
var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: {
const SortOrder = struct {
zcu: *Zcu,
@@ -3360,7 +3363,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
try comp.link_diags.addMessagesToBundle(&bundle, comp.bin_file);
if (comp.zcu) |zcu| {
- if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {
+ if (!zcu.skip_analysis_errors and bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {
const values = zcu.compile_log_sources.values();
// First one will be the error; subsequent ones will be notes.
const src_loc = values[0].src();
@@ -3861,10 +3864,9 @@ fn performAllTheWorkInner(
// We give up right now! No updating of ZIR refs, no nothing. The idea is that this prevents
// us from invalidating lots of incremental dependencies due to files with e.g. parse errors.
// However, this means our analysis data is invalid, so we want to omit all analysis errors.
- // To do that, let's just clear the analysis roots!
assert(zcu.failed_files.count() > 0); // we will get an error
- zcu.analysis_roots.clear(); // no analysis happened
+ zcu.skip_analysis_errors = true;
return;
}
diff --git a/src/InternPool.zig b/src/InternPool.zig
@@ -4011,7 +4011,7 @@ pub const LoadedStructType = struct {
pub fn haveFieldTypes(s: LoadedStructType, ip: *const InternPool) bool {
const types = s.field_types.get(ip);
- return types.len == 0 or types[0] != .none;
+ return types.len == 0 or types[types.len - 1] != .none;
}
pub fn haveFieldInits(s: LoadedStructType, ip: *const InternPool) bool {
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -504,7 +504,17 @@ pub const Block = struct {
};
}
- pub fn wantSafety(block: *const Block) bool {
+ fn wantSafeTypes(block: *const Block) bool {
+ return block.want_safety orelse switch (block.sema.pt.zcu.optimizeMode()) {
+ .Debug => true,
+ .ReleaseSafe => true,
+ .ReleaseFast => false,
+ .ReleaseSmall => false,
+ };
+ }
+
+ fn wantSafety(block: *const Block) bool {
+ if (block.isComptime()) return false; // runtime safety checks are pointless in comptime blocks
return block.want_safety orelse switch (block.sema.pt.zcu.optimizeMode()) {
.Debug => true,
.ReleaseSafe => true,
@@ -1197,6 +1207,7 @@ fn analyzeBodyInner(
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
.slice_start => try sema.zirSliceStart(block, inst),
.slice_length => try sema.zirSliceLength(block, inst),
+ .slice_sentinel_ty => try sema.zirSliceSentinelTy(block, inst),
.str => try sema.zirStr(inst),
.switch_block => try sema.zirSwitchBlock(block, inst, false),
.switch_block_ref => try sema.zirSwitchBlock(block, inst, true),
@@ -3293,7 +3304,7 @@ fn zirUnionDecl(
.tagged
else if (small.layout != .auto)
.none
- else switch (block.wantSafety()) {
+ else switch (block.wantSafeTypes()) {
true => .safety,
false => .none,
},
@@ -9144,6 +9155,7 @@ fn zirErrUnionCode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
return sema.analyzeErrUnionCode(block, src, operand);
}
+/// If `operand` is comptime-known, asserts that it is an error value rather than a payload value.
fn analyzeErrUnionCode(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
@@ -10753,6 +10765,46 @@ fn zirSliceLength(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return sema.analyzeSlice(block, src, array_ptr, start, len, sentinel, sentinel_src, ptr_src, start_src, end_src, true);
}
+fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
+
+ const src = block.nodeOffset(inst_data.src_node);
+ const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
+ const sentinel_src = block.src(.{ .node_offset_slice_sentinel = inst_data.src_node });
+
+ // This is like the logic in `analyzeSlice`; since we've evaluated the LHS as an lvalue, we will
+ // have a double pointer if it was already a pointer.
+
+ const lhs_ptr_ty = sema.typeOf(try sema.resolveInst(inst_data.operand));
+ const lhs_ty = switch (lhs_ptr_ty.zigTypeTag(zcu)) {
+ .pointer => lhs_ptr_ty.childType(zcu),
+ else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{lhs_ptr_ty.fmt(pt)}),
+ };
+
+ const sentinel_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) {
+ .array => lhs_ty.childType(zcu),
+ .pointer => switch (lhs_ty.ptrSize(zcu)) {
+ .many, .c, .slice => lhs_ty.childType(zcu),
+ .one => s: {
+ const lhs_elem_ty = lhs_ty.childType(zcu);
+ break :s switch (lhs_elem_ty.zigTypeTag(zcu)) {
+ .array => lhs_elem_ty.childType(zcu), // array element type
+ else => return sema.fail(block, sentinel_src, "slice of single-item pointer cannot have sentinel", .{}),
+ };
+ },
+ },
+ else => return sema.fail(block, src, "slice of non-array type '{}'", .{lhs_ty.fmt(pt)}),
+ };
+
+ return Air.internedToRef(sentinel_ty.toIntern());
+}
+
/// Holds common data used when analyzing or resolving switch prong bodies,
/// including setting up captures.
const SwitchProngAnalysis = struct {
@@ -17558,10 +17610,16 @@ fn analyzeCmp(
return sema.cmpNumeric(block, src, lhs, rhs, op, lhs_src, rhs_src);
}
if (is_equality_cmp and lhs_ty.zigTypeTag(zcu) == .error_union and rhs_ty.zigTypeTag(zcu) == .error_set) {
+ if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
+ if (lhs_val.errorUnionIsPayload(zcu)) return .bool_false;
+ }
const casted_lhs = try sema.analyzeErrUnionCode(block, lhs_src, lhs);
return sema.cmpSelf(block, src, casted_lhs, rhs, op, lhs_src, rhs_src);
}
if (is_equality_cmp and lhs_ty.zigTypeTag(zcu) == .error_set and rhs_ty.zigTypeTag(zcu) == .error_union) {
+ if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| {
+ if (rhs_val.errorUnionIsPayload(zcu)) return .bool_false;
+ }
const casted_rhs = try sema.analyzeErrUnionCode(block, rhs_src, rhs);
return sema.cmpSelf(block, src, lhs, casted_rhs, op, lhs_src, rhs_src);
}
@@ -18087,10 +18145,16 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
const ret_ty_opt = try pt.intern(.{ .opt = .{
.ty = try pt.intern(.{ .opt_type = .type_type }),
- .val = if (func_ty_info.return_type == .generic_poison_type)
- .none
- else
- func_ty_info.return_type,
+ .val = opt_val: {
+ const ret_ty: Type = .fromInterned(func_ty_info.return_type);
+ if (ret_ty.toIntern() == .generic_poison_type) break :opt_val .none;
+ if (ret_ty.zigTypeTag(zcu) == .error_union) {
+ if (ret_ty.errorUnionPayload(zcu).toIntern() == .generic_poison_type) {
+ break :opt_val .none;
+ }
+ }
+ break :opt_val ret_ty.toIntern();
+ },
} });
const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
@@ -21401,7 +21465,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
try operand_ty.resolveLayout(pt);
const enum_ty = switch (operand_ty.zigTypeTag(zcu)) {
.enum_literal => {
- const val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, operand, undefined);
+ const val = (try sema.resolveDefinedValue(block, operand_src, operand)).?;
const tag_name = ip.indexToKey(val.toIntern()).enum_literal;
return sema.addNullTerminatedStrLit(tag_name);
},
@@ -22171,7 +22235,7 @@ fn reifyUnion(
.tagged
else if (layout != .auto)
.none
- else switch (block.wantSafety()) {
+ else switch (block.wantSafeTypes()) {
true => .safety,
false => .none,
},
@@ -23117,11 +23181,12 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.builtinCallArgSrc(extra.node, 0);
- const base_dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");
+ const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");
const operand = try sema.resolveInst(extra.rhs);
- const base_operand_ty = sema.typeOf(operand);
- const dest_tag = base_dest_ty.zigTypeTag(zcu);
- const operand_tag = base_operand_ty.zigTypeTag(zcu);
+ const operand_ty = sema.typeOf(operand);
+
+ const dest_tag = dest_ty.zigTypeTag(zcu);
+ const operand_tag = operand_ty.zigTypeTag(zcu);
if (dest_tag != .error_set and dest_tag != .error_union) {
return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(dest_tag)});
@@ -23133,107 +23198,133 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData
return sema.fail(block, src, "cannot cast an error union type to error set", .{});
}
if (dest_tag == .error_union and operand_tag == .error_union and
- base_dest_ty.errorUnionPayload(zcu).toIntern() != base_operand_ty.errorUnionPayload(zcu).toIntern())
+ dest_ty.errorUnionPayload(zcu).toIntern() != operand_ty.errorUnionPayload(zcu).toIntern())
{
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "payload types of error unions must match", .{});
errdefer msg.destroy(sema.gpa);
- const dest_ty = base_dest_ty.errorUnionPayload(zcu);
- const operand_ty = base_operand_ty.errorUnionPayload(zcu);
- try sema.errNote(src, msg, "destination payload is '{}'", .{dest_ty.fmt(pt)});
- try sema.errNote(src, msg, "operand payload is '{}'", .{operand_ty.fmt(pt)});
+ const dest_payload_ty = dest_ty.errorUnionPayload(zcu);
+ const operand_payload_ty = operand_ty.errorUnionPayload(zcu);
+ try sema.errNote(src, msg, "destination payload is '{}'", .{dest_payload_ty.fmt(pt)});
+ try sema.errNote(src, msg, "operand payload is '{}'", .{operand_payload_ty.fmt(pt)});
try addDeclaredHereNote(sema, msg, dest_ty);
try addDeclaredHereNote(sema, msg, operand_ty);
break :msg msg;
});
}
- const dest_ty = if (dest_tag == .error_union) base_dest_ty.errorUnionSet(zcu) else base_dest_ty;
- const operand_ty = if (operand_tag == .error_union) base_operand_ty.errorUnionSet(zcu) else base_operand_ty;
-
- // operand must be defined since it can be an invalid error value
- const maybe_operand_val = try sema.resolveDefinedValue(block, operand_src, operand);
+ const dest_err_ty = switch (dest_tag) {
+ .error_union => dest_ty.errorUnionSet(zcu),
+ .error_set => dest_ty,
+ else => unreachable,
+ };
+ const operand_err_ty = switch (operand_tag) {
+ .error_union => operand_ty.errorUnionSet(zcu),
+ .error_set => operand_ty,
+ else => unreachable,
+ };
const disjoint = disjoint: {
// Try avoiding resolving inferred error sets if we can
- if (!dest_ty.isAnyError(zcu) and dest_ty.errorSetIsEmpty(zcu)) break :disjoint true;
- if (!operand_ty.isAnyError(zcu) and operand_ty.errorSetIsEmpty(zcu)) break :disjoint true;
- if (dest_ty.isAnyError(zcu)) break :disjoint false;
- if (operand_ty.isAnyError(zcu)) break :disjoint false;
- const dest_err_names = dest_ty.errorSetNames(zcu);
+ if (!dest_err_ty.isAnyError(zcu) and dest_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
+ if (!operand_err_ty.isAnyError(zcu) and operand_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
+ if (dest_err_ty.isAnyError(zcu)) break :disjoint false;
+ if (operand_err_ty.isAnyError(zcu)) break :disjoint false;
+ const dest_err_names = dest_err_ty.errorSetNames(zcu);
for (0..dest_err_names.len) |dest_err_index| {
- if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
+ if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
break :disjoint false;
}
- if (!ip.isInferredErrorSetType(dest_ty.toIntern()) and
- !ip.isInferredErrorSetType(operand_ty.toIntern()))
+ if (!ip.isInferredErrorSetType(dest_err_ty.toIntern()) and
+ !ip.isInferredErrorSetType(operand_err_ty.toIntern()))
{
break :disjoint true;
}
- _ = try sema.resolveInferredErrorSetTy(block, src, dest_ty.toIntern());
- _ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_ty.toIntern());
+ _ = try sema.resolveInferredErrorSetTy(block, src, dest_err_ty.toIntern());
+ _ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_err_ty.toIntern());
for (0..dest_err_names.len) |dest_err_index| {
- if (Type.errorSetHasFieldIp(ip, operand_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
+ if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
break :disjoint false;
}
break :disjoint true;
};
- if (disjoint and dest_tag != .error_union) {
+ if (disjoint and !(operand_tag == .error_union and dest_tag == .error_union)) {
return sema.fail(block, src, "error sets '{}' and '{}' have no common errors", .{
- operand_ty.fmt(pt), dest_ty.fmt(pt),
+ operand_err_ty.fmt(pt), dest_err_ty.fmt(pt),
});
}
- if (maybe_operand_val) |val| {
- if (!dest_ty.isAnyError(zcu)) check: {
- const operand_val = zcu.intern_pool.indexToKey(val.toIntern());
- var error_name: InternPool.NullTerminatedString = undefined;
- if (operand_tag == .error_union) {
- if (operand_val.error_union.val != .err_name) break :check;
- error_name = operand_val.error_union.val.err_name;
- } else {
- error_name = operand_val.err.name;
- }
- if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), error_name)) {
- return sema.fail(block, src, "'error.{}' not a member of error set '{}'", .{
- error_name.fmt(ip), dest_ty.fmt(pt),
- });
- }
+ // operand must be defined since it can be an invalid error value
+ if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
+ const err_name: InternPool.NullTerminatedString = switch (operand_tag) {
+ .error_set => ip.indexToKey(operand_val.toIntern()).err.name,
+ .error_union => switch (ip.indexToKey(operand_val.toIntern()).error_union.val) {
+ .err_name => |name| name,
+ .payload => |payload_val| {
+ assert(dest_tag == .error_union); // should be guaranteed from the type checks above
+ return sema.coerce(block, dest_ty, Air.internedToRef(payload_val), operand_src);
+ },
+ },
+ else => unreachable,
+ };
+
+ if (!dest_err_ty.isAnyError(zcu) and !Type.errorSetHasFieldIp(ip, dest_err_ty.toIntern(), err_name)) {
+ return sema.fail(block, src, "'error.{}' not a member of error set '{}'", .{
+ err_name.fmt(ip), dest_err_ty.fmt(pt),
+ });
}
- return Air.internedToRef((try pt.getCoerced(val, base_dest_ty)).toIntern());
+ return Air.internedToRef(try pt.intern(switch (dest_tag) {
+ .error_set => .{ .err = .{
+ .ty = dest_ty.toIntern(),
+ .name = err_name,
+ } },
+ .error_union => .{ .error_union = .{
+ .ty = dest_ty.toIntern(),
+ .val = .{ .err_name = err_name },
+ } },
+ else => unreachable,
+ }));
}
- try sema.requireRuntimeBlock(block, src, operand_src);
const err_int_ty = try pt.errorIntType();
- if (block.wantSafety() and !dest_ty.isAnyError(zcu) and
- dest_ty.toIntern() != .adhoc_inferred_error_set_type and
+ if (block.wantSafety() and !dest_err_ty.isAnyError(zcu) and
+ dest_err_ty.toIntern() != .adhoc_inferred_error_set_type and
zcu.backendSupportsFeature(.error_set_has_value))
{
- if (dest_tag == .error_union) {
- const err_code = try sema.analyzeErrUnionCode(block, operand_src, operand);
- const err_int = try block.addBitCast(err_int_ty, err_code);
- const zero_err = try pt.intRef(try pt.errorIntType(), 0);
+ const err_code_inst = switch (operand_tag) {
+ .error_set => operand,
+ .error_union => try block.addTyOp(.unwrap_errunion_err, operand_err_ty, operand),
+ else => unreachable,
+ };
+ const err_int_inst = try block.addBitCast(err_int_ty, err_code_inst);
- const is_zero = try block.addBinOp(.cmp_eq, err_int, zero_err);
+ if (dest_tag == .error_union) {
+ const zero_err = try pt.intRef(err_int_ty, 0);
+ const is_zero = try block.addBinOp(.cmp_eq, err_int_inst, zero_err);
if (disjoint) {
// Error must be zero.
try sema.addSafetyCheck(block, src, is_zero, .invalid_error_code);
} else {
// Error must be in destination set or zero.
- const has_value = try block.addTyOp(.error_set_has_value, dest_ty, err_code);
+ const has_value = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
const ok = try block.addBinOp(.bool_or, has_value, is_zero);
try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
}
} else {
- const err_int_inst = try block.addBitCast(err_int_ty, operand);
- const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst);
+ const ok = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
}
}
- return block.addBitCast(base_dest_ty, operand);
+
+ if (operand_tag == .error_set and dest_tag == .error_union) {
+ const err_val = try block.addBitCast(dest_err_ty, operand);
+ return block.addTyOp(.wrap_errunion_err, dest_ty, err_val);
+ } else {
+ return block.addBitCast(dest_ty, operand);
+ }
}
fn zirPtrCastFull(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
@@ -28952,6 +29043,7 @@ fn elemValArray(
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_ty, array_src);
+ try sema.validateRuntimeValue(block, array_src, array);
if (oob_safety and block.wantSafety()) {
// Runtime check is only needed if unable to comptime check.
@@ -29016,6 +29108,7 @@ fn elemPtrArray(
if (!init) {
try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.elemType2(zcu), array_ty, array_ptr_src);
+ try sema.validateRuntimeValue(block, array_ptr_src, array_ptr);
}
// Runtime check is only needed if unable to comptime check.
@@ -29073,6 +29166,7 @@ fn elemValSlice(
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_ty, slice_src);
+ try sema.validateRuntimeValue(block, slice_src, slice);
if (oob_safety and block.wantSafety()) {
const len_inst = if (maybe_slice_val) |slice_val|
@@ -29129,6 +29223,7 @@ fn elemPtrSlice(
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_ty, slice_src);
+ try sema.validateRuntimeValue(block, slice_src, slice);
if (oob_safety and block.wantSafety()) {
const len_inst = len: {
@@ -33889,6 +33984,17 @@ fn resolvePeerTypes(
else => {},
}
+ // Fast path: check if everything has the same type to bypass the main PTR logic.
+ same_type: {
+ const ty = sema.typeOf(instructions[0]);
+ for (instructions[1..]) |inst| {
+ if (sema.typeOf(inst).toIntern() != ty.toIntern()) {
+ break :same_type;
+ }
+ }
+ return ty;
+ }
+
const peer_tys = try sema.arena.alloc(?Type, instructions.len);
const peer_vals = try sema.arena.alloc(?Value, instructions.len);
@@ -34562,14 +34668,14 @@ fn resolvePeerTypesInner(
}
// Clear existing sentinel
ptr_info.sentinel = .none;
- switch (ip.indexToKey(ptr_info.child)) {
+ if (ptr_info.flags.size == .one) switch (ip.indexToKey(ptr_info.child)) {
.array_type => |array_type| ptr_info.child = (try pt.arrayType(.{
.len = array_type.len,
.child = array_type.child,
.sentinel = .none,
})).toIntern(),
else => {},
- }
+ };
}
opt_ptr_info = ptr_info;
diff --git a/src/Zcu.zig b/src/Zcu.zig
@@ -181,6 +181,8 @@ analysis_roots: std.BoundedArray(*Package.Module, 3) = .{},
/// Allocated into `gpa`.
resolved_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = null,
+skip_analysis_errors: bool = false,
+
stage1_flags: packed struct {
have_winmain: bool = false,
have_wwinmain: bool = false,
diff --git a/src/print_zir.zig b/src/print_zir.zig
@@ -208,6 +208,7 @@ const Writer = struct {
.anyframe_type,
.bit_not,
.bool_not,
+ .slice_sentinel_ty,
.negate,
.negate_wrap,
.load,
diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig
@@ -2649,3 +2649,19 @@ test "bitcast vector" {
const bigsum: u32x8 = @bitCast(zerox32);
try std.testing.expectEqual(0, @reduce(.Add, bigsum));
}
+
+test "peer type resolution: slice of sentinel-terminated array" {
+ var f: bool = undefined;
+ f = false;
+
+ const a: [][2:0]u8 = &.{};
+ const b: []const [2:0]u8 = &.{.{ 10, 20 }};
+
+ const result = if (f) a else b;
+
+ comptime assert(@TypeOf(result) == []const [2:0]u8);
+ try expect(result.len == 1);
+ try expect(result[0].len == 2);
+ try expect(result[0][0] == 10);
+ try expect(result[0][1] == 20);
+}
diff --git a/test/behavior/error.zig b/test/behavior/error.zig
@@ -1060,9 +1060,24 @@ test "errorCast to adhoc inferred error set" {
try std.testing.expect((try S.baz()) == 1234);
}
-test "errorCast from error sets to error unions" {
- const err_union: Set1!void = @errorCast(error.A);
- try expectError(error.A, err_union);
+test "@errorCast from error set to error union" {
+ const S = struct {
+ fn doTheTest(set: error{ A, B }) error{A}!i32 {
+ return @errorCast(set);
+ }
+ };
+ try expectError(error.A, S.doTheTest(error.A));
+ try expectError(error.A, comptime S.doTheTest(error.A));
+}
+
+test "@errorCast from error union to error union" {
+ const S = struct {
+ fn doTheTest(set: error{ A, B }!i32) error{A}!i32 {
+ return @errorCast(set);
+ }
+ };
+ try expectError(error.A, S.doTheTest(error.A));
+ try expectError(error.A, comptime S.doTheTest(error.A));
}
test "result location initialization of error union with OPV payload" {
@@ -1100,3 +1115,14 @@ test "return error union with i65" {
fn add(x: i65, y: i65) anyerror!i65 {
return x + y;
}
+
+test "compare error union to error set" {
+ const S = struct {
+ fn doTheTest(val: error{Foo}!i32) !void {
+ if (error.Foo == val) return error.Unexpected;
+ if (val == error.Foo) return error.Unexpected;
+ }
+ };
+ try S.doTheTest(0);
+ try comptime S.doTheTest(0);
+}
diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig
@@ -1751,3 +1751,14 @@ test "comptime labeled block implicit exit" {
};
comptime assert(result == {});
}
+
+test "comptime block has intermediate runtime-known values" {
+ const arr: [2]u8 = .{ 1, 2 };
+
+ var idx: usize = undefined;
+ idx = 0;
+
+ comptime {
+ _ = arr[idx];
+ }
+}
diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig
@@ -711,3 +711,20 @@ test "inline call propagates comptime-known argument to generic parameter and re
try expect(a1 == 12340);
try expect(b1 == 12340);
}
+
+test "inline function return type is evaluated at comptime" {
+ const S = struct {
+ inline fn assertComptimeAndRet(x: anytype) @TypeOf(x) {
+ if (!@inComptime()) comptime unreachable;
+ return x;
+ }
+
+ inline fn foo(val: anytype) assertComptimeAndRet(u16) {
+ return val;
+ }
+ };
+
+ const result = S.foo(123);
+ comptime assert(@TypeOf(result) == u16);
+ try expect(result == 123);
+}
diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig
@@ -589,3 +589,19 @@ comptime {
// should override the result of the previous analysis.
for (0..2) |_| _ = fn (void) void;
}
+
+test "generic parameter resolves to comptime-only type but is not marked comptime" {
+ const S = struct {
+ fn foo(comptime T: type, rt_false: bool, func: fn (T) void) T {
+ if (rt_false) _ = foo(T, rt_false, func);
+ return 123;
+ }
+ fn bar(_: u8) void {}
+ };
+
+ const rt_result = S.foo(u8, false, S.bar);
+ try expect(rt_result == 123);
+
+ const ct_result = comptime S.foo(u8, false, S.bar);
+ comptime std.debug.assert(ct_result == 123);
+}
diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig
@@ -1037,3 +1037,16 @@ test "peer slices keep abi alignment with empty struct" {
comptime assert(@TypeOf(slice) == []const u32);
try expect(slice.len == 0);
}
+
+test "sentinel expression in slice operation has result type" {
+ const sentinel = std.math.maxInt(u16);
+
+ const arr: [3]u16 = .{ 1, 2, sentinel };
+ const slice = arr[0..2 :@intCast(sentinel)];
+
+ comptime assert(@TypeOf(slice) == *const [2:sentinel]u16);
+ comptime assert(slice[2] == sentinel);
+ comptime assert(slice.len == 2);
+ comptime assert(slice[0] == 1);
+ comptime assert(slice[1] == 2);
+}
diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig
@@ -675,3 +675,12 @@ test "@typeInfo only contains pub decls" {
try std.testing.expectEqualStrings("Enum", decls[0].name);
try std.testing.expectEqualStrings("Struct", decls[1].name);
}
+
+test "@typeInfo function with generic return type and inferred error set" {
+ const S = struct {
+ fn testFn(comptime T: type) !T {}
+ };
+
+ const ret_ty = @typeInfo(@TypeOf(S.testFn)).@"fn".return_type;
+ comptime assert(ret_ty == null);
+}
diff --git a/test/behavior/union.zig b/test/behavior/union.zig
@@ -2322,3 +2322,19 @@ test "assign global tagged union" {
try expect(U.global == .b);
try expect(U.global.b == 123456);
}
+
+test "set mutable union by switching on same union" {
+ const U = union(enum) {
+ foo,
+ bar: usize,
+ };
+
+ var val: U = .foo;
+ val = switch (val) {
+ .foo => .{ .bar = 2 },
+ .bar => .foo,
+ };
+
+ try expect(val == .bar);
+ try expect(val.bar == 2);
+}
diff --git a/test/cases/compile_errors/for_comptime_array_pointer.zig b/test/cases/compile_errors/for_comptime_array_pointer.zig
@@ -0,0 +1,12 @@
+export fn foo() void {
+ comptime var elems: [3]u32 = undefined;
+ for (&elems) |*elem| {
+ _ = elem;
+ }
+}
+
+// error
+//
+// :3:10: error: runtime value contains reference to comptime var
+// :3:10: note: comptime var pointers are not available at runtime
+// :2:34: note: 'runtime_value' points to comptime var declared here
diff --git a/test/cases/compile_errors/struct_depends_on_itself_via_non_initial_field.zig b/test/cases/compile_errors/struct_depends_on_itself_via_non_initial_field.zig
@@ -0,0 +1,12 @@
+const A = struct {
+ a: u8,
+ bytes: [@sizeOf(A)]u8,
+};
+
+comptime {
+ _ = A;
+}
+
+// error
+//
+// :1:11: error: struct 'tmp.A' depends on itself
diff --git a/test/cases/compile_errors/tagName_on_undef_enum_literal.zig b/test/cases/compile_errors/tagName_on_undef_enum_literal.zig
@@ -0,0 +1,8 @@
+comptime {
+ const undef: @Type(.enum_literal) = undefined;
+ _ = @tagName(undef);
+}
+
+// error
+//
+// :3:18: error: use of undefined value here causes undefined behavior
diff --git a/test/incremental/analysis_error_and_syntax_error b/test/incremental/analysis_error_and_syntax_error
@@ -0,0 +1,42 @@
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
+#target=x86_64-windows-cbe
+#target=wasm32-wasi-selfhosted
+#update=initial version
+#file=main.zig
+pub fn main() !void {
+ @compileError("uh oh");
+}
+#expect_error=main.zig:2:5: error: uh oh
+
+#update=add parse error
+#file=main.zig
+pub fn main() !void {
+ @compileError("uh oh");
+#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
+
+#update=fix parse error
+#file=main.zig
+pub fn main() !void {
+ @compileError("uh oh");
+}
+#expect_error=main.zig:2:5: error: uh oh
+
+#update=add parse error again
+#file=main.zig
+pub fn main() !void {
+ @compileError("uh oh");
+#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
+
+#update=comment @compileError call
+#file=main.zig
+pub fn main() !void {
+ //@compileError("uh oh");
+#expect_error=main.zig:3:1: error: expected statement, found 'EOF'
+
+#update=fix parse error again
+#file=main.zig
+pub fn main() !void {
+ //@compileError("uh oh");
+}
+#expect_stdout=""