From 040ce4b43ca92bae5425c7fedc37cb1de0d64caf Mon Sep 17 00:00:00 2001 From: ShadowCurse Date: Tue, 14 Apr 2026 23:15:26 +0100 Subject: [PATCH 1/3] feat: InternPool: add type size/alignment calculations Add utility functions for calculation of size and alignment of a given type. These will be used in the following commit to provide additional hover information. Signed-off-by: ShadowCurse --- src/analyser/InternPool.zig | 723 ++++++++++++++++++++++++++++++++++++ 1 file changed, 723 insertions(+) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 03b372049..fff77e01b 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3604,6 +3604,348 @@ pub fn getNamespace(ip: *InternPool, ty: Index) NamespaceIndex { }; } +pub fn typeAlignment(ip: *InternPool, ty: Index, target: std.Target) ?u16 { + return switch (ty) { + .bool_type => 1, + .void_type => 1, + .u1_type, .u8_type, .i8_type => 1, + .u16_type, .i16_type, .f16_type => 2, + .u29_type, .u32_type, .i32_type, .f32_type => 4, + .u64_type, .i64_type, .f64_type => 8, + .u128_type, .i128_type, .f128_type => 16, + .f80_type => 16, // x86 extended precision + .usize_type, .isize_type => @intCast(target.ptrBitWidth() / 8), + + .c_char_type => target.cTypeBitSize(.char) / 8, + .c_short_type, .c_ushort_type => target.cTypeBitSize(.short) / 8, + .c_int_type, .c_uint_type => target.cTypeBitSize(.int) / 8, + .c_long_type, .c_ulong_type => target.cTypeBitSize(.long) / 8, + .c_longlong_type, .c_ulonglong_type => target.cTypeBitSize(.longlong) / 8, + .c_longdouble_type => target.cTypeBitSize(.longdouble) / 8, + + .anyerror_type => 2, + + .comptime_int_type, + .comptime_float_type, + .type_type, + .noreturn_type, + .anyopaque_type, + .anyframe_type, + .null_type, + .undefined_type, + .enum_literal_type, + .generic_poison_type, + .unknown_type, + => null, + + else => switch (ip.indexToKey(ty)) { + .int_type => |int_info| blk: { + // Round up bits to the nearest power of 2 + var bits = @max(int_info.bits, 8); + bits = std.math.pow(u16, 2, std.math.log2_int_ceil(u16, bits)); + const bytes = bits / 8; + // Any integer type above u128 seems to have 16 byte aligment. Maybe + // AVX-512 changes this + const max_align = 16; + break :blk @min(bytes, max_align); + }, + .pointer_type => target.ptrBitWidth() / 8, + .array_type => |arr| ip.typeAlignment(arr.child, target), + .optional_type => |opt| blk: { + break :blk if (ip.indexToKey(opt.payload_type) == .pointer_type) + target.ptrBitWidth() / 8 + else + ip.typeAlignment(opt.payload_type, target); + }, + .enum_type => |enum_idx| ip.typeAlignment(ip.getEnum(enum_idx).tag_type, target), + .struct_type => |struct_idx| ip.structAlignment(struct_idx, target), + .union_type => |union_idx| ip.unionAlignment(union_idx, target), + .error_union_type => 2, // error_unions are 2 bytes long + .vector_type => blk: { + if (ip.typeBitSize(ty, target)) |vec_size| { + // From experiments, this seems to hold, but maybe AVX-512 adds 64 byte alignment + // as well. Maybe can check the target.cpu.features for this + if (vec_size < 256) break :blk 16 else break :blk 32; + } else break :blk null; + }, + else => null, + }, + }; +} + +fn structAlignment(ip: *InternPool, struct_idx: Struct.Index, target: std.Target) ?u16 { + const struct_info = ip.getStruct(struct_idx); + + if (struct_info.layout == .@"packed" and struct_info.backing_int_ty != .none) { + return ip.typeAlignment(struct_info.backing_int_ty, target); + } + + var max_align: u16 = 1; + for (struct_info.fields.values()) |field| { + if (field.is_comptime) continue; + if (field.alignment != 0) { + max_align = @max(max_align, field.alignment); + } else { + const field_align = ip.typeAlignment(field.ty, target) orelse return null; + max_align = @max(max_align, field_align); + } + } + return max_align; +} + +fn unionAlignment(ip: *InternPool, union_idx: Union.Index, target: std.Target) ?u16 { + const union_info = ip.getUnion(union_idx); + + var max_align: u16 = 1; + if (union_info.tag_type != .none) { + const tag_align = ip.typeAlignment(union_info.tag_type, target) orelse return null; + max_align = @max(max_align, tag_align); + } + + for (union_info.fields.values()) |field| { + if (field.alignment != 0) { + max_align = @max(max_align, field.alignment); + } else { + const field_align = ip.typeAlignment(field.ty, target) orelse return null; + max_align = @max(max_align, field_align); + } + } + return max_align; +} + +pub fn typeBitSize(ip: *InternPool, ty: Index, target: std.Target) ?u64 { + return switch (ty) { + .bool_type => 8, + .void_type => 0, + .u1_type => 1, + .u8_type, .i8_type => 8, + .u16_type, .i16_type => 16, + .u29_type => 29, + .u32_type, .i32_type => 32, + .u64_type, .i64_type => 64, + .u128_type, .i128_type => 128, + .usize_type, .isize_type => target.ptrBitWidth(), + + .f16_type => 16, + .f32_type => 32, + .f64_type => 64, + .f80_type => 80, + .f128_type => 128, + + .c_char_type => target.cTypeBitSize(.char), + .c_short_type, .c_ushort_type => target.cTypeBitSize(.short), + .c_int_type, .c_uint_type => target.cTypeBitSize(.int), + .c_long_type, .c_ulong_type => target.cTypeBitSize(.long), + .c_longlong_type, .c_ulonglong_type => target.cTypeBitSize(.longlong), + .c_longdouble_type => target.cTypeBitSize(.longdouble), + + .anyerror_type => 16, + + .comptime_int_type, + .comptime_float_type, + .type_type, + .noreturn_type, + .anyopaque_type, + .anyframe_type, + .null_type, + .undefined_type, + .enum_literal_type, + .generic_poison_type, + .unknown_type, + => null, + + .empty_struct_type => 0, + + else => switch (ip.indexToKey(ty)) { + .int_type => |int_info| int_info.bits, + .pointer_type => |ptr_info| if (ptr_info.flags.size == .slice) + target.ptrBitWidth() * 2 + else + target.ptrBitWidth(), + .array_type => |arr| blk: { + const elem_size = ip.typeBitSize(arr.child, target) orelse return null; + break :blk elem_size * arr.len; + }, + .optional_type => |opt| ip.optionalBitSize(opt.payload_type, target), + .enum_type => |enum_idx| ip.typeBitSize(ip.getEnum(enum_idx).tag_type, target), + .struct_type => |struct_idx| ip.structBitSize(struct_idx, target), + .union_type => |union_idx| ip.unionBitSize(union_idx, target), + .error_union_type => 16, + .vector_type => |vec| blk: { + if (ip.typeBitSize(vec.child, target)) |child_size| { + const child_bits = std.math.pow(u64, 2, std.math.log2_int_ceil(u64, child_size)); + var total_bits = child_bits * vec.len; + // At lest it would be 128 SIMD lane + total_bits = @max(total_bits, 128); + // And if it is bigger, go for a next power of 2 + total_bits = std.math.pow(u64, 2, std.math.log2_int_ceil(u64, total_bits)); + break :blk total_bits; + } else { + break :blk null; + } + }, + else => null, + }, + }; +} + +fn optionalBitSize(ip: *InternPool, payload_ty: Index, target: std.Target) ?u64 { + if (ip.indexToKey(payload_ty) == .pointer_type) { + return target.ptrBitWidth(); + } + + const payload_bits = ip.typeBitSize(payload_ty, target) orelse return null; + const payload_align = ip.typeAlignment(payload_ty, target) orelse return null; + + // The optional part will be smallest type with same alignment. Just add 8 bits + // pretending it is a u8 value. alignForward will do the rest. + const total_bits = payload_bits + 8; + const align_bits = payload_align * 8; + return std.mem.alignForward(u64, total_bits, align_bits); +} + +fn structBitSize(ip: *InternPool, struct_idx: Struct.Index, target: std.Target) ?u64 { + const struct_info = ip.getStruct(struct_idx); + + return switch (struct_info.layout) { + .@"packed" => ip.structBitSizePacked(struct_info, target), + .@"extern" => ip.structBitSizeExtern(struct_info, target), + .auto => ip.structBitSizeAuto(struct_info, target), + }; +} + +fn structBitSizePacked(ip: *InternPool, struct_info: *const Struct, target: std.Target) ?u64 { + if (struct_info.backing_int_ty != .none) { + return ip.typeBitSize(struct_info.backing_int_ty, target); + } + + var total_bits: u64 = 0; + for (struct_info.fields.values()) |field| { + if (field.is_comptime) continue; + const field_bits = ip.typeBitSize(field.ty, target) orelse return null; + total_bits += field_bits; + } + return total_bits; +} + +fn structBitSizeExtern(ip: *InternPool, struct_info: *const Struct, target: std.Target) ?u64 { + var total_bits: u64 = 0; + var max_align: u16 = 1; + + for (struct_info.fields.values()) |field| { + if (field.is_comptime) continue; + const field_align = if (field.alignment != 0) + field.alignment + else + ip.typeAlignment(field.ty, target) orelse return null; + const field_bits = ip.typeBitSize(field.ty, target) orelse return null; + + max_align = @max(max_align, field_align); + + const align_bits = field_align * 8; + total_bits = std.mem.alignForward(u64, total_bits, align_bits); + + total_bits += field_bits; + } + + const final_align_bits = max_align * 8; + return std.mem.alignForward(u64, total_bits, final_align_bits); +} + +fn structBitSizeAuto(ip: *InternPool, struct_info: *const Struct, target: std.Target) ?u64 { + const Inner = struct { + size_bits: u64 = 0, + alignment: u16 = 0, + + fn cmp(_: void, s1: @This(), s2: @This()) bool { + // inverse sort by alignment + return s2.alignment < s1.alignment; + } + }; + // no allocations, no size calculations + const data = ip.gpa.alloc(Inner, struct_info.fields.values().len) catch return null; + defer ip.gpa.free(data); + + for (struct_info.fields.values(), data) |*field, *d| { + if (field.is_comptime) { + d.* = .{}; + } else { + d.size_bits = ip.typeBitSize(field.ty, target) orelse return null; + d.alignment = if (field.alignment != 0) + field.alignment + else + ip.typeAlignment(field.ty, target) orelse return null; + } + } + std.mem.sortUnstable(Inner, data, {}, Inner.cmp); + + var total_bits: u64 = 0; + var max_align: u16 = 1; + + for (data) |d| { + max_align = @max(max_align, d.alignment); + + const align_bits = d.alignment * 8; + total_bits = std.mem.alignForward(u64, total_bits, align_bits); + + total_bits += d.size_bits; + } + + const final_align_bits = max_align * 8; + return std.mem.alignForward(u64, total_bits, final_align_bits); +} + +fn unionBitSize(ip: *InternPool, union_idx: Union.Index, target: std.Target) ?u64 { + const union_info = ip.getUnion(union_idx); + + if (union_info.layout == .@"packed" or union_info.layout == .@"extern") { + return ip.unionBitSizePackedOrExtern(union_info, target); + } + + return switch (union_info.layout) { + .@"packed", .@"extern" => ip.unionBitSizePackedOrExtern(union_info, target), + .auto => ip.unionBitSizeAuto(union_info, target), + }; +} + +fn unionBitSizeAuto(ip: *InternPool, union_info: *const Union, target: std.Target) ?u64 { + var total_bits: u64 = 0; + var max_align: u16 = 1; + + for (union_info.fields.values()) |field| { + const field_bits = ip.typeBitSize(field.ty, target) orelse return null; + const field_align = if (field.alignment != 0) + field.alignment + else + ip.typeAlignment(field.ty, target) orelse return null; + + total_bits = @max(total_bits, field_bits); + max_align = @max(max_align, field_align); + } + + if (union_info.tag_type != .none) { + const tag_bits = ip.typeBitSize(union_info.tag_type, target) orelse return null; + const tag_align = ip.typeAlignment(union_info.tag_type, target) orelse return null; + max_align = @max(max_align, tag_align); + total_bits += tag_bits; + } else { + // normal unions have at least a byte tag for safety checks + total_bits += 8; + } + + const final_align_bits = max_align * 8; + return std.mem.alignForward(u64, total_bits, final_align_bits); +} + +fn unionBitSizePackedOrExtern(ip: *InternPool, union_info: *const Union, target: std.Target) ?u64 { + var max_size: u64 = 0; + for (union_info.fields.values()) |field| { + const field_size = ip.typeBitSize(field.ty, target) orelse return null; + max_size = @max(max_size, field_size); + } + return max_size; +} + pub fn onePossibleValue(ip: *InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { .simple_type => |simple| switch (simple) { @@ -5358,3 +5700,384 @@ fn testCoerce(ip: *InternPool, dest_ty: Index, inst: Index, expected: Index) !vo return error.TestExpectedEqual; } + +test "typeBitSize/Alignment: basic types" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const Inner = struct { + fn expect(i: *InternPool, t: std.Target, ty: Index, b: u64, a: u16) !void { + try std.testing.expectEqual(b, i.typeBitSize(ty, t)); + try std.testing.expectEqual(a, i.typeAlignment(ty, t)); + } + }; + + try Inner.expect(&ip, target, .bool_type, 8, 1); + try Inner.expect(&ip, target, .void_type, 0, 1); + + try Inner.expect(&ip, target, .u8_type, 8, 1); + try Inner.expect(&ip, target, .i8_type, 8, 1); + try Inner.expect(&ip, target, .u16_type, 16, 2); + try Inner.expect(&ip, target, .i16_type, 16, 2); + try Inner.expect(&ip, target, .u32_type, 32, 4); + try Inner.expect(&ip, target, .i32_type, 32, 4); + try Inner.expect(&ip, target, .u64_type, 64, 8); + try Inner.expect(&ip, target, .i64_type, 64, 8); + try Inner.expect(&ip, target, .u128_type, 128, 16); + try Inner.expect(&ip, target, .i128_type, 128, 16); + try Inner.expect(&ip, target, .usize_type, 64, 8); + try Inner.expect(&ip, target, .isize_type, 64, 8); + + try Inner.expect(&ip, target, .f16_type, 16, 2); + try Inner.expect(&ip, target, .f32_type, 32, 4); + try Inner.expect(&ip, target, .f64_type, 64, 8); + try Inner.expect(&ip, target, .f80_type, 80, 16); + try Inner.expect(&ip, target, .f128_type, 128, 16); + + try Inner.expect(&ip, target, .anyerror_type, 16, 2); + + const u7_type = try ip.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 7 } }); + try Inner.expect(&ip, target, u7_type, 7, 1); + + const u24_type = try ip.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 24 } }); + try Inner.expect(&ip, target, u24_type, 24, 4); + + const u256_type = try ip.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 256 } }); + try Inner.expect(&ip, target, u256_type, 256, 16); +} + +test "typeBitSize/Alignment: slices and pointers" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const Inner = struct { + fn expect(i: *InternPool, t: std.Target, ty: Index, b: u64, a: u16) !void { + try std.testing.expectEqual(b, i.typeBitSize(ty, t)); + try std.testing.expectEqual(a, i.typeAlignment(ty, t)); + } + }; + + // Regular pointer: 64 bits on x86_64 + const ptr_i32 = try ip.get(.{ .pointer_type = .{ + .elem_type = .i32_type, + .flags = .{ .size = .one }, + } }); + try Inner.expect(&ip, target, ptr_i32, 64, 8); + + const slice_u8 = try ip.get(.{ .pointer_type = .{ + .elem_type = .u8_type, + .flags = .{ .size = .slice }, + } }); + try Inner.expect(&ip, target, slice_u8, 128, 8); + + const many_ptr_f64 = try ip.get(.{ .pointer_type = .{ + .elem_type = .f64_type, + .flags = .{ .size = .many }, + } }); + try Inner.expect(&ip, target, many_ptr_f64, 64, 8); +} + +test "typeBitSize/Alignment: vectors" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const Inner = struct { + fn expect(i: *InternPool, t: std.Target, ty: Index, b: u64, a: u16) !void { + try std.testing.expectEqual(b, i.typeBitSize(ty, t)); + try std.testing.expectEqual(a, i.typeAlignment(ty, t)); + } + }; + + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 1, .child = .u8_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 2, .child = .u8_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 4, .child = .u8_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 8, .child = .u8_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 16, .child = .u8_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 32, .child = .u8_type } }), 256, 32); + + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 1, .child = .u16_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 2, .child = .u16_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 4, .child = .u16_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 8, .child = .u16_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 16, .child = .u16_type } }), 256, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 32, .child = .u16_type } }), 512, 32); + + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 1, .child = .u32_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 2, .child = .u32_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 4, .child = .u32_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 8, .child = .u32_type } }), 256, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 16, .child = .u32_type } }), 512, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 32, .child = .u32_type } }), 1024, 32); + + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 1, .child = .u64_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 2, .child = .u64_type } }), 128, 16); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 4, .child = .u64_type } }), 256, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 8, .child = .u64_type } }), 512, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 16, .child = .u64_type } }), 1024, 32); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 32, .child = .u64_type } }), 2048, 32); + + const u7_type = try ip.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 7 } }); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 1, .child = u7_type } }), 128, 16); + const u33_type = try ip.get(.{ .int_type = .{ .signedness = .unsigned, .bits = 33 } }); + try Inner.expect(&ip, target, try ip.get(.{ .vector_type = .{ .len = 4, .child = u33_type } }), 256, 32); +} + +test "typeBitSize/Alignment: structs" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const field_a = try ip.string_pool.getOrPutString(io, gpa, "a"); + const field_b = try ip.string_pool.getOrPutString(io, gpa, "b"); + const field_c = try ip.string_pool.getOrPutString(io, gpa, "c"); + + const auto_struct_idx = try ip.createStruct(.{ + .fields = .empty, + .owner_decl = .none, + .namespace = .none, + .layout = .auto, + .backing_int_ty = .none, + .status = .none, + }); + const auto_struct = ip.getStructMut(auto_struct_idx); + try auto_struct.fields.put(gpa, field_a, .{ .ty = .u8_type }); + try auto_struct.fields.put(gpa, field_b, .{ .ty = .u32_type }); + try auto_struct.fields.put(gpa, field_c, .{ .ty = .u8_type }); + + const auto_struct_type = try ip.get(.{ .struct_type = auto_struct_idx }); + try std.testing.expectEqual(64, ip.typeBitSize(auto_struct_type, target)); + try std.testing.expectEqual(4, ip.typeAlignment(auto_struct_type, target)); + + const extern_struct_idx = try ip.createStruct(.{ + .fields = .empty, + .owner_decl = .none, + .namespace = .none, + .layout = .@"extern", + .backing_int_ty = .none, + .status = .none, + }); + const extern_struct = ip.getStructMut(extern_struct_idx); + try extern_struct.fields.put(gpa, field_a, .{ .ty = .u8_type }); + try extern_struct.fields.put(gpa, field_b, .{ .ty = .u32_type }); + try extern_struct.fields.put(gpa, field_c, .{ .ty = .u8_type }); + + const extern_struct_type = try ip.get(.{ .struct_type = extern_struct_idx }); + try std.testing.expectEqual(96, ip.typeBitSize(extern_struct_type, target)); + try std.testing.expectEqual(4, ip.typeAlignment(extern_struct_type, target)); + + const packed_struct_idx = try ip.createStruct(.{ + .fields = .empty, + .owner_decl = .none, + .namespace = .none, + .layout = .@"packed", + .backing_int_ty = .u32_type, + .status = .none, + }); + const packed_struct = ip.getStructMut(packed_struct_idx); + try packed_struct.fields.put(gpa, field_a, .{ .ty = .u8_type }); + try packed_struct.fields.put(gpa, field_b, .{ .ty = .u16_type }); + try packed_struct.fields.put(gpa, field_c, .{ .ty = .u8_type }); + + const packed_struct_type = try ip.get(.{ .struct_type = packed_struct_idx }); + try std.testing.expectEqual(32, ip.typeBitSize(packed_struct_type, target)); + try std.testing.expectEqual(4, ip.typeAlignment(packed_struct_type, target)); +} + +test "typeBitSize/Alignment: enums" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const enum_u8_idx = try ip.createEnum(.{ + .tag_type = .u8_type, + .fields = .{}, + .values = .{}, + .namespace = .none, + .tag_type_inferred = false, + }); + const enum_u8_type = try ip.get(.{ .enum_type = enum_u8_idx }); + try std.testing.expectEqual(8, ip.typeBitSize(enum_u8_type, target)); + try std.testing.expectEqual(1, ip.typeAlignment(enum_u8_type, target)); + + const enum_u16_idx = try ip.createEnum(.{ + .tag_type = .u16_type, + .fields = .{}, + .values = .{}, + .namespace = .none, + .tag_type_inferred = false, + }); + const enum_u16_type = try ip.get(.{ .enum_type = enum_u16_idx }); + try std.testing.expectEqual(16, ip.typeBitSize(enum_u16_type, target)); + try std.testing.expectEqual(2, ip.typeAlignment(enum_u16_type, target)); + + const enum_u32_idx = try ip.createEnum(.{ + .tag_type = .u32_type, + .fields = .{}, + .values = .{}, + .namespace = .none, + .tag_type_inferred = false, + }); + const enum_u32_type = try ip.get(.{ .enum_type = enum_u32_idx }); + try std.testing.expectEqual(32, ip.typeBitSize(enum_u32_type, target)); + try std.testing.expectEqual(4, ip.typeAlignment(enum_u32_type, target)); +} + +test "typeBitSize/Alignment: unions" { + const gpa = std.testing.allocator; + const io = std.testing.io; + + var ip: InternPool = try .init(io, gpa); + defer ip.deinit(gpa); + + const target: std.Target = .{ + .cpu = .{ + .arch = .x86_64, + .model = undefined, + .features = .empty, + }, + .os = .{ .tag = .linux, .version_range = undefined }, + .abi = .gnu, + .ofmt = .elf, + }; + + const field_a = try ip.string_pool.getOrPutString(io, gpa, "a"); + const field_b = try ip.string_pool.getOrPutString(io, gpa, "b"); + const field_c = try ip.string_pool.getOrPutString(io, gpa, "c"); + + const auto_union_idx = try ip.createUnion(.{ + .tag_type = .none, + .fields = .empty, + .namespace = .none, + .layout = .auto, + .status = .none, + }); + const auto_union = ip.getUnionMut(auto_union_idx); + try auto_union.fields.put(gpa, field_a, .{ .ty = .i32_type, .alignment = 0 }); + try auto_union.fields.put(gpa, field_b, .{ .ty = .f64_type, .alignment = 0 }); + + const auto_union_type = try ip.get(.{ .union_type = auto_union_idx }); + try std.testing.expectEqual(128, ip.typeBitSize(auto_union_type, target)); + try std.testing.expectEqual(8, ip.typeAlignment(auto_union_type, target)); + + const u64_tag_enum_idx = try ip.createEnum(.{ + .tag_type = .u64_type, + .fields = .{}, + .values = .{}, + .namespace = .none, + .tag_type_inferred = false, + }); + const u64_tag_type = try ip.get(.{ .enum_type = u64_tag_enum_idx }); + + const tagged_union_idx = try ip.createUnion(.{ + .tag_type = u64_tag_type, + .fields = .empty, + .namespace = .none, + .layout = .auto, + .status = .none, + }); + const tagged_union = ip.getUnionMut(tagged_union_idx); + try tagged_union.fields.put(gpa, field_a, .{ .ty = .i8_type, .alignment = 0 }); + try tagged_union.fields.put(gpa, field_b, .{ .ty = .u16_type, .alignment = 0 }); + + const tagged_union_type = try ip.get(.{ .union_type = tagged_union_idx }); + try std.testing.expectEqual(128, ip.typeBitSize(tagged_union_type, target)); + try std.testing.expectEqual(8, ip.typeAlignment(tagged_union_type, target)); + + const extern_union_idx = try ip.createUnion(.{ + .tag_type = .none, + .fields = .empty, + .namespace = .none, + .layout = .@"extern", + .status = .none, + }); + const extern_union = ip.getUnionMut(extern_union_idx); + try extern_union.fields.put(gpa, field_a, .{ .ty = .i32_type, .alignment = 0 }); + try extern_union.fields.put(gpa, field_c, .{ .ty = .f64_type, .alignment = 0 }); + + const extern_union_type = try ip.get(.{ .union_type = extern_union_idx }); + try std.testing.expectEqual(64, ip.typeBitSize(extern_union_type, target)); + try std.testing.expectEqual(8, ip.typeAlignment(extern_union_type, target)); + + const packed_union_idx = try ip.createUnion(.{ + .tag_type = .none, + .fields = .empty, + .namespace = .none, + .layout = .@"packed", + .status = .none, + }); + const packed_union = ip.getUnionMut(packed_union_idx); + try packed_union.fields.put(gpa, field_a, .{ .ty = .i32_type, .alignment = 0 }); + try packed_union.fields.put(gpa, field_b, .{ .ty = .f32_type, .alignment = 0 }); + + const packed_union_type = try ip.get(.{ .union_type = packed_union_idx }); + try std.testing.expectEqual(32, ip.typeBitSize(packed_union_type, target)); + try std.testing.expectEqual(4, ip.typeAlignment(packed_union_type, target)); +} From 4ad4f3545806787a095e8e3a283e460574e79a43 Mon Sep 17 00:00:00 2001 From: ShadowCurse Date: Tue, 14 Apr 2026 23:18:23 +0100 Subject: [PATCH 2/3] feat: analyser: add type size/alignment calculations Add functions for size/alignment retrieval for types. The implementation tries to get size/align info from InternPool if it can, but since not all types exist there (as far as I can see, only primitive types do), there is an alternative calculation path using the AST directly. This seems to work pretty well, but has limitations that for `extern` types it is not possible to correctly calculate the size since the order of fields may be unstable. Additionally, manually `align(..)`ed fields also do not work in this version. Next commit will use these to add size/alignment info to the hover tooltips. Signed-off-by: ShadowCurse --- src/analysis.zig | 284 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/src/analysis.zig b/src/analysis.zig index 1c3d9535d..9f6071e5d 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -3875,6 +3875,13 @@ pub const Type = struct { }; } + pub fn ipTypeIndex(self: Type) ?InternPool.Index { + return switch (self.data) { + .ip_index => |payload| payload.type, + else => null, + }; + } + fn withoutIPIndex(self: Type, analyser: *Analyser) Type { return switch (self.data) { .ip_index => |payload| fromIP(analyser, payload.type, null), @@ -7013,3 +7020,280 @@ pub const ReferencedType = struct { } }; }; + +pub const SizeAlign = struct { + bits: u64, + align_bytes: u16, + + pub fn format(self: SizeAlign, arena: std.mem.Allocator) error{OutOfMemory}![]const u8 { + const bytes = self.bits / 8; + return std.fmt.allocPrint(arena, "Size: {d} byte{s} ({d} bit{s}), Align: {d}", .{ + bytes, + if (bytes == 1) "" else "s", + self.bits, + if (self.bits == 1) "" else "s", + self.align_bytes, + }); + } + + pub fn fromIndex(ip: *InternPool, idx: InternPool.Index) ?SizeAlign { + const bits = ip.typeBitSize(idx, builtin.target) orelse return null; + const align_bytes = ip.typeAlignment(idx, builtin.target) orelse return null; + return .{ .bits = bits, .align_bytes = align_bytes }; + } + + pub fn fromType(analyser: *Analyser, ty: Analyser.Type) ?SizeAlign { + // Fast path if type is in the InternPool + const type_index = if (ty.is_type_val) ty.ipIndex() else ty.ipTypeIndex(); + if (type_index) |idx| return fromIndex(analyser.ip, idx); + + // Since not all types are in the InternPool, we need to reimplement some + // parts of it here + return switch (ty.data) { + .pointer => |ptr_info| .{ + .bits = if (ptr_info.size == .slice) + builtin.target.ptrBitWidth() * 2 + else + builtin.target.ptrBitWidth(), + .align_bytes = builtin.target.ptrBitWidth() / 8, + }, + .array => |arr_info| blk: { + const elem_size = fromType(analyser, arr_info.elem_ty.*) orelse break :blk null; + const arr_len = arr_info.elem_count orelse break :blk null; + break :blk .{ + .bits = elem_size.bits * arr_len, + .align_bytes = elem_size.align_bytes, + }; + }, + .optional => |opt_ty| fromOptional(analyser, opt_ty.*), + else => null, + }; + } + + pub fn fromOptional(analyser: *Analyser, payload_ty: Analyser.Type) ?SizeAlign { + if (payload_ty.data == .pointer) { + return .{ + .bits = builtin.target.ptrBitWidth(), + .align_bytes = builtin.target.ptrBitWidth() / 8, + }; + } + const payload = fromType(analyser, payload_ty) orelse return null; + const total_bits = payload.bits + 8; + const align_bits = payload.align_bytes * 8; + return .{ + .bits = std.mem.alignForward(u64, total_bits, align_bits), + .align_bytes = payload.align_bytes, + }; + } + + pub fn addField(self: *SizeAlign, field: SizeAlign, is_packed: bool) void { + // For `extern` structs we would need exact order of fields, but I don't know + // if ast has a stable ordering, so for now only handle simple packed and auto layouts + if (!is_packed) { + // Assume there are no `align(...)` modifiers as well + self.align_bytes = @max(self.align_bytes, field.align_bytes); + } + self.bits += field.bits; + } + + pub fn finalize(self: *SizeAlign) void { + self.bits = std.mem.alignForward(u64, self.bits, self.align_bytes * 8); + } +}; + +pub fn getStructSizeAlign( + analyser: *Analyser, + handle: *DocumentStore.Handle, + container_decl: Ast.full.ContainerDecl, + is_packed: bool, + depth: u8, +) Analyser.Error!?SizeAlign { + // Fast path if there is a backing integer type + if (is_packed) { + if (container_decl.ast.arg.unwrap()) |arg_node| { + if (try analyser.resolveTypeOfNode(.of(arg_node, handle))) |backing_type| { + if (backing_type.ipIndex()) |idx| return SizeAlign.fromIndex(analyser.ip, idx); + } + } + } + + return analyser.getStructSizeAlignFromMembers(handle, container_decl.ast.members, is_packed, depth); +} + +pub fn getStructSizeAlignFromMembers( + analyser: *Analyser, + handle: *DocumentStore.Handle, + members: []const Ast.Node.Index, + is_packed: bool, + depth: u8, +) Analyser.Error!?SizeAlign { + if (10 < depth) return null; + + const tree = &handle.tree; + var result: SizeAlign = .{ .bits = 0, .align_bytes = 1 }; + + for (members) |member| { + switch (tree.nodeTag(member)) { + .container_field, .container_field_init, .container_field_align => { + const field = tree.fullContainerField(member) orelse continue; + const type_node = field.ast.type_expr.unwrap() orelse continue; + + if (try analyser.getFieldSizeAlign(handle, type_node, depth + 1)) |field_size| { + result.addField(field_size, is_packed); + } else return null; + }, + else => {}, + } + } + + result.finalize(); + return result; +} + +pub fn getFieldSizeAlign( + analyser: *Analyser, + handle: *DocumentStore.Handle, + type_node: Ast.Node.Index, + depth: u8, +) Analyser.Error!?SizeAlign { + const field_type = try analyser.resolveTypeOfNode(.of(type_node, handle)) orelse return null; + if (!field_type.is_type_val) return null; + + // Fast path if it is a primitive type + if (SizeAlign.fromType(analyser, field_type)) |size_align| return size_align; + + if (field_type.data == .container) { + const c = field_type.data.container; + const field_handle = c.scope_handle.handle; + const field_tree = &field_handle.tree; + const field_node = c.scope_handle.toNode(); + + return if (field_node == .root) + analyser.getStructSizeAlignFromMembers(field_handle, field_tree.rootDecls(), false, depth) + else + analyser.getSizeAlignFromNode(field_handle, field_node, depth); + } else return null; +} + +pub fn getUnionSizeAlign( + analyser: *Analyser, + handle: *DocumentStore.Handle, + container_decl: Ast.full.ContainerDecl, + is_packed: bool, + depth: u8, +) Analyser.Error!?SizeAlign { + // Fast path if there is a backing integer type + if (is_packed) { + if (container_decl.ast.arg.unwrap()) |arg_node| { + if (try analyser.resolveTypeOfNode(.of(arg_node, handle))) |backing_type| { + if (backing_type.ipIndex()) |idx| return SizeAlign.fromIndex(analyser.ip, idx); + } + } + } + + const tree = &handle.tree; + var max_bits: u64 = 0; + var max_align: u16 = 1; + + for (container_decl.ast.members) |member| { + switch (tree.nodeTag(member)) { + .container_field, .container_field_init, .container_field_align => { + const field = tree.fullContainerField(member) orelse continue; + const type_node = field.ast.type_expr.unwrap() orelse continue; + + if (try analyser.getFieldSizeAlign(handle, type_node, depth + 1)) |field_size| { + max_bits = @max(max_bits, field_size.bits); + max_align = @max(max_align, field_size.align_bytes); + } else return null; + }, + else => {}, + } + } + + if (is_packed) { + return .{ .bits = max_bits, .align_bytes = max_align }; + } else { + // If there is an enum for a tag + if (container_decl.ast.enum_token != null or container_decl.ast.arg.unwrap() != null) { + const tag_size = try analyser.getEnumSizeAlign(handle, container_decl) orelse return null; + max_align = @max(max_align, tag_size.align_bytes); + max_bits += tag_size.bits; + max_bits = std.mem.alignForward(u64, max_bits, max_align * 8); + } else { + // non packed unions have at least 1 byte of tag inside for safety checks + max_bits += 8; + max_bits = std.mem.alignForward(u64, max_bits, max_align * 8); + } + return .{ .bits = max_bits, .align_bytes = max_align }; + } +} + +pub fn getEnumSizeAlign( + analyser: *Analyser, + handle: *DocumentStore.Handle, + container_decl: Ast.full.ContainerDecl, +) Analyser.Error!?SizeAlign { + // Use explicit tag if there is one + if (container_decl.ast.arg.unwrap()) |arg_node| { + if (try analyser.resolveTypeOfNode(.of(arg_node, handle))) |tag_type| { + if (tag_type.ipIndex()) |idx| return SizeAlign.fromIndex(analyser.ip, idx); + } + } + + const tree = &handle.tree; + var field_count: u16 = 0; + for (container_decl.ast.members) |member| { + switch (tree.nodeTag(member)) { + .container_field, .container_field_init => field_count += 1, + else => {}, + } + } + + if (field_count == 0) { + return .{ .bits = 0, .align_bytes = 1 }; + } else { + // If enum does not have an explicit size with the tag, it is at least 8 bits big + field_count = @max(field_count, 8); + const bits = std.math.pow(u16, 2, std.math.log2_int_ceil(u16, field_count)); + return .{ .bits = bits, .align_bytes = bits / 8 }; + } +} + +pub fn getSizeAlignFromNode( + analyser: *Analyser, + handle: *DocumentStore.Handle, + node: Ast.Node.Index, + depth: u8, +) Analyser.Error!?SizeAlign { + const tree = &handle.tree; + var buf: [2]Ast.Node.Index = undefined; + const container_decl = tree.fullContainerDecl(&buf, node) orelse return null; + var is_packed = false; + if (container_decl.layout_token) |lt| { + is_packed = tree.tokenTag(lt) == .keyword_packed; + } + + return switch (tree.tokenTag(container_decl.ast.main_token)) { + .keyword_struct => analyser.getStructSizeAlign(handle, container_decl, is_packed, depth), + .keyword_union => analyser.getUnionSizeAlign(handle, container_decl, is_packed, depth), + .keyword_enum => analyser.getEnumSizeAlign(handle, container_decl), + else => null, + }; +} + +pub fn getSizeAlignFromType(analyser: *Analyser, resolved_type: Analyser.Type) Analyser.Error!?SizeAlign { + // If the type in the InternPool, get info from there + const type_index = if (resolved_type.is_type_val) resolved_type.ipIndex() else resolved_type.ipTypeIndex(); + if (type_index) |idx| if (SizeAlign.fromIndex(analyser.ip, idx)) |sa| return sa; + + if (resolved_type.data == .container) { + const c = resolved_type.data.container; + const handle = c.scope_handle.handle; + const node = c.scope_handle.toNode(); + + return if (node == .root) + analyser.getStructSizeAlignFromMembers(handle, handle.tree.rootDecls(), false, 0) + else + analyser.getSizeAlignFromNode(handle, node, 0); + } else return null; +} From 53f2c05f135bd7b263c4e93f4f56f62b2502e288 Mon Sep 17 00:00:00 2001 From: ShadowCurse Date: Tue, 14 Apr 2026 23:23:51 +0100 Subject: [PATCH 3/3] feat: hover: add type size/alignment information Wire up functions from previous commits to display basic size/alignment information for types. Current limitations: - types which require comptime evaluation do not show anything - if such type is a field, the parent will not show anything as well - `extern` types do not calculate size correctly due to AST not having stable field ordering Signed-off-by: ShadowCurse --- src/features/hover.zig | 86 ++++++++++++++-- tests/lsp_features/hover.zig | 185 +++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 10 deletions(-) diff --git a/src/features/hover.zig b/src/features/hover.zig index 19f512641..0b364ada5 100644 --- a/src/features/hover.zig +++ b/src/features/hover.zig @@ -36,18 +36,37 @@ fn hoverSymbol( } const tree = &decl_handle.handle.tree; + + // Save container node in case we cannot get size/alignment directly from the type + var container_init_node: ?Ast.Node.Index = null; const def_str = switch (decl_handle.decl) { .ast_node => |node| switch (tree.nodeTag(node)) { .global_var_decl, .local_var_decl, .aligned_var_decl, .simple_var_decl, - => try Analyser.getVariableSignature( - arena, - tree, - tree.fullVarDecl(node).?, - true, - ), + => blk: { + const var_decl = tree.fullVarDecl(node).?; + if (var_decl.ast.init_node.unwrap()) |init_node| { + switch (tree.nodeTag(init_node)) { + .container_decl, + .container_decl_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => container_init_node = init_node, + else => {}, + } + } + break :blk try Analyser.getVariableSignature(arena, tree, var_decl, true); + }, .container_field, .container_field_init, .container_field_align, @@ -80,24 +99,30 @@ fn hoverSymbol( return try hoverSymbolResolvedType( analyser, arena, + decl_handle.handle, def_str, markup_kind, &doc_strings, maybe_resolved_type, + container_init_node, ); } fn hoverSymbolResolvedType( analyser: *Analyser, arena: std.mem.Allocator, + handle: *DocumentStore.Handle, def_str: []const u8, markup_kind: types.MarkupKind, doc_strings: *std.ArrayList([]const u8), resolved_type_maybe: ?Analyser.Type, -) error{OutOfMemory}!?[]const u8 { + container_init_node: ?Ast.Node.Index, +) Analyser.Error!?[]const u8 { var referenced: Analyser.ReferencedType.Set = .empty; var resolved_type_strings: std.ArrayList([]const u8) = .empty; var has_more = false; + var size_align_str: ?[]const u8 = null; + if (resolved_type_maybe) |resolved_type| { if (try resolved_type.docComments(arena)) |doc| try doc_strings.append(arena, doc); @@ -121,6 +146,12 @@ fn hoverSymbolResolvedType( try resolved_type_strings.append(arena, type_str); } } + + var size_align = try analyser.getSizeAlignFromType(resolved_type); + if (size_align == null and container_init_node != null) { + size_align = try analyser.getSizeAlignFromNode(handle, container_init_node.?, 0); + } + if (size_align) |sa| size_align_str = try sa.format(arena); } const referenced_types: []const Analyser.ReferencedType = referenced.keys(); return try hoverSymbolResolved( @@ -131,6 +162,7 @@ fn hoverSymbolResolvedType( resolved_type_strings.items, has_more, referenced_types, + size_align_str, ); } @@ -142,6 +174,7 @@ fn hoverSymbolResolved( resolved_type_strings: []const []const u8, has_more: bool, referenced_types: []const Analyser.ReferencedType, + size_align_str: ?[]const u8, ) error{OutOfMemory}![]const u8 { var output: std.ArrayList(u8) = .empty; @@ -153,6 +186,8 @@ fn hoverSymbolResolved( try output.appendSlice(arena, "\n```zig\n(unknown)\n```"); if (has_more) try output.print(arena, "\n```txt\n(...)\n```", .{}); + if (size_align_str) |sa| + try output.print(arena, "\n\n{s}", .{sa}); if (referenced_types.len > 0) try output.print(arena, "\n\n" ++ "Go to ", .{}); for (referenced_types, 0..) |ref, index| { @@ -170,6 +205,8 @@ fn hoverSymbolResolved( try output.appendSlice(arena, "\n(unknown)"); if (has_more) try output.print(arena, "\n(...)", .{}); + if (size_align_str) |sa| + try output.print(arena, "\n\n{s}", .{sa}); } if (doc_strings.len > 0) { @@ -308,7 +345,18 @@ fn hoverDefinitionGlobal( if (std.mem.eql(u8, name, "_")) return null; if (try analyser.resolvePrimitive(name)) |ip_index| { const resolved_type_str = try std.fmt.allocPrint(arena, "{f}", .{analyser.ip.typeOf(ip_index).fmt(analyser.ip)}); - break :blk try hoverSymbolResolved(arena, markup_kind, &.{}, name, &.{resolved_type_str}, false, &.{}); + const size_align = Analyser.SizeAlign.fromIndex(analyser.ip, ip_index); + const size_align_str = if (size_align) |sa| try sa.format(arena) else null; + break :blk try hoverSymbolResolved( + arena, + markup_kind, + &.{}, + name, + &.{resolved_type_str}, + false, + &.{}, + size_align_str, + ); } } const decl = (try analyser.lookupSymbolGlobal(handle, name, pos_index)) orelse return null; @@ -358,7 +406,16 @@ fn hoverDefinitionStructInit( .contents = .{ .markup_content = .{ .kind = markup_kind, - .value = try hoverSymbolResolved(arena, markup_kind, doc_strings.items, def_str, &.{"type"}, false, referenced_types), + .value = try hoverSymbolResolved( + arena, + markup_kind, + doc_strings.items, + def_str, + &.{"type"}, + false, + referenced_types, + null, + ), }, }, .range = offsets.tokenToRange(&handle.tree, token, offset_encoding), @@ -417,7 +474,16 @@ fn hoverDefinitionFieldAccess( for (tys.items) |ty| { const def_str = offsets.locToSlice(handle.tree.source, highlight_loc); var doc_strings: std.ArrayList([]const u8) = .empty; - content.appendAssumeCapacity(try hoverSymbolResolvedType(analyser, arena, def_str, markup_kind, &doc_strings, ty) orelse continue); + content.appendAssumeCapacity(try hoverSymbolResolvedType( + analyser, + arena, + handle, + def_str, + markup_kind, + &doc_strings, + ty, + null, + ) orelse continue); } return .{ diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig index 68e1c1c1e..42d85ef06 100644 --- a/tests/lsp_features/hover.zig +++ b/tests/lsp_features/hover.zig @@ -18,6 +18,8 @@ test "primitive" { \\```zig \\(type) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 ); try testHover( \\const foo = true; @@ -38,6 +40,8 @@ test "primitive" { \\```zig \\(type) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); try testHover( \\const foo = f32; @@ -48,6 +52,8 @@ test "primitive" { \\```zig \\(type) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); try testHover( \\const foo = i64; @@ -58,6 +64,8 @@ test "primitive" { \\```zig \\(type) \\``` + \\ + \\Size: 8 bytes (64 bits), Align: 8 ); try testHover( \\const foo = null; @@ -246,6 +254,8 @@ test "struct" { \\```zig \\(type) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); try testHover( \\const Struct = struct { @@ -258,6 +268,8 @@ test "struct" { \\```zig \\(type) \\``` + \\ + \\Size: 0 bytes (0 bits), Align: 1 ); try testHover( \\const S = struct { @@ -276,6 +288,8 @@ test "struct" { \\```zig \\(type) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 ); try testHover( \\/// Foo doc comment @@ -300,8 +314,48 @@ test "struct" { \\(type) \\``` \\ + \\Size: 8 bytes (64 bits), Align: 4 + \\ \\Foo doc comment ); + try testHover( + \\const FooStruct = struct { + \\ bar: [*]u32, + \\}; + , + \\```zig + \\bar: [*]u32 + \\``` + \\```zig + \\([*]u32) + \\``` + \\ + \\Size: 8 bytes (64 bits), Align: 8 + ); + try testHover( + \\/// Foo doc comment + \\const FooStruct = struct { + \\ bar: u32, + \\ baz: bool, + \\ boo: MyInner, + \\ + \\ pub const MyInner = struct { + \\ another_field: bool, + \\ }; + \\}; + \\const m: FooStruct.MyInner = undefined; + , + \\```zig + \\const MyInner = struct { + \\ another_field: bool, + \\} + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 + ); try testHover( \\const EdgeCases = struct { \\ const str = "something"; @@ -317,6 +371,23 @@ test "struct" { \\```zig \\(type) \\``` + \\ + \\Size: 0 bytes (0 bits), Align: 1 + ); + try testHover( + \\const S = @This(); + \\v: u32, + , + \\```zig + \\const S = @This() + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 + \\ + \\ ); try testHover( \\foo: u32, @@ -327,6 +398,8 @@ test "struct" { \\```zig \\(u32) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); try testHover( \\const S = struct { foo: u32 }; @@ -338,6 +411,8 @@ test "struct" { \\```zig \\(u32) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); } @@ -352,6 +427,8 @@ test "root struct" { \\(Untitled-0) \\``` \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\Go to [Untitled-0](untitled:///Untitled-0.zig#L1) ); } @@ -400,6 +477,8 @@ test "decl literal" { \\(S) \\``` \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\Go to [S](untitled:///Untitled-0.zig#L1) ); try testHover( @@ -574,6 +653,8 @@ test "enum" { \\```zig \\(type) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 ); try testHover( \\pub const Mode = enum { zig, zon }; @@ -584,6 +665,20 @@ test "enum" { \\```zig \\(type) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 + ); + try testHover( + \\const Enum = enum(u32) {a, b}; + , + \\```zig + \\const Enum = enum(u32) {a, b} + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); } @@ -624,6 +719,42 @@ test "union" { \\(type) \\``` ); + try testHover( + \\const Union = union {a: u32, b: u16}; + , + \\```zig + \\const Union = union {a: u32, b: u16} + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 8 bytes (64 bits), Align: 4 + ); + try testHover( + \\const Union = union(enum) {a: u32, b: u16}; + , + \\```zig + \\const Union = union(enum) {a: u32, b: u16} + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 8 bytes (64 bits), Align: 4 + ); + try testHover( + \\const Union = packed union(u2) {a: u2, b: i2}; + , + \\```zig + \\const Union = packed union(u2) {a: u2, b: i2} + \\``` + \\```zig + \\(type) + \\``` + \\ + \\Size: 0 bytes (2 bits), Align: 1 + ); } test "enum member" { @@ -638,6 +769,8 @@ test "enum member" { \\(Enum) \\``` \\ + \\Size: 1 byte (8 bits), Align: 1 + \\ \\Go to [Enum](untitled:///Untitled-0.zig#L1) ); } @@ -660,6 +793,8 @@ test "generic type" { \\(GenericType(StructType,EnumType)) \\``` \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\Go to [GenericType](untitled:///Untitled-0.zig#L3) | [StructType](untitled:///Untitled-0.zig#L1) | [EnumType](untitled:///Untitled-0.zig#L2) ); } @@ -677,6 +812,8 @@ test "block label" { \\```zig \\(i32) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); try testHover( \\const foo: i32 = undefined; @@ -690,6 +827,8 @@ test "block label" { \\```zig \\(i32) \\``` + \\ + \\Size: 4 bytes (32 bits), Align: 4 ); } @@ -705,6 +844,8 @@ test "enum literal" { \\(E) \\``` \\ + \\Size: 1 byte (8 bits), Align: 1 + \\ \\Go to [E](untitled:///Untitled-0.zig#L1) ); } @@ -760,6 +901,8 @@ test "function" { \\```zig \\(enum { fizz, buzz }) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 ); try testHover( \\fn foo() !i32 {} @@ -805,6 +948,8 @@ test "function parameter" { \\(u32) \\``` \\ + \\Size: 4 bytes (32 bits), Align: 4 + \\ \\hello world ); } @@ -862,6 +1007,8 @@ test "either types" { \\(type = u32) \\``` \\ + \\Size: 4 bytes (32 bits), Align: 4 + \\ \\small type \\ \\```zig @@ -871,6 +1018,8 @@ test "either types" { \\(type = u64) \\``` \\ + \\Size: 8 bytes (64 bits), Align: 8 + \\ \\large type ); try testHoverWithOptions( @@ -888,11 +1037,15 @@ test "either types" { \\const T = u32 \\(type = u32) \\ + \\Size: 4 bytes (32 bits), Align: 4 + \\ \\small type \\ \\const T = u64 \\(type = u64) \\ + \\Size: 8 bytes (64 bits), Align: 8 + \\ \\large type , .{ .markup_kind = .plaintext }); } @@ -1098,6 +1251,8 @@ test "alias with different type" { , \\const foo: i32 = 1 \\(?i32 = 1) + \\ + \\Size: 8 bytes (64 bits), Align: 4 , .{ .markup_kind = .plaintext }); } @@ -1184,6 +1339,8 @@ test "escaped identifier in enum literal" { \\(E) \\``` \\ + \\Size: 1 byte (8 bits), Align: 1 + \\ \\Go to [E](untitled:///Untitled-0.zig#L1) , .{ .highlight = "@\"hello world\"", @@ -1223,6 +1380,8 @@ test "integer overflow on top level container" { \\```zig \\(enum { foo.bar: baz,}) \\``` + \\ + \\Size: 1 byte (8 bits), Align: 1 ); } @@ -1242,6 +1401,8 @@ test "combine doc comments of declaration and definition" { \\(type) \\``` \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\Foo \\ \\Bar @@ -1257,6 +1418,8 @@ test "combine doc comments of declaration and definition" { \\const baz = struct \\(type) \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\Foo \\ \\Bar @@ -1277,6 +1440,8 @@ test "top-level doc comment" { \\(type) \\``` \\ + \\Size: 0 bytes (0 bits), Align: 1 + \\ \\A \\ \\B @@ -1303,6 +1468,8 @@ test "slice properties" { , \\len \\(usize) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .markup_kind = .plaintext }); try testHoverWithOptions( \\const foo: []const u8 = undefined; @@ -1310,6 +1477,8 @@ test "slice properties" { , \\ptr \\([*]const u8) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .markup_kind = .plaintext }); } @@ -1320,6 +1489,8 @@ test "array properties" { , \\len \\(usize = 3) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .markup_kind = .plaintext }); } @@ -1330,6 +1501,8 @@ test "tuple properties" { , \\len \\(usize = 2) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .markup_kind = .plaintext }); try testHoverWithOptions( \\const foo: struct { i32, bool } = undefined; @@ -1337,6 +1510,8 @@ test "tuple properties" { , \\@"0" \\(i32) + \\ + \\Size: 4 bytes (32 bits), Align: 4 , .{ .highlight = "@\"0\"", .markup_kind = .plaintext, @@ -1347,6 +1522,8 @@ test "tuple properties" { , \\@"1" \\(bool) + \\ + \\Size: 1 byte (8 bits), Align: 1 , .{ .highlight = "@\"1\"", .markup_kind = .plaintext, @@ -1360,6 +1537,8 @@ test "optional unwrap" { , \\? \\(f64) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .highlight = "?", .markup_kind = .plaintext, @@ -1370,6 +1549,8 @@ test "optional unwrap" { , \\? \\(f64) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .highlight = "?", .markup_kind = .plaintext, @@ -1383,6 +1564,8 @@ test "pointer dereference" { , \\* \\(f64) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .highlight = "*", .markup_kind = .plaintext, @@ -1393,6 +1576,8 @@ test "pointer dereference" { , \\* \\(f64) + \\ + \\Size: 8 bytes (64 bits), Align: 8 , .{ .highlight = "*", .markup_kind = .plaintext,