From 8a78e9fa01458d67ed18dd858314abd7cdc0d3f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Wed, 12 Feb 2025 21:58:06 -0800
Subject: [PATCH 1/6] Add helper method

---
 src/analysis.zig | 47 +++++++++++++++--------------------------------
 1 file changed, 15 insertions(+), 32 deletions(-)

diff --git a/src/analysis.zig b/src/analysis.zig
index deb608e5d..6ac7ec28d 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -67,6 +67,12 @@ pub fn deinit(self: *Analyser) void {
     self.arena.deinit();
 }
 
+fn allocType(analyser: *Analyser, ty: Type) error{OutOfMemory}!*Type {
+    const ptr = try analyser.arena.allocator().create(Type);
+    ptr.* = ty;
+    return ptr;
+}
+
 pub fn getDocCommentsBeforeToken(allocator: std.mem.Allocator, tree: Ast, base: Ast.TokenIndex) error{OutOfMemory}!?[]const u8 {
     const tokens = tree.tokens.items(.tag);
     const doc_comment_index = getDocCommentTokenIndex(tokens, base) orelse return null;
@@ -842,12 +848,10 @@ pub fn resolveReturnType(analyser: *Analyser, func_type_param: Type) error{OutOf
     if (!child_type.is_type_val) return null;
 
     if (ast.hasInferredError(tree, fn_proto)) {
-        const child_type_ptr = try analyser.arena.allocator().create(Type);
-        child_type_ptr.* = child_type;
         return .{
             .data = .{ .error_union = .{
                 .error_set = null,
-                .payload = child_type_ptr,
+                .payload = try analyser.allocType(child_type),
             } },
             .is_type_val = false,
         };
@@ -894,15 +898,13 @@ pub fn resolveOptionalChildType(analyser: *Analyser, optional_type: Type) error{
 }
 
 pub fn resolveAddressOf(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type {
-    const base_type_ptr = try analyser.arena.allocator().create(Type);
-    base_type_ptr.* = ty.typeOf(analyser);
     return .{
         .data = .{
             .pointer = .{
                 .size = .one,
                 .sentinel = .none,
                 .is_const = false,
-                .elem_ty = base_type_ptr,
+                .elem_ty = try analyser.allocType(ty.typeOf(analyser)),
             },
         },
         .is_type_val = false,
@@ -949,9 +951,7 @@ fn resolveTaggedUnionFieldType(analyser: *Analyser, ty: Type, symbol: []const u8
         return try child.resolveType(analyser);
 
     if (container_decl.ast.enum_token != null) {
-        const union_type_ptr = try analyser.arena.allocator().create(Type);
-        union_type_ptr.* = ty;
-        return .{ .data = .{ .union_tag = union_type_ptr }, .is_type_val = false };
+        return .{ .data = .{ .union_tag = try analyser.allocType(ty) }, .is_type_val = false };
     }
 
     if (container_decl.ast.arg != 0) {
@@ -1608,10 +1608,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
             const child_ty = try analyser.resolveTypeOfNodeInternal(.{ .node = datas[node].lhs, .handle = handle }) orelse return null;
             if (!child_ty.is_type_val) return null;
 
-            const child_ty_ptr = try analyser.arena.allocator().create(Type);
-            child_ty_ptr.* = child_ty;
-
-            return .{ .data = .{ .optional = child_ty_ptr }, .is_type_val = true };
+            return .{ .data = .{ .optional = try analyser.allocType(child_ty) }, .is_type_val = true };
         },
         .ptr_type_aligned,
         .ptr_type_sentinel,
@@ -1625,15 +1622,13 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
             const elem_ty = try analyser.resolveTypeOfNodeInternal(.{ .node = ptr_info.ast.child_type, .handle = handle }) orelse return null;
             if (!elem_ty.is_type_val) return null;
 
-            const elem_ty_ptr = try analyser.arena.allocator().create(Type);
-            elem_ty_ptr.* = elem_ty;
             return .{
                 .data = .{
                     .pointer = .{
                         .size = ptr_info.size,
                         .sentinel = sentinel,
                         .is_const = ptr_info.const_token != null,
-                        .elem_ty = elem_ty_ptr,
+                        .elem_ty = try analyser.allocType(elem_ty),
                     },
                 },
                 .is_type_val = true,
@@ -1650,14 +1645,11 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
             const elem_ty = try analyser.resolveTypeOfNodeInternal(.{ .node = array_info.ast.elem_type, .handle = handle }) orelse return null;
             if (!elem_ty.is_type_val) return null;
 
-            const elem_ty_ptr = try analyser.arena.allocator().create(Type);
-            elem_ty_ptr.* = elem_ty;
-
             return .{
                 .data = .{ .array = .{
                     .elem_count = elem_count,
                     .sentinel = sentinel,
-                    .elem_ty = elem_ty_ptr,
+                    .elem_ty = try analyser.allocType(elem_ty),
                 } },
                 .is_type_val = true,
             };
@@ -1696,16 +1688,10 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
             const payload = try analyser.resolveTypeOfNodeInternal(.{ .node = datas[node].rhs, .handle = handle }) orelse return null;
             if (!payload.is_type_val) return null;
 
-            const error_set_ptr = try analyser.arena.allocator().create(Type);
-            error_set_ptr.* = error_set;
-
-            const payload_ptr = try analyser.arena.allocator().create(Type);
-            payload_ptr.* = payload;
-
             return .{
                 .data = .{ .error_union = .{
-                    .error_set = error_set_ptr,
-                    .payload = payload_ptr,
+                    .error_set = try analyser.allocType(error_set),
+                    .payload = try analyser.allocType(payload),
                 } },
                 .is_type_val = true,
             };
@@ -4102,12 +4088,9 @@ pub const DeclWithHandle = struct {
 
         if (!self.isCaptureByRef()) return resolved_ty;
 
-        const resolved_ty_ptr = try analyser.arena.allocator().create(Type);
-        resolved_ty_ptr.* = resolved_ty.typeOf(analyser);
-
         return .{
             .data = .{ .pointer = .{
-                .elem_ty = resolved_ty_ptr,
+                .elem_ty = try analyser.allocType(resolved_ty.typeOf(analyser)),
                 .sentinel = .none,
                 .is_const = false,
                 .size = .one,

From 65f858a4766a6c2812fc54e29f9119a5dbf0f031 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Sat, 15 Feb 2025 00:57:31 -0800
Subject: [PATCH 2/6] Fix type resolution of slice with integer literals

---
 src/analysis.zig             | 225 ++++++++++++++++++++++++++---------
 tests/analysis/array.zig     |  12 +-
 tests/analysis/pointer.zig   |   9 +-
 tests/lsp_features/hover.zig |   4 +-
 4 files changed, 179 insertions(+), 71 deletions(-)

diff --git a/src/analysis.zig b/src/analysis.zig
index 6ac7ec28d..2138d04b6 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -995,20 +995,20 @@ pub fn resolveDerefType(analyser: *Analyser, pointer: Type) error{OutOfMemory}!?
     }
 }
 
-const BracketAccessKind = enum {
+const BracketAccess = union(enum) {
     /// `lhs[index]`
-    Single,
+    single: ?u64,
     /// `lhs[start..]`
-    Open,
+    open: ?u64,
     /// `lhs[start..end]`
-    Range,
+    range: ?struct { u64, u64 },
 };
 
 /// Resolves slicing and array access
-/// - `lhs[index]` (Single)
-/// - `lhs[start..]` (Open)
-/// - `lhs[start..end]` (Range)
-fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKind) error{OutOfMemory}!?Type {
+/// - `lhs[index]` (single)
+/// - `lhs[start..]` (open)
+/// - `lhs[start..end]` (range)
+fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccess) error{OutOfMemory}!?Type {
     if (lhs.is_type_val) return null;
 
     switch (lhs.data) {
@@ -1017,8 +1017,35 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi
             else => return null,
         },
         .array => |info| switch (rhs) {
-            .Single => return info.elem_ty.instanceTypeVal(analyser),
-            .Open => {
+            .single => return info.elem_ty.instanceTypeVal(analyser),
+            .open => |start_maybe| {
+                if (start_maybe) |start| {
+                    const elem_count = blk: {
+                        const elem_count = info.elem_count orelse break :blk null;
+                        if (start > elem_count) break :blk null;
+                        break :blk elem_count - start;
+                    };
+                    return .{
+                        .data = .{
+                            .pointer = .{
+                                .size = .one,
+                                .sentinel = .none,
+                                .is_const = false,
+                                .elem_ty = try analyser.allocType(.{
+                                    .data = .{
+                                        .array = .{
+                                            .elem_count = elem_count,
+                                            .sentinel = info.sentinel,
+                                            .elem_ty = info.elem_ty,
+                                        },
+                                    },
+                                    .is_type_val = true,
+                                }),
+                            },
+                        },
+                        .is_type_val = false,
+                    };
+                }
                 return .{
                     .data = .{
                         .pointer = .{
@@ -1031,7 +1058,35 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi
                     .is_type_val = false,
                 };
             },
-            .Range => {
+            .range => |range_maybe| {
+                if (range_maybe) |range| {
+                    const start, const end = range;
+                    const elem_count = blk: {
+                        const elem_count = info.elem_count orelse break :blk null;
+                        if (start > end or start > elem_count or end > elem_count) break :blk null;
+                        break :blk end - start;
+                    };
+                    return .{
+                        .data = .{
+                            .pointer = .{
+                                .size = .one,
+                                .sentinel = .none,
+                                .is_const = false,
+                                .elem_ty = try analyser.allocType(.{
+                                    .data = .{
+                                        .array = .{
+                                            .elem_count = elem_count,
+                                            .sentinel = .none,
+                                            .elem_ty = info.elem_ty,
+                                        },
+                                    },
+                                    .is_type_val = true,
+                                }),
+                            },
+                        },
+                        .is_type_val = false,
+                    };
+                }
                 return .{
                     .data = .{
                         .pointer = .{
@@ -1048,42 +1103,39 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi
         .pointer => |info| return switch (info.size) {
             .one => switch (info.elem_ty.data) {
                 .array => |array_info| {
-                    switch (rhs) {
-                        .Single => return array_info.elem_ty.instanceTypeVal(analyser),
-                        .Open => {
-                            return .{
-                                .data = .{
-                                    .pointer = .{
-                                        .size = .slice,
-                                        .sentinel = array_info.sentinel,
-                                        .is_const = false,
-                                        .elem_ty = array_info.elem_ty,
-                                    },
-                                },
-                                .is_type_val = false,
-                            };
-                        },
-                        .Range => {
-                            return .{
-                                .data = .{
-                                    .pointer = .{
-                                        .size = .slice,
-                                        .sentinel = .none,
-                                        .is_const = false,
-                                        .elem_ty = array_info.elem_ty,
-                                    },
-                                },
-                                .is_type_val = false,
-                            };
-                        },
-                    }
+                    const inner_ty: Type = .{ .data = .{ .array = array_info }, .is_type_val = false };
+                    return analyser.resolveBracketAccessType(inner_ty, rhs);
                 },
                 else => return null,
             },
             .many => switch (rhs) {
-                .Single => info.elem_ty.instanceTypeVal(analyser),
-                .Open => lhs,
-                .Range => {
+                .single => info.elem_ty.instanceTypeVal(analyser),
+                .open => lhs,
+                .range => |range_maybe| {
+                    if (range_maybe) |range| {
+                        const start, const end = range;
+                        const elem_count = if (start > end) null else end - start;
+                        return .{
+                            .data = .{
+                                .pointer = .{
+                                    .size = .one,
+                                    .sentinel = .none,
+                                    .is_const = info.is_const,
+                                    .elem_ty = try analyser.allocType(.{
+                                        .data = .{
+                                            .array = .{
+                                                .elem_count = elem_count,
+                                                .sentinel = .none,
+                                                .elem_ty = info.elem_ty,
+                                            },
+                                        },
+                                        .is_type_val = true,
+                                    }),
+                                },
+                            },
+                            .is_type_val = false,
+                        };
+                    }
                     return .{
                         .data = .{
                             .pointer = .{
@@ -1098,13 +1150,60 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi
                 },
             },
             .slice => switch (rhs) {
-                .Single => info.elem_ty.instanceTypeVal(analyser),
-                .Open, .Range => lhs,
+                .single => info.elem_ty.instanceTypeVal(analyser),
+                .open => lhs,
+                .range => |range_maybe| {
+                    const start, const end = range_maybe orelse return lhs;
+                    const elem_count = if (start > end) null else end - start;
+                    return .{
+                        .data = .{
+                            .pointer = .{
+                                .size = .one,
+                                .sentinel = .none,
+                                .is_const = info.is_const,
+                                .elem_ty = try analyser.allocType(.{
+                                    .data = .{
+                                        .array = .{
+                                            .elem_count = elem_count,
+                                            .sentinel = .none,
+                                            .elem_ty = info.elem_ty,
+                                        },
+                                    },
+                                    .is_type_val = true,
+                                }),
+                            },
+                        },
+                        .is_type_val = false,
+                    };
+                },
             },
             .c => switch (rhs) {
-                .Single => info.elem_ty.instanceTypeVal(analyser),
-                .Open => lhs,
-                .Range => .{
+                .single => info.elem_ty.instanceTypeVal(analyser),
+                .open => lhs,
+                .range => |range_maybe| if (range_maybe) |range| {
+                    const start, const end = range;
+                    const elem_count = if (start > end) null else end - start;
+                    return .{
+                        .data = .{
+                            .pointer = .{
+                                .size = .one,
+                                .sentinel = .none,
+                                .is_const = info.is_const,
+                                .elem_ty = try analyser.allocType(.{
+                                    .data = .{
+                                        .array = .{
+                                            .elem_count = elem_count,
+                                            .sentinel = .none,
+                                            .elem_ty = info.elem_ty,
+                                        },
+                                    },
+                                    .is_type_val = true,
+                                }),
+                            },
+                        },
+                        .is_type_val = false,
+                    };
+                } else .{
                     .data = .{
                         .pointer = .{
                             .size = .slice,
@@ -1576,12 +1675,24 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
                 .slice_open,
                 => {
                     const slice_info = tree.fullSlice(node).?;
-                    const kind: BracketAccessKind = if (slice_info.ast.end == 0) .Open else .Range;
+                    const kind: BracketAccess = if (slice_info.ast.end == 0)
+                        .{ .open = try analyser.resolveIntegerLiteral(u64, .{ .node = slice_info.ast.start, .handle = handle }) }
+                    else
+                        .{ .range = blk: {
+                            const start = try analyser.resolveIntegerLiteral(u64, .{ .node = slice_info.ast.start, .handle = handle }) orelse
+                                break :blk null;
+                            const end = try analyser.resolveIntegerLiteral(u64, .{ .node = slice_info.ast.end, .handle = handle }) orelse
+                                break :blk null;
+                            break :blk .{ start, end };
+                        } };
                     return try analyser.resolveBracketAccessType(base_type, kind);
                 },
                 .deref => try analyser.resolveDerefType(base_type),
                 .unwrap_optional => try analyser.resolveOptionalUnwrap(base_type),
-                .array_access => try analyser.resolveBracketAccessType(base_type, .Single),
+                .array_access => try analyser.resolveBracketAccessType(
+                    base_type,
+                    .{ .single = try analyser.resolveIntegerLiteral(u64, .{ .node = datas[node].rhs, .handle = handle }) },
+                ),
                 .@"orelse" => {
                     const type_right = try analyser.resolveTypeOfNodeInternal(.{ .node = datas[node].rhs, .handle = handle }) orelse return try analyser.resolveOptionalUnwrap(base_type);
                     return try analyser.resolveOrelseType(base_type, type_right);
@@ -3305,7 +3416,7 @@ pub fn getFieldAccessType(
             },
             .l_bracket => {
                 var bracket_count: usize = 1;
-                var kind: BracketAccessKind = .Single;
+                var kind: BracketAccess = .{ .single = null };
 
                 while (true) {
                     const token = tokenizer.next();
@@ -3320,12 +3431,12 @@ pub fn getFieldAccessType(
                         },
                         .ellipsis2 => {
                             if (bracket_count == 1) {
-                                kind = .Open;
+                                kind = .{ .open = null };
                             }
                         },
                         else => {
-                            if (bracket_count == 1 and kind == .Open) {
-                                kind = .Range;
+                            if (bracket_count == 1 and kind == .open) {
+                                kind = .{ .range = null };
                             }
                         },
                     }
@@ -4037,7 +4148,7 @@ pub const DeclWithHandle = struct {
                     .node = pay.condition,
                     .handle = self.handle,
                 })) orelse return null,
-                .Single,
+                .{ .single = null },
             ),
             .assign_destructure => |pay| blk: {
                 const type_node = pay.getFullVarDecl(tree).ast.type_node;
@@ -4626,7 +4737,7 @@ pub fn resolveExpressionTypeFromAncestors(
                 ancestors[0],
                 ancestors[1..],
             )) |array_type| {
-                return (try analyser.resolveBracketAccessType(array_type, .Single)) orelse
+                return (try analyser.resolveBracketAccessType(array_type, .{ .single = null })) orelse
                     (try analyser.resolveTupleFieldType(array_type, element_index));
             }
 
@@ -4636,7 +4747,7 @@ pub fn resolveExpressionTypeFromAncestors(
                     ancestors[1],
                     ancestors[2..],
                 )) |slice_type| {
-                    return try analyser.resolveBracketAccessType(slice_type, .Single);
+                    return try analyser.resolveBracketAccessType(slice_type, .{ .single = null });
                 }
             }
         },
diff --git a/tests/analysis/array.zig b/tests/analysis/array.zig
index 45e2a3a91..c567e553f 100644
--- a/tests/analysis/array.zig
+++ b/tests/analysis/array.zig
@@ -29,22 +29,22 @@ const array_indexing = some_array[0];
 
 // TODO this should be `*const [2]u8`
 const array_slice_open_1 = some_array[1..];
-//    ^^^^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^^^^ (*[2]u8)()
 
 // TODO this should be `*const [0]u8`
 const array_slice_open_3 = some_array[3..];
-//    ^^^^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^^^^ (*[0]u8)()
 
 // TODO this should be `*const [?]u8`
 const array_slice_open_4 = some_array[4..];
-//    ^^^^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^^^^ (*[?]u8)()
 
 const array_slice_open_runtime = some_array[runtime_index..];
 //    ^^^^^^^^^^^^^^^^^^^^^^^^ ([]u8)()
 
 // TODO this should be `*const [2]u8`
 const array_slice_0_2 = some_array[0..2];
-//    ^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^ (*[2]u8)()
 
 // TODO this should be `*const [2 :0]u8`
 const array_slice_0_2_sentinel = some_array[0..2 :0];
@@ -52,11 +52,11 @@ const array_slice_0_2_sentinel = some_array[0..2 :0];
 
 // TODO this should be `*const [?]u8`
 const array_slice_0_5 = some_array[0..5];
-//    ^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^ (*[?]u8)()
 
 // TODO this should be `*const [?]u8`
 const array_slice_3_2 = some_array[3..2];
-//    ^^^^^^^^^^^^^^^ ([]u8)()
+//    ^^^^^^^^^^^^^^^ (*[?]u8)()
 
 const array_slice_0_runtime = some_array[0..runtime_index];
 //    ^^^^^^^^^^^^^^^^^^^^^ ([]u8)()
diff --git a/tests/analysis/pointer.zig b/tests/analysis/pointer.zig
index ee87e3951..9dd16a698 100644
--- a/tests/analysis/pointer.zig
+++ b/tests/analysis/pointer.zig
@@ -45,9 +45,8 @@ const many_u32_deref = many_u32.*;
 const many_u32_indexing = many_u32[0];
 //    ^^^^^^^^^^^^^^^^^ (u32)()
 
-// TODO this should be `*const [2]u32`
 const many_u32_slice_len_comptime = many_u32[0..2];
-//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ (*const [2]u32)()
 
 const many_u32_slice_len_runtime = many_u32[0..runtime_index];
 //    ^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
@@ -74,9 +73,8 @@ const slice_u32_deref = slice_u32.*;
 const slice_u32_indexing = slice_u32[0];
 //    ^^^^^^^^^^^^^^^^^^ (u32)()
 
-// TODO this should be `*const [2]u32`
 const slice_u32_slice_len_comptime = slice_u32[0..2];
-//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (*const [2]u32)()
 
 const slice_u32_slice_len_runtime = slice_u32[0..runtime_index];
 //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
@@ -104,9 +102,8 @@ const c_u32_deref = c_u32.*;
 const c_u32_indexing = c_u32[0];
 //    ^^^^^^^^^^^^^^ (u32)()
 
-// TODO this should be `*const [2]u32`
 const c_u32_slice_len_comptime = c_u32[0..2];
-//    ^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^^^^ (*const [2]u32)()
 
 const c_u32_slice_len_runtime = c_u32[0..runtime_index];
 //    ^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()
diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig
index ab0454d4f..17131f215 100644
--- a/tests/lsp_features/hover.zig
+++ b/tests/lsp_features/hover.zig
@@ -706,7 +706,7 @@ test "sentinel values" {
         \\const range = array[0..2]
         \\```
         \\```zig
-        \\([]u8)
+        \\(*[?]u8)
         \\```
     );
     try testHover(
@@ -717,7 +717,7 @@ test "sentinel values" {
         \\const open = array[1..]
         \\```
         \\```zig
-        \\([:0]u8)
+        \\(*[?:0]u8)
         \\```
     );
     // try testHover(

From 86576a0b8be81cee41abdc5762833a0213003170 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Sat, 15 Feb 2025 01:15:03 -0800
Subject: [PATCH 3/6] Resolve type of slice on single-item pointer

---
 src/analysis.zig           | 29 ++++++++++++++++++++++++++++-
 tests/analysis/pointer.zig |  6 +++---
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/src/analysis.zig b/src/analysis.zig
index 2138d04b6..30a12b3cc 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -1106,7 +1106,34 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccess)
                     const inner_ty: Type = .{ .data = .{ .array = array_info }, .is_type_val = false };
                     return analyser.resolveBracketAccessType(inner_ty, rhs);
                 },
-                else => return null,
+                else => switch (rhs) {
+                    .single, .open => return null,
+                    .range => |range_maybe| {
+                        const start, const end = range_maybe orelse return null;
+                        if (start > end or start > 1 or end > 1) return null;
+                        const elem_count = end - start;
+                        return .{
+                            .data = .{
+                                .pointer = .{
+                                    .size = .one,
+                                    .sentinel = .none,
+                                    .is_const = info.is_const,
+                                    .elem_ty = try analyser.allocType(.{
+                                        .data = .{
+                                            .array = .{
+                                                .elem_count = elem_count,
+                                                .sentinel = .none,
+                                                .elem_ty = info.elem_ty,
+                                            },
+                                        },
+                                        .is_type_val = true,
+                                    }),
+                                },
+                            },
+                            .is_type_val = false,
+                        };
+                    },
+                },
             },
             .many => switch (rhs) {
                 .single => info.elem_ty.instanceTypeVal(analyser),
diff --git a/tests/analysis/pointer.zig b/tests/analysis/pointer.zig
index 9dd16a698..09156b849 100644
--- a/tests/analysis/pointer.zig
+++ b/tests/analysis/pointer.zig
@@ -15,13 +15,13 @@ const one_u32_slice_len_0_5 = one_u32[0..5];
 //    ^^^^^^^^^^^^^^^^^^^^^ (unknown)()
 
 const one_u32_slice_len_0_0 = one_u32[0..0];
-// TODO   ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()
 
 const one_u32_slice_len_0_1 = one_u32[0..1];
-// TODO   ^^^^^^^^^^^^^^^^^^^^^ (*const [1]u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^ (*const [1]u32)()
 
 const one_u32_slice_len_1_1 = one_u32[1..1];
-// TODO   ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()
+//    ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()
 
 const one_u32_slice_open = one_u32[1..];
 //    ^^^^^^^^^^^^^^^^^^ (unknown)()

From a49641f2afbb866f4072d02da61503466c213c87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Sat, 15 Feb 2025 01:11:43 -0800
Subject: [PATCH 4/6] Resolve type of tuple field accessed with brackets

---
 src/analysis.zig         |  8 ++++++++
 tests/analysis/tuple.zig | 31 +++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+)
 create mode 100644 tests/analysis/tuple.zig

diff --git a/src/analysis.zig b/src/analysis.zig
index 30a12b3cc..7c93e0933 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -1016,6 +1016,14 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccess)
             .for_range => return Type.fromIP(analyser, .usize_type, null),
             else => return null,
         },
+        .tuple => |fields| switch (rhs) {
+            .single => |index_maybe| {
+                const index = index_maybe orelse return null;
+                if (index >= fields.len) return null;
+                return fields[index];
+            },
+            .open, .range => return null,
+        },
         .array => |info| switch (rhs) {
             .single => return info.elem_ty.instanceTypeVal(analyser),
             .open => |start_maybe| {
diff --git a/tests/analysis/tuple.zig b/tests/analysis/tuple.zig
new file mode 100644
index 000000000..3ea73d6d7
--- /dev/null
+++ b/tests/analysis/tuple.zig
@@ -0,0 +1,31 @@
+const TupleType = struct { i64, f32 };
+//    ^^^^^^^^^ (type)(struct { i64, f32 })
+
+const some_tuple: struct { i64, f32 } = undefined;
+//    ^^^^^^^^^^ (struct { i64, f32 })()
+
+comptime {
+    const some_tuple_0, const some_tuple_1 = some_tuple;
+    //    ^^^^^^^^^^^^ (i64)()
+    //                        ^^^^^^^^^^^^ (f32)()
+    _ = some_tuple_0;
+    _ = some_tuple_1;
+}
+
+const int: i64 = undefined;
+const float: f32 = undefined;
+const inferred_tuple = .{ int, float };
+//    ^^^^^^^^^^^^^^ (struct { i64, f32 })()
+
+comptime {
+    const inferred_tuple_0, const inferred_tuple_1 = inferred_tuple;
+    //    ^^^^^^^^^^^^^^^^ (i64)()
+    //                            ^^^^^^^^^^^^^^^^ (f32)()
+    _ = inferred_tuple_0;
+    _ = inferred_tuple_1;
+}
+
+comptime {
+    // Use @compileLog to verify the expected type with the compiler:
+    // @compileLog(some_tuple);
+}

From 95388ab2c202ed72aa6097b47c99fc717461cc77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Sat, 15 Feb 2025 01:28:48 -0800
Subject: [PATCH 5/6] Fix resolved type of inferred-size array

---
 src/analysis.zig             | 16 +++++++++++++++-
 tests/analysis/array.zig     |  4 ++--
 tests/lsp_features/hover.zig | 14 +++++++-------
 3 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/src/analysis.zig b/src/analysis.zig
index 7c93e0933..7a54c20fb 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -1704,7 +1704,16 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
                 .struct_init_comma,
                 .struct_init_one,
                 .struct_init_one_comma,
-                => base_type.instanceTypeVal(analyser),
+                => {
+                    var buffer: [2]Ast.Node.Index = undefined;
+                    const struct_init_info = tree.fullStructInit(&buffer, node).?;
+                    if (base_type.data == .array and base_type.data.array.elem_count == null) {
+                        var ty = base_type;
+                        ty.data.array.elem_count = struct_init_info.ast.fields.len;
+                        return ty.instanceTypeVal(analyser);
+                    }
+                    return base_type.instanceTypeVal(analyser);
+                },
                 .slice,
                 .slice_sentinel,
                 .slice_open,
@@ -1814,6 +1823,11 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
 
             if (array_init_info.ast.type_expr != 0) blk: {
                 const array_ty = try analyser.resolveTypeOfNode(.{ .node = array_init_info.ast.type_expr, .handle = handle }) orelse break :blk;
+                if (array_ty.data == .array and array_ty.data.array.elem_count == null) {
+                    var ty = array_ty;
+                    ty.data.array.elem_count = array_init_info.ast.elements.len;
+                    return ty.instanceTypeVal(analyser);
+                }
                 return array_ty.instanceTypeVal(analyser);
             }
 
diff --git a/tests/analysis/array.zig b/tests/analysis/array.zig
index c567e553f..78a707216 100644
--- a/tests/analysis/array.zig
+++ b/tests/analysis/array.zig
@@ -71,9 +71,9 @@ const array_slice_with_sentinel = some_array[0..runtime_index :0];
 const array_init = [length]u8{};
 //    ^^^^^^^^^^ ([3]u8)()
 const array_init_inferred_len_0 = [_]u8{};
-// TODO   ^^^^^^^^^^^^^^^^^^^^^^^^^ ([0]u8)()
+//    ^^^^^^^^^^^^^^^^^^^^^^^^^ ([0]u8)()
 const array_init_inferred_len_3 = [_]u8{ 1, 2, 3 };
-// TODO   ^^^^^^^^^^^^^^^^^^^^^^^^^ ([0]u8)()
+//    ^^^^^^^^^^^^^^^^^^^^^^^^^ ([3]u8)()
 
 comptime {
     // Use @compileLog to verify the expected type with the compiler:
diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig
index 17131f215..18449565a 100644
--- a/tests/lsp_features/hover.zig
+++ b/tests/lsp_features/hover.zig
@@ -571,7 +571,7 @@ test "array cat and mult" {
         \\const a = [_]u8{0} ++ [_]u8{1}
         \\```
         \\```zig
-        \\([?]u8)
+        \\([2]u8)
         \\```
     );
     try testHover(
@@ -581,7 +581,7 @@ test "array cat and mult" {
         \\const a = [1]u8{0} ++ [_]u8{1}
         \\```
         \\```zig
-        \\([?]u8)
+        \\([2]u8)
         \\```
     );
     try testHover(
@@ -591,7 +591,7 @@ test "array cat and mult" {
         \\const a = [_]u8{0} ++ [1]u8{1}
         \\```
         \\```zig
-        \\([?]u8)
+        \\([2]u8)
         \\```
     );
     try testHover(
@@ -621,7 +621,7 @@ test "array cat and mult" {
         \\const a = [_]u8{0} ** 2
         \\```
         \\```zig
-        \\([?]u8)
+        \\([2]u8)
         \\```
     );
     try testHover(
@@ -684,7 +684,7 @@ test "sentinel values" {
         \\const array = [_:0]u8{ 1, 2, 3, 4 }
         \\```
         \\```zig
-        \\([?:0]u8)
+        \\([4:0]u8)
         \\```
     );
     try testHover(
@@ -706,7 +706,7 @@ test "sentinel values" {
         \\const range = array[0..2]
         \\```
         \\```zig
-        \\(*[?]u8)
+        \\(*[2]u8)
         \\```
     );
     try testHover(
@@ -717,7 +717,7 @@ test "sentinel values" {
         \\const open = array[1..]
         \\```
         \\```zig
-        \\(*[?:0]u8)
+        \\(*[3:0]u8)
         \\```
     );
     // try testHover(

From cea41168396f3616c174b2c07c28e7969c1eff8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?=
 <70830482+FnControlOption@users.noreply.github.com>
Date: Sat, 15 Feb 2025 01:35:03 -0800
Subject: [PATCH 6/6] Resolve value of `array.len`

---
 src/analysis.zig         | 19 +++++++++----------
 tests/analysis/array.zig |  2 +-
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/analysis.zig b/src/analysis.zig
index 7a54c20fb..e9c78536c 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -1271,15 +1271,7 @@ fn resolvePropertyType(analyser: *Analyser, ty: Type, name: []const u8) error{Ou
 
     switch (ty.data) {
         .pointer => |info| switch (info.size) {
-            .one => switch (info.elem_ty.data) {
-                .array => {
-                    std.debug.assert(!info.elem_ty.is_type_val);
-                    if (std.mem.eql(u8, "len", name)) {
-                        return Type.fromIP(analyser, .usize_type, null);
-                    }
-                },
-                else => {},
-            },
+            .one => {}, // One level of indirection is handled by resolveDerefType
             .slice => {
                 if (std.mem.eql(u8, "len", name)) {
                     return Type.fromIP(analyser, .usize_type, null);
@@ -1302,8 +1294,15 @@ fn resolvePropertyType(analyser: *Analyser, ty: Type, name: []const u8) error{Ou
             .many, .c => {},
         },
 
-        .array => {
+        .array => |info| {
             if (std.mem.eql(u8, "len", name)) {
+                if (info.elem_count) |elem_count| {
+                    const index = try analyser.ip.get(
+                        analyser.gpa,
+                        .{ .int_u64_value = .{ .ty = .usize_type, .int = elem_count } },
+                    );
+                    return Type.fromIP(analyser, .usize_type, index);
+                }
                 return Type.fromIP(analyser, .usize_type, null);
             }
         },
diff --git a/tests/analysis/array.zig b/tests/analysis/array.zig
index 78a707216..3afc6108d 100644
--- a/tests/analysis/array.zig
+++ b/tests/analysis/array.zig
@@ -19,7 +19,7 @@ const some_unsized_array: [unknown_length]u8 = undefined;
 //    ^^^^^^^^^^^^^^^^^^ ([?]u8)()
 
 const some_array_len = some_array.len;
-//    ^^^^^^^^^^^^^^ (usize)()
+//    ^^^^^^^^^^^^^^ (usize)(3)
 
 const some_unsized_array_len = some_unsized_array.len;
 //    ^^^^^^^^^^^^^^^^^^^^^^ (usize)()