diff --git a/src/analysis.zig b/src/analysis.zig index 60f68acd4..1d171098f 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2876,8 +2876,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), @@ -4359,6 +4359,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) { @@ -6383,7 +6384,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, @@ -6405,18 +6406,27 @@ 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, } @@ -6424,8 +6434,10 @@ pub fn lookupSymbolFieldInit( 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; } @@ -6468,8 +6480,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); } } }, @@ -6611,8 +6624,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; @@ -6872,7 +6885,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(); diff --git a/src/features/completions.zig b/src/features/completions.zig index 3cdb6c1f8..41ee03463 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -1480,6 +1480,19 @@ 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, @@ -1487,8 +1500,13 @@ fn collectContainerFields( 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, }; @@ -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)); @@ -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) { @@ -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; diff --git a/src/features/goto.zig b/src/features/goto.zig index 953aa594f..d85bf5d8c 100644 --- a/src/features/goto.zig +++ b/src/features/goto.zig @@ -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); } diff --git a/src/features/hover.zig b/src/features/hover.zig index 19f512641..c3816a2ae 100644 --- a/src/features/hover.zig +++ b/src/features/hover.zig @@ -18,6 +18,16 @@ 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(); @@ -25,7 +35,7 @@ fn hoverSymbol( 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| { @@ -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), diff --git a/src/features/inlay_hints.zig b/src/features/inlay_hints.zig index c3afbd5f3..7677046b8 100644 --- a/src/features/inlay_hints.zig +++ b/src/features/inlay_hints.zig @@ -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( diff --git a/src/features/references.zig b/src/features/references.zig index 44542d5ab..2f804086b 100644 --- a/src/features/references.zig +++ b/src/features/references.zig @@ -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], @@ -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, @@ -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; diff --git a/src/features/signature_help.zig b/src/features/signature_help.zig index 7a5eeb264..d24cf3419 100644 --- a/src/features/signature_help.zig +++ b/src/features/signature_help.zig @@ -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, }; diff --git a/tests/analysis/meta.zig b/tests/analysis/meta.zig index 16dab1e0e..4833e633f 100644 --- a/tests/analysis/meta.zig +++ b/tests/analysis/meta.zig @@ -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 }) diff --git a/tests/analysis_check.zig b/tests/analysis_check.zig index 7339e32f5..7ada71e53 100644 --- a/tests/analysis_check.zig +++ b/tests/analysis_check.zig @@ -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), }; diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 2139ef12f..e1e7d221e 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -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) = . + , &.{ + .{ .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 = . }; + , &.{ + .{ .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'