fix(lsp): identifier array dimension no longer panics the analyzer [0099]

`Analyzer.resolveTypeNode` read the array `.length` node's `.int_literal`
union field unconditionally. For a named-const dimension (`MAX :: 4;
[MAX]u8`) that node is an `identifier`, so the access tripped Zig's
checked-union panic and `sx lsp` aborted on didOpen. The main compiler
was unaffected (it folds the dim through the IR).

- New `arrayDimLength` helper switches on the dimension node tag:
  int_literal → value; identifier → a recorded module-const int value;
  anything else / out-of-u32-range → unknown. Never assumes a node shape.
- `Type.ArrayTypeInfo.length` is now `?u32`; null is an explicit "editor
  couldn't fold this dimension" marker (rendered `[_]T`), never a
  fabricated concrete length.
- New `const_int_values` registry records integer-literal consts at
  registration time for the identifier path.

Regression: first `src/lsp/*.test.zig` (the minimal LSP harness), wired
into the test graph via `src/root.zig`. Drives `analyzeDocument` over
`[MAX]u8` (folds to 4, no panic), `[64]u8` (happy-path guard), and
`[N]u8` (explicit unknown). Fail-before/pass-after verified.

Sibling audit of the resolveTypeNode/fieldType family: the array dim was
the only unchecked union-field access; all other arms recurse or
tag-check first. Noted a non-crashing display gap in server.zig hover
rendering for step B.
This commit is contained in:
agra
2026-06-05 23:33:22 +03:00
parent 8eb514a804
commit d515696e61
5 changed files with 228 additions and 3 deletions

View File

@@ -76,7 +76,11 @@ pub const Type = union(enum) {
pub const ArrayTypeInfo = struct {
element_name: []const u8,
length: u32,
/// null = the dimension could not be folded to a compile-time integer
/// by the editor index (an identifier const it couldn't resolve, or a
/// non-const expression). Explicit "unknown" rather than a fabricated
/// concrete length — this is hover/metadata, not codegen (issue 0099).
length: ?u32,
is_raw: bool,
};
@@ -296,7 +300,10 @@ pub const Type = union(enum) {
.slice_type => |info| return fmtAlloc(allocator, "[]{s}", .{info.element_name}),
.pointer_type => |info| return fmtAlloc(allocator, "*{s}", .{info.pointee_name}),
.many_pointer_type => |info| return fmtAlloc(allocator, "[*]{s}", .{info.element_name}),
.array_type => |info| return fmtAlloc(allocator, "[{d}]{s}", .{ info.length, info.element_name }),
.array_type => |info| {
if (info.length) |n| return fmtAlloc(allocator, "[{d}]{s}", .{ n, info.element_name });
return fmtAlloc(allocator, "[_]{s}", .{info.element_name});
},
.vector_type => |info| return fmtAlloc(allocator, "Vector({d},{s})", .{ info.length, info.element_name }),
.function_type => |info| {
var buf = std.ArrayList(u8).empty;