diff --git a/src/analysis.zig b/src/analysis.zig index e27446c01..6f5da61e6 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) { @@ -1016,108 +1016,101 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi .for_range => return try Type.typeValFromIP(analyser, .usize_type), else => return null, }, - .array => |info| switch (rhs) { - .Single => return try info.elem_ty.instanceTypeVal(analyser), - .Open => { + else => switch (rhs) { + .single => |index_maybe| { + const elem_ty = lhs.bracketAccessElemType(index_maybe) orelse return null; + return try elem_ty.instanceTypeVal(analyser); + }, + .open => |start_maybe| { + const is_const, const array_info = switch (lhs.data) { + .array => |info| .{ false, info }, // TODO: should only be false for `var` + .pointer => |info| switch (info.size) { + .one => switch (info.elem_ty.data) { + .array => |array_info| .{ info.is_const, array_info }, + else => return null, + }, + .many, + .slice, + .c, + => return lhs, + }, + else => return null, + }; + const elem_count = blk: { + const start = start_maybe orelse break :blk null; + const elem_count = array_info.elem_count orelse break :blk null; + if (start > elem_count) break :blk null; + break :blk elem_count - start; + }; return .{ .data = .{ .pointer = .{ - .size = .slice, - .sentinel = info.sentinel, - .is_const = false, - .elem_ty = info.elem_ty, + .size = .one, + .sentinel = .none, + .is_const = is_const, + .elem_ty = blk: { + const array_ty = try analyser.arena.allocator().create(Type); + array_ty.* = .{ + .data = .{ + .array = .{ + .elem_count = elem_count, + .sentinel = array_info.sentinel, + .elem_ty = array_info.elem_ty, + }, + }, + .is_type_val = true, + }; + break :blk array_ty; + }, }, }, .is_type_val = false, }; }, - .Range => { - return .{ + .range => |range_maybe| { + const elem_ty = lhs.bracketAccessElemType(null) orelse return null; + const is_const = switch (lhs.data) { + .array => false, // TODO: should only be false for `var` + .pointer => |info| info.is_const, + else => return null, + }; + const start, const end = range_maybe orelse return .{ .data = .{ .pointer = .{ .size = .slice, .sentinel = .none, - .is_const = false, - .elem_ty = info.elem_ty, + .is_const = is_const, + .elem_ty = elem_ty, }, }, .is_type_val = false, }; - }, - }, - .pointer => |info| return switch (info.size) { - .one => switch (info.elem_ty.data) { - .array => |array_info| { - switch (rhs) { - .Single => return try 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, - }; - }, - } - }, - else => return null, - }, - .many => switch (rhs) { - .Single => try info.elem_ty.instanceTypeVal(analyser), - .Open => lhs, - .Range => { - return .{ - .data = .{ - .pointer = .{ - .size = .slice, - .sentinel = .none, - .is_const = info.is_const, - .elem_ty = info.elem_ty, - }, - }, - .is_type_val = false, - }; - }, - }, - .slice => switch (rhs) { - .Single => try info.elem_ty.instanceTypeVal(analyser), - .Open, .Range => lhs, - }, - .c => switch (rhs) { - .Single => try info.elem_ty.instanceTypeVal(analyser), - .Open => lhs, - .Range => .{ + return .{ .data = .{ .pointer = .{ - .size = .slice, + .size = .one, .sentinel = .none, - .is_const = info.is_const, - .elem_ty = info.elem_ty, + .is_const = is_const, + .elem_ty = blk: { + const array_ty = try analyser.arena.allocator().create(Type); + array_ty.* = .{ + .data = .{ + .array = .{ + .elem_count = end - start, + .sentinel = .none, + .elem_ty = elem_ty, + }, + }, + .is_type_val = true, + }; + break :blk array_ty; + }, }, }, .is_type_val = false, - }, + }; }, }, - else => return null, } } @@ -1137,15 +1130,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 try Type.typeValFromIP(analyser, .usize_type); - } - }, - else => {}, - }, + .one => unreachable, // handled by resolveDerefType .slice => { if (std.mem.eql(u8, "len", name)) { return try Type.typeValFromIP(analyser, .usize_type); @@ -1168,8 +1153,18 @@ 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 .{ + .data = .{ .ip_index = .{ .index = index } }, + .is_type_val = false, + }; + } return try Type.typeValFromIP(analyser, .usize_type); } }, @@ -1579,12 +1574,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); @@ -1677,9 +1684,13 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e var buffer: [2]Ast.Node.Index = undefined; const array_init_info = tree.fullArrayInit(&buffer, node).?; - 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; - return try array_ty.instanceTypeVal(analyser); + if (array_init_info.ast.type_expr != 0) { + var array_ty = try analyser.resolveTypeOfNode(.{ .node = array_init_info.ast.type_expr, .handle = handle }) orelse return null; + array_ty = try array_ty.instanceTypeVal(analyser) orelse return null; + if (array_ty.data == .array and array_ty.data.array.elem_count == null) { + array_ty.data.array.elem_count = array_init_info.ast.elements.len; + } + return array_ty; } const elem_ty_slice = try analyser.arena.allocator().alloc(Type, array_init_info.ast.elements.len); @@ -2899,6 +2910,25 @@ pub const Type = struct { return analyser.lookupSymbolContainer(scope_handle, symbol, .other); } + fn bracketAccessElemType(self: Type, index_maybe: ?u64) ?*Type { + return switch (self.data) { + .tuple => |fields| { + const index = index_maybe orelse return null; + if (index >= fields.len) return null; + return &fields[index]; + }, + .array => |info| info.elem_ty, + .pointer => |info| switch (info.size) { + .one => switch (info.elem_ty.data) { + .array => |array_info| array_info.elem_ty, + else => null, + }, + else => info.elem_ty, + }, + else => null, + }; + } + pub fn fmt(ty: Type, analyser: *Analyser, options: FormatOptions) std.fmt.Formatter(format) { const typeof = ty.typeOf(analyser); return .{ .data = .{ .ty = typeof, .analyser = analyser, .options = options } }; @@ -3345,7 +3375,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(); @@ -3360,12 +3390,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 }; } }, } @@ -4077,7 +4107,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; @@ -4669,7 +4699,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)); } @@ -4679,7 +4709,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/lsp_features/hover.zig b/tests/lsp_features/hover.zig index ab0454d4f..237875175 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( @@ -699,25 +699,25 @@ test "sentinel values" { \\``` ); try testHover( - \\const array = [_:0]u8{ 1, 2, 3, 4 }; + \\var array = [_:0]u8{ 1, 2, 3, 4 }; \\const range = array[0..2]; , \\```zig \\const range = array[0..2] \\``` \\```zig - \\([]u8) + \\(*[2]u8) \\``` ); try testHover( - \\const array = [_:0]u8{ 1, 2, 3, 4 }; + \\var array = [_:0]u8{ 1, 2, 3, 4 }; \\const open = array[1..]; , \\```zig \\const open = array[1..] \\``` \\```zig - \\([:0]u8) + \\(*[3:0]u8) \\``` ); // try testHover( diff --git a/tests/lsp_features/inlay_hints.zig b/tests/lsp_features/inlay_hints.zig index 6fbe85316..f7802d93f 100644 --- a/tests/lsp_features/inlay_hints.zig +++ b/tests/lsp_features/inlay_hints.zig @@ -549,6 +549,169 @@ test "tuple fields" { , .{ .kind = .Type }); } +test "tuple fields accessed with brackets" { + try testInlayHints( + \\fn foo() void { + \\ var a: f32 = 0; + \\ var b: i64 = 1; + \\ const tmp = .{ b, a }; + \\ const x = tmp[0]; + \\ const y = tmp[1]; + \\} + , .{ .kind = .Type }); + try testInlayHints( + \\fn foo() void { + \\ const tmp: struct { i64, f32 } = .{ 1, 0 }; + \\ const x = tmp[0]; + \\ const y = tmp[1]; + \\} + , .{ .kind = .Type }); +} + +test "mutable range slices" { + try testInlayHints( + \\var foo: []f32 = undefined; + \\const bar<*[2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*]f32 = undefined; + \\const bar<*[2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*c]f32 = undefined; + \\const bar<*[2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *[4]f32 = undefined; + \\const bar<*[2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [4]f32 = undefined; + \\const bar<*[2]f32> = foo[1..3]; + , .{ .kind = .Type }); +} + +test "immutable range slices" { + try testInlayHints( + \\var foo: []const f32 = undefined; + \\const bar<*const [2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*]const f32 = undefined; + \\const bar<*const [2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*c]const f32 = undefined; + \\const bar<*const [2]f32> = foo[1..3]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *const [4]f32 = undefined; + \\const bar<*const [2]f32> = foo[1..3]; + , .{ .kind = .Type }); + // try testInlayHints( + // \\const foo: [4]f32 = undefined; + // \\const bar<*const [2]f32> = foo[1..3]; + // , .{ .kind = .Type }); +} + +test "mutable open slices" { + try testInlayHints( + \\var foo: []f32 = undefined; + \\const bar<[]f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*]f32 = undefined; + \\const bar<[*]f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*c]f32 = undefined; + \\const bar<[*c]f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *[4]f32 = undefined; + \\const bar<*[3]f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [4]f32 = undefined; + \\const bar<*[3]f32> = foo[1..]; + , .{ .kind = .Type }); +} + +test "mutable open slices with sentinel" { + try testInlayHints( + \\var foo: [:0]i32 = undefined; + \\const bar<[:0]i32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*:0]i32 = undefined; + \\const bar<[*:0]i32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *[4:0]i32 = undefined; + \\const bar<*[3:0]i32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [4:0]i32 = undefined; + \\const bar<*[3:0]i32> = foo[1..]; + , .{ .kind = .Type }); +} + +test "immutable open slices" { + try testInlayHints( + \\var foo: []const f32 = undefined; + \\const bar<[]const f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*]const f32 = undefined; + \\const bar<[*]const f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*c]const f32 = undefined; + \\const bar<[*c]const f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *const [4]f32 = undefined; + \\const bar<*const [3]f32> = foo[1..]; + , .{ .kind = .Type }); + // try testInlayHints( + // \\const foo: [4]f32 = undefined; + // \\const bar<*const [3]f32> = foo[1..]; + // , .{ .kind = .Type }); +} + +test "immutable open slices with sentinel" { + try testInlayHints( + \\var foo: [:0]const f32 = undefined; + \\const bar<[:0]const f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: [*:0]const f32 = undefined; + \\const bar<[*:0]const f32> = foo[1..]; + , .{ .kind = .Type }); + try testInlayHints( + \\var foo: *const [4:0]f32 = undefined; + \\const bar<*const [3:0]f32> = foo[1..]; + , .{ .kind = .Type }); + // try testInlayHints( + // \\const foo: [4:0]f32 = undefined; + // \\const bar<*const [3:0]f32> = foo[1..]; + // , .{ .kind = .Type }); +} + +test "inferred-size arrays" { + try testInlayHints( + \\var array<[4]i32> = [_]i32{ 1, 2, 3, 4 }; + , .{ .kind = .Type }); +} + +test "array.len" { + try testInlayHints( + \\var array: [4]i32 = undefined; + \\const len = array.len; + \\const ptr<*[4]i32> = array[0..len]; + , .{ .kind = .Type }); +} + const Options = struct { kind: types.InlayHintKind, show_builtin: bool = true,