Skip to content

Commit 1ebcc79

Browse files
authored
Merge pull request #2656 from FnControlOption/tag
Add completions for enum literal of union tag
2 parents a786b02 + 7358346 commit 1ebcc79

10 files changed

Lines changed: 118 additions & 39 deletions

File tree

src/analysis.zig

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2862,8 +2862,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error
28622862
defer analyser.arena.free(lineage);
28632863

28642864
const tag = offsets.identifierTokenToNameSlice(tree, tree.nodeMainToken(node));
2865-
const decl = (try analyser.lookupSymbolFieldInit(handle, tag, node, lineage[1..])) orelse return Type.fromIP(analyser, .enum_literal_type, null);
2866-
return decl.resolveType(analyser);
2865+
const decl, const type_maybe = (try analyser.lookupSymbolFieldInit(handle, tag, node, lineage[1..])) orelse return Type.fromIP(analyser, .enum_literal_type, null);
2866+
return type_maybe orelse decl.resolveType(analyser);
28672867
},
28682868

28692869
.unreachable_literal => return Type.fromIP(analyser, .noreturn_type, null),
@@ -4345,6 +4345,7 @@ pub const Type = struct {
43454345
}
43464346

43474347
pub fn resolveDeclLiteralResultType(ty: Type) Type {
4348+
std.debug.assert(ty.is_type_val);
43484349
var result_type = ty;
43494350
while (true) {
43504351
result_type = switch (result_type.data) {
@@ -6363,7 +6364,7 @@ pub fn lookupSymbolFieldInit(
63636364
field_name: []const u8,
63646365
node: Ast.Node.Index,
63656366
ancestors: []const Ast.Node.Index,
6366-
) Error!?DeclWithHandle {
6367+
) Error!?struct { DeclWithHandle, ?Type } {
63676368
var container_type = (try analyser.resolveExpressionType(
63686369
handle,
63696370
node,
@@ -6385,27 +6386,38 @@ pub fn lookupSymbolFieldInit(
63856386
else => false,
63866387
};
63876388

6388-
container_type = try container_type
6389-
.resolveDeclLiteralResultType()
6390-
.instanceTypeVal(analyser) orelse container_type;
6389+
container_type = try container_type.typeOf(analyser);
6390+
container_type = container_type.resolveDeclLiteralResultType();
6391+
container_type = try container_type.instanceUnchecked(analyser);
63916392

63926393
if (is_struct_init) {
6393-
return try container_type.lookupSymbol(analyser, field_name);
6394+
const decl = try container_type.lookupSymbol(analyser, field_name) orelse return null;
6395+
return .{ decl, null };
6396+
}
6397+
6398+
switch (container_type.data) {
6399+
.union_tag => |t| {
6400+
const decl = try t.lookupSymbol(analyser, field_name) orelse return null;
6401+
return .{ decl, container_type };
6402+
},
6403+
else => {},
63946404
}
63956405

63966406
switch (container_type.getContainerKind() orelse return null) {
63976407
.keyword_struct, .keyword_opaque => {},
6398-
.keyword_enum => if (try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name)) |ty| return ty,
6399-
.keyword_union => if (try container_type.lookupSymbol(analyser, field_name)) |ty| return ty,
6408+
.keyword_enum => if (try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name)) |decl| return .{ decl, null },
6409+
.keyword_union => if (try container_type.lookupSymbol(analyser, field_name)) |decl| return .{ decl, null },
64006410
else => return null,
64016411
}
64026412

64036413
// Assume we are doing decl literals
64046414
const decl = try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name) orelse return null;
64056415
var resolved_type = try decl.resolveType(analyser) orelse return null;
64066416
resolved_type = try analyser.resolveReturnType(resolved_type) orelse resolved_type;
6417+
resolved_type = try resolved_type.typeOf(analyser);
64076418
resolved_type = resolved_type.resolveDeclLiteralResultType();
6408-
if (resolved_type.eql(container_type) or resolved_type.eql(try container_type.typeOf(analyser))) return decl;
6419+
resolved_type = try resolved_type.instanceUnchecked(analyser);
6420+
if (resolved_type.eql(container_type)) return .{ decl, null };
64096421
return null;
64106422
}
64116423

@@ -6448,8 +6460,9 @@ pub fn resolveExpressionTypeFromAncestors(
64486460
const field_name_token = tree.firstToken(node) - 2;
64496461
if (tree.tokenTag(field_name_token) != .identifier) return null;
64506462
const field_name = offsets.identifierTokenToNameSlice(tree, field_name_token);
6451-
if (try analyser.lookupSymbolFieldInit(handle, field_name, ancestors[0], ancestors[1..])) |field_decl| {
6452-
return try field_decl.resolveType(analyser);
6463+
if (try analyser.lookupSymbolFieldInit(handle, field_name, ancestors[0], ancestors[1..])) |field| {
6464+
const decl, const type_maybe = field;
6465+
return type_maybe orelse try decl.resolveType(analyser);
64536466
}
64546467
}
64556468
},
@@ -6591,8 +6604,8 @@ pub fn resolveExpressionTypeFromAncestors(
65916604

65926605
var fn_type = if (tree.nodeTag(call.ast.fn_expr) == .enum_literal) blk: {
65936606
const field_name = offsets.identifierTokenToNameSlice(tree, tree.nodeMainToken(call.ast.fn_expr));
6594-
const decl = try analyser.lookupSymbolFieldInit(handle, field_name, call.ast.fn_expr, ancestors) orelse return null;
6595-
const ty = try decl.resolveType(analyser) orelse return null;
6607+
const decl, const type_maybe = try analyser.lookupSymbolFieldInit(handle, field_name, call.ast.fn_expr, ancestors) orelse return null;
6608+
const ty = type_maybe orelse try decl.resolveType(analyser) orelse return null;
65966609
break :blk try analyser.resolveFuncProtoOfCallable(ty) orelse return null;
65976610
} else blk: {
65986611
const ty = try analyser.resolveTypeOfNode(.of(call.ast.fn_expr, handle)) orelse return null;
@@ -6852,7 +6865,7 @@ pub fn getSymbolEnumLiteral(
68526865
handle: *DocumentStore.Handle,
68536866
source_index: usize,
68546867
name: []const u8,
6855-
) Error!?DeclWithHandle {
6868+
) Error!?struct { DeclWithHandle, ?Type } {
68566869
const tracy_zone = tracy.trace(@src());
68576870
defer tracy_zone.end();
68586871

src/features/completions.zig

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,15 +1454,33 @@ fn getReturnTypeNode(tree: *const Ast, nodes: []const Ast.Node.Index) ?Ast.Node.
14541454
return null;
14551455
}
14561456

1457+
fn isTaggedUnionFieldWithOPV(
1458+
builder: *Builder,
1459+
container: Analyser.Type,
1460+
field_type: Analyser.Type,
1461+
) bool {
1462+
if (!container.isTaggedUnion()) return false;
1463+
const ip_index = switch (field_type.data) {
1464+
.ip_index => |payload| payload.type,
1465+
else => return false,
1466+
};
1467+
return builder.analyser.ip.onePossibleValue(ip_index) != .none;
1468+
}
1469+
14571470
/// Given a Type that is a container, adds it's `.container_field*`s to completions
14581471
fn collectContainerFields(
14591472
builder: *Builder,
14601473
likely: EnumLiteralContext.Likely,
14611474
container: Analyser.Type,
14621475
omit_members: std.BufSet,
14631476
) Analyser.Error!void {
1464-
const info = switch (container.data) {
1465-
.container => |info| info,
1477+
const info, const type_maybe = switch (container.data) {
1478+
.container => |info| .{ info, null },
1479+
.union_tag => |union_ty| blk: {
1480+
const info = union_ty.data.container;
1481+
const ty = try container.instanceTypeVal(builder.analyser) orelse container;
1482+
break :blk .{ info, ty };
1483+
},
14661484
else => return,
14671485
};
14681486

@@ -1474,7 +1492,7 @@ fn collectContainerFields(
14741492
const decl = document_scope.declarations.get(@intFromEnum(decl_index));
14751493
if (decl != .ast_node) continue;
14761494
const decl_handle: Analyser.DeclWithHandle = .{ .decl = decl, .handle = scope_handle.handle, .container_type = container };
1477-
const maybe_resolved_ty = try decl_handle.resolveType(builder.analyser);
1495+
const maybe_resolved_ty = type_maybe orelse try decl_handle.resolveType(builder.analyser);
14781496
const tree = &scope_handle.handle.tree;
14791497

14801498
const name = offsets.tokenToSlice(tree, decl.nameToken(tree));
@@ -1487,18 +1505,16 @@ fn collectContainerFields(
14871505
=> {
14881506
const field = tree.fullContainerField(decl.ast_node).?;
14891507

1490-
const kind: types.completion.Item.Kind =
1491-
if (field.ast.tuple_like) .EnumMember else .Field;
1508+
const kind: types.completion.Item.Kind = switch (container.data) {
1509+
.union_tag => .EnumMember,
1510+
else => if (field.ast.tuple_like) .EnumMember else .Field,
1511+
};
14921512

14931513
const insert_text, const insert_text_format: types.InsertTextFormat = insert_text: {
14941514
if (likely != .struct_field and likely != .enum_comparison and likely != .switch_case and kind == .Field) {
1495-
if (container.isTaggedUnion() and
1496-
maybe_resolved_ty != null and
1497-
maybe_resolved_ty.?.data == .ip_index and
1498-
maybe_resolved_ty.?.data.ip_index.type != .unknown_type and
1499-
builder.analyser.ip.onePossibleValue(maybe_resolved_ty.?.data.ip_index.type) != .none)
1500-
{
1501-
break :insert_text .{ name, .PlainText };
1515+
if (maybe_resolved_ty) |ty| {
1516+
if (isTaggedUnionFieldWithOPV(builder, container, ty))
1517+
break :insert_text .{ name, .PlainText };
15021518
}
15031519

15041520
if (!builder.use_snippets) {
@@ -1588,6 +1604,7 @@ fn collectContainerFields(
15881604
// decl literal
15891605
const resolved_ty = maybe_resolved_ty orelse continue;
15901606
var expected_ty = try builder.analyser.resolveReturnType(resolved_ty) orelse continue;
1607+
expected_ty = try expected_ty.typeOf(builder.analyser);
15911608
expected_ty = expected_ty.resolveDeclLiteralResultType();
15921609
if (expected_ty.data != .container) continue;
15931610
if (!expected_ty.data.container.scope_handle.eql(container.data.container.scope_handle)) continue;

src/features/goto.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ fn gotoDefinitionEnumLiteral(
149149
return gotoDefinitionStructInit(analyser, handle, source_index, kind, offset_encoding);
150150
};
151151
const name = offsets.locToSlice(handle.tree.source, name_loc);
152-
const decl = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
152+
const decl, _ = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
153153
return try gotoDefinitionSymbol(analyser, offsets.tokenToRange(&handle.tree, name_token, offset_encoding), decl, kind, offset_encoding);
154154
}
155155

src/features/hover.zig

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,24 @@ fn hoverSymbol(
1818
arena: std.mem.Allocator,
1919
param_decl_handle: Analyser.DeclWithHandle,
2020
markup_kind: types.MarkupKind,
21+
) Analyser.Error!?[]const u8 {
22+
return try hoverSymbolWithType(analyser, arena, param_decl_handle, null, markup_kind);
23+
}
24+
25+
fn hoverSymbolWithType(
26+
analyser: *Analyser,
27+
arena: std.mem.Allocator,
28+
param_decl_handle: Analyser.DeclWithHandle,
29+
type_maybe: ?Analyser.Type,
30+
markup_kind: types.MarkupKind,
2131
) Analyser.Error!?[]const u8 {
2232
const tracy_zone = tracy.trace(@src());
2333
defer tracy_zone.end();
2434

2535
var doc_strings: std.ArrayList([]const u8) = .empty;
2636

2737
var decl_handle: Analyser.DeclWithHandle = param_decl_handle;
28-
var maybe_resolved_type = try param_decl_handle.resolveType(analyser);
38+
var maybe_resolved_type = type_maybe orelse try param_decl_handle.resolveType(analyser);
2939

3040
while (true) {
3141
if (try decl_handle.docComments(arena)) |doc_string| {
@@ -352,13 +362,13 @@ fn hoverDefinitionEnumLiteral(
352362
return try hoverDefinitionStructInit(analyser, arena, handle, source_index, markup_kind, offset_encoding);
353363
};
354364
const name = offsets.locToSlice(handle.tree.source, name_loc);
355-
const decl = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
365+
const decl, const type_maybe = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
356366

357367
return .{
358368
.contents = .{
359369
.markup_content = .{
360370
.kind = markup_kind,
361-
.value = (try hoverSymbol(analyser, arena, decl, markup_kind)) orelse return null,
371+
.value = (try hoverSymbolWithType(analyser, arena, decl, type_maybe, markup_kind)) orelse return null,
362372
},
363373
},
364374
.range = offsets.tokenToRange(&handle.tree, name_token, offset_encoding),

src/features/inlay_hints.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,8 @@ fn writeNodeInlayHint(
526526
const name_token = tree.firstToken(value_node) - 2; // math our way two token indexes back to get the `name`
527527
const name_loc = offsets.tokenToLoc(tree, name_token);
528528
const name = offsets.locToSlice(tree.source, name_loc);
529-
const decl = (try builder.analyser.getSymbolEnumLiteral(builder.handle, name_loc.start, name)) orelse continue;
530-
const ty = try decl.resolveType(builder.analyser) orelse continue;
529+
const decl, const type_maybe = (try builder.analyser.getSymbolEnumLiteral(builder.handle, name_loc.start, name)) orelse continue;
530+
const ty = type_maybe orelse try decl.resolveType(builder.analyser) orelse continue;
531531
const type_str = try ty.stringifyTypeOf(builder.analyser, .{ .truncate_container_decls = true });
532532
if (type_str.len == 0) continue;
533533
try appendTypeHintString(

src/features/references.zig

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ const Builder = struct {
186186
else => unreachable,
187187
};
188188

189-
const candidate = try builder.analyser.lookupSymbolFieldInit(
189+
const candidate, _ = try builder.analyser.lookupSymbolFieldInit(
190190
handle,
191191
name,
192192
nodes[0],
@@ -204,7 +204,7 @@ const Builder = struct {
204204
const name_token = tree.nodeMainToken(node);
205205
const name = offsets.identifierTokenToNameSlice(&handle.tree, name_token);
206206
if (!std.mem.eql(u8, name, target_symbol_name)) return;
207-
const candidate = try builder.analyser.getSymbolEnumLiteral(handle, tree.tokenStart(name_token), name) orelse return;
207+
const candidate, _ = try builder.analyser.getSymbolEnumLiteral(handle, tree.tokenStart(name_token), name) orelse return;
208208
break :candidate .{ candidate, name_token };
209209
},
210210
.global_var_decl,
@@ -728,7 +728,10 @@ pub fn referencesHandler(server: *Server, arena: std.mem.Allocator, request: Gen
728728
break :z null;
729729
},
730730
.label_access, .label_decl => try Analyser.lookupLabel(handle, name, source_index),
731-
.enum_literal => try analyser.getSymbolEnumLiteral(handle, source_index, name),
731+
.enum_literal => blk: {
732+
const decl, _ = try analyser.getSymbolEnumLiteral(handle, source_index, name) orelse break :blk null;
733+
break :blk decl;
734+
},
732735
.keyword => null,
733736
else => null,
734737
} orelse return null;

src/features/signature_help.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,12 @@ pub fn getSignatureInfo(
266266
var ty = switch (tree.tokenTag(expr_first_token)) {
267267
.period => blk: { // decl literal
268268
loc.start += 1;
269-
const decl = try analyser.getSymbolEnumLiteral(
269+
const decl, const type_maybe = try analyser.getSymbolEnumLiteral(
270270
handle,
271271
loc.start,
272272
offsets.locToSlice(tree.source, loc),
273273
) orelse continue;
274-
break :blk try decl.resolveType(analyser) orelse continue;
274+
break :blk type_maybe orelse try decl.resolveType(analyser) orelse continue;
275275
},
276276
else => try analyser.getFieldAccessType(handle, loc.start, loc) orelse continue,
277277
};

tests/analysis/meta.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ const TagA = std.meta.Tag(TaggedUnionA);
2121
const TagB = std.meta.Tag(TaggedUnionB);
2222
// ^^^^ (type)(@typeInfo(TaggedUnionB).@"union".tag_type.?)
2323

24+
const tag_a: TagA = .foo;
25+
// ^^^^ (EnumA)()
26+
27+
const tag_b: TagB = .fizz;
28+
// ^^^^^ (@typeInfo(TaggedUnionB).@"union".tag_type.?)()
29+
2430
const ArgsTupleA = std.meta.ArgsTuple(fn (u8, i32) void);
2531
// ^^^^^^^^^^ (type)(struct { u8, i32 })
2632

tests/analysis_check.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,11 @@ pub fn main(init: std.process.Init) Error!void {
190190
const ty = blk: {
191191
const decl_maybe = switch (ctx) {
192192
.global => try analyser.lookupSymbolGlobal(handle, identifier, identifier_loc.start),
193-
.enum_literal => try analyser.getSymbolEnumLiteral(handle, identifier_loc.start, identifier),
193+
.enum_literal => decl: {
194+
const decl, const type_maybe = try analyser.getSymbolEnumLiteral(handle, identifier_loc.start, identifier) orelse break :decl null;
195+
if (type_maybe) |ty| break :blk ty;
196+
break :decl decl;
197+
},
194198
.struct_init => break :blk try analyser.resolveStructInitType(handle, identifier_loc.start),
195199
};
196200

tests/lsp_features/completion.zig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,32 @@ test "tagged union" {
17611761
});
17621762
}
17631763

1764+
test "tagged union - tag" {
1765+
try testCompletion(
1766+
\\const std = @import("std");
1767+
\\const Ue = union(enum) {
1768+
\\ alpha,
1769+
\\ beta: []const u8,
1770+
\\};
1771+
\\const foo: std.meta.Tag(Ue) = .<cursor>
1772+
, &.{
1773+
.{ .label = "alpha", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
1774+
.{ .label = "beta", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
1775+
});
1776+
try testCompletion(
1777+
\\const std = @import("std");
1778+
\\const Ue = union(enum) {
1779+
\\ alpha,
1780+
\\ beta: []const u8,
1781+
\\};
1782+
\\const S = struct { foo: std.meta.Tag(Ue) };
1783+
\\const s = S{ .foo = .<cursor> };
1784+
, &.{
1785+
.{ .label = "alpha", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
1786+
.{ .label = "beta", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
1787+
});
1788+
}
1789+
17641790
test "switch cases" {
17651791
// Because current logic is to list all enums if all else fails,
17661792
// the following tests include an extra enum to ensure that we're not just 'getting lucky'

0 commit comments

Comments
 (0)