authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-18 14:18:03+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-01-18 14:30:06+00:00
logf7b9f84df2183e01c5f21b7fc5b358b86b73f74d
tree9285e9846e8c65cddbe2dcecc0c54df944264a19
parentf38d7a92cc1ca8059ba909a82f2f944966b52296
signaturelock-open Commit is signed but in an unrecognized format.

incremental: fix enum resolution bugs


3 files changed, 154 insertions(+), 28 deletions(-)

src/Sema.zig+87-22
......@@ -3190,6 +3190,15 @@ fn zirEnumDecl(
31903190
31913191 try sema.declareDependency(.{ .interned = new_ty });
31923192 try sema.addTypeReferenceEntry(src, new_ty);
3193
3194 // Since this is an enum, it has to be resolved immediately.
3195 // `ensureTypeUpToDate` has resolved the new type if necessary.
3196 // We just need to check for resolution failures.
3197 const ty_unit: AnalUnit = .wrap(.{ .type = new_ty });
3198 if (zcu.failed_analysis.contains(ty_unit) or zcu.transitive_failed_analysis.contains(ty_unit)) {
3199 return error.AnalysisFail;
3200 }
3201
31933202 return Air.internedToRef(new_ty);
31943203 },
31953204 .wip => |wip| wip,
......@@ -17801,12 +17810,23 @@ fn zirThis(
1780117810) CompileError!Air.Inst.Ref {
1780217811 _ = extended;
1780317812 const pt = sema.pt;
17813 const zcu = pt.zcu;
1780417814 const namespace = pt.zcu.namespacePtr(block.namespace);
1780517815
1780617816 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
1780717817
1780817818 switch (pt.zcu.intern_pool.indexToKey(new_ty)) {
17809 .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }),
17819 .struct_type, .union_type => try sema.declareDependency(.{ .interned = new_ty }),
17820 .enum_type => {
17821 try sema.declareDependency(.{ .interned = new_ty });
17822 // Since this is an enum, it has to be resolved immediately.
17823 // `ensureTypeUpToDate` has resolved the new type if necessary.
17824 // We just need to check for resolution failures.
17825 const ty_unit: AnalUnit = .wrap(.{ .type = new_ty });
17826 if (zcu.failed_analysis.contains(ty_unit) or zcu.transitive_failed_analysis.contains(ty_unit)) {
17827 return error.AnalysisFail;
17828 }
17829 },
1781017830 .opaque_type => {},
1781117831 else => unreachable,
1781217832 }
......@@ -38330,22 +38350,16 @@ pub fn resolveDeclaredEnum(
3833038350 fields_len: u32,
3833138351 zir: Zir,
3833238352 body_end: usize,
38333) Zcu.CompileError!void {
38353) Zcu.SemaError!void {
3833438354 const zcu = pt.zcu;
3833538355 const gpa = zcu.gpa;
38336 const ip = &zcu.intern_pool;
38337
38338 const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
3833938356
3834038357 const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };
38341 const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } };
3834238358
38343 const anal_unit = AnalUnit.wrap(.{ .type = wip_ty.index });
38344
38345 var arena = std.heap.ArenaAllocator.init(gpa);
38359 var arena: std.heap.ArenaAllocator = .init(gpa);
3834638360 defer arena.deinit();
3834738361
38348 var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa);
38362 var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa);
3834938363 defer comptime_err_ret_trace.deinit();
3835038364
3835138365 var sema: Sema = .{
......@@ -38353,7 +38367,7 @@ pub fn resolveDeclaredEnum(
3835338367 .gpa = gpa,
3835438368 .arena = arena.allocator(),
3835538369 .code = zir,
38356 .owner = anal_unit,
38370 .owner = .wrap(.{ .type = wip_ty.index }),
3835738371 .func_index = .none,
3835838372 .func_is_naked = false,
3835938373 .fn_ret_ty = Type.void,
......@@ -38379,15 +38393,66 @@ pub fn resolveDeclaredEnum(
3837938393 };
3838038394 defer block.instructions.deinit(gpa);
3838138395
38396 sema.resolveDeclaredEnumInner(
38397 &block,
38398 wip_ty,
38399 inst,
38400 tracked_inst,
38401 src,
38402 small,
38403 body,
38404 tag_type_ref,
38405 any_values,
38406 fields_len,
38407 zir,
38408 body_end,
38409 ) catch |err| switch (err) {
38410 error.GenericPoison => unreachable,
38411 error.ComptimeBreak => unreachable,
38412 error.ComptimeReturn => unreachable,
38413 error.OutOfMemory => |e| return e,
38414 error.AnalysisFail => {
38415 if (!zcu.failed_analysis.contains(sema.owner)) {
38416 try zcu.transitive_failed_analysis.put(gpa, sema.owner, {});
38417 }
38418 return error.AnalysisFail;
38419 },
38420 };
38421}
38422
38423fn resolveDeclaredEnumInner(
38424 sema: *Sema,
38425 block: *Block,
38426 wip_ty: InternPool.WipEnumType,
38427 inst: Zir.Inst.Index,
38428 tracked_inst: InternPool.TrackedInst.Index,
38429 src: LazySrcLoc,
38430 small: Zir.Inst.EnumDecl.Small,
38431 body: []const Zir.Inst.Index,
38432 tag_type_ref: Zir.Inst.Ref,
38433 any_values: bool,
38434 fields_len: u32,
38435 zir: Zir,
38436 body_end: usize,
38437) Zcu.CompileError!void {
38438 const pt = sema.pt;
38439 const zcu = pt.zcu;
38440 const gpa = zcu.gpa;
38441 const ip = &zcu.intern_pool;
38442
38443 const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
38444
38445 const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } };
38446
3838238447 const int_tag_ty = ty: {
3838338448 if (body.len != 0) {
38384 _ = try sema.analyzeInlineBody(&block, body, inst);
38449 _ = try sema.analyzeInlineBody(block, body, inst);
3838538450 }
3838638451
3838738452 if (tag_type_ref != .none) {
38388 const ty = try sema.resolveType(&block, tag_ty_src, tag_type_ref);
38453 const ty = try sema.resolveType(block, tag_ty_src, tag_type_ref);
3838938454 if (ty.zigTypeTag(zcu) != .int and ty.zigTypeTag(zcu) != .comptime_int) {
38390 return sema.fail(&block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(pt)});
38455 return sema.fail(block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(pt)});
3839138456 }
3839238457 break :ty ty;
3839338458 } else if (fields_len == 0) {
......@@ -38402,7 +38467,7 @@ pub fn resolveDeclaredEnum(
3840238467
3840338468 if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) {
3840438469 if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(zcu)) {
38405 return sema.fail(&block, src, "non-exhaustive enum specifies every value", .{});
38470 return sema.fail(block, src, "non-exhaustive enum specifies every value", .{});
3840638471 }
3840738472 }
3840838473
......@@ -38434,7 +38499,7 @@ pub fn resolveDeclaredEnum(
3843438499 const tag_val_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
3843538500 extra_index += 1;
3843638501 const tag_inst = try sema.resolveInst(tag_val_ref);
38437 last_tag_val = try sema.resolveConstDefinedValue(&block, .{
38502 last_tag_val = try sema.resolveConstDefinedValue(block, .{
3843838503 .base_node_inst = tracked_inst,
3843938504 .offset = .{ .container_field_name = field_i },
3844038505 }, tag_inst, .{ .simple = .enum_field_tag_value });
......@@ -38447,12 +38512,12 @@ pub fn resolveDeclaredEnum(
3844738512 .offset = .{ .container_field_value = conflict.prev_field_idx },
3844838513 };
3844938514 const msg = msg: {
38450 const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, &sema)});
38515 const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
3845138516 errdefer msg.destroy(gpa);
3845238517 try sema.errNote(other_field_src, msg, "other occurrence here", .{});
3845338518 break :msg msg;
3845438519 };
38455 return sema.failWithOwnedErrorMsg(&block, msg);
38520 return sema.failWithOwnedErrorMsg(block, msg);
3845638521 }
3845738522 break :overflow false;
3845838523 } else if (any_values) overflow: {
......@@ -38469,12 +38534,12 @@ pub fn resolveDeclaredEnum(
3846938534 .offset = .{ .container_field_value = conflict.prev_field_idx },
3847038535 };
3847138536 const msg = msg: {
38472 const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, &sema)});
38537 const msg = try sema.errMsg(value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
3847338538 errdefer msg.destroy(gpa);
3847438539 try sema.errNote(other_field_src, msg, "other occurrence here", .{});
3847538540 break :msg msg;
3847638541 };
38477 return sema.failWithOwnedErrorMsg(&block, msg);
38542 return sema.failWithOwnedErrorMsg(block, msg);
3847838543 }
3847938544 break :overflow false;
3848038545 } else overflow: {
......@@ -38487,9 +38552,9 @@ pub fn resolveDeclaredEnum(
3848738552
3848838553 if (tag_overflow) {
3848938554 const msg = try sema.errMsg(value_src, "enumeration value '{}' too large for type '{}'", .{
38490 last_tag_val.?.fmtValueSema(pt, &sema), int_tag_ty.fmt(pt),
38555 last_tag_val.?.fmtValueSema(pt, sema), int_tag_ty.fmt(pt),
3849138556 });
38492 return sema.failWithOwnedErrorMsg(&block, msg);
38557 return sema.failWithOwnedErrorMsg(block, msg);
3849338558 }
3849438559 }
3849538560}
src/Zcu/PerThread.zig+8-6
......@@ -3669,6 +3669,8 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
36693669/// If the type cannot be recreated because it has been lost, `error.AnalysisFail` is returned.
36703670/// If `ty` is not outdated, that same `InternPool.Index` is returned.
36713671/// If `ty` has already been replaced by this function, the new index will not be returned again.
3672/// Also, if `ty` is an enum, this function will resolve the new type if needed, and the call site
3673/// is responsible for checking `[transitive_]failed_analysis` to detect resolution failures.
36723674pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {
36733675 const zcu = pt.zcu;
36743676 const gpa = zcu.gpa;
......@@ -3878,12 +3880,14 @@ fn recreateUnionType(
38783880 return wip_ty.finish(ip, namespace_index);
38793881}
38803882
3881// TODO: is it safe for this to return `SemaError`? enum type resolution is a bit weird...
3883/// This *does* call `Sema.resolveDeclaredEnum`, but errors from it are not propagated.
3884/// Call sites are resposible for checking `[transitive_]failed_analysis` after `ensureTypeUpToDate`
3885/// returns in order to detect resolution failures.
38823886fn recreateEnumType(
38833887 pt: Zcu.PerThread,
38843888 old_ty: InternPool.Index,
38853889 key: InternPool.Key.NamespaceType.Declared,
3886) Zcu.SemaError!InternPool.Index {
3890) Allocator.Error!InternPool.Index {
38873891 const zcu = pt.zcu;
38883892 const gpa = zcu.gpa;
38893893 const ip = &zcu.intern_pool;
......@@ -3993,10 +3997,8 @@ fn recreateEnumType(
39933997 zir,
39943998 body_end,
39953999 ) catch |err| switch (err) {
3996 error.GenericPoison => unreachable,
3997 error.ComptimeBreak => unreachable,
3998 error.ComptimeReturn => unreachable,
3999 error.AnalysisFail, error.OutOfMemory => |e| return e,
4000 error.OutOfMemory => |e| return e,
4001 error.AnalysisFail => {}, // call sites are responsible for checking `[transitive_]failed_analysis` to detect this
40004002 };
40014003
40024004 return wip_ty.index;
test/incremental/change_enum_tag_type created+59
......@@ -0,0 +1,59 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
4#target=wasm32-wasi-selfhosted
5#update=initial version
6#file=main.zig
7const Tag = u2;
8const Foo = enum(Tag) {
9 a,
10 b,
11 c,
12 d,
13};
14pub fn main() !void {
15 var val: Foo = undefined;
16 val = .a;
17 try std.io.getStdOut().writer().print("{s}\n", .{@tagName(val)});
18}
19const std = @import("std");
20#expect_stdout="a\n"
21#update=too many enum fields
22#file=main.zig
23const Tag = u2;
24const Foo = enum(Tag) {
25 a,
26 b,
27 c,
28 d,
29 e,
30};
31pub fn main() !void {
32 var val: Foo = undefined;
33 val = .a;
34 try std.io.getStdOut().writer().print("{s}\n", .{@tagName(val)});
35}
36comptime {
37 // These can't be true at the same time; analysis should stop as soon as it sees `Foo`
38 std.debug.assert(@intFromEnum(Foo.e) == 4);
39 std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag);
40}
41const std = @import("std");
42#expect_error=ignored
43#update=increase tag size
44#file=main.zig
45const Tag = u3;
46const Foo = enum(Tag) {
47 a,
48 b,
49 c,
50 d,
51 e,
52};
53pub fn main() !void {
54 var val: Foo = undefined;
55 val = .a;
56 try std.io.getStdOut().writer().print("{s}\n", .{@tagName(val)});
57}
58const std = @import("std");
59#expect_stdout="a\n"