Merge pull request #22777 from mlugg/some-bugs

Fix a bunch of frontend bugs
This commit is contained in:
Matthew Lugg 2025-02-06 22:19:25 +00:00 committed by GitHub
commit 43e52ec5c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 395 additions and 74 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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)) {
// 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 '{}'", .{
error_name.fmt(ip), dest_ty.fmt(pt),
err_name.fmt(ip), dest_err_ty.fmt(pt),
});
}
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,
}));
}
return Air.internedToRef((try pt.getCoerced(val, base_dest_ty)).toIntern());
}
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;

View File

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

View File

@ -208,6 +208,7 @@ const Writer = struct {
.anyframe_type,
.bit_not,
.bool_not,
.slice_sentinel_ty,
.negate,
.negate_wrap,
.load,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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=""