| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const mem = std.mem; |
| 4 | |
| 5 | const Sema = @import("../Sema.zig"); |
| 6 | const Block = Sema.Block; |
| 7 | const Type = @import("../Type.zig"); |
| 8 | const Value = @import("../Value.zig"); |
| 9 | const Zcu = @import("../Zcu.zig"); |
| 10 | const CompileError = Zcu.CompileError; |
| 11 | const SemaError = Zcu.SemaError; |
| 12 | const LazySrcLoc = Zcu.LazySrcLoc; |
| 13 | const InternPool = @import("../InternPool.zig"); |
| 14 | const Alignment = InternPool.Alignment; |
| 15 | const arith = @import("arith.zig"); |
| 16 | const trace = @import("../tracy.zig").trace; |
| 17 | |
| 18 | pub const LayoutResolveReason = enum { |
| 19 | variable, |
| 20 | constant, |
| 21 | parameter, |
| 22 | return_type, |
| 23 | field, |
| 24 | backing_enum, |
| 25 | init, |
| 26 | coerce, |
| 27 | ptr_access, |
| 28 | ptr_offset, |
| 29 | field_used, |
| 30 | field_queried, |
| 31 | size_of, |
| 32 | align_of, |
| 33 | type_info, |
| 34 | align_check, |
| 35 | bit_ptr_child, |
| 36 | @"export", |
| 37 | @"extern", |
| 38 | asm_out_type, |
| 39 | std_lang_type, |
| 40 | |
| 41 | /// Written after string: "while resolving type 'T' " |
| 42 | /// e.g. "while resolving type 'MyStruct' for variable declared here" |
| 43 | pub fn msg(r: LayoutResolveReason) []const u8 { |
| 44 | return switch (r) { |
| 45 | // zig fmt: off |
| 46 | .variable => "for variable declared here", |
| 47 | .constant => "for constant declared here", |
| 48 | .parameter => "for function parameter declared here", |
| 49 | .return_type => "for function return type declared here", |
| 50 | .field => "for field declared here", |
| 51 | .backing_enum => "for backing enum type declared here", |
| 52 | .init => "for initialization performed here", |
| 53 | .coerce => "for coercion performed here", |
| 54 | .ptr_access => "for pointer access here", |
| 55 | .ptr_offset => "for pointer offset here", |
| 56 | .field_used => "for field usage here", |
| 57 | .field_queried => "for field query here", |
| 58 | .size_of => "for size query here", |
| 59 | .align_of => "for alignment query here", |
| 60 | .type_info => "for type information query here", |
| 61 | .align_check => "for alignment check here", |
| 62 | .bit_ptr_child => "for bit size check here", |
| 63 | .@"export" => "for export here", |
| 64 | .@"extern" => "for extern declaration here", |
| 65 | .asm_out_type => "for inline assembly output type declared here", |
| 66 | .std_lang_type => "from 'std.lang'", |
| 67 | // zig fmt: on |
| 68 | }; |
| 69 | } |
| 70 | }; |
| 71 | |
| 72 | /// Ensures that `ty` has known layout, including alignment, size, and (where relevant) field offsets. |
| 73 | /// `ty` may be any type; its layout is resolved *recursively* if necessary. |
| 74 | /// Adds incremental dependencies tracking any required type resolution. |
| 75 | pub fn ensureLayoutResolved(sema: *Sema, ty: Type, src: LazySrcLoc, reason: LayoutResolveReason) SemaError!void { |
| 76 | return ensureLayoutResolvedInner(sema, ty, ty, &.{ |
| 77 | .src = src, |
| 78 | .type_layout_reason = reason, |
| 79 | }); |
| 80 | } |
| 81 | fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *const Zcu.DependencyReason) SemaError!void { |
| 82 | const pt = sema.pt; |
| 83 | const zcu = pt.zcu; |
| 84 | const ip = &zcu.intern_pool; |
| 85 | switch (ip.indexToKey(ty.toIntern())) { |
| 86 | .int_type, |
| 87 | .ptr_type, |
| 88 | .anyframe_type, |
| 89 | .simple_type, |
| 90 | .opaque_type, |
| 91 | .error_set_type, |
| 92 | .inferred_error_set_type, |
| 93 | => {}, |
| 94 | |
| 95 | .spirv_type => if (ty.isSpirvRuntimeArray(zcu)) { |
| 96 | return ensureLayoutResolvedInner(sema, ty.childType(zcu), orig_ty, reason); |
| 97 | }, |
| 98 | |
| 99 | .func_type => |func_type| { |
| 100 | for (func_type.param_types.get(ip)) |param_ty| { |
| 101 | try ensureLayoutResolvedInner(sema, .fromInterned(param_ty), orig_ty, reason); |
| 102 | } |
| 103 | try ensureLayoutResolvedInner(sema, .fromInterned(func_type.return_type), orig_ty, reason); |
| 104 | }, |
| 105 | |
| 106 | .array_type => |arr| return ensureLayoutResolvedInner(sema, .fromInterned(arr.child), orig_ty, reason), |
| 107 | .vector_type => |vec| return ensureLayoutResolvedInner(sema, .fromInterned(vec.child), orig_ty, reason), |
| 108 | .opt_type => |child| return ensureLayoutResolvedInner(sema, .fromInterned(child), orig_ty, reason), |
| 109 | .error_union_type => |eu| return ensureLayoutResolvedInner(sema, .fromInterned(eu.payload_type), orig_ty, reason), |
| 110 | .tuple_type => |tuple| for (tuple.types.get(ip)) |field_ty| { |
| 111 | try ensureLayoutResolvedInner(sema, .fromInterned(field_ty), orig_ty, reason); |
| 112 | }, |
| 113 | .struct_type, .union_type, .enum_type => { |
| 114 | try sema.declareDependency(.{ .type_layout = ty.toIntern() }); |
| 115 | try sema.addReferenceEntry(null, reason.src, .wrap(.{ .type_layout = ty.toIntern() })); |
| 116 | if (zcu.analysis_in_progress.contains(.wrap(.{ .type_layout = ty.toIntern() }))) { |
| 117 | return sema.failWithDependencyLoop(.wrap(.{ .type_layout = ty.toIntern() }), reason); |
| 118 | } |
| 119 | pt.ensureTypeLayoutUpToDate(ty, reason) catch |err| switch (err) { |
| 120 | error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .type_layout = ty.toIntern() }) }), |
| 121 | else => |e| return e, |
| 122 | }; |
| 123 | }, |
| 124 | |
| 125 | // values, not types |
| 126 | .undef, |
| 127 | .simple_value, |
| 128 | .@"extern", |
| 129 | .func, |
| 130 | .int, |
| 131 | .err, |
| 132 | .error_union, |
| 133 | .enum_literal, |
| 134 | .enum_tag, |
| 135 | .float, |
| 136 | .ptr, |
| 137 | .slice, |
| 138 | .opt, |
| 139 | .aggregate, |
| 140 | .un, |
| 141 | .bitpack, |
| 142 | // memoization, not types |
| 143 | .memoized_call, |
| 144 | => unreachable, |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /// Asserts that `ty` is a non-tuple `struct` type, and ensures that its fields' default values |
| 149 | /// are resolved. Adds incremental dependencies tracking the required type resolution. |
| 150 | /// |
| 151 | /// It is not necessary to call this function to query the values of comptime fields: those values |
| 152 | /// are available from type *layout* resolution, see `ensureLayoutResolved`. |
| 153 | /// |
| 154 | /// Asserts that the *layout* of `ty` has already been resolved---see `ensureLayoutResolved`. |
| 155 | pub fn ensureStructDefaultsResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!void { |
| 156 | const pt = sema.pt; |
| 157 | const zcu = pt.zcu; |
| 158 | const ip = &zcu.intern_pool; |
| 159 | |
| 160 | assert(ip.indexToKey(ty.toIntern()) == .struct_type); |
| 161 | ty.assertHasLayout(zcu); |
| 162 | |
| 163 | try sema.declareDependency(.{ .struct_defaults = ty.toIntern() }); |
| 164 | try sema.addReferenceEntry(null, src, .wrap(.{ .struct_defaults = ty.toIntern() })); |
| 165 | |
| 166 | const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined }; |
| 167 | |
| 168 | if (zcu.analysis_in_progress.contains(.wrap(.{ .struct_defaults = ty.toIntern() }))) { |
| 169 | return sema.failWithDependencyLoop(.wrap(.{ .struct_defaults = ty.toIntern() }), &reason); |
| 170 | } |
| 171 | |
| 172 | pt.ensureStructDefaultsUpToDate(ty, &reason) catch |err| switch (err) { |
| 173 | error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .struct_defaults = ty.toIntern() }) }), |
| 174 | else => |e| return e, |
| 175 | }; |
| 176 | } |
| 177 | |
| 178 | /// Asserts that `struct_ty` is a non-packed non-tuple struct, and that `sema.owner` is that type. |
| 179 | /// This function *does* register the `src_hash` dependency on the struct. |
| 180 | pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { |
| 181 | const pt = sema.pt; |
| 182 | const zcu = pt.zcu; |
| 183 | const comp = zcu.comp; |
| 184 | const io = comp.io; |
| 185 | const gpa = comp.gpa; |
| 186 | const ip = &zcu.intern_pool; |
| 187 | |
| 188 | const tracy = trace(@src()); |
| 189 | defer tracy.end(); |
| 190 | tracy.addText(struct_ty.containerTypeName(ip).fqn.toSlice(ip)); |
| 191 | tracy.addTextFmt("ip_index={d}", .{struct_ty.toIntern()}); |
| 192 | |
| 193 | assert(sema.owner.unwrap().type_layout == struct_ty.toIntern()); |
| 194 | |
| 195 | const struct_obj = ip.loadStructType(struct_ty.toIntern()); |
| 196 | assert(struct_obj.want_layout); |
| 197 | const zir_index = struct_obj.zir_index.resolve(ip) orelse { |
| 198 | return sema.failTransitive(.{ .lost_tracking = struct_obj.zir_index }); |
| 199 | }; |
| 200 | |
| 201 | var block: Block = .{ |
| 202 | .parent = null, |
| 203 | .sema = sema, |
| 204 | .namespace = struct_obj.namespace, |
| 205 | .instructions = .empty, |
| 206 | .inlining = null, |
| 207 | .comptime_reason = undefined, // always set before using `block` |
| 208 | .src_base_inst = struct_obj.zir_index, |
| 209 | .type_name_ctx = struct_obj.name, |
| 210 | .type_fqn_ctx = struct_obj.fqn, |
| 211 | }; |
| 212 | defer block.instructions.deinit(gpa); |
| 213 | |
| 214 | // There may be old field names in here from a previous update. |
| 215 | struct_obj.field_name_map.get(ip).clearRetainingCapacity(); |
| 216 | |
| 217 | if (struct_obj.is_reified) { |
| 218 | // The field names are populated, but we haven't checked for duplicates (nor populated the map) yet. |
| 219 | for (0..struct_obj.field_names.len) |field_index| { |
| 220 | const name = struct_obj.field_names.get(ip)[field_index]; |
| 221 | if (ip.addFieldName(struct_obj.field_names, struct_obj.field_name_map, name)) |prev_field_index| { |
| 222 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 223 | const src = block.builtinCallArgSrc(.zero, 2); |
| 224 | const msg = try sema.errMsg(src, "duplicate struct field '{f}' at index '{d}", .{ name.fmt(ip), field_index }); |
| 225 | errdefer msg.destroy(gpa); |
| 226 | try sema.errNote(src, msg, "previous field at index '{d}'", .{prev_field_index}); |
| 227 | break :msg msg; |
| 228 | }); |
| 229 | } |
| 230 | } |
| 231 | } else { |
| 232 | // Declared structs do not yet have field information populated: |
| 233 | // * field names |
| 234 | // * field comptime-ness |
| 235 | // * field types |
| 236 | // * field aligns |
| 237 | // It's our job to populate these now. |
| 238 | try sema.declareDependency(.{ .src_hash = struct_obj.zir_index }); |
| 239 | |
| 240 | // Likewise, comptime bits may be set. We clear them all first because it avoids needing |
| 241 | // "unset bit with AND" logic below (instead we only need the "set bit with OR" case). |
| 242 | @memset(struct_obj.field_is_comptime_bits.getAll(ip), 0); |
| 243 | |
| 244 | const zir_struct = sema.code.getStructDecl(zir_index); |
| 245 | var field_it = zir_struct.iterateFields(); |
| 246 | var any_comptime_fields = false; |
| 247 | while (field_it.next()) |zir_field| { |
| 248 | { |
| 249 | const name_slice = sema.code.nullTerminatedString(zir_field.name); |
| 250 | const name = try ip.getOrPutString(gpa, io, pt.tid, name_slice, .no_embedded_nulls); |
| 251 | assert(ip.addFieldName(struct_obj.field_names, struct_obj.field_name_map, name) == null); // AstGen validated this for us |
| 252 | } |
| 253 | |
| 254 | if (zir_field.is_comptime) { |
| 255 | const bit_bag_index = zir_field.idx / 32; |
| 256 | const mask = @as(u32, 1) << @intCast(zir_field.idx % 32); |
| 257 | struct_obj.field_is_comptime_bits.getAll(ip)[bit_bag_index] |= mask; |
| 258 | any_comptime_fields = true; |
| 259 | } |
| 260 | |
| 261 | { |
| 262 | const field_ty_src = block.src(.{ .container_field_type = zir_field.idx }); |
| 263 | const field_ty: Type = field_ty: { |
| 264 | block.comptime_reason = .{ .reason = .{ |
| 265 | .src = field_ty_src, |
| 266 | .r = .{ .simple = .struct_field_types }, |
| 267 | } }; |
| 268 | const type_ref = try sema.resolveInlineBody(&block, zir_field.type_body, zir_index); |
| 269 | break :field_ty try sema.analyzeAsType(&block, field_ty_src, .struct_field_types, type_ref); |
| 270 | }; |
| 271 | struct_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern(); |
| 272 | } |
| 273 | |
| 274 | if (struct_obj.field_aligns.len == 0) { |
| 275 | assert(zir_field.align_body == null); |
| 276 | } else { |
| 277 | const field_align_src = block.src(.{ .container_field_align = zir_field.idx }); |
| 278 | const field_align: Alignment = a: { |
| 279 | block.comptime_reason = .{ .reason = .{ |
| 280 | .src = field_align_src, |
| 281 | .r = .{ .simple = .struct_field_attrs }, |
| 282 | } }; |
| 283 | const align_body = zir_field.align_body orelse break :a .none; |
| 284 | const align_ref = try sema.resolveInlineBody(&block, align_body, zir_index); |
| 285 | break :a try sema.analyzeAsAlign(&block, field_align_src, align_ref); |
| 286 | }; |
| 287 | struct_obj.field_aligns.get(ip)[zir_field.idx] = field_align; |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | // We also resolve the default values of any `comptime` fields now. This is not necessary in |
| 292 | // the case of a reified struct because the the default values were already poulated and |
| 293 | // validated by `Sema.zirReifyStruct`. |
| 294 | if (any_comptime_fields) { |
| 295 | try resolveStructDefaultsInner(sema, &block, &struct_obj, .comptime_fields); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | if (struct_obj.layout == .@"packed") { |
| 300 | return resolvePackedStructLayout(sema, &block, struct_ty, &struct_obj); |
| 301 | } |
| 302 | |
| 303 | // Resolve the layout of all fields, and check their types are allowed. |
| 304 | const fields_len = struct_obj.field_types.len; |
| 305 | for (struct_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { |
| 306 | const field_ty: Type = .fromInterned(field_ty_ip); |
| 307 | assert(!field_ty.isGenericPoison()); |
| 308 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); |
| 309 | const field_name_src = block.src(.{ .container_field_name = @intCast(field_index) }); |
| 310 | try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); |
| 311 | if (field_ty.zigTypeTag(zcu) == .@"opaque") { |
| 312 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 313 | const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in struct", .{field_ty.fmt(pt)}); |
| 314 | errdefer msg.destroy(gpa); |
| 315 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 316 | try sema.addDeclaredHereNote(msg, field_ty); |
| 317 | break :msg msg; |
| 318 | }); |
| 319 | } |
| 320 | if (field_ty.zigTypeTag(zcu) == .spirv) { |
| 321 | if (field_ty.isSpirvRuntimeArray(zcu)) { |
| 322 | if (struct_obj.layout != .@"extern") { |
| 323 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 324 | const msg = try sema.errMsg(struct_ty.srcLoc(zcu), "non-extern struct cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); |
| 325 | errdefer msg.destroy(gpa); |
| 326 | try sema.errNote(field_name_src, msg, "while checking this field", .{}); |
| 327 | break :msg msg; |
| 328 | }); |
| 329 | } |
| 330 | if (field_index != fields_len - 1) { |
| 331 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 332 | const msg = try sema.errMsg(struct_ty.srcLoc(zcu), "struct field of type '{f}' must be the last field", .{field_ty.fmt(pt)}); |
| 333 | errdefer msg.destroy(gpa); |
| 334 | try sema.errNote(field_name_src, msg, "while checking this field", .{}); |
| 335 | break :msg msg; |
| 336 | }); |
| 337 | } |
| 338 | |
| 339 | const elem_ty: Type = field_ty.childType(zcu); |
| 340 | if (elem_ty.zigTypeTag(zcu) == .spirv) { |
| 341 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 342 | const msg = try sema.errMsg(field_ty_src, "cannot embed SPIR-V type '{f}' in struct", .{elem_ty.fmt(pt)}); |
| 343 | errdefer msg.destroy(gpa); |
| 344 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 345 | try sema.addDeclaredHereNote(msg, field_ty); |
| 346 | break :msg msg; |
| 347 | }); |
| 348 | } |
| 349 | } else { |
| 350 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 351 | const msg = try sema.errMsg(field_ty_src, "cannot directly embed SPIR-V type '{f}' in struct", .{field_ty.fmt(pt)}); |
| 352 | errdefer msg.destroy(gpa); |
| 353 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 354 | try sema.addDeclaredHereNote(msg, field_ty); |
| 355 | break :msg msg; |
| 356 | }); |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | if (struct_obj.layout == .@"extern" and !field_ty.validateExtern(.struct_field, zcu)) { |
| 361 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 362 | const msg = try sema.errMsg(field_ty_src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); |
| 363 | errdefer msg.destroy(gpa); |
| 364 | try sema.explainWhyTypeIsNotExtern(msg, field_ty_src, field_ty, .struct_field); |
| 365 | try sema.addDeclaredHereNote(msg, field_ty); |
| 366 | break :msg msg; |
| 367 | }); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | // Fields are okay. Now we need to resolve the struct's overall layout (size, field offsets, etc). |
| 372 | |
| 373 | var any_comptime_fields = false; |
| 374 | var struct_align: Alignment = .@"1"; |
| 375 | var has_no_possible_value = false; |
| 376 | var has_runtime_state = false; |
| 377 | var has_comptime_state = false; |
| 378 | // Unlike `struct_obj.field_aligns`, these are not `.none`. |
| 379 | const resolved_field_aligns = try sema.arena.alloc(Alignment, struct_obj.field_names.len); |
| 380 | for (resolved_field_aligns, 0..) |*align_out, field_idx| { |
| 381 | const field_ty: Type = .fromInterned(struct_obj.field_types.get(ip)[field_idx]); |
| 382 | const field_align: Alignment = a: { |
| 383 | if (struct_obj.field_aligns.len != 0) { |
| 384 | const a = struct_obj.field_aligns.get(ip)[field_idx]; |
| 385 | if (a != .none) break :a a; |
| 386 | } |
| 387 | break :a field_ty.abiAlignment(zcu); |
| 388 | }; |
| 389 | align_out.* = field_align; |
| 390 | if (struct_obj.field_is_comptime_bits.get(ip, field_idx)) { |
| 391 | assert(struct_obj.layout == .auto); // comptime fields not allowed in extern or packed structs |
| 392 | struct_obj.field_runtime_order.get(ip)[field_idx] = .omitted; // comptime fields are not in the runtime order |
| 393 | any_comptime_fields = true; |
| 394 | continue; // `comptime` fields do not contribute to the struct layout |
| 395 | } |
| 396 | struct_align = struct_align.maxStrict(field_align); |
| 397 | if (struct_obj.layout == .auto) { |
| 398 | struct_obj.field_runtime_order.get(ip)[field_idx] = @fromBackingInt(@intCast(field_idx)); |
| 399 | } |
| 400 | switch (field_ty.classify(zcu)) { |
| 401 | .one_possible_value => {}, |
| 402 | .no_possible_value => has_no_possible_value = true, |
| 403 | .runtime => has_runtime_state = true, |
| 404 | .fully_comptime => has_comptime_state = true, |
| 405 | .partially_comptime => { |
| 406 | has_runtime_state = true; |
| 407 | has_comptime_state = true; |
| 408 | }, |
| 409 | } |
| 410 | } |
| 411 | const class: Type.Class = class: { |
| 412 | if (has_no_possible_value) break :class .no_possible_value; |
| 413 | if (has_comptime_state) { |
| 414 | break :class if (has_runtime_state) .partially_comptime else .fully_comptime; |
| 415 | } else { |
| 416 | break :class if (has_runtime_state) .runtime else .one_possible_value; |
| 417 | } |
| 418 | }; |
| 419 | |
| 420 | switch (struct_obj.layout) { |
| 421 | .auto => {}, |
| 422 | .@"extern" => assert(class != .no_possible_value), // field types are all extern, so are not NPV |
| 423 | .@"packed" => unreachable, |
| 424 | } |
| 425 | |
| 426 | if (struct_obj.layout == .auto) { |
| 427 | const runtime_order = struct_obj.field_runtime_order.get(ip); |
| 428 | // This logic does not reorder fields; it only moves the omitted ones to the end so that logic |
| 429 | // elsewhere does not need to special-case. TODO: support field reordering in all the backends! |
| 430 | if (!zcu.backendSupportsFeature(.field_reordering)) { |
| 431 | var i: usize = 0; |
| 432 | var off: usize = 0; |
| 433 | while (i + off < runtime_order.len) { |
| 434 | if (runtime_order[i + off] == .omitted) { |
| 435 | off += 1; |
| 436 | } else { |
| 437 | runtime_order[i] = runtime_order[i + off]; |
| 438 | i += 1; |
| 439 | } |
| 440 | } |
| 441 | } else { |
| 442 | // Sort by descending alignment to minimize padding. |
| 443 | const RuntimeOrder = InternPool.LoadedStructType.RuntimeOrder; |
| 444 | const AlignSortCtx = struct { |
| 445 | aligns: []const Alignment, |
| 446 | fn lessThan(ctx: @This(), a: RuntimeOrder, b: RuntimeOrder) bool { |
| 447 | assert(a != .unresolved); |
| 448 | assert(b != .unresolved); |
| 449 | if (a == .omitted) return false; |
| 450 | if (b == .omitted) return true; |
| 451 | const a_align = ctx.aligns[@backingInt(a)]; |
| 452 | const b_align = ctx.aligns[@backingInt(b)]; |
| 453 | return a_align.compare(.gt, b_align); |
| 454 | } |
| 455 | }; |
| 456 | mem.sortUnstable( |
| 457 | RuntimeOrder, |
| 458 | runtime_order, |
| 459 | @as(AlignSortCtx, .{ .aligns = resolved_field_aligns }), |
| 460 | AlignSortCtx.lessThan, |
| 461 | ); |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | var runtime_order_it = struct_obj.iterateRuntimeOrder(ip); |
| 466 | var cur_offset: u64 = 0; |
| 467 | while (runtime_order_it.next()) |field_idx| { |
| 468 | const field_ty: Type = .fromInterned(struct_obj.field_types.get(ip)[field_idx]); |
| 469 | const offset = resolved_field_aligns[field_idx].forward(cur_offset); |
| 470 | struct_obj.field_offsets.get(ip)[field_idx] = @truncate(offset); // truncate because the overflow is handled below |
| 471 | // A SPIR-V `runtime_array` always trails the struct and |
| 472 | // contributes nothing to the struct's static size. |
| 473 | const field_size = if (field_ty.isSpirvRuntimeArray(zcu)) 0 else field_ty.abiSize(zcu); |
| 474 | cur_offset = offset + field_size; |
| 475 | } |
| 476 | const struct_size: u32 = switch (class) { |
| 477 | .no_possible_value => 0, |
| 478 | else => std.math.cast(u32, struct_align.forward(cur_offset)) orelse return sema.fail( |
| 479 | &block, |
| 480 | struct_ty.srcLoc(zcu), |
| 481 | "struct layout requires size {d}, this compiler implementation supports up to {d}", |
| 482 | .{ struct_align.forward(cur_offset), std.math.maxInt(u32) }, |
| 483 | ), |
| 484 | }; |
| 485 | ip.resolveStructLayout( |
| 486 | io, |
| 487 | struct_ty.toIntern(), |
| 488 | struct_size, |
| 489 | struct_align, |
| 490 | class, |
| 491 | ); |
| 492 | } |
| 493 | |
| 494 | /// Asserts that `struct_ty` is a packed struct, and that `sema.owner` is that type. |
| 495 | /// This function *does* register the `src_hash` dependency on the struct. |
| 496 | fn resolvePackedStructLayout( |
| 497 | sema: *Sema, |
| 498 | block: *Block, |
| 499 | struct_ty: Type, |
| 500 | struct_obj: *const InternPool.LoadedStructType, |
| 501 | ) CompileError!void { |
| 502 | const pt = sema.pt; |
| 503 | const zcu = pt.zcu; |
| 504 | const comp = zcu.comp; |
| 505 | const io = comp.io; |
| 506 | const gpa = comp.gpa; |
| 507 | const ip = &zcu.intern_pool; |
| 508 | |
| 509 | // Resolve the layout of all fields, and check their types are allowed. |
| 510 | // Also count the number of bits while we're at it. |
| 511 | var field_bits: u64 = 0; |
| 512 | for (struct_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { |
| 513 | const field_ty: Type = .fromInterned(field_ty_ip); |
| 514 | assert(!field_ty.isGenericPoison()); |
| 515 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); |
| 516 | try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); |
| 517 | if (field_ty.zigTypeTag(zcu) == .@"opaque") { |
| 518 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 519 | const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in struct", .{field_ty.fmt(pt)}); |
| 520 | errdefer msg.destroy(gpa); |
| 521 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 522 | try sema.addDeclaredHereNote(msg, field_ty); |
| 523 | break :msg msg; |
| 524 | }); |
| 525 | } |
| 526 | if (field_ty.unpackable(zcu)) |reason| return sema.failWithOwnedErrorMsg(block, msg: { |
| 527 | const msg = try sema.errMsg(field_ty_src, "packed structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); |
| 528 | errdefer msg.destroy(gpa); |
| 529 | try sema.explainWhyTypeIsUnpackable(msg, field_ty_src, reason); |
| 530 | break :msg msg; |
| 531 | }); |
| 532 | switch (field_ty.classify(zcu)) { |
| 533 | .one_possible_value, .runtime => {}, |
| 534 | .no_possible_value => unreachable, // packable types are not NPV |
| 535 | .partially_comptime => unreachable, // packable types are not comptime-only |
| 536 | .fully_comptime => unreachable, // packable types are not comptime-only |
| 537 | } |
| 538 | field_bits += field_ty.bitSize(zcu); |
| 539 | } |
| 540 | |
| 541 | const explicit_backing_int_ty: ?Type = if (struct_obj.is_reified) ty: { |
| 542 | break :ty switch (struct_obj.packed_backing_mode) { |
| 543 | .explicit => .fromInterned(struct_obj.packed_backing_int_type), |
| 544 | .auto => null, |
| 545 | }; |
| 546 | } else ty: { |
| 547 | const zir_index = struct_obj.zir_index.resolve(ip).?; |
| 548 | const zir_struct = sema.code.getStructDecl(zir_index); |
| 549 | const backing_int_type_body = zir_struct.backing_int_type_body orelse { |
| 550 | break :ty null; // inferred backing type |
| 551 | }; |
| 552 | // Explicitly specified, so evaluate the backing int type expression. |
| 553 | const backing_int_type_src = block.src(.container_arg); |
| 554 | block.comptime_reason = .{ .reason = .{ |
| 555 | .src = backing_int_type_src, |
| 556 | .r = .{ .simple = .packed_struct_backing_int_type }, |
| 557 | } }; |
| 558 | const type_ref = try sema.resolveInlineBody(block, backing_int_type_body, zir_index); |
| 559 | break :ty try sema.analyzeAsType(block, backing_int_type_src, .packed_struct_backing_int_type, type_ref); |
| 560 | }; |
| 561 | |
| 562 | // Finally, either validate or infer the backing int type. |
| 563 | const backing_int_ty: Type = if (explicit_backing_int_ty) |backing_ty| ty: { |
| 564 | if (backing_ty.zigTypeTag(zcu) != .int) return sema.fail( |
| 565 | block, |
| 566 | block.src(.container_arg), |
| 567 | "expected backing integer type, found '{f}'", |
| 568 | .{backing_ty.fmt(pt)}, |
| 569 | ); |
| 570 | if (field_bits != backing_ty.intInfo(zcu).bits) return sema.failWithOwnedErrorMsg(block, msg: { |
| 571 | const src = struct_ty.srcLoc(zcu); |
| 572 | const msg = try sema.errMsg(src, "backing integer bit width does not match total bit width of fields", .{}); |
| 573 | errdefer msg.destroy(gpa); |
| 574 | try sema.errNote( |
| 575 | block.src(.container_arg), |
| 576 | msg, |
| 577 | "backing integer '{f}' has bit width '{d}'", |
| 578 | .{ backing_ty.fmt(pt), backing_ty.bitSize(zcu) }, |
| 579 | ); |
| 580 | try sema.errNote(src, msg, "struct fields have total bit width '{d}'", .{field_bits}); |
| 581 | break :msg msg; |
| 582 | }); |
| 583 | break :ty backing_ty; |
| 584 | } else ty: { |
| 585 | // We need to generate the inferred tag. |
| 586 | const backing_int_bits = std.math.cast(u16, field_bits) orelse return sema.fail( |
| 587 | block, |
| 588 | struct_ty.srcLoc(zcu), |
| 589 | "packed struct bit width '{d}' exceeds maximum bit width of 65535", |
| 590 | .{field_bits}, |
| 591 | ); |
| 592 | break :ty try pt.intType(.unsigned, backing_int_bits); |
| 593 | }; |
| 594 | ip.resolvePackedStructLayout( |
| 595 | io, |
| 596 | struct_ty.toIntern(), |
| 597 | backing_int_ty.toIntern(), |
| 598 | ); |
| 599 | } |
| 600 | |
| 601 | /// Asserts that `struct_ty` is a non-tuple struct, and that `sema.owner` is that type. |
| 602 | /// |
| 603 | /// Also asserts that the layout of `struct_ty` has *already* been resolved (though it is okay for |
| 604 | /// that resolution to have failed). This requirement exists to ensure better error messages in the |
| 605 | /// event of a dependency loop. |
| 606 | /// |
| 607 | /// This function *does* register the `src_hash` dependency on the struct. |
| 608 | pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void { |
| 609 | const pt = sema.pt; |
| 610 | const zcu = pt.zcu; |
| 611 | const comp = zcu.comp; |
| 612 | const gpa = comp.gpa; |
| 613 | const ip = &zcu.intern_pool; |
| 614 | |
| 615 | const tracy = trace(@src()); |
| 616 | defer tracy.end(); |
| 617 | tracy.addText(struct_ty.containerTypeName(ip).fqn.toSlice(ip)); |
| 618 | tracy.addTextFmt("ip_index={d}", .{struct_ty.toIntern()}); |
| 619 | |
| 620 | assert(sema.owner.unwrap().struct_defaults == struct_ty.toIntern()); |
| 621 | |
| 622 | // We always depend on the layout of `struct_ty`. However, we don't actually need to resolve it |
| 623 | // now, because the caller has done so for us. Just mark the dependency so that the incremental |
| 624 | // compilation handling understands the dependency graph. |
| 625 | try sema.declareDependency(.{ .type_layout = struct_ty.toIntern() }); |
| 626 | struct_ty.assertHasLayout(zcu); |
| 627 | const layout_unit: InternPool.AnalUnit = .wrap(.{ .type_layout = struct_ty.toIntern() }); |
| 628 | if (zcu.failed_analysis.contains(layout_unit) or zcu.transitive_failed_analysis.contains(layout_unit)) { |
| 629 | return sema.failTransitive(.{ .failed_unit = layout_unit }); |
| 630 | } |
| 631 | |
| 632 | const struct_obj = ip.loadStructType(struct_ty.toIntern()); |
| 633 | assert(struct_obj.want_layout); |
| 634 | |
| 635 | if (struct_obj.is_reified) { |
| 636 | // `Sema.zirReifyStruct` has already populated the default field values *and* (by loading |
| 637 | // the default values from pointers) validated their types, so we have nothing to do. |
| 638 | return; |
| 639 | } |
| 640 | |
| 641 | try sema.declareDependency(.{ .src_hash = struct_obj.zir_index }); |
| 642 | |
| 643 | if (struct_obj.field_defaults.len == 0) { |
| 644 | // The struct has no default field values, so the slice has been omitted. |
| 645 | return; |
| 646 | } |
| 647 | |
| 648 | var block: Block = .{ |
| 649 | .parent = null, |
| 650 | .sema = sema, |
| 651 | .namespace = struct_obj.namespace, |
| 652 | .instructions = .empty, |
| 653 | .inlining = null, |
| 654 | .comptime_reason = undefined, // always set before using `block` |
| 655 | .src_base_inst = struct_obj.zir_index, |
| 656 | .type_name_ctx = struct_obj.name, |
| 657 | .type_fqn_ctx = struct_obj.fqn, |
| 658 | }; |
| 659 | defer block.instructions.deinit(gpa); |
| 660 | |
| 661 | return resolveStructDefaultsInner(sema, &block, &struct_obj, .normal_fields); |
| 662 | } |
| 663 | |
| 664 | /// Asserts that the struct is not reified, and that `struct_obj.field_defaults.len` is non-zero. |
| 665 | fn resolveStructDefaultsInner( |
| 666 | sema: *Sema, |
| 667 | block: *Block, |
| 668 | struct_obj: *const InternPool.LoadedStructType, |
| 669 | mode: enum { comptime_fields, normal_fields }, |
| 670 | ) CompileError!void { |
| 671 | const pt = sema.pt; |
| 672 | const zcu = pt.zcu; |
| 673 | const comp = zcu.comp; |
| 674 | const gpa = comp.gpa; |
| 675 | const ip = &zcu.intern_pool; |
| 676 | |
| 677 | assert(struct_obj.field_defaults.len > 0); |
| 678 | |
| 679 | // We'll need to map the struct decl instruction to provide result types |
| 680 | const zir_index = struct_obj.zir_index.resolve(ip) orelse { |
| 681 | return sema.failTransitive(.{ .lost_tracking = struct_obj.zir_index }); |
| 682 | }; |
| 683 | try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index}); |
| 684 | |
| 685 | const field_types = struct_obj.field_types.get(ip); |
| 686 | |
| 687 | const zir_struct = sema.code.getStructDecl(zir_index); |
| 688 | var field_it = zir_struct.iterateFields(); |
| 689 | while (field_it.next()) |zir_field| { |
| 690 | switch (mode) { |
| 691 | .comptime_fields => if (!zir_field.is_comptime) continue, |
| 692 | .normal_fields => if (zir_field.is_comptime) continue, |
| 693 | } |
| 694 | |
| 695 | const default_val_src = block.src(.{ .container_field_value = zir_field.idx }); |
| 696 | block.comptime_reason = .{ .reason = .{ |
| 697 | .src = default_val_src, |
| 698 | .r = .{ .simple = .struct_field_default_value }, |
| 699 | } }; |
| 700 | const default_body = zir_field.default_body orelse { |
| 701 | struct_obj.field_defaults.get(ip)[zir_field.idx] = .none; |
| 702 | continue; |
| 703 | }; |
| 704 | const field_ty: Type = .fromInterned(field_types[zir_field.idx]); |
| 705 | const uncoerced = ref: { |
| 706 | // Provide the result type |
| 707 | sema.inst_map.putAssumeCapacity(zir_index, .fromIntern(field_ty.toIntern())); |
| 708 | defer assert(sema.inst_map.remove(zir_index)); |
| 709 | break :ref try sema.resolveInlineBody(block, default_body, zir_index); |
| 710 | }; |
| 711 | const coerced = try sema.coerce(block, field_ty, uncoerced, default_val_src); |
| 712 | const default_val = try sema.resolveConstValue(block, default_val_src, coerced, null); |
| 713 | if (default_val.canMutateComptimeVarState(zcu)) { |
| 714 | const field_name = struct_obj.field_names.get(ip)[zir_field.idx]; |
| 715 | return sema.failWithContainsReferenceToComptimeVar(block, default_val_src, field_name, "field default value", default_val); |
| 716 | } |
| 717 | struct_obj.field_defaults.get(ip)[zir_field.idx] = default_val.toIntern(); |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | /// This logic must be kept in sync with `Type.getUnionLayout`. |
| 722 | pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { |
| 723 | const pt = sema.pt; |
| 724 | const zcu = pt.zcu; |
| 725 | const comp = zcu.comp; |
| 726 | const io = comp.io; |
| 727 | const gpa = comp.gpa; |
| 728 | const ip = &zcu.intern_pool; |
| 729 | |
| 730 | const tracy = trace(@src()); |
| 731 | defer tracy.end(); |
| 732 | tracy.addText(union_ty.containerTypeName(ip).fqn.toSlice(ip)); |
| 733 | tracy.addTextFmt("ip_index={d}", .{union_ty.toIntern()}); |
| 734 | |
| 735 | assert(sema.owner.unwrap().type_layout == union_ty.toIntern()); |
| 736 | |
| 737 | const union_obj = ip.loadUnionType(union_ty.toIntern()); |
| 738 | assert(union_obj.want_layout); |
| 739 | const zir_index = union_obj.zir_index.resolve(ip) orelse { |
| 740 | return sema.failTransitive(.{ .lost_tracking = union_obj.zir_index }); |
| 741 | }; |
| 742 | |
| 743 | var block: Block = .{ |
| 744 | .parent = null, |
| 745 | .sema = sema, |
| 746 | .namespace = union_obj.namespace, |
| 747 | .instructions = .empty, |
| 748 | .inlining = null, |
| 749 | .comptime_reason = undefined, // always set before using `block` |
| 750 | .src_base_inst = union_obj.zir_index, |
| 751 | .type_name_ctx = union_obj.name, |
| 752 | .type_fqn_ctx = union_obj.fqn, |
| 753 | }; |
| 754 | defer block.instructions.deinit(gpa); |
| 755 | |
| 756 | const enum_tag_ty: Type = switch (union_obj.enum_tag_mode) { |
| 757 | .explicit => validated_tag_ty: { |
| 758 | // If the union is reified, its enum tag type is already populated. If the union is |
| 759 | // declared, we need to evaluate the enum tag type expression (the `E` in `union(E)`). |
| 760 | const tag_ty: Type = switch (union_obj.is_reified) { |
| 761 | true => .fromInterned(union_obj.enum_tag_type), |
| 762 | false => tag_ty: { |
| 763 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 764 | assert(zir_union.kind == .tagged_explicit); // `Zcu.mapOldZirToNew` guarantees that the ZIR mapping preserves `kind` |
| 765 | const tag_type_body = zir_union.arg_type_body.?; |
| 766 | const tag_type_src = block.src(.container_arg); |
| 767 | block.comptime_reason = .{ .reason = .{ |
| 768 | .src = tag_type_src, |
| 769 | .r = .{ .simple = .union_enum_tag_type }, |
| 770 | } }; |
| 771 | const type_ref = try sema.resolveInlineBody(&block, tag_type_body, zir_index); |
| 772 | break :tag_ty try sema.analyzeAsType(&block, tag_type_src, .union_enum_tag_type, type_ref); |
| 773 | }, |
| 774 | }; |
| 775 | // Because the type is explicitly specified, we need to validate it. |
| 776 | if (tag_ty.zigTypeTag(zcu) != .@"enum") return sema.fail( |
| 777 | &block, |
| 778 | block.src(.container_arg), |
| 779 | "expected enum tag type, found '{f}'", |
| 780 | .{tag_ty.fmt(pt)}, |
| 781 | ); |
| 782 | break :validated_tag_ty tag_ty; |
| 783 | }, |
| 784 | // If no tag type was specified, we generate one keyed on this union type. |
| 785 | .auto => switch (try ip.getGeneratedEnumTagType(gpa, io, pt.tid, .{ |
| 786 | .union_type = union_ty.toIntern(), |
| 787 | // The int tag for this enum is usually inferred---the exception is `union(enum(T))`. |
| 788 | .int_tag_mode = switch (union_obj.is_reified) { |
| 789 | true => .auto, |
| 790 | false => switch (sema.code.getUnionDecl(zir_index).kind) { |
| 791 | .tagged_enum_explicit => .explicit, |
| 792 | else => .auto, |
| 793 | }, |
| 794 | }, |
| 795 | .fields_len = @intCast(union_obj.field_types.len), |
| 796 | })) { |
| 797 | .existing => |tag_ty| .fromInterned(tag_ty), |
| 798 | .wip => |wip| tag_ty: { |
| 799 | errdefer wip.cancel(ip, pt.tid); |
| 800 | _ = wip.setName(ip, try ip.getOrPutStringFmt( |
| 801 | gpa, |
| 802 | io, |
| 803 | pt.tid, |
| 804 | "@typeInfo({f}).@\"union\".tag_type.?", |
| 805 | .{union_obj.name.fmt(ip)}, |
| 806 | .no_embedded_nulls, |
| 807 | ), try ip.getOrPutStringFmt( |
| 808 | gpa, |
| 809 | io, |
| 810 | pt.tid, |
| 811 | "@typeInfo({f}).@\"union\".tag_type.?", |
| 812 | .{union_obj.fqn.fmt(ip)}, |
| 813 | .no_embedded_nulls, |
| 814 | ), .none); |
| 815 | const new_namespace_index: InternPool.NamespaceIndex = try pt.createNamespace(.{ |
| 816 | .parent = union_obj.namespace.toOptional(), |
| 817 | .owner_type = wip.index, |
| 818 | .file_scope = zcu.namespacePtr(union_obj.namespace).file_scope, |
| 819 | .generation = zcu.generation, |
| 820 | }); |
| 821 | if (comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index); |
| 822 | break :tag_ty .fromInterned(wip.finish(ip, new_namespace_index)); |
| 823 | }, |
| 824 | }, |
| 825 | }; |
| 826 | |
| 827 | try sema.ensureLayoutResolved(enum_tag_ty, block.src(.container_arg), .backing_enum); |
| 828 | const enum_obj = ip.loadEnumType(enum_tag_ty.toIntern()); |
| 829 | |
| 830 | if (union_obj.is_reified) { |
| 831 | // We have field names in `union_obj.reified_field_names`, but we haven't |
| 832 | // checked them against the backing type yet. |
| 833 | const union_field_names = union_obj.reified_field_names.get(ip); |
| 834 | match_fields: { |
| 835 | // We can efficiently *check* if the fields match... |
| 836 | if (union_field_names.len == enum_obj.field_names.len) { |
| 837 | for (union_field_names, enum_obj.field_names.get(ip)) |union_field_name, enum_field_name| { |
| 838 | if (!std.mem.eql(u8, union_field_name.toSlice(ip), enum_field_name.toSlice(ip))) break; |
| 839 | } else { |
| 840 | break :match_fields; |
| 841 | } |
| 842 | } |
| 843 | // ...but if they don't, reporting a nice error is a little more involved. If some field |
| 844 | // is present in the enum but not the union, or vice versa, we will report that instead |
| 845 | // of a generic "field order mismatch" error. Of course, this error is impossible for a |
| 846 | // generated tag type, because we populated that from the union ZIR! |
| 847 | assert(enum_obj.owner_union != union_ty.toIntern()); |
| 848 | return failUnionFieldMismatch(sema, &block, union_field_names, enum_tag_ty, &enum_obj); |
| 849 | } |
| 850 | } else { |
| 851 | // Declared unions do not have field types or aligns populated yet. |
| 852 | // We also need to check the field names match the backing enum. |
| 853 | try sema.declareDependency(.{ .src_hash = union_obj.zir_index }); |
| 854 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 855 | |
| 856 | // We'll first check the field names against the backing enum, and only analyze the types |
| 857 | // once we know the fields match one-to-one. |
| 858 | match_fields: { |
| 859 | // We can efficiently *check* if the fields match... |
| 860 | if (zir_union.field_names.len == enum_obj.field_names.len) { |
| 861 | for (zir_union.field_names, enum_obj.field_names.get(ip)) |union_field_name_zir, enum_field_name| { |
| 862 | const union_field_name_slice = sema.code.nullTerminatedString(union_field_name_zir); |
| 863 | if (!std.mem.eql(u8, union_field_name_slice, enum_field_name.toSlice(ip))) break; |
| 864 | } else { |
| 865 | break :match_fields; |
| 866 | } |
| 867 | } |
| 868 | // ...but if they don't, reporting a nice error is a little more involved. If some field |
| 869 | // is present in the enum but not the union, or vice versa, we will report that instead |
| 870 | // of a generic "field order mismatch" error. Of course, this error is impossible for a |
| 871 | // generated tag type, because we populated that from the union ZIR! |
| 872 | assert(enum_obj.owner_union != union_ty.toIntern()); |
| 873 | const union_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, zir_union.field_names.len); |
| 874 | for (zir_union.field_names, union_field_names) |name_zir, *name| { |
| 875 | name.* = try ip.getOrPutString(gpa, io, pt.tid, sema.code.nullTerminatedString(name_zir), .no_embedded_nulls); |
| 876 | } |
| 877 | return failUnionFieldMismatch(sema, &block, union_field_names, enum_tag_ty, &enum_obj); |
| 878 | } |
| 879 | |
| 880 | // Field names okay; populate types and aligns. |
| 881 | var field_it = zir_union.iterateFields(); |
| 882 | while (field_it.next()) |zir_field| { |
| 883 | const field_ty_src = block.src(.{ .container_field_type = zir_field.idx }); |
| 884 | const field_ty: Type = field_ty: { |
| 885 | block.comptime_reason = .{ .reason = .{ |
| 886 | .src = field_ty_src, |
| 887 | .r = .{ .simple = .union_field_types }, |
| 888 | } }; |
| 889 | const type_body = zir_field.type_body orelse break :field_ty .void; |
| 890 | const type_ref = try sema.resolveInlineBody(&block, type_body, zir_index); |
| 891 | break :field_ty try sema.analyzeAsType(&block, field_ty_src, .union_field_types, type_ref); |
| 892 | }; |
| 893 | union_obj.field_types.get(ip)[zir_field.idx] = field_ty.toIntern(); |
| 894 | |
| 895 | const field_align_src = block.src(.{ .container_field_align = zir_field.idx }); |
| 896 | const explicit_field_align: Alignment = a: { |
| 897 | block.comptime_reason = .{ .reason = .{ |
| 898 | .src = field_align_src, |
| 899 | .r = .{ .simple = .union_field_attrs }, |
| 900 | } }; |
| 901 | const align_body = zir_field.align_body orelse break :a .none; |
| 902 | const align_ref = try sema.resolveInlineBody(&block, align_body, zir_index); |
| 903 | break :a try sema.analyzeAsAlign(&block, field_align_src, align_ref); |
| 904 | }; |
| 905 | if (union_obj.field_aligns.len != 0) { |
| 906 | union_obj.field_aligns.get(ip)[zir_field.idx] = explicit_field_align; |
| 907 | } else { |
| 908 | assert(explicit_field_align == .none); |
| 909 | } |
| 910 | } |
| 911 | } |
| 912 | |
| 913 | if (union_obj.layout == .@"packed") { |
| 914 | return resolvePackedUnionLayout(sema, &block, union_ty, &union_obj, enum_tag_ty); |
| 915 | } |
| 916 | |
| 917 | // Resolve the layout of all fields, and check their types are allowed. |
| 918 | for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { |
| 919 | const field_ty: Type = .fromInterned(field_ty_ip); |
| 920 | assert(!field_ty.isGenericPoison()); |
| 921 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); |
| 922 | try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); |
| 923 | if (field_ty.zigTypeTag(zcu) == .@"opaque") { |
| 924 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 925 | const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in union", .{field_ty.fmt(pt)}); |
| 926 | errdefer msg.destroy(gpa); |
| 927 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 928 | try sema.addDeclaredHereNote(msg, field_ty); |
| 929 | break :msg msg; |
| 930 | }); |
| 931 | } |
| 932 | if (field_ty.zigTypeTag(zcu) == .spirv) { |
| 933 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 934 | const msg = try sema.errMsg(field_ty_src, "SPIR-V type '{f}' have unknown size and therefore cannot be directly embedded in unions", .{field_ty.fmt(pt)}); |
| 935 | errdefer msg.destroy(gpa); |
| 936 | try sema.addDeclaredHereNote(msg, field_ty); |
| 937 | break :msg msg; |
| 938 | }); |
| 939 | } |
| 940 | if (union_obj.layout == .@"extern" and !field_ty.validateExtern(.union_field, zcu)) { |
| 941 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 942 | const msg = try sema.errMsg(field_ty_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); |
| 943 | errdefer msg.destroy(gpa); |
| 944 | try sema.explainWhyTypeIsNotExtern(msg, field_ty_src, field_ty, .union_field); |
| 945 | try sema.addDeclaredHereNote(msg, field_ty); |
| 946 | break :msg msg; |
| 947 | }); |
| 948 | } |
| 949 | } |
| 950 | |
| 951 | // Fields are okay. Now we need to resolve the union's overall layout (size, alignment, etc). |
| 952 | var payload_align: Alignment = .@"1"; |
| 953 | var payload_size: u64 = 0; |
| 954 | var possible_tags: u32 = 0; |
| 955 | var payload_has_comptime_state = false; |
| 956 | for (0..union_obj.field_types.len) |field_idx| { |
| 957 | const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]); |
| 958 | const field_align: Alignment = a: { |
| 959 | if (union_obj.field_aligns.len != 0) { |
| 960 | const a = union_obj.field_aligns.get(ip)[field_idx]; |
| 961 | if (a != .none) break :a a; |
| 962 | } |
| 963 | break :a field_ty.abiAlignment(zcu); |
| 964 | }; |
| 965 | payload_align = payload_align.maxStrict(field_align); |
| 966 | payload_size = @max(payload_size, field_ty.abiSize(zcu)); |
| 967 | |
| 968 | switch (field_ty.classify(zcu)) { |
| 969 | .no_possible_value => {}, // uninstantiable field has no effect |
| 970 | .one_possible_value, .runtime => { |
| 971 | possible_tags += 1; |
| 972 | }, |
| 973 | .partially_comptime, .fully_comptime => { |
| 974 | possible_tags += 1; |
| 975 | payload_has_comptime_state = true; |
| 976 | }, |
| 977 | } |
| 978 | } |
| 979 | |
| 980 | // Uninstantiable `extern union`s don't make sense; disallow them. |
| 981 | if (possible_tags == 0 and union_obj.layout != .auto) { |
| 982 | // Field types are all extern, so not NPV; thus zero possible tags means no tags at all. |
| 983 | assert(union_obj.field_types.len == 0); |
| 984 | return sema.fail(&block, union_ty.srcLoc(zcu), "extern union has no fields", .{}); |
| 985 | } |
| 986 | |
| 987 | // We only need a runtime tag if there are multiple possible active fields *and* the union is |
| 988 | // not going to be comptime-only. Even if there are still runtime bits in the payload, the tag |
| 989 | // does not require runtime bits in a comptime-only union, because it is impossible to get a |
| 990 | // pointer to a union's tag. |
| 991 | const has_runtime_tag = switch (possible_tags) { |
| 992 | 0, 1 => false, |
| 993 | else => union_obj.tag_usage != .none and !payload_has_comptime_state, |
| 994 | }; |
| 995 | |
| 996 | const class: Type.Class = class: { |
| 997 | if (possible_tags == 0) { |
| 998 | break :class .no_possible_value; |
| 999 | } |
| 1000 | if (payload_has_comptime_state) { |
| 1001 | break :class if (payload_size > 0) .partially_comptime else .fully_comptime; |
| 1002 | } |
| 1003 | const have_runtime_bits = has_runtime_tag or payload_size > 0; |
| 1004 | break :class if (have_runtime_bits) .runtime else .one_possible_value; |
| 1005 | }; |
| 1006 | |
| 1007 | const size: u64, const padding: u64, const alignment: Alignment = layout: { |
| 1008 | if (!has_runtime_tag) { |
| 1009 | break :layout .{ payload_align.forward(payload_size), 0, payload_align }; |
| 1010 | } |
| 1011 | const tag_align = enum_tag_ty.abiAlignment(zcu); |
| 1012 | const tag_size = enum_tag_ty.abiSize(zcu); |
| 1013 | // The layout will either be (tag, payload, padding) or (payload, tag, padding) depending on |
| 1014 | // which has larger alignment. So the overall size is just the tag and payload sizes, added, |
| 1015 | // and padded to the larger alignment. |
| 1016 | const alignment = tag_align.maxStrict(payload_align); |
| 1017 | const unpadded_size = tag_size + payload_size; |
| 1018 | const size = alignment.forward(unpadded_size); |
| 1019 | break :layout .{ size, size - unpadded_size, alignment }; |
| 1020 | }; |
| 1021 | |
| 1022 | if (class == .no_possible_value or class == .one_possible_value) { |
| 1023 | assert(size == 0); |
| 1024 | assert(padding == 0); |
| 1025 | } |
| 1026 | |
| 1027 | const casted_size = std.math.cast(u32, size) orelse return sema.fail( |
| 1028 | &block, |
| 1029 | union_ty.srcLoc(zcu), |
| 1030 | "union layout requires size {d}, this compiler implementation supports up to {d}", |
| 1031 | .{ size, std.math.maxInt(u32) }, |
| 1032 | ); |
| 1033 | ip.resolveUnionLayout( |
| 1034 | io, |
| 1035 | union_ty.toIntern(), |
| 1036 | enum_tag_ty.toIntern(), |
| 1037 | class, |
| 1038 | has_runtime_tag, |
| 1039 | casted_size, |
| 1040 | @intCast(padding), // okay because padding is no greater than size |
| 1041 | alignment, |
| 1042 | ); |
| 1043 | } |
| 1044 | fn failUnionFieldMismatch(sema: *Sema, block: *Block, union_field_names: []const InternPool.NullTerminatedString, enum_tag_ty: Type, enum_obj: *const InternPool.LoadedEnumType) CompileError { |
| 1045 | const pt = sema.pt; |
| 1046 | const zcu = pt.zcu; |
| 1047 | const comp = zcu.comp; |
| 1048 | const gpa = comp.gpa; |
| 1049 | const ip = &zcu.intern_pool; |
| 1050 | const enum_to_union_map = try sema.arena.alloc(?u32, enum_obj.field_names.len); |
| 1051 | @memset(enum_to_union_map, null); |
| 1052 | for (union_field_names, 0..) |field_name, union_field_index| { |
| 1053 | if (enum_obj.nameIndex(ip, field_name)) |enum_field_index| { |
| 1054 | enum_to_union_map[enum_field_index] = @intCast(union_field_index); |
| 1055 | continue; |
| 1056 | } |
| 1057 | const union_field_src = block.src(.{ .container_field_name = @intCast(union_field_index) }); |
| 1058 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 1059 | const msg = try sema.errMsg(union_field_src, "no field named '{f}' in enum '{f}'", .{ field_name.fmt(ip), enum_tag_ty.fmt(pt) }); |
| 1060 | errdefer msg.destroy(gpa); |
| 1061 | try sema.addDeclaredHereNote(msg, enum_tag_ty); |
| 1062 | break :msg msg; |
| 1063 | }); |
| 1064 | } |
| 1065 | for (enum_to_union_map, 0..) |union_field_index, enum_field_index| { |
| 1066 | if (union_field_index != null) continue; |
| 1067 | const field_name_ip = enum_obj.field_names.get(ip)[enum_field_index]; |
| 1068 | const enum_field_src: LazySrcLoc = .{ |
| 1069 | .base_node_inst = enum_tag_ty.typeDeclInstAllowGeneratedTag(zcu).?, |
| 1070 | .offset = .{ .container_field_name = @intCast(enum_field_index) }, |
| 1071 | }; |
| 1072 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 1073 | const msg = try sema.errMsg(block.nodeOffset(.zero), "enum field '{f}' missing from union", .{field_name_ip.fmt(ip)}); |
| 1074 | errdefer msg.destroy(gpa); |
| 1075 | try sema.errNote(enum_field_src, msg, "enum field here", .{}); |
| 1076 | break :msg msg; |
| 1077 | }); |
| 1078 | } |
| 1079 | // The only problem is the field ordering. |
| 1080 | for (enum_to_union_map, 0..) |union_field_index, enum_field_index| { |
| 1081 | if (union_field_index.? == enum_field_index) continue; |
| 1082 | const field_name = enum_obj.field_names.get(ip)[enum_field_index]; |
| 1083 | const union_field_src = block.src(.{ .container_field_name = union_field_index.? }); |
| 1084 | const enum_field_src: LazySrcLoc = .{ |
| 1085 | .base_node_inst = enum_tag_ty.typeDeclInstAllowGeneratedTag(zcu).?, |
| 1086 | .offset = .{ .container_field_name = @intCast(enum_field_index) }, |
| 1087 | }; |
| 1088 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 1089 | const msg = try sema.errMsg(block.nodeOffset(.zero), "union field order does not match tag enum field order", .{}); |
| 1090 | errdefer msg.destroy(gpa); |
| 1091 | try sema.errNote(union_field_src, msg, "union field '{f}' is index {d}", .{ field_name.fmt(ip), union_field_index.? }); |
| 1092 | try sema.errNote(enum_field_src, msg, "enum field '{f}' is index {d}", .{ field_name.fmt(ip), enum_field_index }); |
| 1093 | break :msg msg; |
| 1094 | }); |
| 1095 | } |
| 1096 | unreachable; // we already determined that *something* is wrong |
| 1097 | } |
| 1098 | fn resolvePackedUnionLayout( |
| 1099 | sema: *Sema, |
| 1100 | block: *Block, |
| 1101 | union_ty: Type, |
| 1102 | union_obj: *const InternPool.LoadedUnionType, |
| 1103 | enum_tag_ty: Type, |
| 1104 | ) CompileError!void { |
| 1105 | const pt = sema.pt; |
| 1106 | const zcu = pt.zcu; |
| 1107 | const comp = zcu.comp; |
| 1108 | const io = comp.io; |
| 1109 | const gpa = comp.gpa; |
| 1110 | const ip = &zcu.intern_pool; |
| 1111 | |
| 1112 | // Uninstantiable `packed union`s don't make sense; disallow them. |
| 1113 | if (union_obj.field_types.len == 0) { |
| 1114 | return sema.fail(block, union_ty.srcLoc(zcu), "packed union has no fields", .{}); |
| 1115 | } |
| 1116 | |
| 1117 | // Resolve the layout of all fields, and check their types are allowed. |
| 1118 | for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { |
| 1119 | const field_ty: Type = .fromInterned(field_ty_ip); |
| 1120 | assert(!field_ty.isGenericPoison()); |
| 1121 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); |
| 1122 | try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); |
| 1123 | if (field_ty.zigTypeTag(zcu) == .@"opaque") { |
| 1124 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 1125 | const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in union", .{field_ty.fmt(pt)}); |
| 1126 | errdefer msg.destroy(gpa); |
| 1127 | try sema.errNote(field_ty_src, msg, "opaque types have unknown size", .{}); |
| 1128 | try sema.addDeclaredHereNote(msg, field_ty); |
| 1129 | break :msg msg; |
| 1130 | }); |
| 1131 | } |
| 1132 | if (field_ty.unpackable(zcu)) |reason| return sema.failWithOwnedErrorMsg(block, msg: { |
| 1133 | const msg = try sema.errMsg(field_ty_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)}); |
| 1134 | errdefer msg.destroy(gpa); |
| 1135 | try sema.explainWhyTypeIsUnpackable(msg, field_ty_src, reason); |
| 1136 | break :msg msg; |
| 1137 | }); |
| 1138 | assert(!field_ty.comptimeOnly(zcu)); // packable types are not comptime-only |
| 1139 | } |
| 1140 | |
| 1141 | const explicit_backing_int_ty: ?Type = if (union_obj.is_reified) ty: { |
| 1142 | switch (union_obj.packed_backing_mode) { |
| 1143 | .explicit => break :ty .fromInterned(union_obj.packed_backing_int_type), |
| 1144 | .auto => break :ty null, |
| 1145 | } |
| 1146 | } else ty: { |
| 1147 | const zir_index = union_obj.zir_index.resolve(ip).?; |
| 1148 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 1149 | const backing_int_type_body = zir_union.arg_type_body orelse { |
| 1150 | break :ty null; // inferred backing type |
| 1151 | }; |
| 1152 | // Explicitly specified, so evaluate the backing int type expression. |
| 1153 | const backing_int_type_src = block.src(.container_arg); |
| 1154 | block.comptime_reason = .{ .reason = .{ |
| 1155 | .src = backing_int_type_src, |
| 1156 | .r = .{ .simple = .packed_union_backing_int_type }, |
| 1157 | } }; |
| 1158 | const type_ref = try sema.resolveInlineBody(block, backing_int_type_body, zir_index); |
| 1159 | break :ty try sema.analyzeAsType(block, backing_int_type_src, .packed_union_backing_int_type, type_ref); |
| 1160 | }; |
| 1161 | |
| 1162 | // Finally, either validate or infer the backing int type. |
| 1163 | const backing_int_ty: Type = if (explicit_backing_int_ty) |backing_ty| ty: { |
| 1164 | if (backing_ty.zigTypeTag(zcu) != .int) return sema.fail( |
| 1165 | block, |
| 1166 | block.src(.container_arg), |
| 1167 | "expected backing integer type, found '{f}'", |
| 1168 | .{backing_ty.fmt(pt)}, |
| 1169 | ); |
| 1170 | const backing_int_bits = backing_ty.intInfo(zcu).bits; |
| 1171 | for (union_obj.field_types.get(ip), 0..) |field_type_ip, field_idx| { |
| 1172 | const field_type: Type = .fromInterned(field_type_ip); |
| 1173 | const field_bits = field_type.bitSize(zcu); |
| 1174 | if (field_bits != backing_int_bits) return sema.failWithOwnedErrorMsg(block, msg: { |
| 1175 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_idx) }); |
| 1176 | const msg = try sema.errMsg(field_ty_src, "field bit width does not match backing integer", .{}); |
| 1177 | errdefer msg.destroy(gpa); |
| 1178 | try sema.errNote(field_ty_src, msg, "field type '{f}' has bit width '{d}'", .{ field_type.fmt(pt), field_bits }); |
| 1179 | try sema.errNote( |
| 1180 | block.src(.container_arg), |
| 1181 | msg, |
| 1182 | "backing integer '{f}' has bit width '{d}'", |
| 1183 | .{ backing_ty.fmt(pt), backing_int_bits }, |
| 1184 | ); |
| 1185 | try sema.errNote(field_ty_src, msg, "all fields in a packed union must have the same bit width", .{}); |
| 1186 | break :msg msg; |
| 1187 | }); |
| 1188 | } |
| 1189 | break :ty backing_ty; |
| 1190 | } else ty: { |
| 1191 | const field_types = union_obj.field_types.get(ip); |
| 1192 | const first_field_type: Type = .fromInterned(field_types[0]); |
| 1193 | const first_field_bits = first_field_type.bitSize(zcu); |
| 1194 | for (field_types[1..], 1..) |field_type_ip, field_idx| { |
| 1195 | const field_type: Type = .fromInterned(field_type_ip); |
| 1196 | const field_bits = field_type.bitSize(zcu); |
| 1197 | if (field_bits != first_field_bits) return sema.failWithOwnedErrorMsg(block, msg: { |
| 1198 | const first_field_ty_src = block.src(.{ .container_field_type = 0 }); |
| 1199 | const field_ty_src = block.src(.{ .container_field_type = @intCast(field_idx) }); |
| 1200 | const msg = try sema.errMsg(field_ty_src, "field bit width does not match earlier field", .{}); |
| 1201 | errdefer msg.destroy(gpa); |
| 1202 | try sema.errNote(field_ty_src, msg, "field type '{f}' has bit width '{d}'", .{ field_type.fmt(pt), field_bits }); |
| 1203 | try sema.errNote(first_field_ty_src, msg, "other field type '{f}' has bit width '{d}'", .{ first_field_type.fmt(pt), first_field_bits }); |
| 1204 | try sema.errNote(field_ty_src, msg, "all fields in a packed union must have the same bit width", .{}); |
| 1205 | break :msg msg; |
| 1206 | }); |
| 1207 | } |
| 1208 | const backing_int_bits = std.math.cast(u16, first_field_bits) orelse return sema.fail( |
| 1209 | block, |
| 1210 | union_ty.srcLoc(zcu), |
| 1211 | "packed union bit width '{d}' exceeds maximum bit width of 65535", |
| 1212 | .{first_field_bits}, |
| 1213 | ); |
| 1214 | break :ty try pt.intType(.unsigned, backing_int_bits); |
| 1215 | }; |
| 1216 | ip.resolvePackedUnionLayout( |
| 1217 | io, |
| 1218 | union_ty.toIntern(), |
| 1219 | enum_tag_ty.toIntern(), |
| 1220 | backing_int_ty.toIntern(), |
| 1221 | ); |
| 1222 | } |
| 1223 | |
| 1224 | pub fn resolveEnumLayout(sema: *Sema, enum_ty: Type) CompileError!void { |
| 1225 | const pt = sema.pt; |
| 1226 | const zcu = pt.zcu; |
| 1227 | const comp = zcu.comp; |
| 1228 | const io = comp.io; |
| 1229 | const gpa = comp.gpa; |
| 1230 | const ip = &zcu.intern_pool; |
| 1231 | |
| 1232 | const tracy = trace(@src()); |
| 1233 | defer tracy.end(); |
| 1234 | tracy.addText(enum_ty.containerTypeName(ip).fqn.toSlice(ip)); |
| 1235 | tracy.addTextFmt("ip_index={d}", .{enum_ty.toIntern()}); |
| 1236 | |
| 1237 | assert(sema.owner.unwrap().type_layout == enum_ty.toIntern()); |
| 1238 | |
| 1239 | const enum_obj = ip.loadEnumType(enum_ty.toIntern()); |
| 1240 | assert(enum_obj.want_layout); |
| 1241 | |
| 1242 | const maybe_parent_union_obj: ?InternPool.LoadedUnionType = un: { |
| 1243 | if (enum_obj.owner_union == .none) break :un null; |
| 1244 | break :un ip.loadUnionType(enum_obj.owner_union); |
| 1245 | }; |
| 1246 | |
| 1247 | const tracked_inst = enum_obj.zir_index.unwrap() orelse maybe_parent_union_obj.?.zir_index; |
| 1248 | const zir_index = tracked_inst.resolve(ip) orelse { |
| 1249 | return sema.failTransitive(.{ .lost_tracking = tracked_inst }); |
| 1250 | }; |
| 1251 | |
| 1252 | var block: Block = .{ |
| 1253 | .parent = null, |
| 1254 | .sema = sema, |
| 1255 | .namespace = enum_obj.namespace, |
| 1256 | .instructions = .empty, |
| 1257 | .inlining = null, |
| 1258 | .comptime_reason = undefined, // always set before using `block` |
| 1259 | .src_base_inst = tracked_inst, |
| 1260 | .type_name_ctx = enum_obj.name, |
| 1261 | .type_fqn_ctx = enum_obj.fqn, |
| 1262 | }; |
| 1263 | defer block.instructions.deinit(gpa); |
| 1264 | |
| 1265 | // There may be old field names in the map from a previous update. |
| 1266 | enum_obj.field_name_map.get(ip).clearRetainingCapacity(); |
| 1267 | |
| 1268 | if (maybe_parent_union_obj) |*union_obj| { |
| 1269 | if (union_obj.is_reified) { |
| 1270 | // In the case of reification, the union stores the field names, just for us to copy. |
| 1271 | @memcpy(enum_obj.field_names.get(ip), union_obj.reified_field_names.get(ip)); |
| 1272 | // The list of field names is now populated, but we haven't checked for duplicates yet, |
| 1273 | // nor have we populated the hash map. |
| 1274 | for (0..enum_obj.field_names.len) |field_index| { |
| 1275 | const name = enum_obj.field_names.get(ip)[field_index]; |
| 1276 | if (ip.addFieldName(enum_obj.field_names, enum_obj.field_name_map, name)) |prev_field_index| { |
| 1277 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 1278 | const src = block.builtinCallArgSrc(.zero, 2); |
| 1279 | const msg = try sema.errMsg(src, "duplicate union field '{f}' at index '{d}", .{ name.fmt(ip), field_index }); |
| 1280 | errdefer msg.destroy(gpa); |
| 1281 | try sema.errNote(src, msg, "previous field at index '{d}'", .{prev_field_index}); |
| 1282 | break :msg msg; |
| 1283 | }); |
| 1284 | } |
| 1285 | } |
| 1286 | } else { |
| 1287 | // Generated tag enums for declared unions do not yet have field names populated. It is |
| 1288 | // our job to populate them now. |
| 1289 | try sema.declareDependency(.{ .src_hash = union_obj.zir_index }); |
| 1290 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 1291 | for (zir_union.field_names) |zir_field_name| { |
| 1292 | const name_slice = sema.code.nullTerminatedString(zir_field_name); |
| 1293 | const name = try ip.getOrPutString(gpa, io, pt.tid, name_slice, .no_embedded_nulls); |
| 1294 | assert(ip.addFieldName(enum_obj.field_names, enum_obj.field_name_map, name) == null); // AstGen validated this for us |
| 1295 | } |
| 1296 | } |
| 1297 | } else { |
| 1298 | if (enum_obj.is_reified) { |
| 1299 | // The field names are populated, but we haven't checked for duplicates (nor populated the map) yet. |
| 1300 | for (0..enum_obj.field_names.len) |field_index| { |
| 1301 | const name = enum_obj.field_names.get(ip)[field_index]; |
| 1302 | if (ip.addFieldName(enum_obj.field_names, enum_obj.field_name_map, name)) |prev_field_index| { |
| 1303 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 1304 | const src = block.builtinCallArgSrc(.zero, 2); |
| 1305 | const msg = try sema.errMsg(src, "duplicate enum field '{f}' at index '{d}'", .{ name.fmt(ip), field_index }); |
| 1306 | errdefer msg.destroy(gpa); |
| 1307 | try sema.errNote(src, msg, "previous field at index '{d}'", .{prev_field_index}); |
| 1308 | break :msg msg; |
| 1309 | }); |
| 1310 | } |
| 1311 | } |
| 1312 | } else { |
| 1313 | // Declared enums do not yet have field names populated. It is our job to populate them now. |
| 1314 | try sema.declareDependency(.{ .src_hash = enum_obj.zir_index.unwrap().? }); |
| 1315 | const zir_enum = sema.code.getEnumDecl(zir_index); |
| 1316 | for (zir_enum.field_names) |zir_field_name| { |
| 1317 | const name_slice = sema.code.nullTerminatedString(zir_field_name); |
| 1318 | const name = try ip.getOrPutString(gpa, io, pt.tid, name_slice, .no_embedded_nulls); |
| 1319 | assert(ip.addFieldName(enum_obj.field_names, enum_obj.field_name_map, name) == null); // AstGen validated this for us |
| 1320 | } |
| 1321 | } |
| 1322 | } |
| 1323 | |
| 1324 | // Field names populated; now deal with the backing integer type. If explicitly provided, |
| 1325 | // validate it; otherwise, infer it. |
| 1326 | |
| 1327 | const explicit_int_tag_ty: ?Type = if (enum_obj.is_reified) ty: { |
| 1328 | break :ty switch (enum_obj.int_tag_mode) { |
| 1329 | .explicit => .fromInterned(enum_obj.int_tag_type), |
| 1330 | .auto => null, |
| 1331 | }; |
| 1332 | } else if (maybe_parent_union_obj) |*union_obj| ty: { |
| 1333 | if (union_obj.is_reified) { |
| 1334 | // Reification has no equivalent of 'union(enum(T))'. |
| 1335 | break :ty null; |
| 1336 | } |
| 1337 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 1338 | if (zir_union.kind != .tagged_enum_explicit) { |
| 1339 | break :ty null; // int tag type will be inferred |
| 1340 | } |
| 1341 | // Explicitly specified, so evaluate the int tag type expression. |
| 1342 | const tag_type_body = zir_union.arg_type_body.?; |
| 1343 | const tag_type_src = block.src(.container_arg); |
| 1344 | block.comptime_reason = .{ .reason = .{ |
| 1345 | .src = tag_type_src, |
| 1346 | .r = .{ .simple = .enum_int_tag_type }, |
| 1347 | } }; |
| 1348 | const type_ref = try sema.resolveInlineBody(&block, tag_type_body, zir_index); |
| 1349 | break :ty try sema.analyzeAsType(&block, tag_type_src, .enum_int_tag_type, type_ref); |
| 1350 | } else ty: { |
| 1351 | const zir_enum = sema.code.getEnumDecl(zir_index); |
| 1352 | const tag_type_body = zir_enum.tag_type_body orelse { |
| 1353 | break :ty null; // int tag type will be inferred |
| 1354 | }; |
| 1355 | // Explicitly specified, so evaluate the int tag type expression. |
| 1356 | const tag_type_src = block.src(.container_arg); |
| 1357 | block.comptime_reason = .{ .reason = .{ |
| 1358 | .src = tag_type_src, |
| 1359 | .r = .{ .simple = .enum_int_tag_type }, |
| 1360 | } }; |
| 1361 | const type_ref = try sema.resolveInlineBody(&block, tag_type_body, zir_index); |
| 1362 | break :ty try sema.analyzeAsType(&block, tag_type_src, .enum_int_tag_type, type_ref); |
| 1363 | }; |
| 1364 | const empty_exhaustive = enum_obj.field_names.len == 0 and !enum_obj.nonexhaustive; |
| 1365 | const int_tag_ty: Type = if (explicit_int_tag_ty) |int_tag_ty| ty: { |
| 1366 | switch (int_tag_ty.zigTypeTag(zcu)) { |
| 1367 | .int => if (empty_exhaustive) return sema.fail( |
| 1368 | &block, |
| 1369 | block.src(.container_arg), |
| 1370 | "empty exhaustive enums must be backed by 'noreturn'", |
| 1371 | .{}, |
| 1372 | ), |
| 1373 | .noreturn => if (!empty_exhaustive) return sema.fail( |
| 1374 | &block, |
| 1375 | block.src(.container_arg), |
| 1376 | "non-empty enums cannot be backed by 'noreturn'", |
| 1377 | .{}, |
| 1378 | ), |
| 1379 | else => return sema.fail( |
| 1380 | &block, |
| 1381 | block.src(.container_arg), |
| 1382 | "expected integer tag type, found '{f}'", |
| 1383 | .{int_tag_ty.fmt(pt)}, |
| 1384 | ), |
| 1385 | } |
| 1386 | break :ty int_tag_ty; |
| 1387 | } else ty: { |
| 1388 | if (empty_exhaustive) break :ty .noreturn; |
| 1389 | // Infer the int tag type from the field count |
| 1390 | const bits = Type.smallestUnsignedBits(enum_obj.field_names.len -| 1); |
| 1391 | break :ty try pt.intType(.unsigned, bits); |
| 1392 | }; |
| 1393 | |
| 1394 | ip.resolveEnumLayout(io, enum_ty.toIntern(), int_tag_ty.toIntern()); |
| 1395 | |
| 1396 | // Finally, deal with field values. For declared types we need to analyze the expressions, while |
| 1397 | // reified types already have them populated; but either way, we need to populate the hash map |
| 1398 | // (and validate the values along the way). |
| 1399 | |
| 1400 | // We'll populate this map. |
| 1401 | const field_value_map = enum_obj.field_value_map.unwrap() orelse { |
| 1402 | // The enum is auto-numbered with an inferred tag type. We know that the tag type generated |
| 1403 | // earlier is sufficient for the number of fields, so we have nothing more to do. |
| 1404 | assert(enum_obj.int_tag_mode == .auto); |
| 1405 | return; |
| 1406 | }; |
| 1407 | |
| 1408 | // There may be old field values in here from a previous update. |
| 1409 | field_value_map.get(ip).clearRetainingCapacity(); |
| 1410 | |
| 1411 | // Map the enum (or union) decl instruction to provide the tag type as the result type |
| 1412 | try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index}); |
| 1413 | sema.inst_map.putAssumeCapacity(zir_index, .fromIntern(int_tag_ty.toIntern())); |
| 1414 | defer assert(sema.inst_map.remove(zir_index)); |
| 1415 | |
| 1416 | // First, populate any explicitly provided values. This is the part that actually depends on |
| 1417 | // the ZIR, and hence depends on whether this is a declared or generated enum. If any explicit |
| 1418 | // value is straight-up invalid, we'll emit an error here. |
| 1419 | if (maybe_parent_union_obj) |union_obj| { |
| 1420 | if (union_obj.is_reified) { |
| 1421 | // Generated tag type for reified union; values already populated. |
| 1422 | } else { |
| 1423 | // Generated tag type for declared union; evaluate the expressions given in the union declaration. |
| 1424 | const zir_union = sema.code.getUnionDecl(zir_index); |
| 1425 | var field_it = zir_union.iterateFields(); |
| 1426 | while (field_it.next()) |zir_field| { |
| 1427 | const field_val_src = block.src(.{ .container_field_value = zir_field.idx }); |
| 1428 | block.comptime_reason = .{ .reason = .{ |
| 1429 | .src = field_val_src, |
| 1430 | .r = .{ .simple = .enum_field_values }, |
| 1431 | } }; |
| 1432 | const value_body = zir_field.value_body orelse { |
| 1433 | enum_obj.field_values.get(ip)[zir_field.idx] = .none; |
| 1434 | continue; |
| 1435 | }; |
| 1436 | const uncoerced = try sema.resolveInlineBody(&block, value_body, zir_index); |
| 1437 | const coerced = try sema.coerce(&block, int_tag_ty, uncoerced, field_val_src); |
| 1438 | const val = try sema.resolveConstValue(&block, field_val_src, coerced, null); |
| 1439 | enum_obj.field_values.get(ip)[zir_field.idx] = val.toIntern(); |
| 1440 | } |
| 1441 | } |
| 1442 | } else if (enum_obj.is_reified) { |
| 1443 | // Reified enum; values already populated. |
| 1444 | } else { |
| 1445 | // Declared enum; evaluate the expressions given in the enum declaration. |
| 1446 | const zir_enum = sema.code.getEnumDecl(zir_index); |
| 1447 | var field_it = zir_enum.iterateFields(); |
| 1448 | while (field_it.next()) |zir_field| { |
| 1449 | const field_val_src = block.src(.{ .container_field_value = zir_field.idx }); |
| 1450 | block.comptime_reason = .{ .reason = .{ |
| 1451 | .src = field_val_src, |
| 1452 | .r = .{ .simple = .enum_field_values }, |
| 1453 | } }; |
| 1454 | const value_body = zir_field.value_body orelse { |
| 1455 | enum_obj.field_values.get(ip)[zir_field.idx] = .none; |
| 1456 | continue; |
| 1457 | }; |
| 1458 | const uncoerced = try sema.resolveInlineBody(&block, value_body, zir_index); |
| 1459 | const coerced = try sema.coerce(&block, int_tag_ty, uncoerced, field_val_src); |
| 1460 | const val = try sema.resolveConstDefinedValue(&block, field_val_src, coerced, null); |
| 1461 | enum_obj.field_values.get(ip)[zir_field.idx] = val.toIntern(); |
| 1462 | } |
| 1463 | } |
| 1464 | |
| 1465 | // Explicit values are set. Now we'll go through the whole array and figure out the final |
| 1466 | // field values. This is also where we'll detect duplicates. |
| 1467 | |
| 1468 | for (0..enum_obj.field_names.len) |field_idx| { |
| 1469 | const field_val_src = block.src(.{ .container_field_value = @intCast(field_idx) }); |
| 1470 | // If the field value was not specified, compute the implicit value. |
| 1471 | const field_val = val: { |
| 1472 | const explicit_val = enum_obj.field_values.get(ip)[field_idx]; |
| 1473 | if (explicit_val != .none) { |
| 1474 | assert(ip.typeOf(explicit_val) == int_tag_ty.toIntern()); |
| 1475 | break :val explicit_val; |
| 1476 | } |
| 1477 | if (field_idx == 0) { |
| 1478 | // Implicit value is 0, which is valid for every integer type. |
| 1479 | const val = (try pt.intValue(int_tag_ty, 0)).toIntern(); |
| 1480 | enum_obj.field_values.get(ip)[field_idx] = val; |
| 1481 | break :val val; |
| 1482 | } |
| 1483 | // Implicit non-initial value: take the previous field value and add one. |
| 1484 | const prev_field_val: Value = .fromInterned(enum_obj.field_values.get(ip)[field_idx - 1]); |
| 1485 | const result = try arith.incrementDefinedInt(sema, int_tag_ty, prev_field_val); |
| 1486 | if (result.overflow) return sema.fail( |
| 1487 | &block, |
| 1488 | field_val_src, |
| 1489 | "enum tag value '{f}' too large for type '{f}'", |
| 1490 | .{ result.val.fmtValueSema(pt, sema), int_tag_ty.fmt(pt) }, |
| 1491 | ); |
| 1492 | const val = result.val.toIntern(); |
| 1493 | enum_obj.field_values.get(ip)[field_idx] = val; |
| 1494 | break :val val; |
| 1495 | }; |
| 1496 | if (ip.addFieldTagValue(enum_obj.field_values, field_value_map, field_val)) |prev_field_index| { |
| 1497 | return sema.failWithOwnedErrorMsg(&block, msg: { |
| 1498 | const prev_field_val_src = block.src(.{ .container_field_value = prev_field_index }); |
| 1499 | const msg = try sema.errMsg(field_val_src, "enum tag value '{f}' for field '{f}' already taken", .{ |
| 1500 | Value.fromInterned(field_val).fmtValueSema(pt, sema), |
| 1501 | enum_obj.field_names.get(ip)[field_idx].fmt(ip), |
| 1502 | }); |
| 1503 | errdefer msg.destroy(gpa); |
| 1504 | try sema.errNote(prev_field_val_src, msg, "previous occurrence in field '{f}'", .{ |
| 1505 | enum_obj.field_names.get(ip)[prev_field_index].fmt(ip), |
| 1506 | }); |
| 1507 | break :msg msg; |
| 1508 | }); |
| 1509 | } |
| 1510 | } |
| 1511 | |
| 1512 | if (enum_obj.nonexhaustive) { |
| 1513 | const fields_len = enum_obj.field_names.len; |
| 1514 | if (fields_len >= 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(zcu)) { |
| 1515 | return sema.fail(&block, block.nodeOffset(.zero), "non-exhaustive enum specifies every value", .{}); |
| 1516 | } |
| 1517 | } |
| 1518 | } |