Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4754,7 +4754,7 @@ pub const PositionContext = union(enum) {
/// XXX: Internal use only, currently points to the loc of the first l_paren
parens_expr: offsets.Loc,
keyword: Ast.TokenIndex,
error_access,
error_access: offsets.Loc,
comment,
other,
empty,
Expand Down Expand Up @@ -5100,7 +5100,7 @@ pub fn getPositionContext(
stack.pop(curr_ctx.scope == .braces);
continue;
},
.keyword_error => .error_access,
.keyword_error => .{ .error_access = tok.loc },
.number_literal => {
if (tok.loc.start <= source_index and tok.loc.end >= source_index) {
return .{ .number_literal = tok.loc };
Expand Down
45 changes: 45 additions & 0 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,50 @@ fn completeDot(builder: *Builder, loc: offsets.Loc) Analyser.Error!void {
}
}

fn completeError(builder: *Builder, loc: offsets.Loc) Analyser.Error!void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

const tree = &builder.orig_handle.tree;

const err_token_index = offsets.sourceIndexToTokenIndex(tree, loc.start).pickPreferred(&.{.period}, tree) orelse return;
if (err_token_index < 2) return;
const dot_token_index = err_token_index + 1;

const nodes = try ast.nodesOverlappingIndexIncludingParseErrors(builder.arena, tree, loc.start);
const dot_context = getSwitchOrStructInitContext(tree, dot_token_index, nodes) orelse return;
const used_members_set = try collectUsedMembersSet(builder, dot_context.likely, dot_token_index);
const containers = try collectContainerNodes(builder, builder.orig_handle, dot_context);
for (containers) |container| {
try collectErrorSetNames(builder, container, used_members_set);
}
}

fn collectErrorSetNames(
builder: *Builder,
container: Analyser.Type,
omit_members: std.BufSet,
) Analyser.Error!void {
const ip = builder.analyser.ip;
const key = switch (container.data) {
.ip_index => |info| ip.indexToKey(info.type),
else => return,
};
if (key != .error_set_type) return;
const names = key.error_set_type.names;
for (0..names.len) |idx| {
const str = names.at(@intCast(idx), ip);
const name = try std.fmt.allocPrint(builder.arena, "{f}", .{str.fmt(ip.io, &ip.string_pool)});
if (omit_members.contains(name)) continue;
try builder.completions.append(builder.arena, .{
.label = try std.fmt.allocPrint(builder.arena, "error.{s}", .{name}),
.filterText = name,
.insertText = name,
.kind = .Constant,
});
}
}

/// Asserts that `pos_context` is one of the following:
/// - `.import_string_literal`
/// - `.cinclude_string_literal`
Expand Down Expand Up @@ -953,6 +997,7 @@ pub fn completionAtIndex(
.var_access, .empty => try completeGlobal(&builder),
.field_access => |loc| try completeFieldAccess(&builder, loc),
.enum_literal => |loc| try completeDot(&builder, loc),
.error_access => |loc| try completeError(&builder, loc),
.label_access, .label_decl => try completeLabel(&builder),
.import_string_literal,
.cinclude_string_literal,
Expand Down
119 changes: 119 additions & 0 deletions tests/lsp_features/completion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,125 @@ test "switch cases" {
});
}

test "switch on error set - all values are suggested" {
try testCompletion(
\\const err: error{E1, E2} = undefined;
\\switch(err) {
\\ error.<cursor>
\\}
, &.{
.{ .label = "error.E1", .kind = .Constant },
.{ .label = "error.E2", .kind = .Constant },
});
}

test "switch on error set - already used values are not suggested" {
try testCompletion(
\\const err: error{E1, E2} = undefined;
\\switch(err) {
\\ error.E1 => {},
\\ error.<cursor>
\\}
, &.{
.{ .label = "error.E2", .kind = .Constant },
});
}

test "switch on error set - error unions get all values" {
try testCompletion(
\\const Err2 = error{F1, F2};
\\const Err = error{E1, E2} || Err2;
\\const err: Err = undefined;
\\switch(err) {
\\ error.<cursor>
\\}
, &.{
.{ .label = "error.E1", .kind = .Constant },
.{ .label = "error.E2", .kind = .Constant },
.{ .label = "error.F1", .kind = .Constant },
.{ .label = "error.F2", .kind = .Constant },
});
}

test "switch on error set - text edits result" {
try testCompletionTextEdit(.{
.source =
\\const err: error{E1, E2} = undefined;
\\switch(err) {
\\ error.<cursor>
\\}
,
.label = "error.E1",
.expected_insert_line = " error.E1",
.expected_replace_line = " error.E1",
.enable_snippets = false,
});
}
test "switch on error set - insert/replace text edits" {
try testCompletionTextEdit(.{
.source =
\\const err: error{Err1, Err2} = undefined;
\\switch(err) {
\\ error.E<cursor>0
\\}
,
.label = "error.Err1",
.expected_insert_line = " error.Err10",
.expected_replace_line = " error.Err1",
.enable_snippets = false,
});
}

test "switch on error set - completion inside catch block works" {
try testCompletion(
\\fn idk() error{ E1, E2 }!void {}
\\test {
\\ idk() catch |err| {
\\ switch (err) {
\\ error.<cursor>
\\ }
\\ };
\\}
, &.{
.{ .label = "error.E1", .kind = .Constant },
.{ .label = "error.E2", .kind = .Constant },
});
}

test "switch on error set - completion inside catch statement" {
if (true) return error.SkipZigTest; // TODO un-skip after https://github.com/zigtools/zls/issues/2341 and/or https://github.com/zigtools/zls/issues/1112
try testCompletion(
\\fn idk() error{ E1, E2 }!void {}
\\test {
\\ idk() catch |err| switch (err) {
\\ error.<cursor>
\\ };
\\}
, &.{
.{ .label = "error.E1", .kind = .Constant },
.{ .label = "error.E2", .kind = .Constant },
});
}

test "switch on error set - Works in a function of a container" {
if (true) return error.SkipZigTest; // TODO un-skip after https://github.com/zigtools/zls/issues/1535
try testCompletion(
\\fn idk() error{ E1, E2 }!void {}
\\pub const Manager = struct {
\\ pub fn testIt() void {
\\ _ = idk() catch |err| {
\\ switch(err) {
\\ error.<cursor>
\\ }
\\ };
\\ }
\\};
, &.{
.{ .label = "error.E1", .kind = .Constant },
.{ .label = "error.E2", .kind = .Constant },
});
}

test "error set" {
try testCompletion(
\\const E = error {
Expand Down