Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2830,8 +2830,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error
defer analyser.arena.free(lineage);

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

.unreachable_literal => return Type.fromIP(analyser, .noreturn_type, null),
Expand Down Expand Up @@ -4313,6 +4313,7 @@ pub const Type = struct {
}

pub fn resolveDeclLiteralResultType(ty: Type) Type {
std.debug.assert(ty.is_type_val);
var result_type = ty;
while (true) {
result_type = switch (result_type.data) {
Expand Down Expand Up @@ -6337,7 +6338,7 @@ pub fn lookupSymbolFieldInit(
field_name: []const u8,
node: Ast.Node.Index,
ancestors: []const Ast.Node.Index,
) Error!?DeclWithHandle {
) Error!?struct { DeclWithHandle, ?Type } {
var container_type = (try analyser.resolveExpressionType(
handle,
node,
Expand All @@ -6359,27 +6360,38 @@ pub fn lookupSymbolFieldInit(
else => false,
};

container_type = try container_type
.resolveDeclLiteralResultType()
.instanceTypeVal(analyser) orelse container_type;
container_type = try container_type.typeOf(analyser);
container_type = container_type.resolveDeclLiteralResultType();
container_type = try container_type.instanceUnchecked(analyser);

if (is_struct_init) {
return try container_type.lookupSymbol(analyser, field_name);
const decl = try container_type.lookupSymbol(analyser, field_name) orelse return null;
return .{ decl, null };
}

switch (container_type.data) {
.union_tag => |t| {
const decl = try t.lookupSymbol(analyser, field_name) orelse return null;
return .{ decl, container_type };
},
else => {},
}

switch (container_type.getContainerKind() orelse return null) {
.keyword_struct, .keyword_opaque => {},
.keyword_enum => if (try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name)) |ty| return ty,
.keyword_union => if (try container_type.lookupSymbol(analyser, field_name)) |ty| return ty,
.keyword_enum => if (try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name)) |decl| return .{ decl, null },
.keyword_union => if (try container_type.lookupSymbol(analyser, field_name)) |decl| return .{ decl, null },
else => return null,
}

// Assume we are doing decl literals
const decl = try (try container_type.typeOf(analyser)).lookupSymbol(analyser, field_name) orelse return null;
var resolved_type = try decl.resolveType(analyser) orelse return null;
resolved_type = try analyser.resolveReturnType(resolved_type) orelse resolved_type;
resolved_type = try resolved_type.typeOf(analyser);
resolved_type = resolved_type.resolveDeclLiteralResultType();
if (resolved_type.eql(container_type) or resolved_type.eql(try container_type.typeOf(analyser))) return decl;
resolved_type = try resolved_type.instanceUnchecked(analyser);
if (resolved_type.eql(container_type)) return .{ decl, null };
return null;
}

Expand Down Expand Up @@ -6422,8 +6434,9 @@ pub fn resolveExpressionTypeFromAncestors(
const field_name_token = tree.firstToken(node) - 2;
if (tree.tokenTag(field_name_token) != .identifier) return null;
const field_name = offsets.identifierTokenToNameSlice(tree, field_name_token);
if (try analyser.lookupSymbolFieldInit(handle, field_name, ancestors[0], ancestors[1..])) |field_decl| {
return try field_decl.resolveType(analyser);
if (try analyser.lookupSymbolFieldInit(handle, field_name, ancestors[0], ancestors[1..])) |field| {
const decl, const type_maybe = field;
return type_maybe orelse try decl.resolveType(analyser);
}
}
},
Expand Down Expand Up @@ -6565,8 +6578,8 @@ pub fn resolveExpressionTypeFromAncestors(

var fn_type = if (tree.nodeTag(call.ast.fn_expr) == .enum_literal) blk: {
const field_name = offsets.identifierTokenToNameSlice(tree, tree.nodeMainToken(call.ast.fn_expr));
const decl = try analyser.lookupSymbolFieldInit(handle, field_name, call.ast.fn_expr, ancestors) orelse return null;
const ty = try decl.resolveType(analyser) orelse return null;
const decl, const type_maybe = try analyser.lookupSymbolFieldInit(handle, field_name, call.ast.fn_expr, ancestors) orelse return null;
const ty = type_maybe orelse try decl.resolveType(analyser) orelse return null;
break :blk try analyser.resolveFuncProtoOfCallable(ty) orelse return null;
} else blk: {
const ty = try analyser.resolveTypeOfNode(.of(call.ast.fn_expr, handle)) orelse return null;
Expand Down Expand Up @@ -6826,7 +6839,7 @@ pub fn getSymbolEnumLiteral(
handle: *DocumentStore.Handle,
source_index: usize,
name: []const u8,
) Error!?DeclWithHandle {
) Error!?struct { DeclWithHandle, ?Type } {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

Expand Down
41 changes: 29 additions & 12 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1480,15 +1480,33 @@ fn getReturnTypeNode(tree: *const Ast, nodes: []const Ast.Node.Index) ?Ast.Node.
return null;
}

fn isTaggedUnionFieldWithOPV(
builder: *Builder,
container: Analyser.Type,
field_type: Analyser.Type,
) bool {
if (!container.isTaggedUnion()) return false;
const ip_index = switch (field_type.data) {
.ip_index => |payload| payload.type,
else => return false,
};
return builder.analyser.ip.onePossibleValue(ip_index) != .none;
}

/// Given a Type that is a container, adds it's `.container_field*`s to completions
fn collectContainerFields(
builder: *Builder,
likely: EnumLiteralContext.Likely,
container: Analyser.Type,
omit_members: std.BufSet,
) Analyser.Error!void {
const info = switch (container.data) {
.container => |info| info,
const info, const type_maybe = switch (container.data) {
.container => |info| .{ info, null },
.union_tag => |union_ty| blk: {
const info = union_ty.data.container;
const ty = try container.instanceTypeVal(builder.analyser) orelse container;
break :blk .{ info, ty };
},
else => return,
};

Expand All @@ -1500,7 +1518,7 @@ fn collectContainerFields(
const decl = document_scope.declarations.get(@intFromEnum(decl_index));
if (decl != .ast_node) continue;
const decl_handle: Analyser.DeclWithHandle = .{ .decl = decl, .handle = scope_handle.handle, .container_type = container };
const maybe_resolved_ty = try decl_handle.resolveType(builder.analyser);
const maybe_resolved_ty = type_maybe orelse try decl_handle.resolveType(builder.analyser);
const tree = &scope_handle.handle.tree;

const name = offsets.tokenToSlice(tree, decl.nameToken(tree));
Expand All @@ -1513,18 +1531,16 @@ fn collectContainerFields(
=> {
const field = tree.fullContainerField(decl.ast_node).?;

const kind: types.completion.Item.Kind =
if (field.ast.tuple_like) .EnumMember else .Field;
const kind: types.completion.Item.Kind = switch (container.data) {
.union_tag => .EnumMember,
else => if (field.ast.tuple_like) .EnumMember else .Field,
};

const insert_text, const insert_text_format: types.InsertTextFormat = insert_text: {
if (likely != .struct_field and likely != .enum_comparison and likely != .switch_case and kind == .Field) {
if (container.isTaggedUnion() and
maybe_resolved_ty != null and
maybe_resolved_ty.?.data == .ip_index and
maybe_resolved_ty.?.data.ip_index.type != .unknown_type and
builder.analyser.ip.onePossibleValue(maybe_resolved_ty.?.data.ip_index.type) != .none)
{
break :insert_text .{ name, .PlainText };
if (maybe_resolved_ty) |ty| {
if (isTaggedUnionFieldWithOPV(builder, container, ty))
break :insert_text .{ name, .PlainText };
}

if (!builder.use_snippets) {
Expand Down Expand Up @@ -1614,6 +1630,7 @@ fn collectContainerFields(
// decl literal
const resolved_ty = maybe_resolved_ty orelse continue;
var expected_ty = try builder.analyser.resolveReturnType(resolved_ty) orelse continue;
expected_ty = try expected_ty.typeOf(builder.analyser);
expected_ty = expected_ty.resolveDeclLiteralResultType();
if (expected_ty.data != .container) continue;
if (!expected_ty.data.container.scope_handle.eql(container.data.container.scope_handle)) continue;
Expand Down
2 changes: 1 addition & 1 deletion src/features/goto.zig
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ fn gotoDefinitionEnumLiteral(
return gotoDefinitionStructInit(analyser, handle, source_index, kind, offset_encoding);
};
const name = offsets.locToSlice(handle.tree.source, name_loc);
const decl = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
const decl, _ = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
return try gotoDefinitionSymbol(analyser, offsets.tokenToRange(&handle.tree, name_token, offset_encoding), decl, kind, offset_encoding);
}

Expand Down
16 changes: 13 additions & 3 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@ fn hoverSymbol(
arena: std.mem.Allocator,
param_decl_handle: Analyser.DeclWithHandle,
markup_kind: types.MarkupKind,
) Analyser.Error!?[]const u8 {
return try hoverSymbolWithType(analyser, arena, param_decl_handle, null, markup_kind);
}

fn hoverSymbolWithType(
analyser: *Analyser,
arena: std.mem.Allocator,
param_decl_handle: Analyser.DeclWithHandle,
type_maybe: ?Analyser.Type,
markup_kind: types.MarkupKind,
) Analyser.Error!?[]const u8 {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

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

var decl_handle: Analyser.DeclWithHandle = param_decl_handle;
var maybe_resolved_type = try param_decl_handle.resolveType(analyser);
var maybe_resolved_type = type_maybe orelse try param_decl_handle.resolveType(analyser);

while (true) {
if (try decl_handle.docComments(arena)) |doc_string| {
Expand Down Expand Up @@ -380,13 +390,13 @@ fn hoverDefinitionEnumLiteral(
return try hoverDefinitionStructInit(analyser, arena, handle, source_index, markup_kind, offset_encoding);
};
const name = offsets.locToSlice(handle.tree.source, name_loc);
const decl = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;
const decl, const type_maybe = (try analyser.getSymbolEnumLiteral(handle, source_index, name)) orelse return null;

return .{
.contents = .{
.markup_content = .{
.kind = markup_kind,
.value = (try hoverSymbol(analyser, arena, decl, markup_kind)) orelse return null,
.value = (try hoverSymbolWithType(analyser, arena, decl, type_maybe, markup_kind)) orelse return null,
},
},
.range = offsets.tokenToRange(&handle.tree, name_token, offset_encoding),
Expand Down
4 changes: 2 additions & 2 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,8 @@ fn writeNodeInlayHint(
const name_token = tree.firstToken(value_node) - 2; // math our way two token indexes back to get the `name`
const name_loc = offsets.tokenToLoc(tree, name_token);
const name = offsets.locToSlice(tree.source, name_loc);
const decl = (try builder.analyser.getSymbolEnumLiteral(builder.handle, name_loc.start, name)) orelse continue;
const ty = try decl.resolveType(builder.analyser) orelse continue;
const decl, const type_maybe = (try builder.analyser.getSymbolEnumLiteral(builder.handle, name_loc.start, name)) orelse continue;
const ty = type_maybe orelse try decl.resolveType(builder.analyser) orelse continue;
const type_str = try ty.stringifyTypeOf(builder.analyser, .{ .truncate_container_decls = true });
if (type_str.len == 0) continue;
try appendTypeHintString(
Expand Down
9 changes: 6 additions & 3 deletions src/features/references.zig
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const Builder = struct {
else => unreachable,
};

const candidate = try builder.analyser.lookupSymbolFieldInit(
const candidate, _ = try builder.analyser.lookupSymbolFieldInit(
handle,
name,
nodes[0],
Expand All @@ -204,7 +204,7 @@ const Builder = struct {
const name_token = tree.nodeMainToken(node);
const name = offsets.identifierTokenToNameSlice(&handle.tree, name_token);
if (!std.mem.eql(u8, name, target_symbol_name)) return;
const candidate = try builder.analyser.getSymbolEnumLiteral(handle, tree.tokenStart(name_token), name) orelse return;
const candidate, _ = try builder.analyser.getSymbolEnumLiteral(handle, tree.tokenStart(name_token), name) orelse return;
break :candidate .{ candidate, name_token };
},
.global_var_decl,
Expand Down Expand Up @@ -728,7 +728,10 @@ pub fn referencesHandler(server: *Server, arena: std.mem.Allocator, request: Gen
break :z null;
},
.label_access, .label_decl => try Analyser.lookupLabel(handle, name, source_index),
.enum_literal => try analyser.getSymbolEnumLiteral(handle, source_index, name),
.enum_literal => blk: {
const decl, _ = try analyser.getSymbolEnumLiteral(handle, source_index, name) orelse break :blk null;
break :blk decl;
},
.keyword => null,
else => null,
} orelse return null;
Expand Down
4 changes: 2 additions & 2 deletions src/features/signature_help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,12 @@ pub fn getSignatureInfo(
var ty = switch (tree.tokenTag(expr_first_token)) {
.period => blk: { // decl literal
loc.start += 1;
const decl = try analyser.getSymbolEnumLiteral(
const decl, const type_maybe = try analyser.getSymbolEnumLiteral(
handle,
loc.start,
offsets.locToSlice(tree.source, loc),
) orelse continue;
break :blk try decl.resolveType(analyser) orelse continue;
break :blk type_maybe orelse try decl.resolveType(analyser) orelse continue;
},
else => try analyser.getFieldAccessType(handle, loc.start, loc) orelse continue,
};
Expand Down
6 changes: 6 additions & 0 deletions tests/analysis/meta.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const TagA = std.meta.Tag(TaggedUnionA);
const TagB = std.meta.Tag(TaggedUnionB);
// ^^^^ (type)(@typeInfo(TaggedUnionB).@"union".tag_type.?)

const tag_a: TagA = .foo;
// ^^^^ (EnumA)()

const tag_b: TagB = .fizz;
// ^^^^^ (@typeInfo(TaggedUnionB).@"union".tag_type.?)()

const ArgsTupleA = std.meta.ArgsTuple(fn (u8, i32) void);
// ^^^^^^^^^^ (type)(struct { u8, i32 })

Expand Down
6 changes: 5 additions & 1 deletion tests/analysis_check.zig
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ pub fn main(init: std.process.Init) Error!void {
const ty = blk: {
const decl_maybe = switch (ctx) {
.global => try analyser.lookupSymbolGlobal(handle, identifier, identifier_loc.start),
.enum_literal => try analyser.getSymbolEnumLiteral(handle, identifier_loc.start, identifier),
.enum_literal => decl: {
const decl, const type_maybe = try analyser.getSymbolEnumLiteral(handle, identifier_loc.start, identifier) orelse break :decl null;
if (type_maybe) |ty| break :blk ty;
break :decl decl;
},
.struct_init => break :blk try analyser.resolveStructInitType(handle, identifier_loc.start),
};

Expand Down
26 changes: 26 additions & 0 deletions tests/lsp_features/completion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1761,6 +1761,32 @@ test "tagged union" {
});
}

test "tagged union - tag" {
try testCompletion(
\\const std = @import("std");
\\const Ue = union(enum) {
\\ alpha,
\\ beta: []const u8,
\\};
\\const foo: std.meta.Tag(Ue) = .<cursor>
, &.{
.{ .label = "alpha", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
.{ .label = "beta", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
});
try testCompletion(
\\const std = @import("std");
\\const Ue = union(enum) {
\\ alpha,
\\ beta: []const u8,
\\};
\\const S = struct { foo: std.meta.Tag(Ue) };
\\const s = S{ .foo = .<cursor> };
, &.{
.{ .label = "alpha", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
.{ .label = "beta", .kind = .EnumMember, .detail = "@typeInfo(Ue).@\"union\".tag_type.?" },
});
}

test "switch cases" {
// Because current logic is to list all enums if all else fails,
// the following tests include an extra enum to ensure that we're not just 'getting lucky'
Expand Down
Loading