| author | |
| committer | |
| log | f7204c7f37ee69462b9ad41a76454831e0df09d0 |
| tree | f6a68e9131f8bf8eec7ce7161209c3a52e84390a |
| parent | 515e1c93e18d81435410f2cb45f3788c6be13fbf |
| parent | e70a0b2a6b329a76e9edc4d22c7b923841703a24 |
| signature |
Sema: improve error message of field access of wrapped type6 files changed, 134 insertions(+), 2 deletions(-)
src/Sema.zig+46-2| ... | ... | @@ -2127,6 +2127,50 @@ fn failWithUseOfAsync(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError |
| 2127 | 2127 | return sema.failWithOwnedErrorMsg(msg); |
| 2128 | 2128 | } |
| 2129 | 2129 | |
| 2130 | fn failWithInvalidFieldAccess(sema: *Sema, block: *Block, src: LazySrcLoc, object_ty: Type, field_name: []const u8) CompileError { | |
| 2131 | const inner_ty = if (object_ty.isSinglePointer()) object_ty.childType() else object_ty; | |
| 2132 | ||
| 2133 | if (inner_ty.zigTypeTag() == .Optional) opt: { | |
| 2134 | var buf: Type.Payload.ElemType = undefined; | |
| 2135 | const child_ty = inner_ty.optionalChild(&buf); | |
| 2136 | if (!typeSupportsFieldAccess(child_ty, field_name)) break :opt; | |
| 2137 | const msg = msg: { | |
| 2138 | const msg = try sema.errMsg(block, src, "optional type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); | |
| 2139 | errdefer msg.destroy(sema.gpa); | |
| 2140 | try sema.errNote(block, src, msg, "consider using '.?', 'orelse', or 'if'", .{}); | |
| 2141 | break :msg msg; | |
| 2142 | }; | |
| 2143 | return sema.failWithOwnedErrorMsg(msg); | |
| 2144 | } else if (inner_ty.zigTypeTag() == .ErrorUnion) err: { | |
| 2145 | const child_ty = inner_ty.errorUnionPayload(); | |
| 2146 | if (!typeSupportsFieldAccess(child_ty, field_name)) break :err; | |
| 2147 | const msg = msg: { | |
| 2148 | const msg = try sema.errMsg(block, src, "error union type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); | |
| 2149 | errdefer msg.destroy(sema.gpa); | |
| 2150 | try sema.errNote(block, src, msg, "consider using 'try', 'catch', or 'if'", .{}); | |
| 2151 | break :msg msg; | |
| 2152 | }; | |
| 2153 | return sema.failWithOwnedErrorMsg(msg); | |
| 2154 | } | |
| 2155 | return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); | |
| 2156 | } | |
| 2157 | ||
| 2158 | fn typeSupportsFieldAccess(ty: Type, field_name: []const u8) bool { | |
| 2159 | switch (ty.zigTypeTag()) { | |
| 2160 | .Array => return mem.eql(u8, field_name, "len"), | |
| 2161 | .Pointer => { | |
| 2162 | const ptr_info = ty.ptrInfo().data; | |
| 2163 | if (ptr_info.size == .Slice) { | |
| 2164 | return mem.eql(u8, field_name, "ptr") or mem.eql(u8, field_name, "len"); | |
| 2165 | } else if (ptr_info.pointee_type.zigTypeTag() == .Array) { | |
| 2166 | return mem.eql(u8, field_name, "len"); | |
| 2167 | } else return false; | |
| 2168 | }, | |
| 2169 | .Type, .Struct, .Union => return true, | |
| 2170 | else => return false, | |
| 2171 | } | |
| 2172 | } | |
| 2173 | ||
| 2130 | 2174 | /// We don't return a pointer to the new error note because the pointer |
| 2131 | 2175 | /// becomes invalid when you add another one. |
| 2132 | 2176 | fn errNote( |
| ... | ... | @@ -23321,7 +23365,7 @@ fn fieldVal( |
| 23321 | 23365 | }, |
| 23322 | 23366 | else => {}, |
| 23323 | 23367 | } |
| 23324 | return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); | |
| 23368 | return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name); | |
| 23325 | 23369 | } |
| 23326 | 23370 | |
| 23327 | 23371 | fn fieldPtr( |
| ... | ... | @@ -23535,7 +23579,7 @@ fn fieldPtr( |
| 23535 | 23579 | }, |
| 23536 | 23580 | else => {}, |
| 23537 | 23581 | } |
| 23538 | return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); | |
| 23582 | return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name); | |
| 23539 | 23583 | } |
| 23540 | 23584 | |
| 23541 | 23585 | fn fieldCallBind( |
src/codegen/llvm.zig+2| ... | ... | @@ -3815,6 +3815,8 @@ pub const DeclGen = struct { |
| 3815 | 3815 | |
| 3816 | 3816 | const field_ty = union_obj.fields.values()[field_index].ty; |
| 3817 | 3817 | if (union_obj.layout == .Packed) { |
| 3818 | if (!field_ty.hasRuntimeBits()) | |
| 3819 | return llvm_union_ty.constNull(); | |
| 3818 | 3820 | const non_int_val = try lowerValue(dg, .{ .ty = field_ty, .val = tag_and_val.val }); |
| 3819 | 3821 | const ty_bit_size = @intCast(u16, field_ty.bitSize(target)); |
| 3820 | 3822 | const small_int_ty = dg.context.intType(ty_bit_size); |
src/value.zig+15| ... | ... | @@ -1113,6 +1113,14 @@ pub const Value = extern union { |
| 1113 | 1113 | .bool_true, |
| 1114 | 1114 | => return BigIntMutable.init(&space.limbs, 1).toConst(), |
| 1115 | 1115 | |
| 1116 | .enum_field_index => { | |
| 1117 | const index = val.castTag(.enum_field_index).?.data; | |
| 1118 | return BigIntMutable.init(&space.limbs, index).toConst(); | |
| 1119 | }, | |
| 1120 | .runtime_value => { | |
| 1121 | const sub_val = val.castTag(.runtime_value).?.data; | |
| 1122 | return sub_val.toBigIntAdvanced(space, target, opt_sema); | |
| 1123 | }, | |
| 1116 | 1124 | .int_u64 => return BigIntMutable.init(&space.limbs, val.castTag(.int_u64).?.data).toConst(), |
| 1117 | 1125 | .int_i64 => return BigIntMutable.init(&space.limbs, val.castTag(.int_i64).?.data).toConst(), |
| 1118 | 1126 | .int_big_positive => return val.castTag(.int_big_positive).?.asBigInt(), |
| ... | ... | @@ -1979,6 +1987,13 @@ pub const Value = extern union { |
| 1979 | 1987 | .variable, |
| 1980 | 1988 | => .gt, |
| 1981 | 1989 | |
| 1990 | .enum_field_index => return std.math.order(lhs.castTag(.enum_field_index).?.data, 0), | |
| 1991 | .runtime_value => { | |
| 1992 | // This is needed to correctly handle hashing the value. | |
| 1993 | // Checks in Sema should prevent direct comparisons from reaching here. | |
| 1994 | const val = lhs.castTag(.runtime_value).?.data; | |
| 1995 | return val.orderAgainstZeroAdvanced(opt_sema); | |
| 1996 | }, | |
| 1982 | 1997 | .int_u64 => std.math.order(lhs.castTag(.int_u64).?.data, 0), |
| 1983 | 1998 | .int_i64 => std.math.order(lhs.castTag(.int_i64).?.data, 0), |
| 1984 | 1999 | .int_big_positive => lhs.castTag(.int_big_positive).?.asBigInt().orderAgainstScalar(0), |
test/behavior/src.zig+11| ... | ... | @@ -32,3 +32,14 @@ test "@src used as a comptime parameter" { |
| 32 | 32 | const T2 = S.Foo(@src()); |
| 33 | 33 | try expect(T1 != T2); |
| 34 | 34 | } |
| 35 | ||
| 36 | test "@src in tuple passed to anytype function" { | |
| 37 | const S = struct { | |
| 38 | fn Foo(a: anytype) u32 { | |
| 39 | return a[0].line; | |
| 40 | } | |
| 41 | }; | |
| 42 | const l1 = S.Foo(.{@src()}); | |
| 43 | const l2 = S.Foo(.{@src()}); | |
| 44 | try expect(l1 != l2); | |
| 45 | } |
test/behavior/union.zig+40| ... | ... | @@ -1492,3 +1492,43 @@ test "union reassignment can use previous value" { |
| 1492 | 1492 | a = U{ .b = a.a }; |
| 1493 | 1493 | try expect(a.b == 32); |
| 1494 | 1494 | } |
| 1495 | ||
| 1496 | test "packed union with zero-bit field" { | |
| 1497 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | |
| 1498 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO | |
| 1499 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | |
| 1500 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | |
| 1501 | ||
| 1502 | const S = packed struct { | |
| 1503 | nested: packed union { | |
| 1504 | zero: void, | |
| 1505 | sized: u32, | |
| 1506 | }, | |
| 1507 | bar: u32, | |
| 1508 | ||
| 1509 | fn doTest(self: @This()) !void { | |
| 1510 | try expect(self.bar == 42); | |
| 1511 | } | |
| 1512 | }; | |
| 1513 | try S.doTest(.{ .nested = .{ .zero = {} }, .bar = 42 }); | |
| 1514 | } | |
| 1515 | ||
| 1516 | test "reinterpreting enum value inside packed union" { | |
| 1517 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | |
| 1518 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO | |
| 1519 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | |
| 1520 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | |
| 1521 | ||
| 1522 | const U = packed union { | |
| 1523 | tag: enum { a, b }, | |
| 1524 | val: u8, | |
| 1525 | ||
| 1526 | fn doTest() !void { | |
| 1527 | var u: @This() = .{ .tag = .a }; | |
| 1528 | u.val += 1; | |
| 1529 | try expect(u.tag == .b); | |
| 1530 | } | |
| 1531 | }; | |
| 1532 | try U.doTest(); | |
| 1533 | comptime try U.doTest(); | |
| 1534 | } |
test/cases/compile_errors/field_access_of_wrapped_type.zig created+20| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | const Foo = struct { | |
| 2 | a: i32, | |
| 3 | }; | |
| 4 | export fn f1() void { | |
| 5 | var foo: ?Foo = undefined; | |
| 6 | foo.a += 1; | |
| 7 | } | |
| 8 | export fn f2() void { | |
| 9 | var foo: anyerror!Foo = undefined; | |
| 10 | foo.a += 1; | |
| 11 | } | |
| 12 | ||
| 13 | // error | |
| 14 | // backend=stage2 | |
| 15 | // target=native | |
| 16 | // | |
| 17 | // :6:8: error: optional type '?tmp.Foo' does not support field access | |
| 18 | // :6:8: note: consider using '.?', 'orelse', or 'if' | |
| 19 | // :10:8: error: error union type 'anyerror!tmp.Foo' does not support field access | |
| 20 | // :10:8: note: consider using 'try', 'catch', or 'if' |