diff --git a/src/Sema.zig b/src/Sema.zig index 0afce47e76..6d6a9a13e8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2127,6 +2127,50 @@ fn failWithUseOfAsync(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError return sema.failWithOwnedErrorMsg(msg); } +fn failWithInvalidFieldAccess(sema: *Sema, block: *Block, src: LazySrcLoc, object_ty: Type, field_name: []const u8) CompileError { + const inner_ty = if (object_ty.isSinglePointer()) object_ty.childType() else object_ty; + + if (inner_ty.zigTypeTag() == .Optional) opt: { + var buf: Type.Payload.ElemType = undefined; + const child_ty = inner_ty.optionalChild(&buf); + if (!typeSupportsFieldAccess(child_ty, field_name)) break :opt; + const msg = msg: { + const msg = try sema.errMsg(block, src, "optional type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "consider using '.?', 'orelse', or 'if'", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } else if (inner_ty.zigTypeTag() == .ErrorUnion) err: { + const child_ty = inner_ty.errorUnionPayload(); + if (!typeSupportsFieldAccess(child_ty, field_name)) break :err; + const msg = msg: { + const msg = try sema.errMsg(block, src, "error union type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "consider using 'try', 'catch', or 'if'", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); +} + +fn typeSupportsFieldAccess(ty: Type, field_name: []const u8) bool { + switch (ty.zigTypeTag()) { + .Array => return mem.eql(u8, field_name, "len"), + .Pointer => { + const ptr_info = ty.ptrInfo().data; + if (ptr_info.size == .Slice) { + return mem.eql(u8, field_name, "ptr") or mem.eql(u8, field_name, "len"); + } else if (ptr_info.pointee_type.zigTypeTag() == .Array) { + return mem.eql(u8, field_name, "len"); + } else return false; + }, + .Type, .Struct, .Union => return true, + else => return false, + } +} + /// We don't return a pointer to the new error note because the pointer /// becomes invalid when you add another one. fn errNote( @@ -23321,7 +23365,7 @@ fn fieldVal( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); + return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name); } fn fieldPtr( @@ -23535,7 +23579,7 @@ fn fieldPtr( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); + return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name); } fn fieldCallBind( diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 11fc44747e..dd13087afe 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3815,6 +3815,8 @@ pub const DeclGen = struct { const field_ty = union_obj.fields.values()[field_index].ty; if (union_obj.layout == .Packed) { + if (!field_ty.hasRuntimeBits()) + return llvm_union_ty.constNull(); const non_int_val = try lowerValue(dg, .{ .ty = field_ty, .val = tag_and_val.val }); const ty_bit_size = @intCast(u16, field_ty.bitSize(target)); const small_int_ty = dg.context.intType(ty_bit_size); diff --git a/src/value.zig b/src/value.zig index e5283d1270..e677414c0f 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1113,6 +1113,14 @@ pub const Value = extern union { .bool_true, => return BigIntMutable.init(&space.limbs, 1).toConst(), + .enum_field_index => { + const index = val.castTag(.enum_field_index).?.data; + return BigIntMutable.init(&space.limbs, index).toConst(); + }, + .runtime_value => { + const sub_val = val.castTag(.runtime_value).?.data; + return sub_val.toBigIntAdvanced(space, target, opt_sema); + }, .int_u64 => return BigIntMutable.init(&space.limbs, val.castTag(.int_u64).?.data).toConst(), .int_i64 => return BigIntMutable.init(&space.limbs, val.castTag(.int_i64).?.data).toConst(), .int_big_positive => return val.castTag(.int_big_positive).?.asBigInt(), @@ -1979,6 +1987,13 @@ pub const Value = extern union { .variable, => .gt, + .enum_field_index => return std.math.order(lhs.castTag(.enum_field_index).?.data, 0), + .runtime_value => { + // This is needed to correctly handle hashing the value. + // Checks in Sema should prevent direct comparisons from reaching here. + const val = lhs.castTag(.runtime_value).?.data; + return val.orderAgainstZeroAdvanced(opt_sema); + }, .int_u64 => std.math.order(lhs.castTag(.int_u64).?.data, 0), .int_i64 => std.math.order(lhs.castTag(.int_i64).?.data, 0), .int_big_positive => lhs.castTag(.int_big_positive).?.asBigInt().orderAgainstScalar(0), diff --git a/test/behavior/src.zig b/test/behavior/src.zig index 77e420afcf..e6b84e5d56 100644 --- a/test/behavior/src.zig +++ b/test/behavior/src.zig @@ -32,3 +32,14 @@ test "@src used as a comptime parameter" { const T2 = S.Foo(@src()); try expect(T1 != T2); } + +test "@src in tuple passed to anytype function" { + const S = struct { + fn Foo(a: anytype) u32 { + return a[0].line; + } + }; + const l1 = S.Foo(.{@src()}); + const l2 = S.Foo(.{@src()}); + try expect(l1 != l2); +} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 3b040fcba9..f247bf6fa2 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1492,3 +1492,43 @@ test "union reassignment can use previous value" { a = U{ .b = a.a }; try expect(a.b == 32); } + +test "packed union with zero-bit field" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const S = packed struct { + nested: packed union { + zero: void, + sized: u32, + }, + bar: u32, + + fn doTest(self: @This()) !void { + try expect(self.bar == 42); + } + }; + try S.doTest(.{ .nested = .{ .zero = {} }, .bar = 42 }); +} + +test "reinterpreting enum value inside packed union" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const U = packed union { + tag: enum { a, b }, + val: u8, + + fn doTest() !void { + var u: @This() = .{ .tag = .a }; + u.val += 1; + try expect(u.tag == .b); + } + }; + try U.doTest(); + comptime try U.doTest(); +} diff --git a/test/cases/compile_errors/field_access_of_wrapped_type.zig b/test/cases/compile_errors/field_access_of_wrapped_type.zig new file mode 100644 index 0000000000..9d8a7ef17c --- /dev/null +++ b/test/cases/compile_errors/field_access_of_wrapped_type.zig @@ -0,0 +1,20 @@ +const Foo = struct { + a: i32, +}; +export fn f1() void { + var foo: ?Foo = undefined; + foo.a += 1; +} +export fn f2() void { + var foo: anyerror!Foo = undefined; + foo.a += 1; +} + +// error +// backend=stage2 +// target=native +// +// :6:8: error: optional type '?tmp.Foo' does not support field access +// :6:8: note: consider using '.?', 'orelse', or 'if' +// :10:8: error: error union type 'anyerror!tmp.Foo' does not support field access +// :10:8: note: consider using 'try', 'catch', or 'if'