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(...@@ -3190,6 +3190,15 @@ fn zirEnumDecl(
31903190
3191 try sema.declareDependency(.{ .interned = new_ty });3191 try sema.declareDependency(.{ .interned = new_ty });
3192 try sema.addTypeReferenceEntry(src, new_ty);3192 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
3193 return Air.internedToRef(new_ty);3202 return Air.internedToRef(new_ty);
3194 },3203 },
3195 .wip => |wip| wip,3204 .wip => |wip| wip,
...@@ -17801,12 +17810,23 @@ fn zirThis(...@@ -17801,12 +17810,23 @@ fn zirThis(
17801) CompileError!Air.Inst.Ref {17810) CompileError!Air.Inst.Ref {
17802 _ = extended;17811 _ = extended;
17803 const pt = sema.pt;17812 const pt = sema.pt;
17813 const zcu = pt.zcu;
17804 const namespace = pt.zcu.namespacePtr(block.namespace);17814 const namespace = pt.zcu.namespacePtr(block.namespace);
1780517815
17806 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);17816 const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
1780717817
17808 switch (pt.zcu.intern_pool.indexToKey(new_ty)) {17818 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 },
17810 .opaque_type => {},17830 .opaque_type => {},
17811 else => unreachable,17831 else => unreachable,
17812 }17832 }
...@@ -38330,22 +38350,16 @@ pub fn resolveDeclaredEnum(...@@ -38330,22 +38350,16 @@ pub fn resolveDeclaredEnum(
38330 fields_len: u32,38350 fields_len: u32,
38331 zir: Zir,38351 zir: Zir,
38332 body_end: usize,38352 body_end: usize,
38333) Zcu.CompileError!void {38353) Zcu.SemaError!void {
38334 const zcu = pt.zcu;38354 const zcu = pt.zcu;
38335 const gpa = zcu.gpa;38355 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
38340 const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };38357 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 });38359 var arena: std.heap.ArenaAllocator = .init(gpa);
38344
38345 var arena = std.heap.ArenaAllocator.init(gpa);
38346 defer arena.deinit();38360 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);
38349 defer comptime_err_ret_trace.deinit();38363 defer comptime_err_ret_trace.deinit();
3835038364
38351 var sema: Sema = .{38365 var sema: Sema = .{
...@@ -38353,7 +38367,7 @@ pub fn resolveDeclaredEnum(...@@ -38353,7 +38367,7 @@ pub fn resolveDeclaredEnum(
38353 .gpa = gpa,38367 .gpa = gpa,
38354 .arena = arena.allocator(),38368 .arena = arena.allocator(),
38355 .code = zir,38369 .code = zir,
38356 .owner = anal_unit,38370 .owner = .wrap(.{ .type = wip_ty.index }),
38357 .func_index = .none,38371 .func_index = .none,
38358 .func_is_naked = false,38372 .func_is_naked = false,
38359 .fn_ret_ty = Type.void,38373 .fn_ret_ty = Type.void,
...@@ -38379,15 +38393,66 @@ pub fn resolveDeclaredEnum(...@@ -38379,15 +38393,66 @@ pub fn resolveDeclaredEnum(
38379 };38393 };
38380 defer block.instructions.deinit(gpa);38394 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
38382 const int_tag_ty = ty: {38447 const int_tag_ty = ty: {
38383 if (body.len != 0) {38448 if (body.len != 0) {
38384 _ = try sema.analyzeInlineBody(&block, body, inst);38449 _ = try sema.analyzeInlineBody(block, body, inst);
38385 }38450 }
3838638451
38387 if (tag_type_ref != .none) {38452 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);
38389 if (ty.zigTypeTag(zcu) != .int and ty.zigTypeTag(zcu) != .comptime_int) {38454 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)});
38391 }38456 }
38392 break :ty ty;38457 break :ty ty;
38393 } else if (fields_len == 0) {38458 } else if (fields_len == 0) {
...@@ -38402,7 +38467,7 @@ pub fn resolveDeclaredEnum(...@@ -38402,7 +38467,7 @@ pub fn resolveDeclaredEnum(
3840238467
38403 if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) {38468 if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) {
38404 if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(zcu)) {38469 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", .{});
38406 }38471 }
38407 }38472 }
3840838473
...@@ -38434,7 +38499,7 @@ pub fn resolveDeclaredEnum(...@@ -38434,7 +38499,7 @@ pub fn resolveDeclaredEnum(
38434 const tag_val_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);38499 const tag_val_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
38435 extra_index += 1;38500 extra_index += 1;
38436 const tag_inst = try sema.resolveInst(tag_val_ref);38501 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, .{
38438 .base_node_inst = tracked_inst,38503 .base_node_inst = tracked_inst,
38439 .offset = .{ .container_field_name = field_i },38504 .offset = .{ .container_field_name = field_i },
38440 }, tag_inst, .{ .simple = .enum_field_tag_value });38505 }, tag_inst, .{ .simple = .enum_field_tag_value });
...@@ -38447,12 +38512,12 @@ pub fn resolveDeclaredEnum(...@@ -38447,12 +38512,12 @@ pub fn resolveDeclaredEnum(
38447 .offset = .{ .container_field_value = conflict.prev_field_idx },38512 .offset = .{ .container_field_value = conflict.prev_field_idx },
38448 };38513 };
38449 const msg = msg: {38514 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)});
38451 errdefer msg.destroy(gpa);38516 errdefer msg.destroy(gpa);
38452 try sema.errNote(other_field_src, msg, "other occurrence here", .{});38517 try sema.errNote(other_field_src, msg, "other occurrence here", .{});
38453 break :msg msg;38518 break :msg msg;
38454 };38519 };
38455 return sema.failWithOwnedErrorMsg(&block, msg);38520 return sema.failWithOwnedErrorMsg(block, msg);
38456 }38521 }
38457 break :overflow false;38522 break :overflow false;
38458 } else if (any_values) overflow: {38523 } else if (any_values) overflow: {
...@@ -38469,12 +38534,12 @@ pub fn resolveDeclaredEnum(...@@ -38469,12 +38534,12 @@ pub fn resolveDeclaredEnum(
38469 .offset = .{ .container_field_value = conflict.prev_field_idx },38534 .offset = .{ .container_field_value = conflict.prev_field_idx },
38470 };38535 };
38471 const msg = msg: {38536 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)});
38473 errdefer msg.destroy(gpa);38538 errdefer msg.destroy(gpa);
38474 try sema.errNote(other_field_src, msg, "other occurrence here", .{});38539 try sema.errNote(other_field_src, msg, "other occurrence here", .{});
38475 break :msg msg;38540 break :msg msg;
38476 };38541 };
38477 return sema.failWithOwnedErrorMsg(&block, msg);38542 return sema.failWithOwnedErrorMsg(block, msg);
38478 }38543 }
38479 break :overflow false;38544 break :overflow false;
38480 } else overflow: {38545 } else overflow: {
...@@ -38487,9 +38552,9 @@ pub fn resolveDeclaredEnum(...@@ -38487,9 +38552,9 @@ pub fn resolveDeclaredEnum(
3848738552
38488 if (tag_overflow) {38553 if (tag_overflow) {
38489 const msg = try sema.errMsg(value_src, "enumeration value '{}' too large for type '{}'", .{38554 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),
38491 });38556 });
38492 return sema.failWithOwnedErrorMsg(&block, msg);38557 return sema.failWithOwnedErrorMsg(block, msg);
38493 }38558 }
38494 }38559 }
38495}38560}
src/Zcu/PerThread.zig+8-6
...@@ -3669,6 +3669,8 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo...@@ -3669,6 +3669,8 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
3669/// If the type cannot be recreated because it has been lost, `error.AnalysisFail` is returned.3669/// If the type cannot be recreated because it has been lost, `error.AnalysisFail` is returned.
3670/// If `ty` is not outdated, that same `InternPool.Index` is returned.3670/// If `ty` is not outdated, that same `InternPool.Index` is returned.
3671/// If `ty` has already been replaced by this function, the new index will not be returned again.3671/// 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.
3672pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {3674pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {
3673 const zcu = pt.zcu;3675 const zcu = pt.zcu;
3674 const gpa = zcu.gpa;3676 const gpa = zcu.gpa;
...@@ -3878,12 +3880,14 @@ fn recreateUnionType(...@@ -3878,12 +3880,14 @@ fn recreateUnionType(
3878 return wip_ty.finish(ip, namespace_index);3880 return wip_ty.finish(ip, namespace_index);
3879}3881}
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.
3882fn recreateEnumType(3886fn recreateEnumType(
3883 pt: Zcu.PerThread,3887 pt: Zcu.PerThread,
3884 old_ty: InternPool.Index,3888 old_ty: InternPool.Index,
3885 key: InternPool.Key.NamespaceType.Declared,3889 key: InternPool.Key.NamespaceType.Declared,
3886) Zcu.SemaError!InternPool.Index {3890) Allocator.Error!InternPool.Index {
3887 const zcu = pt.zcu;3891 const zcu = pt.zcu;
3888 const gpa = zcu.gpa;3892 const gpa = zcu.gpa;
3889 const ip = &zcu.intern_pool;3893 const ip = &zcu.intern_pool;
...@@ -3993,10 +3997,8 @@ fn recreateEnumType(...@@ -3993,10 +3997,8 @@ fn recreateEnumType(
3993 zir,3997 zir,
3994 body_end,3998 body_end,
3995 ) catch |err| switch (err) {3999 ) catch |err| switch (err) {
3996 error.GenericPoison => unreachable,4000 error.OutOfMemory => |e| return e,
3997 error.ComptimeBreak => unreachable,4001 error.AnalysisFail => {}, // call sites are responsible for checking `[transitive_]failed_analysis` to detect this
3998 error.ComptimeReturn => unreachable,
3999 error.AnalysisFail, error.OutOfMemory => |e| return e,
4000 };4002 };
40014003
4002 return wip_ty.index;4004 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"