From fe2ad9bb156e6db58eda7487a6e078adabdede9c Mon Sep 17 00:00:00 2001 From: psznm Date: Wed, 2 Sep 2026 20:40:44 +0200 Subject: [PATCH] Optional and pointer strct parameter completions --- src/analysis.zig | 5 ++ src/features/completions.zig | 22 ++++-- tests/lsp_features/completion.zig | 122 ++++++++++++++++++++++++++++++ tests/lsp_features/hover.zig | 33 ++++++++ 4 files changed, 177 insertions(+), 5 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 4c8caf346..59c16396e 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -6767,6 +6767,11 @@ pub fn resolveExpressionTypeFromAncestors( if (try analyser.resolveDerefType(expr_ty)) |ty| { return ty; } + if (try analyser.resolveOptionalUnwrap(expr_ty)) |ty| { + if (try analyser.resolveDerefType(ty)) |typ| { + return typ; + } + } switch (expr_ty.data) { .pointer => |info| switch (info.size) { diff --git a/src/features/completions.zig b/src/features/completions.zig index 618a28ecb..b659de15e 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -1184,7 +1184,7 @@ fn getEnumLiteralContext( var dot_context: EnumLiteralContext = .{ .likely = .enum_literal }; - switch (tree.tokenTag(token_index)) { + tag: switch (tree.tokenTag(token_index)) { .equal => { token_index -= 1; dot_context.need_ret_type = tree.tokenTag(token_index) == .r_paren; @@ -1231,6 +1231,10 @@ fn getEnumLiteralContext( .l_brace, .comma, .l_paren => { dot_context = getSwitchOrStructInitContext(tree, dot_token_index, nodes) orelse return null; }, + .ampersand => { + token_index -= 1; + continue :tag tree.tokenTag(token_index); + }, else => return null, } return dot_context; @@ -1482,8 +1486,14 @@ fn collectContainerFields( container: Analyser.Type, omit_members: std.BufSet, ) Analyser.Error!void { - const info, const type_maybe = switch (container.data) { + const info, const type_maybe = info: switch (container.data) { .container => |info| .{ info, null }, + .optional => |opt| { + continue :info opt.data; + }, + .pointer => |ptr| { + continue :info ptr.elem_ty.data; + }, .union_tag => |union_ty| blk: { const info = union_ty.data.container; const ty = try container.instanceTypeVal(builder.analyser) orelse container; @@ -1735,7 +1745,9 @@ fn collectVarAccessContainerNodes( const symbol_decl = try analyser.lookupSymbolGlobal(handle, handle.tree.source[loc.start..loc.end], loc.end) orelse return; const result = try symbol_decl.resolveType(analyser) orelse return; - const type_expr = try analyser.resolveDerefType(result) orelse result; + var type_expr = try analyser.resolveDerefType(result) orelse result; + if (type_expr.is_type_val) type_expr = type_expr.resolveDeclLiteralResultType(); + if (!type_expr.isFunc()) { _ = try type_expr.getAllTypesWithHandlesArraySet(analyser, types_with_handles); return; @@ -1751,7 +1763,7 @@ fn collectVarAccessContainerNodes( } const param_index = dot_context.fn_arg_index; if (param_index >= info.parameters.len) return; - const param_type = info.parameters[param_index].type; + const param_type = info.parameters[param_index].type.resolveDeclLiteralResultType(); _ = try param_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles); } @@ -1785,7 +1797,7 @@ fn collectFieldAccessTypes( const params = info.parameters; const param_index = dot_context.fn_arg_index + @intFromBool(has_self_param); if (param_index >= params.len) return; - const param_type = params[param_index].type; + const param_type = params[param_index].type.resolveDeclLiteralResultType(); _ = try param_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles); } diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 2564b13bf..71028da81 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -1751,6 +1751,67 @@ test "decl literal function call" { }); } +test "function call struct parameter completion" { + try testCompletion( + \\fn foo(s: struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.{.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: ?struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: ?struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.{.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: *const struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.); + \\} + , &.{ + // While the completion won't actually result in code that will compile due to creating not a pointer + // This will still assist user in actually writing code - the compiler will do the rest. + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: *const struct {field: u32}) void {} + \\fn bar() void { + \\ foo(.{.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\fn foo(s: *const struct {field: u32}) void {} + \\fn bar() void { + \\ foo(&.); + \\} + , &.{ + .{ .label = "field", .kind = .Field, .detail = "u32" }, + }); +} + test "enum literal" { try testCompletion( \\const literal = .foo; @@ -2558,6 +2619,67 @@ test "structinit - fields with and without default value" { }); } +test "structinit - anonymous" { + try testCompletion( + \\fn foo() E { + \\ const foo2: struct { alpha: u32 } = .a + \\} + , &.{ + .{ .label = "alpha", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\var foo: struct { alpha: u32 } = undefined; + \\foo = . + , &.{ + .{ .label = "alpha", .kind = .Field, .detail = "u32" }, + }); + // try testCompletion( + // \\fn foo() E { + // \\ const foo2: struct { alpha: u32 } = .{. + // \\} + // , &.{ + // .{ .label = "alpha", .kind = .Field, .detail = "u32" }, + // }); + // try testCompletion( + // \\fn foo() E { + // \\ const foo2: struct { alpha: u32 } = . + // \\} + // , &.{ + // .{ .label = "alpha", .kind = .Field, .detail = "u32" }, + // }); + // try testCompletion( + // \\const foo2: struct { alpha: u32 } = .a + // , &.{ + // .{ .label = "alpha", .kind = .Field, .detail = "u32" }, + // }); +} + +test "structinit - address of" { + try testCompletion( + \\const T = struct { a: u32 }; + \\var t: ?*const T = &. + , &.{ + .{ .label = "a", .kind = .Field, .detail = "u32" }, + }); + try testCompletion( + \\const T = struct { a: u32 }; + \\var t: ?*const T = undefined; + \\t = &. + , &.{ + .{ .label = "a", .kind = .Field, .detail = "u32" }, + }); + // try testCompletion( + // \\var t: *const struct { a: u32 } = &. + // , &.{ + // .{ .label = "a", .kind = .Field, .detail = "u32" }, + // }); + // try testCompletion( + // \\var t: ?*const struct { a: u32 } = &. + // , &.{ + // .{ .label = "a", .kind = .Field, .detail = "u32" }, + // }); +} + test "return - enum" { try testCompletion( \\const E = enum { diff --git a/tests/lsp_features/hover.zig b/tests/lsp_features/hover.zig index 8e34e474f..c62ae506f 100644 --- a/tests/lsp_features/hover.zig +++ b/tests/lsp_features/hover.zig @@ -387,6 +387,39 @@ test "inferred struct init" { \\ \\Go to [S](untitled:///Untitled-0.zig#L1) ); + try testHover( + \\const S = struct { a: u32 }; + \\var t: ?*const S = &.{ .a = 5 }; + , + \\```zig + \\a: u32 + \\``` + \\```zig + \\(u32) + \\``` + ); + try testHover( + \\var t: ?*const struct { a: u32 } = &.{ .a = 5 }; + , + \\```zig + \\a: u32 + \\``` + \\```zig + \\(u32) + \\``` + ); + try testHover( + \\const S = struct { a: u32 }; + \\var t: ?*const S = undefined; + \\fn f(_: S) void { t = &.{ .a = 5 }; } + , + \\```zig + \\a: u32 + \\``` + \\```zig + \\(u32) + \\``` + ); } test "decl literal" {