stage2: implement switching on unions

* AstGen: Move `refToIndex` and `indexToRef` to Zir
 * ZIR: the switch_block_*_* instruction tags are collapsed into one
   switch_block tag which uses 4 bits for flags, and reduces the
   scalar_cases_len field from 32 to 28 bits.
   This freed up more ZIR tags, 2 of which are now used for
   `switch_cond` and `switch_cond_ref` for producing the switch
   condition value. For example, for union values it returns the
   corresponding enum value.
 * switching with multiple cases and ranges is not yet supported because
   I want to change the ZIR encoding to store index pointers into the
   extra array rather than storing prong indexes. This will avoid O(N^2)
   iteration over prongs.
 * AstGen now adds a `switch_cond` on the operand and then passes the
   result of that to the `switch_block` instruction.
 * Sema: partially implement `switch_capture_*` instructions.
 * Sema: `unionToTag` notices if the enum type has only one possible value.
This commit is contained in:
Andrew Kelley 2021-10-19 20:14:10 -07:00
parent 4a76523b92
commit dfb3231959
8 changed files with 428 additions and 480 deletions

View File

@ -11,6 +11,8 @@ const StringIndexAdapter = std.hash_map.StringIndexAdapter;
const StringIndexContext = std.hash_map.StringIndexContext;
const Zir = @import("Zir.zig");
const refToIndex = Zir.refToIndex;
const indexToRef = Zir.indexToRef;
const trace = @import("tracy.zig").trace;
const BuiltinFn = @import("BuiltinFn.zig");
@ -57,6 +59,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
Zir.Inst.Ref => @enumToInt(@field(extra, field.name)),
i32 => @bitCast(u32, @field(extra, field.name)),
Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)),
Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)),
else => @compileError("bad field type"),
});
}
@ -2133,17 +2136,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.slice_sentinel,
.import,
.switch_block,
.switch_block_multi,
.switch_block_else,
.switch_block_else_multi,
.switch_block_under,
.switch_block_under_multi,
.switch_block_ref,
.switch_block_ref_multi,
.switch_block_ref_else,
.switch_block_ref_else_multi,
.switch_block_ref_under,
.switch_block_ref_under_multi,
.switch_cond,
.switch_cond_ref,
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
@ -5127,11 +5121,12 @@ fn fieldAccess(
rl: ResultLoc,
node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref {
if (rl == .ref) {
return addFieldAccess(.field_ptr, gz, scope, .ref, node);
} else {
const access = try addFieldAccess(.field_val, gz, scope, .none, node);
return rvalue(gz, rl, access, node);
switch (rl) {
.ref => return addFieldAccess(.field_ptr, gz, scope, .ref, node),
else => {
const access = try addFieldAccess(.field_val, gz, scope, .none, node);
return rvalue(gz, rl, access, node);
},
}
}
@ -6028,11 +6023,13 @@ fn switchExpr(
}
const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
const operand = try expr(parent_gz, scope, operand_rl, operand_node);
const raw_operand = try expr(parent_gz, scope, operand_rl, operand_node);
const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond;
const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node);
// We need the type of the operand to use as the result location for all the prong items.
const typeof_tag: Zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof;
const operand_ty_inst = try parent_gz.addUnNode(typeof_tag, operand, operand_node);
const item_rl: ResultLoc = .{ .ty = operand_ty_inst };
const cond_ty_inst = try parent_gz.addUnNode(typeof_tag, cond, operand_node);
const item_rl: ResultLoc = .{ .ty = cond_ty_inst };
// These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti.
// This is the optional else prong body.
@ -6050,7 +6047,7 @@ fn switchExpr(
defer block_scope.instructions.deinit(gpa);
// This gets added to the parent block later, after the item expressions.
const switch_block = try parent_gz.addBlock(undefined, switch_node);
const switch_block = try parent_gz.addBlock(.switch_block, switch_node);
// We re-use this same scope for all cases, including the special prong, if any.
var case_scope = parent_gz.makeSubBlock(&block_scope.base);
@ -6203,44 +6200,32 @@ fn switchExpr(
// Now that the item expressions are generated we can add this.
try parent_gz.instructions.append(gpa, switch_block);
const ref_bit: u4 = @boolToInt(any_payload_is_ref);
const multi_bit: u4 = @boolToInt(multi_cases_len != 0);
const special_prong_bits: u4 = @enumToInt(special_prong);
comptime {
assert(@enumToInt(Zir.SpecialProng.none) == 0b00);
assert(@enumToInt(Zir.SpecialProng.@"else") == 0b01);
assert(@enumToInt(Zir.SpecialProng.under) == 0b10);
}
const zir_tags = astgen.instructions.items(.tag);
zir_tags[switch_block] = switch ((ref_bit << 3) | (special_prong_bits << 1) | multi_bit) {
0b0_00_0 => .switch_block,
0b0_00_1 => .switch_block_multi,
0b0_01_0 => .switch_block_else,
0b0_01_1 => .switch_block_else_multi,
0b0_10_0 => .switch_block_under,
0b0_10_1 => .switch_block_under_multi,
0b1_00_0 => .switch_block_ref,
0b1_00_1 => .switch_block_ref_multi,
0b1_01_0 => .switch_block_ref_else,
0b1_01_1 => .switch_block_ref_else_multi,
0b1_10_0 => .switch_block_ref_under,
0b1_10_1 => .switch_block_ref_under_multi,
else => unreachable,
};
const payload_index = astgen.extra.items.len;
const zir_datas = astgen.instructions.items(.data);
zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index);
// Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`.
try astgen.extra.ensureUnusedCapacity(gpa, @as(usize, 2) + // operand, scalar_cases_len
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).Struct.fields.len +
@boolToInt(multi_cases_len != 0) +
special_case_payload.items.len +
scalar_cases_payload.items.len +
multi_cases_payload.items.len);
astgen.extra.appendAssumeCapacity(@enumToInt(operand));
astgen.extra.appendAssumeCapacity(scalar_cases_len);
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
.operand = cond,
.bits = Zir.Inst.SwitchBlock.Bits{
.is_ref = any_payload_is_ref,
.has_multi_cases = multi_cases_len != 0,
.has_else = special_prong == .@"else",
.has_under = special_prong == .under,
.scalar_cases_len = @intCast(u28, scalar_cases_len),
},
});
const zir_datas = astgen.instructions.items(.data);
const zir_tags = astgen.instructions.items(.tag);
zir_datas[switch_block].pl_node.payload_index = payload_index;
if (multi_cases_len != 0) {
astgen.extra.appendAssumeCapacity(multi_cases_len);
}
const strat = rl.strategy(&block_scope);
switch (strat.tag) {
.break_operand => {
@ -10622,21 +10607,6 @@ fn advanceSourceCursor(astgen: *AstGen, source: []const u8, end: usize) void {
astgen.source_column = column;
}
const ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len;
fn indexToRef(inst: Zir.Inst.Index) Zir.Inst.Ref {
return @intToEnum(Zir.Inst.Ref, ref_start_index + inst);
}
fn refToIndex(inst: Zir.Inst.Ref) ?Zir.Inst.Index {
const ref_int = @enumToInt(inst);
if (ref_int >= ref_start_index) {
return ref_int - ref_start_index;
} else {
return null;
}
}
fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.Node.Index) !void {
const gpa = astgen.gpa;
const tree = astgen.tree;

View File

@ -550,18 +550,9 @@ pub fn analyzeBody(
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
.slice_start => try sema.zirSliceStart(block, inst),
.str => try sema.zirStr(block, inst),
.switch_block => try sema.zirSwitchBlock(block, inst, false, .none),
.switch_block_multi => try sema.zirSwitchBlockMulti(block, inst, false, .none),
.switch_block_else => try sema.zirSwitchBlock(block, inst, false, .@"else"),
.switch_block_else_multi => try sema.zirSwitchBlockMulti(block, inst, false, .@"else"),
.switch_block_under => try sema.zirSwitchBlock(block, inst, false, .under),
.switch_block_under_multi => try sema.zirSwitchBlockMulti(block, inst, false, .under),
.switch_block_ref => try sema.zirSwitchBlock(block, inst, true, .none),
.switch_block_ref_multi => try sema.zirSwitchBlockMulti(block, inst, true, .none),
.switch_block_ref_else => try sema.zirSwitchBlock(block, inst, true, .@"else"),
.switch_block_ref_else_multi => try sema.zirSwitchBlockMulti(block, inst, true, .@"else"),
.switch_block_ref_under => try sema.zirSwitchBlock(block, inst, true, .under),
.switch_block_ref_under_multi => try sema.zirSwitchBlockMulti(block, inst, true, .under),
.switch_block => try sema.zirSwitchBlock(block, inst),
.switch_cond => try sema.zirSwitchCond(block, inst, false),
.switch_cond_ref => try sema.zirSwitchCond(block, inst, true),
.switch_capture => try sema.zirSwitchCapture(block, inst, false, false),
.switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true),
.switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false),
@ -5433,11 +5424,80 @@ fn zirSwitchCapture(
const zir_datas = sema.code.instructions.items(.data);
const capture_info = zir_datas[inst].switch_capture;
const switch_info = zir_datas[capture_info.switch_inst].pl_node;
const src = switch_info.src();
const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index);
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node };
const switch_src = switch_info.src();
const operand_is_ref = switch_extra.data.bits.is_ref;
const cond_inst = Zir.refToIndex(switch_extra.data.operand).?;
const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node;
const operand_ptr = sema.resolveInst(cond_info.operand);
const operand_ptr_ty = sema.typeOf(operand_ptr);
const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty;
_ = is_ref;
_ = is_multi;
return sema.fail(block, src, "TODO implement Sema for zirSwitchCapture", .{});
if (is_multi) {
return sema.fail(block, switch_src, "TODO implement Sema for switch capture multi", .{});
}
const scalar_prong = switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index);
const item = sema.resolveInst(scalar_prong.item);
// Previous switch validation ensured this will succeed
const item_val = sema.resolveConstValue(block, .unneeded, item) catch unreachable;
switch (operand_ty.zigTypeTag()) {
.Union => {
const union_obj = operand_ty.cast(Type.Payload.Union).?.data;
const enum_ty = union_obj.tag_ty;
const field_index_usize = enum_ty.enumTagFieldIndex(item_val).?;
const field_index = @intCast(u32, field_index_usize);
const field = union_obj.fields.values()[field_index];
// TODO handle multiple union tags which have compatible types
if (is_ref) {
assert(operand_is_ref);
const field_ty_ptr = try Type.ptr(sema.arena, .{
.pointee_type = field.ty,
.@"addrspace" = .generic,
.mutable = operand_ptr_ty.ptrIsMutable(),
});
if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
return sema.addConstant(
field_ty_ptr,
try Value.Tag.field_ptr.create(sema.arena, .{
.container_ptr = op_ptr_val,
.field_index = field_index,
}),
);
}
try sema.requireRuntimeBlock(block, operand_src);
return block.addStructFieldPtr(operand_ptr, field_index, field.ty);
}
const operand = if (operand_is_ref)
try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src)
else
operand_ptr;
if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
return sema.addConstant(
field.ty,
operand_val.castTag(.@"union").?.data.val,
);
}
try sema.requireRuntimeBlock(block, operand_src);
return block.addStructFieldVal(operand, field_index, field.ty);
},
.ErrorSet => {
return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCapture for error sets", .{});
},
else => {
return sema.fail(block, operand_src, "switch on type '{}' provides no capture value", .{
operand_ty,
});
},
}
}
fn zirSwitchCaptureElse(
@ -5452,96 +5512,108 @@ fn zirSwitchCaptureElse(
const zir_datas = sema.code.instructions.items(.data);
const capture_info = zir_datas[inst].switch_capture;
const switch_info = zir_datas[capture_info.switch_inst].pl_node;
const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index).data;
const src = switch_info.src();
const operand_is_ref = switch_extra.bits.is_ref;
assert(!is_ref or operand_is_ref);
_ = is_ref;
return sema.fail(block, src, "TODO implement Sema for zirSwitchCaptureElse", .{});
}
fn zirSwitchBlock(
fn zirSwitchCond(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_ref: bool,
special_prong: Zir.SpecialProng,
) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
const operand_ptr = sema.resolveInst(inst_data.operand);
const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, src) else operand_ptr;
const operand_ty = sema.typeOf(operand);
switch (operand_ty.zigTypeTag()) {
.Type,
.Void,
.Bool,
.Int,
.Float,
.ComptimeFloat,
.ComptimeInt,
.EnumLiteral,
.Pointer,
.Fn,
.ErrorSet,
.Enum,
=> {
if ((try sema.typeHasOnePossibleValue(block, src, operand_ty))) |opv| {
return sema.addConstant(operand_ty, opv);
}
return operand;
},
.Union => {
const enum_ty = operand_ty.unionTagType() orelse {
const msg = msg: {
const msg = try sema.errMsg(block, src, "switch on untagged union", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, operand_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
};
return sema.unionToTag(block, enum_ty, operand, src);
},
.ErrorUnion,
.NoReturn,
.Array,
.Struct,
.Undefined,
.Null,
.Optional,
.BoundFn,
.Opaque,
.Vector,
.Frame,
.AnyFrame,
=> return sema.fail(block, src, "switch on type '{}'", .{operand_ty}),
}
}
fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
const src_node_offset = inst_data.src_node;
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
const operand_ptr = sema.resolveInst(extra.data.operand);
const operand = if (is_ref)
const operand = if (extra.data.bits.is_ref)
try sema.analyzeLoad(block, src, operand_ptr, operand_src)
else
operand_ptr;
return sema.analyzeSwitch(
block,
operand,
extra.end,
special_prong,
extra.data.cases_len,
0,
inst,
inst_data.src_node,
);
}
var header_extra_index: usize = extra.end;
fn zirSwitchBlockMulti(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_ref: bool,
special_prong: Zir.SpecialProng,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
const extra = sema.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index);
const operand_ptr = sema.resolveInst(extra.data.operand);
const operand = if (is_ref)
try sema.analyzeLoad(block, src, operand_ptr, operand_src)
else
operand_ptr;
return sema.analyzeSwitch(
block,
operand,
extra.end,
special_prong,
extra.data.scalar_cases_len,
extra.data.multi_cases_len,
inst,
inst_data.src_node,
);
}
fn analyzeSwitch(
sema: *Sema,
block: *Block,
operand: Air.Inst.Ref,
extra_end: usize,
special_prong: Zir.SpecialProng,
scalar_cases_len: usize,
multi_cases_len: usize,
switch_inst: Zir.Inst.Index,
src_node_offset: i32,
) CompileError!Air.Inst.Ref {
const gpa = sema.gpa;
const scalar_cases_len = extra.data.bits.scalar_cases_len;
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
const multi_cases_len = sema.code.extra[header_extra_index];
header_extra_index += 1;
break :blk multi_cases_len;
} else 0;
const special_prong = extra.data.bits.specialProng();
const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra_end },
.none => .{ .body = &.{}, .end = header_extra_index },
.under, .@"else" => blk: {
const body_len = sema.code.extra[extra_end];
const extra_body_start = extra_end + 1;
const body_len = sema.code.extra[header_extra_index];
const extra_body_start = header_extra_index + 1;
break :blk .{
.body = sema.code.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
@ -5549,9 +5621,6 @@ fn analyzeSwitch(
},
};
const src: LazySrcLoc = .{ .node_offset = src_node_offset };
const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
const operand_ty = sema.typeOf(operand);
// Validate usage of '_' prongs.
@ -5945,7 +6014,7 @@ fn analyzeSwitch(
.data = undefined,
});
var label: Block.Label = .{
.zir_block = switch_inst,
.zir_block = inst,
.merges = .{
.results = .{},
.br_list = .{},
@ -8934,8 +9003,9 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool)
const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
const field_index = union_obj.fields.getIndex(field_name) orelse
const field_index_usize = union_obj.fields.getIndex(field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
const field_index = @intCast(u32, field_index_usize);
if (is_ref) {
return sema.fail(block, src, "TODO: Sema.zirStructInit is_ref=true union", .{});
@ -8943,12 +9013,10 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool)
const init_inst = sema.resolveInst(item.data.init);
if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| {
const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index);
return sema.addConstant(
resolved_ty,
try Value.Tag.@"union".create(sema.arena, .{
.tag = try Value.Tag.int_u64.create(sema.arena, field_index),
.val = val,
}),
try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }),
);
}
return sema.fail(block, src, "TODO: Sema.zirStructInit for runtime-known union values", .{});
@ -9152,8 +9220,8 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
const val = try sema.resolveConstValue(block, operand_src, type_info);
const union_val = val.cast(Value.Payload.Union).?.data;
const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo);
const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt());
const tag_ty = type_info_ty.unionTagType().?;
const tag_index = tag_ty.enumTagFieldIndex(union_val.tag).?;
switch (@intToEnum(std.builtin.TypeId, tag_index)) {
.Type => return Air.Inst.Ref.type_type,
.Void => return Air.Inst.Ref.void_type,
@ -10819,10 +10887,39 @@ fn fieldVal(
try Value.Tag.@"error".create(arena, .{ .name = name }),
);
},
.Struct, .Opaque, .Union => {
.Union => {
if (child_type.getNamespace()) |namespace| {
if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
return sema.analyzeLoad(block, src, inst, src);
if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
return inst;
}
}
if (child_type.unionTagType()) |enum_ty| {
if (enum_ty.enumFieldIndex(field_name)) |field_index_usize| {
const field_index = @intCast(u32, field_index_usize);
return sema.addConstant(
enum_ty,
try Value.Tag.enum_field_index.create(sema.arena, field_index),
);
}
}
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
},
.Enum => {
if (child_type.getNamespace()) |namespace| {
if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
return inst;
}
}
const field_index_usize = child_type.enumFieldIndex(field_name) orelse
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
const field_index = @intCast(u32, field_index_usize);
const enum_val = try Value.Tag.enum_field_index.create(arena, field_index);
return sema.addConstant(try child_type.copy(arena), enum_val);
},
.Struct, .Opaque => {
if (child_type.getNamespace()) |namespace| {
if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| {
return inst;
}
}
// TODO add note: declared here
@ -10836,35 +10933,6 @@ fn fieldVal(
kw_name, child_type, field_name,
});
},
.Enum => {
if (child_type.getNamespace()) |namespace| {
if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
return sema.analyzeLoad(block, src, inst, src);
}
}
const field_index = child_type.enumFieldIndex(field_name) orelse {
const msg = msg: {
const msg = try sema.errMsg(
block,
src,
"enum '{}' has no member named '{s}'",
.{ child_type, field_name },
);
errdefer msg.destroy(sema.gpa);
try sema.mod.errNoteNonLazy(
child_type.declSrcLoc(),
msg,
"enum declared here",
.{},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
};
const field_index_u32 = @intCast(u32, field_index);
const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32);
return sema.addConstant(try child_type.copy(arena), enum_val);
},
else => return sema.fail(block, src, "type '{}' has no members", .{child_type}),
}
},
@ -11244,6 +11312,17 @@ fn namespaceLookupRef(
return try sema.analyzeDeclRef(decl);
}
fn namespaceLookupVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
namespace: *Namespace,
decl_name: []const u8,
) CompileError!?Air.Inst.Ref {
const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null;
return try sema.analyzeDeclVal(block, src, decl);
}
fn structFieldPtr(
sema: *Sema,
block: *Block,
@ -11370,10 +11449,9 @@ fn unionFieldVal(
const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty);
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
const field_index_big = union_obj.fields.getIndex(field_name) orelse
const field_index_usize = union_obj.fields.getIndex(field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
const field_index = @intCast(u32, field_index_big);
const field_index = @intCast(u32, field_index_usize);
const field = union_obj.fields.values()[field_index];
if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| {
@ -12960,15 +13038,18 @@ fn wrapErrorUnion(
fn unionToTag(
sema: *Sema,
block: *Block,
dest_ty: Type,
enum_ty: Type,
un: Air.Inst.Ref,
un_src: LazySrcLoc,
) !Air.Inst.Ref {
if ((try sema.typeHasOnePossibleValue(block, un_src, enum_ty))) |opv| {
return sema.addConstant(enum_ty, opv);
}
if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| {
return sema.addConstant(dest_ty, un_val.unionTag());
return sema.addConstant(enum_ty, un_val.unionTag());
}
try sema.requireRuntimeBlock(block, un_src);
return block.addTyOp(.get_union_tag, dest_ty, un);
return block.addTyOp(.get_union_tag, enum_ty, un);
}
fn resolvePeerTypes(

View File

@ -72,6 +72,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en
Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),
i32 => @bitCast(i32, code.extra[i]),
Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]),
Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
@ -618,39 +619,16 @@ pub const Inst = struct {
enum_literal,
/// A switch expression. Uses the `pl_node` union field.
/// AST node is the switch, payload is `SwitchBlock`.
/// All prongs of target handled.
switch_block,
/// Same as switch_block, except one or more prongs have multiple items.
/// Payload is `SwitchBlockMulti`
switch_block_multi,
/// Same as switch_block, except has an else prong.
switch_block_else,
/// Same as switch_block_else, except one or more prongs have multiple items.
/// Payload is `SwitchBlockMulti`
switch_block_else_multi,
/// Same as switch_block, except has an underscore prong.
switch_block_under,
/// Same as switch_block, except one or more prongs have multiple items.
/// Payload is `SwitchBlockMulti`
switch_block_under_multi,
/// Same as `switch_block` but the target is a pointer to the value being switched on.
switch_block_ref,
/// Same as `switch_block_multi` but the target is a pointer to the value being switched on.
/// Payload is `SwitchBlockMulti`
switch_block_ref_multi,
/// Same as `switch_block_else` but the target is a pointer to the value being switched on.
switch_block_ref_else,
/// Same as `switch_block_else_multi` but the target is a pointer to the
/// value being switched on.
/// Payload is `SwitchBlockMulti`
switch_block_ref_else_multi,
/// Same as `switch_block_under` but the target is a pointer to the value
/// being switched on.
switch_block_ref_under,
/// Same as `switch_block_under_multi` but the target is a pointer to
/// the value being switched on.
/// Payload is `SwitchBlockMulti`
switch_block_ref_under_multi,
/// Produces the value that will be switched on. For example, for
/// integers, it returns the integer with no modifications. For tagged unions, it
/// returns the active enum tag.
/// Uses the `un_node` union field.
switch_cond,
/// Same as `switch_cond`, except the input operand is a pointer to
/// what will be switched on.
/// Uses the `un_node` union field.
switch_cond_ref,
/// Produces the capture value for a switch prong.
/// Uses the `switch_capture` field.
switch_capture,
@ -1109,17 +1087,8 @@ pub const Inst = struct {
.switch_capture_else,
.switch_capture_else_ref,
.switch_block,
.switch_block_multi,
.switch_block_else,
.switch_block_else_multi,
.switch_block_under,
.switch_block_under_multi,
.switch_block_ref,
.switch_block_ref_multi,
.switch_block_ref_else,
.switch_block_ref_else_multi,
.switch_block_ref_under,
.switch_block_ref_under_multi,
.switch_cond,
.switch_cond_ref,
.validate_struct_init,
.validate_array_init,
.struct_init_empty,
@ -1367,17 +1336,8 @@ pub const Inst = struct {
.ensure_err_payload_void = .un_tok,
.enum_literal = .str_tok,
.switch_block = .pl_node,
.switch_block_multi = .pl_node,
.switch_block_else = .pl_node,
.switch_block_else_multi = .pl_node,
.switch_block_under = .pl_node,
.switch_block_under_multi = .pl_node,
.switch_block_ref = .pl_node,
.switch_block_ref_multi = .pl_node,
.switch_block_ref_else = .pl_node,
.switch_block_ref_else_multi = .pl_node,
.switch_block_ref_under = .pl_node,
.switch_block_ref_under_multi = .pl_node,
.switch_cond = .un_node,
.switch_cond_ref = .un_node,
.switch_capture = .switch_capture,
.switch_capture_ref = .switch_capture,
.switch_capture_multi = .switch_capture,
@ -2466,37 +2426,17 @@ pub const Inst = struct {
index: u32,
};
/// This form is supported when there are no ranges, and exactly 1 item per block.
/// Depending on zir tag and len fields, extra fields trail
/// this one in the extra array.
/// 0. else_body { // If the tag has "_else" or "_under" in it.
/// 0. multi_cases_len: u32 // If has_multi_cases is set.
/// 1. else_body { // If has_else or has_under is set.
/// body_len: u32,
/// body member Index for every body_len
/// }
/// 1. cases: {
/// item: Ref,
/// body_len: u32,
/// body member Index for every body_len
/// } for every cases_len
pub const SwitchBlock = struct {
operand: Ref,
cases_len: u32,
};
/// This form is required when there exists a block which has more than one item,
/// or a range.
/// Depending on zir tag and len fields, extra fields trail
/// this one in the extra array.
/// 0. else_body { // If the tag has "_else" or "_under" in it.
/// body_len: u32,
/// body member Index for every body_len
/// }
/// 1. scalar_cases: { // for every scalar_cases_len
/// 2. scalar_cases: { // for every scalar_cases_len
/// item: Ref,
/// body_len: u32,
/// body member Index for every body_len
/// }
/// 2. multi_cases: { // for every multi_cases_len
/// 3. multi_cases: { // for every multi_cases_len
/// items_len: u32,
/// ranges_len: u32,
/// body_len: u32,
@ -2507,10 +2447,78 @@ pub const Inst = struct {
/// }
/// body member Index for every body_len
/// }
pub const SwitchBlockMulti = struct {
pub const SwitchBlock = struct {
operand: Ref,
scalar_cases_len: u32,
multi_cases_len: u32,
bits: Bits,
pub const Bits = packed struct {
/// If true, one or more prongs have multiple items.
has_multi_cases: bool,
/// If true, there is an else prong. This is mutually exclusive with `has_under`.
has_else: bool,
/// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
has_under: bool,
/// If true, the `operand` is a pointer to the value being switched on.
is_ref: bool,
scalar_cases_len: u28,
pub fn specialProng(bits: Bits) SpecialProng {
const has_else: u2 = @boolToInt(bits.has_else);
const has_under: u2 = @boolToInt(bits.has_under);
return switch ((has_else << 1) | has_under) {
0b00 => .none,
0b01 => .under,
0b10 => .@"else",
0b11 => unreachable,
};
}
};
pub const ScalarProng = struct {
item: Ref,
body: []const Index,
};
/// TODO performance optimization: instead of having this helper method
/// change the definition of switch_capture instruction to store extra_index
/// instead of prong_index. This way, Sema won't be doing O(N^2) iterations
/// over the switch prongs.
pub fn getScalarProng(
self: SwitchBlock,
zir: Zir,
extra_end: usize,
prong_index: usize,
) ScalarProng {
var extra_index: usize = extra_end;
if (self.bits.has_multi_cases) {
extra_index += 1;
}
if (self.bits.specialProng() != .none) {
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
}
var scalar_i: usize = 0;
while (true) : (scalar_i += 1) {
const item = @intToEnum(Ref, zir.extra[extra_index]);
extra_index += 1;
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body.len;
if (scalar_i < prong_index) continue;
return .{
.item = item,
.body = body,
};
}
}
};
pub const Field = struct {
@ -2934,7 +2942,7 @@ pub const Inst = struct {
/// Trailing: for each `imports_len` there is an Item
pub const Imports = struct {
imports_len: Zir.Inst.Index,
imports_len: Inst.Index,
pub const Item = struct {
/// null terminated string index
@ -3077,7 +3085,7 @@ pub fn declIteratorInner(zir: Zir, extra_index: usize, decls_len: u32) DeclItera
/// The iterator would have to allocate memory anyway to iterate. So here we populate
/// an ArrayList as the result.
pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index: u32) !void {
pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_sub_index: u32) !void {
const block_inst = zir.extra[decl_sub_index + 6];
list.clearRetainingCapacity();
@ -3086,8 +3094,8 @@ pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index:
fn findDeclsInner(
zir: Zir,
list: *std.ArrayList(Zir.Inst.Index),
inst: Zir.Inst.Index,
list: *std.ArrayList(Inst.Index),
inst: Inst.Index,
) Allocator.Error!void {
const tags = zir.instructions.items(.tag);
const datas = zir.instructions.items(.data);
@ -3148,19 +3156,7 @@ fn findDeclsInner(
try zir.findDeclsBody(list, then_body);
try zir.findDeclsBody(list, else_body);
},
.switch_block => return findDeclsSwitch(zir, list, inst, .none),
.switch_block_else => return findDeclsSwitch(zir, list, inst, .@"else"),
.switch_block_under => return findDeclsSwitch(zir, list, inst, .under),
.switch_block_ref => return findDeclsSwitch(zir, list, inst, .none),
.switch_block_ref_else => return findDeclsSwitch(zir, list, inst, .@"else"),
.switch_block_ref_under => return findDeclsSwitch(zir, list, inst, .under),
.switch_block_multi => return findDeclsSwitchMulti(zir, list, inst, .none),
.switch_block_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"),
.switch_block_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under),
.switch_block_ref_multi => return findDeclsSwitchMulti(zir, list, inst, .none),
.switch_block_ref_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"),
.switch_block_ref_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under),
.switch_block => return findDeclsSwitch(zir, list, inst),
.suspend_block => @panic("TODO iterate suspend block"),
@ -3170,71 +3166,34 @@ fn findDeclsInner(
fn findDeclsSwitch(
zir: Zir,
list: *std.ArrayList(Zir.Inst.Index),
inst: Zir.Inst.Index,
special_prong: SpecialProng,
list: *std.ArrayList(Inst.Index),
inst: Inst.Index,
) Allocator.Error!void {
const inst_data = zir.instructions.items(.data)[inst].pl_node;
const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index);
const special: struct {
body: []const Inst.Index,
end: usize,
} = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra.end },
.under, .@"else" => blk: {
const body_len = zir.extra[extra.end];
const extra_body_start = extra.end + 1;
break :blk .{
.body = zir.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
};
},
};
try zir.findDeclsBody(list, special.body);
var extra_index: usize = extra.end;
var extra_index: usize = special.end;
var scalar_i: usize = 0;
while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
const multi_cases_len = zir.extra[extra_index];
extra_index += 1;
break :blk multi_cases_len;
} else 0;
const special_prong = extra.data.bits.specialProng();
if (special_prong != .none) {
const body_len = zir.extra[extra_index];
extra_index += 1;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body_len;
extra_index += body.len;
try zir.findDeclsBody(list, body);
}
}
fn findDeclsSwitchMulti(
zir: Zir,
list: *std.ArrayList(Zir.Inst.Index),
inst: Zir.Inst.Index,
special_prong: SpecialProng,
) Allocator.Error!void {
const inst_data = zir.instructions.items(.data)[inst].pl_node;
const extra = zir.extraData(Inst.SwitchBlockMulti, inst_data.payload_index);
const special: struct {
body: []const Inst.Index,
end: usize,
} = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra.end },
.under, .@"else" => blk: {
const body_len = zir.extra[extra.end];
const extra_body_start = extra.end + 1;
break :blk .{
.body = zir.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
};
},
};
try zir.findDeclsBody(list, special.body);
var extra_index: usize = special.end;
{
const scalar_cases_len = extra.data.bits.scalar_cases_len;
var scalar_i: usize = 0;
while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const body_len = zir.extra[extra_index];
extra_index += 1;
@ -3246,7 +3205,7 @@ fn findDeclsSwitchMulti(
}
{
var multi_i: usize = 0;
while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = zir.extra[extra_index];
extra_index += 1;
const ranges_len = zir.extra[extra_index];
@ -3353,3 +3312,18 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
.total_params_len = total_params_len,
};
}
const ref_start_index: u32 = Inst.Ref.typed_value_map.len;
pub fn indexToRef(inst: Inst.Index) Inst.Ref {
return @intToEnum(Inst.Ref, ref_start_index + inst);
}
pub fn refToIndex(inst: Inst.Ref) ?Inst.Index {
const ref_int = @enumToInt(inst);
if (ref_int >= ref_start_index) {
return ref_int - ref_start_index;
} else {
return null;
}
}

View File

@ -234,6 +234,8 @@ const Writer = struct {
.@"await",
.await_nosuspend,
.fence,
.switch_cond,
.switch_cond_ref,
=> try self.writeUnNode(stream, inst),
.ref,
@ -379,19 +381,7 @@ const Writer = struct {
.error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon),
.error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func),
.switch_block => try self.writePlNodeSwitchBr(stream, inst, .none),
.switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
.switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under),
.switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none),
.switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
.switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under),
.switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
.switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
.switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
.switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
.switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
.switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
.switch_block => try self.writePlNodeSwitchBlock(stream, inst),
.field_ptr,
.field_val,
@ -1649,113 +1639,46 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
fn writePlNodeSwitchBr(
self: *Writer,
stream: anytype,
inst: Zir.Inst.Index,
special_prong: Zir.SpecialProng,
) !void {
fn writePlNodeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
const special: struct {
body: []const Zir.Inst.Index,
end: usize,
} = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra.end },
.under, .@"else" => blk: {
const body_len = self.code.extra[extra.end];
const extra_body_start = extra.end + 1;
break :blk .{
.body = self.code.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
};
},
};
var extra_index: usize = extra.end;
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
const multi_cases_len = self.code.extra[extra_index];
extra_index += 1;
break :blk multi_cases_len;
} else 0;
try self.writeInstRef(stream, extra.data.operand);
try self.writeFlag(stream, ", ref", extra.data.bits.is_ref);
self.indent += 2;
if (special.body.len != 0) {
else_prong: {
const special_prong = extra.data.bits.specialProng();
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
else => break :else_prong,
};
const body_len = self.code.extra[extra_index];
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body.len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
try stream.print("{s} => ", .{prong_name});
try self.writeBracedBody(stream, special.body);
try self.writeBracedBody(stream, body);
}
var extra_index: usize = special.end;
{
const scalar_cases_len = extra.data.bits.scalar_cases_len;
var scalar_i: usize = 0;
while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
extra_index += 1;
const body_len = self.code.extra[extra_index];
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
try self.writeInstRef(stream, item_ref);
try stream.writeAll(" => ");
try self.writeBracedBody(stream, body);
}
}
self.indent -= 2;
try stream.writeAll(") ");
try self.writeSrc(stream, inst_data.src());
}
fn writePlNodeSwitchBlockMulti(
self: *Writer,
stream: anytype,
inst: Zir.Inst.Index,
special_prong: Zir.SpecialProng,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index);
const special: struct {
body: []const Zir.Inst.Index,
end: usize,
} = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra.end },
.under, .@"else" => blk: {
const body_len = self.code.extra[extra.end];
const extra_body_start = extra.end + 1;
break :blk .{
.body = self.code.extra[extra_body_start..][0..body_len],
.end = extra_body_start + body_len,
};
},
};
try self.writeInstRef(stream, extra.data.operand);
self.indent += 2;
if (special.body.len != 0) {
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
};
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
try stream.print("{s} => ", .{prong_name});
try self.writeBracedBody(stream, special.body);
}
var extra_index: usize = special.end;
{
var scalar_i: usize = 0;
while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
extra_index += 1;
const body_len = self.code.extra[extra_index];
@ -1772,7 +1695,7 @@ const Writer = struct {
}
{
var multi_i: usize = 0;
while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = self.code.extra[extra_index];
extra_index += 1;
const ranges_len = self.code.extra[extra_index];

View File

@ -116,7 +116,7 @@ pub const Value = extern union {
decl_ref_mut,
/// Pointer to a specific element of an array.
elem_ptr,
/// Pointer to a specific field of a struct.
/// Pointer to a specific field of a struct or union.
field_ptr,
/// A slice of u8 whose memory is managed externally.
bytes,

View File

@ -71,3 +71,34 @@ test "0-sized extern union definition" {
try expect(U.f == 1);
}
const Value = union(enum) {
Int: u64,
Array: [9]u8,
};
const Agg = struct {
val1: Value,
val2: Value,
};
const v1 = Value{ .Int = 1234 };
const v2 = Value{ .Array = [_]u8{3} ** 9 };
const err = @as(anyerror!Agg, Agg{
.val1 = v1,
.val2 = v2,
});
const array = [_]Value{ v1, v2, v1, v2 };
test "unions embedded in aggregate types" {
switch (array[1]) {
Value.Array => |arr| try expect(arr[4] == 3),
else => unreachable,
}
switch ((err catch unreachable).val1) {
Value.Int => |x| try expect(x == 1234),
else => unreachable,
}
}

View File

@ -3,37 +3,6 @@ const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const Tag = std.meta.Tag;
const Value = union(enum) {
Int: u64,
Array: [9]u8,
};
const Agg = struct {
val1: Value,
val2: Value,
};
const v1 = Value{ .Int = 1234 };
const v2 = Value{ .Array = [_]u8{3} ** 9 };
const err = @as(anyerror!Agg, Agg{
.val1 = v1,
.val2 = v2,
});
const array = [_]Value{ v1, v2, v1, v2 };
test "unions embedded in aggregate types" {
switch (array[1]) {
Value.Array => |arr| try expect(arr[4] == 3),
else => unreachable,
}
switch ((err catch unreachable).val1) {
Value.Int => |x| try expect(x == 1234),
else => unreachable,
}
}
const Letter = enum { A, B, C };
const Payload = union(Letter) {
A: i32,

View File

@ -852,7 +852,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ _ = E.d;
\\}
, &.{
":3:10: error: enum 'tmp.E' has no member named 'd'",
":3:11: error: enum 'tmp.E' has no member named 'd'",
":1:11: note: enum declared here",
});