authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-02-08 13:10:21+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-03-10 10:26:09+00:00
log4e92592fee6b414757b2e6c088e20ca9a1b21f44
tree3a77726603576cea01ff2d65905c436ec5184ae6
parent03e23bcbdea2e832307085e5855ca20b51ac9d9a
signaturelock-open Commit is signed but in an unrecognized format.

compiler: error set bugfixes


2 files changed, 124 insertions(+), 56 deletions(-)

src/Sema.zig+87-30
...@@ -15629,10 +15629,10 @@ fn zirCmpEq(...@@ -15629,10 +15629,10 @@ fn zirCmpEq(
1562915629
15630 // comparing null with optionals15630 // comparing null with optionals
15631 if (lhs_ty_tag == .null and (rhs_ty_tag == .optional or rhs_ty.isCPtr(zcu))) {15631 if (lhs_ty_tag == .null and (rhs_ty_tag == .optional or rhs_ty.isCPtr(zcu))) {
15632 return sema.analyzeIsNull(block, rhs, op == .neq);15632 return sema.analyzeIsNull(block, src, rhs, op == .neq);
15633 }15633 }
15634 if (rhs_ty_tag == .null and (lhs_ty_tag == .optional or lhs_ty.isCPtr(zcu))) {15634 if (rhs_ty_tag == .null and (lhs_ty_tag == .optional or lhs_ty.isCPtr(zcu))) {
15635 return sema.analyzeIsNull(block, lhs, op == .neq);15635 return sema.analyzeIsNull(block, src, lhs, op == .neq);
15636 }15636 }
1563715637
15638 if (lhs_ty_tag == .null or rhs_ty_tag == .null) {15638 if (lhs_ty_tag == .null or rhs_ty_tag == .null) {
...@@ -17375,7 +17375,7 @@ fn zirIsNonNull(...@@ -17375,7 +17375,7 @@ fn zirIsNonNull(
17375 const src = block.nodeOffset(inst_data.src_node);17375 const src = block.nodeOffset(inst_data.src_node);
17376 const operand = sema.resolveInst(inst_data.operand);17376 const operand = sema.resolveInst(inst_data.operand);
17377 try sema.checkNullableType(block, src, sema.typeOf(operand));17377 try sema.checkNullableType(block, src, sema.typeOf(operand));
17378 return sema.analyzeIsNull(block, operand, true);17378 return sema.analyzeIsNull(block, src, operand, true);
17379}17379}
1738017380
17381fn zirIsNonNullPtr(17381fn zirIsNonNullPtr(
...@@ -17394,15 +17394,19 @@ fn zirIsNonNullPtr(...@@ -17394,15 +17394,19 @@ fn zirIsNonNullPtr(
17394 const ptr_ty = sema.typeOf(ptr);17394 const ptr_ty = sema.typeOf(ptr);
17395 assert(ptr_ty.zigTypeTag(zcu) == .pointer);17395 assert(ptr_ty.zigTypeTag(zcu) == .pointer);
17396 const nullable_ty = ptr_ty.childType(zcu);17396 const nullable_ty = ptr_ty.childType(zcu);
17397
17397 try sema.checkNullableType(block, src, nullable_ty);17398 try sema.checkNullableType(block, src, nullable_ty);
17399
17400 if (try sema.resolveIsNullFromType(block, src, nullable_ty)) |is_null| {
17401 return .fromValue(.makeBool(!is_null));
17402 }
17403
17398 if (sema.resolveValue(ptr)) |ptr_val| {17404 if (sema.resolveValue(ptr)) |ptr_val| {
17399 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |nullable_val| {17405 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |nullable_val| {
17400 return sema.analyzeIsNull(block, .fromValue(nullable_val), true);17406 return sema.analyzeIsNull(block, src, .fromValue(nullable_val), true);
17401 }17407 }
17402 }17408 }
17403 if (nullable_ty.isNullFromType(zcu)) |is_null| {17409
17404 return if (is_null) .bool_false else .bool_true;
17405 }
17406 return block.addUnOp(.is_non_null_ptr, ptr);17410 return block.addUnOp(.is_non_null_ptr, ptr);
17407}17411}
1740817412
...@@ -30147,25 +30151,25 @@ fn analyzeSliceLen(...@@ -30147,25 +30151,25 @@ fn analyzeSliceLen(
30147fn analyzeIsNull(30151fn analyzeIsNull(
30148 sema: *Sema,30152 sema: *Sema,
30149 block: *Block,30153 block: *Block,
30154 src: LazySrcLoc,
30150 operand: Air.Inst.Ref,30155 operand: Air.Inst.Ref,
30151 invert_logic: bool,30156 invert_logic: bool,
30152) CompileError!Air.Inst.Ref {30157) CompileError!Air.Inst.Ref {
30153 const pt = sema.pt;30158 const pt = sema.pt;
30154 const zcu = pt.zcu;30159 const zcu = pt.zcu;
30155 const result_ty: Type = .bool;30160
30161 if (try sema.resolveIsNullFromType(block, src, sema.typeOf(operand))) |is_null| {
30162 return .fromValue(.makeBool(is_null != invert_logic)); // XOR
30163 }
30164
30156 if (sema.resolveValue(operand)) |opt_val| {30165 if (sema.resolveValue(operand)) |opt_val| {
30157 if (opt_val.isUndef(zcu)) {30166 if (opt_val.isUndef(zcu)) {
30158 return pt.undefRef(result_ty);30167 return pt.undefRef(.bool);
30159 }30168 }
30160 const is_null = opt_val.isNull(zcu);30169 const is_null = opt_val.isNull(zcu);
30161 const bool_value = if (invert_logic) !is_null else is_null;30170 return .fromValue(.makeBool(is_null != invert_logic)); // XOR
30162 return if (bool_value) .bool_true else .bool_false;
30163 }30171 }
3016430172
30165 if (sema.typeOf(operand).isNullFromType(zcu)) |is_null| {
30166 const result = is_null != invert_logic;
30167 return if (result) .bool_true else .bool_false;
30168 }
30169 const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;30173 const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;
30170 return block.addUnOp(air_tag, operand);30174 return block.addUnOp(air_tag, operand);
30171}30175}
...@@ -30218,6 +30222,35 @@ fn resolveIsNonErrVal(...@@ -30218,6 +30222,35 @@ fn resolveIsNonErrVal(
30218 return null;30222 return null;
30219}30223}
3022030224
30225fn resolveIsNullFromType(
30226 sema: *Sema,
30227 block: *Block,
30228 src: LazySrcLoc,
30229 ty: Type,
30230) CompileError!?bool {
30231 const zcu = sema.pt.zcu;
30232 return switch (ty.zigTypeTag(zcu)) {
30233 else => false,
30234 .null => true,
30235 .pointer => switch (ty.ptrSize(zcu)) {
30236 .c => null,
30237 else => false,
30238 },
30239 .optional => {
30240 const payload_ty = ty.optionalChild(zcu);
30241 if (payload_ty.classify(zcu) == .no_possible_value) {
30242 return true; // e.g. `?noreturn`
30243 }
30244 if (payload_ty.zigTypeTag(zcu) == .error_set and
30245 try sema.resolveErrSetIsEmpty(block, src, payload_ty))
30246 {
30247 return true; // e.g. `?error{}`
30248 }
30249 return null;
30250 },
30251 };
30252}
30253
30221fn resolveIsNonErrFromType(30254fn resolveIsNonErrFromType(
30222 sema: *Sema,30255 sema: *Sema,
30223 block: *Block,30256 block: *Block,
...@@ -30226,7 +30259,6 @@ fn resolveIsNonErrFromType(...@@ -30226,7 +30259,6 @@ fn resolveIsNonErrFromType(
30226) CompileError!?Value {30259) CompileError!?Value {
30227 const pt = sema.pt;30260 const pt = sema.pt;
30228 const zcu = pt.zcu;30261 const zcu = pt.zcu;
30229 const ip = &zcu.intern_pool;
30230 const ot = operand_ty.zigTypeTag(zcu);30262 const ot = operand_ty.zigTypeTag(zcu);
30231 if (ot != .error_set and ot != .error_union) return .true;30263 if (ot != .error_set and ot != .error_union) return .true;
30232 if (ot == .error_set) return .false;30264 if (ot == .error_set) return .false;
...@@ -30236,29 +30268,54 @@ fn resolveIsNonErrFromType(...@@ -30236,29 +30268,54 @@ fn resolveIsNonErrFromType(
30236 if (payload_ty.classify(zcu) == .no_possible_value) {30268 if (payload_ty.classify(zcu) == .no_possible_value) {
30237 return .false;30269 return .false;
30238 }30270 }
30271 if (try sema.resolveErrSetIsEmpty(block, src, operand_ty.errorUnionSet(zcu))) {
30272 return .true;
30273 }
30274 return null;
30275}
3023930276
30240 // exception if the error union error set is known to be empty,30277/// Returns `true` iff the error set type `orig_err_set_ty` contains no errors.
30241 // we allow the comparison but always make it comptime-known.30278///
30242 return err_set: switch (ip.errorUnionSet(operand_ty.toIntern())) {30279/// This is used to give comptime answers for whether `error{}!T` is an error or a payload, as well
30243 .anyerror_type => null,30280/// as whether `?error{}` is null. The type `error{}` cannot be NPV, as it has runtime bits, but the
30281/// only value of that type which can exist is `undefined`; semantically it has no "legal" value.
30282/// TODO: this runs into some unsolved language design questions about such types. Performing a
30283/// coercion from `@as(E, undefined)` to `E!T` needs to semantically result in an `undefined` error
30284/// union if our implementation is to be legal, and likewise for coercing `@as(E, undefined)` to
30285/// `?E` (for an error set `E`) because our implementation uses the zero error value at runtime to
30286/// represent `null`. The unsolved problem is the exact rules for `undefined` propagation through
30287/// these types: for instance, what if `@as(u32, undfined)` is coerced to `?u32`? What about error
30288/// union *payloads*, i.e. `@as(u32, undefined)` to `E!u32`? That one is analagous to the optional
30289/// example in some ways, but right now I believe there is code which relies on that coercion giving
30290/// a well-defined error union with an `undefined` payload.
30291/// Relevant issues/discussions:
30292/// * https://github.com/ziglang/zig/issues/1831
30293/// * https://github.com/ziglang/zig/issues/6762
30294/// * https://github.com/ziglang/zig/issues/1831#issuecomment-722129239
30295fn resolveErrSetIsEmpty(
30296 sema: *Sema,
30297 block: *Block,
30298 src: LazySrcLoc,
30299 orig_err_set_ty: Type,
30300) CompileError!bool {
30301 const ip = &sema.pt.zcu.intern_pool;
30302 err_set: switch (orig_err_set_ty.toIntern()) {
30303 .anyerror_type => return false,
30244 .adhoc_inferred_error_set_type => {30304 .adhoc_inferred_error_set_type => {
30245 // This is *our* error set; that is, we're currently analyzing the function30305 // This is *our* error set; that is, we're currently analyzing the function
30246 // which owns it. Trying to resolve it now would cause a dependency loop.30306 // which owns it. Trying to resolve it now would cause a dependency loop.
30247 // Instead, accept that we don't know.30307 // Instead, accept that we don't know.
30248 return null;30308 return false;
30249 },30309 },
30250 else => |set_ty| switch (ip.indexToKey(set_ty)) {30310 else => |err_set_ty| switch (ip.indexToKey(err_set_ty)) {
30251 .error_set_type => |error_set_type| switch (error_set_type.names.len) {30311 .error_set_type => |es| return es.names.len == 0,
30252 0 => .true,
30253 else => null,
30254 },
30255 .inferred_error_set_type => |func_index| {30312 .inferred_error_set_type => |func_index| {
30256 if (sema.fn_ret_ty_ies) |ies| {30313 if (sema.fn_ret_ty_ies) |ies| {
30257 if (ies.func == func_index) {30314 if (ies.func == func_index) {
30258 // This is *our* error set; that is, we're currently analyzing the function30315 // This is *our* error set; that is, we're currently analyzing the function
30259 // which owns it. Trying to resolve it now would cause a dependency loop.30316 // which owns it. Trying to resolve it now would cause a dependency loop.
30260 // Instead, accept that we don't know.30317 // Instead, accept that we don't know.
30261 return null;30318 return false;
30262 }30319 }
30263 }30320 }
30264 try sema.ensureFuncIesResolved(block, src, func_index);30321 try sema.ensureFuncIesResolved(block, src, func_index);
...@@ -30266,7 +30323,7 @@ fn resolveIsNonErrFromType(...@@ -30266,7 +30323,7 @@ fn resolveIsNonErrFromType(
30266 },30323 },
30267 else => unreachable,30324 else => unreachable,
30268 },30325 },
30269 };30326 }
30270}30327}
3027130328
30272fn analyzeIsNonErr(30329fn analyzeIsNonErr(
...@@ -30732,7 +30789,7 @@ fn analyzeSlice(...@@ -30732,7 +30789,7 @@ fn analyzeSlice(
30732 if (block.wantSafety()) {30789 if (block.wantSafety()) {
30733 // requirement: slicing C ptr is non-null30790 // requirement: slicing C ptr is non-null
30734 if (ptr_ptr_child_ty.isCPtr(zcu)) {30791 if (ptr_ptr_child_ty.isCPtr(zcu)) {
30735 const is_non_null = try sema.analyzeIsNull(block, ptr, true);30792 const is_non_null = try block.addUnOp(.is_non_null, ptr);
30736 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);30793 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
30737 }30794 }
3073830795
...@@ -30792,7 +30849,7 @@ fn analyzeSlice(...@@ -30792,7 +30849,7 @@ fn analyzeSlice(
30792 if (block.wantSafety()) {30849 if (block.wantSafety()) {
30793 // requirement: slicing C ptr is non-null30850 // requirement: slicing C ptr is non-null
30794 if (ptr_ptr_child_ty.isCPtr(zcu)) {30851 if (ptr_ptr_child_ty.isCPtr(zcu)) {
30795 const is_non_null = try sema.analyzeIsNull(block, ptr, true);30852 const is_non_null = try block.addUnOp(.is_non_null, ptr);
30796 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);30853 try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
30797 }30854 }
3079830855
src/Type.zig+37-26
...@@ -661,15 +661,28 @@ pub fn toValue(self: Type) Value {...@@ -661,15 +661,28 @@ pub fn toValue(self: Type) Value {
661 return .fromInterned(self.toIntern());661 return .fromInterned(self.toIntern());
662}662}
663663
664/// true if and only if the type takes up space in memory at runtime.664/// Returns `true` if and only if the type takes up space in memory at runtime. This is also exactly
665/// There are two reasons a type will return false:665/// whether or not the backend/linker needs to be sent values of this type to emit to the binary.
666/// * the type is a comptime-only type. For example, the type `type` itself.666///
667/// - note, however, that a struct can have mixed fields and only the non-comptime-only667/// Types without runtime bits have an ABI size of 0; all other types have a non-zero ABI size. All
668/// fields will count towards the ABI size. For example, `struct {T: type, x: i32}`668/// types, regardless of whether they have runtime bits, have a non-zero ABI alignment.
669/// hasRuntimeBits()=true and abiSize()=4669///
670/// * the type has only one possible value, making its ABI size 0.670/// Comptime-only types may still have runtime bits. For instance, `struct { a: u32, b: type }` is a
671/// - an enum with an explicit tag type has the ABI size of the integer tag type,671/// comptime-only type, but it nonetheless has runtime bits and a runtime memory layout (where the
672/// making it one-possible-value only if the integer tag type has 0 bits.672/// field `b: type` is omitted). This is because a user may take a pointer to the field `a`, which
673/// must then be valid to use at runtime.
674///
675/// This function is a trivial wrapper around `classify`:
676///
677/// * Types with one possible value, such as `void`, or no possible value, such as `noreturn`, do
678/// not have runtime bits and have an ABI size of 0 because they simply contain no state.
679///
680/// * Types which are fully comptime, such as `type` and `comptime_int`, do not have runtime bits
681/// because they contain only comptime state. (This compiler implementation also currently makes
682/// types like `struct { x: comptime_int }` fully comptime, but that could change in the future if
683/// we start inserting hidden safety fields into them.)
684///
685/// * All other types contain some runtime state, so have runtime bits and a non-zero ABI size.
673pub fn hasRuntimeBits(ty: Type, zcu: *const Zcu) bool {686pub fn hasRuntimeBits(ty: Type, zcu: *const Zcu) bool {
674 return switch (ty.classify(zcu)) {687 return switch (ty.classify(zcu)) {
675 .no_possible_value, .one_possible_value, .fully_comptime => false,688 .no_possible_value, .one_possible_value, .fully_comptime => false,
...@@ -1576,6 +1589,11 @@ pub fn errorUnionSet(ty: Type, zcu: *const Zcu) Type {...@@ -1576,6 +1589,11 @@ pub fn errorUnionSet(ty: Type, zcu: *const Zcu) Type {
1576}1589}
15771590
1578/// Returns false for unresolved inferred error sets.1591/// Returns false for unresolved inferred error sets.
1592///
1593/// TODO: this function will behave incorrectly under incremental compilation, because in that case
1594/// it may see an outdated resolved error set. This function must be either deleted, or its contract
1595/// changed to require the caller to resolve the error set beforehand. If you must introduce new
1596/// call sites, please make sure the error set in question is definitely resolved first!
1579pub fn errorSetIsEmpty(ty: Type, zcu: *const Zcu) bool {1597pub fn errorSetIsEmpty(ty: Type, zcu: *const Zcu) bool {
1580 const ip = &zcu.intern_pool;1598 const ip = &zcu.intern_pool;
1581 return switch (ty.toIntern()) {1599 return switch (ty.toIntern()) {
...@@ -1594,6 +1612,11 @@ pub fn errorSetIsEmpty(ty: Type, zcu: *const Zcu) bool {...@@ -1594,6 +1612,11 @@ pub fn errorSetIsEmpty(ty: Type, zcu: *const Zcu) bool {
1594/// Returns true if it is an error set that includes anyerror, false otherwise.1612/// Returns true if it is an error set that includes anyerror, false otherwise.
1595/// Note that the result may be a false negative if the type did not get error set1613/// Note that the result may be a false negative if the type did not get error set
1596/// resolution prior to this call.1614/// resolution prior to this call.
1615///
1616/// TODO: this function will behave incorrectly under incremental compilation, because in that case
1617/// it may see an outdated resolved error set. This function must be either deleted, or its contract
1618/// changed to require the caller to resolve the error set beforehand. If you must introduce new
1619/// call sites, please make sure the error set in question is definitely resolved first!
1597pub fn isAnyError(ty: Type, zcu: *const Zcu) bool {1620pub fn isAnyError(ty: Type, zcu: *const Zcu) bool {
1598 const ip = &zcu.intern_pool;1621 const ip = &zcu.intern_pool;
1599 return switch (ty.toIntern()) {1622 return switch (ty.toIntern()) {
...@@ -1616,6 +1639,11 @@ pub fn isError(ty: Type, zcu: *const Zcu) bool {...@@ -1616,6 +1639,11 @@ pub fn isError(ty: Type, zcu: *const Zcu) bool {
1616/// Returns whether ty, which must be an error set, includes an error `name`.1639/// Returns whether ty, which must be an error set, includes an error `name`.
1617/// Might return a false negative if `ty` is an inferred error set and not fully1640/// Might return a false negative if `ty` is an inferred error set and not fully
1618/// resolved yet.1641/// resolved yet.
1642///
1643/// TODO: this function will behave incorrectly under incremental compilation, because in that case
1644/// it may see an outdated resolved error set. This function must be either deleted, or its contract
1645/// changed to require the caller to resolve the error set beforehand. If you must introduce new
1646/// call sites, please make sure the error set in question is definitely resolved first!
1619pub fn errorSetHasField(1647pub fn errorSetHasField(
1620 ty: Type,1648 ty: Type,
1621 name: InternPool.NullTerminatedString,1649 name: InternPool.NullTerminatedString,
...@@ -2939,23 +2967,6 @@ pub fn containerTypeName(ty: Type, ip: *const InternPool) InternPool.NullTermina...@@ -2939,23 +2967,6 @@ pub fn containerTypeName(ty: Type, ip: *const InternPool) InternPool.NullTermina
2939 };2967 };
2940}2968}
29412969
2942/// Returns `true` if a value of this type is always `null`.
2943/// Returns `false` if a value of this type is never `null`.
2944/// Returns `null` otherwise.
2945pub fn isNullFromType(ty: Type, zcu: *const Zcu) ?bool {
2946 if (ty.zigTypeTag(zcu) != .optional and !ty.isCPtr(zcu)) return false;
2947 const payload_ty = ty.optionalChild(zcu);
2948 if (payload_ty.classify(zcu) == .no_possible_value) return true; // `?noreturn` etc
2949
2950 // Although it has runtime bits, `?error{}` is always null. MLUGG TODO: think for a bit...
2951 switch (zcu.intern_pool.indexToKey(payload_ty.toIntern())) {
2952 .error_set_type => |error_set| if (error_set.names.len == 0) return true,
2953 else => {},
2954 }
2955
2956 return null;
2957}
2958
2959pub const UnpackableReason = union(enum) {2970pub const UnpackableReason = union(enum) {
2960 comptime_only,2971 comptime_only,
2961 pointer,2972 pointer,