Skip to content

Commit 3675825

Browse files
committed
Complete errors
1 parent faa3f27 commit 3675825

3 files changed

Lines changed: 166 additions & 2 deletions

File tree

src/analysis.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4754,7 +4754,7 @@ pub const PositionContext = union(enum) {
47544754
/// XXX: Internal use only, currently points to the loc of the first l_paren
47554755
parens_expr: offsets.Loc,
47564756
keyword: Ast.TokenIndex,
4757-
error_access,
4757+
error_access: offsets.Loc,
47584758
comment,
47594759
other,
47604760
empty,
@@ -5100,7 +5100,7 @@ pub fn getPositionContext(
51005100
stack.pop(curr_ctx.scope == .braces);
51015101
continue;
51025102
},
5103-
.keyword_error => .error_access,
5103+
.keyword_error => .{ .error_access = tok.loc },
51045104
.number_literal => {
51055105
if (tok.loc.start <= source_index and tok.loc.end >= source_index) {
51065106
return .{ .number_literal = tok.loc };

src/features/completions.zig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,50 @@ fn completeDot(builder: *Builder, loc: offsets.Loc) Analyser.Error!void {
731731
}
732732
}
733733

734+
fn completeError(builder: *Builder, loc: offsets.Loc) Analyser.Error!void {
735+
const tracy_zone = tracy.trace(@src());
736+
defer tracy_zone.end();
737+
738+
const tree = &builder.orig_handle.tree;
739+
740+
const err_token_index = offsets.sourceIndexToTokenIndex(tree, loc.start).pickPreferred(&.{.period}, tree) orelse return;
741+
if (err_token_index < 2) return;
742+
const dot_token_index = err_token_index + 1;
743+
744+
const nodes = try ast.nodesOverlappingIndexIncludingParseErrors(builder.arena, tree, loc.start);
745+
const dot_context = getSwitchOrStructInitContext(tree, dot_token_index, nodes) orelse return;
746+
const used_members_set = try collectUsedMembersSet(builder, dot_context.likely, dot_token_index);
747+
const containers = try collectContainerNodes(builder, builder.orig_handle, dot_context);
748+
for (containers) |container| {
749+
try collectErrorSetNames(builder, container, used_members_set);
750+
}
751+
}
752+
753+
fn collectErrorSetNames(
754+
builder: *Builder,
755+
container: Analyser.Type,
756+
omit_members: std.BufSet,
757+
) Analyser.Error!void {
758+
const ip = builder.analyser.ip;
759+
const key = switch (container.data) {
760+
.ip_index => |info| ip.indexToKey(info.type),
761+
else => return,
762+
};
763+
if (key != .error_set_type) return;
764+
const names = key.error_set_type.names;
765+
for (0..names.len) |idx| {
766+
const str = names.at(@intCast(idx), ip);
767+
const name = try std.fmt.allocPrint(builder.arena, "{f}", .{str.fmt(ip.io, &ip.string_pool)});
768+
if (omit_members.contains(name)) continue;
769+
try builder.completions.append(builder.arena, .{
770+
.label = try std.fmt.allocPrint(builder.arena, "error.{s}", .{name}),
771+
.filterText = name,
772+
.insertText = name,
773+
.kind = .Constant,
774+
});
775+
}
776+
}
777+
734778
/// Asserts that `pos_context` is one of the following:
735779
/// - `.import_string_literal`
736780
/// - `.cinclude_string_literal`
@@ -953,6 +997,7 @@ pub fn completionAtIndex(
953997
.var_access, .empty => try completeGlobal(&builder),
954998
.field_access => |loc| try completeFieldAccess(&builder, loc),
955999
.enum_literal => |loc| try completeDot(&builder, loc),
1000+
.error_access => |loc| try completeError(&builder, loc),
9561001
.label_access, .label_decl => try completeLabel(&builder),
9571002
.import_string_literal,
9581003
.cinclude_string_literal,

tests/lsp_features/completion.zig

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,125 @@ test "switch cases" {
19251925
});
19261926
}
19271927

1928+
test "switch on error set - all values are suggested" {
1929+
try testCompletion(
1930+
\\const err: error{E1, E2} = undefined;
1931+
\\switch(err) {
1932+
\\ error.<cursor>
1933+
\\}
1934+
, &.{
1935+
.{ .label = "error.E1", .kind = .Constant },
1936+
.{ .label = "error.E2", .kind = .Constant },
1937+
});
1938+
}
1939+
1940+
test "switch on error set - already used values are not suggested" {
1941+
try testCompletion(
1942+
\\const err: error{E1, E2} = undefined;
1943+
\\switch(err) {
1944+
\\ error.E1 => {},
1945+
\\ error.<cursor>
1946+
\\}
1947+
, &.{
1948+
.{ .label = "error.E2", .kind = .Constant },
1949+
});
1950+
}
1951+
1952+
test "switch on error set - error unions get all values" {
1953+
try testCompletion(
1954+
\\const Err2 = error{F1, F2};
1955+
\\const Err = error{E1, E2} || Err2;
1956+
\\const err: Err = undefined;
1957+
\\switch(err) {
1958+
\\ error.<cursor>
1959+
\\}
1960+
, &.{
1961+
.{ .label = "error.E1", .kind = .Constant },
1962+
.{ .label = "error.E2", .kind = .Constant },
1963+
.{ .label = "error.F1", .kind = .Constant },
1964+
.{ .label = "error.F2", .kind = .Constant },
1965+
});
1966+
}
1967+
1968+
test "switch on error set - text edits result" {
1969+
try testCompletionTextEdit(.{
1970+
.source =
1971+
\\const err: error{E1, E2} = undefined;
1972+
\\switch(err) {
1973+
\\ error.<cursor>
1974+
\\}
1975+
,
1976+
.label = "error.E1",
1977+
.expected_insert_line = " error.E1",
1978+
.expected_replace_line = " error.E1",
1979+
.enable_snippets = false,
1980+
});
1981+
}
1982+
test "switch on error set - insert/replace text edits" {
1983+
try testCompletionTextEdit(.{
1984+
.source =
1985+
\\const err: error{Err1, Err2} = undefined;
1986+
\\switch(err) {
1987+
\\ error.E<cursor>0
1988+
\\}
1989+
,
1990+
.label = "error.Err1",
1991+
.expected_insert_line = " error.Err10",
1992+
.expected_replace_line = " error.Err1",
1993+
.enable_snippets = false,
1994+
});
1995+
}
1996+
1997+
test "switch on error set - completion inside catch block works" {
1998+
try testCompletion(
1999+
\\fn idk() error{ E1, E2 }!void {}
2000+
\\test {
2001+
\\ idk() catch |err| {
2002+
\\ switch (err) {
2003+
\\ error.<cursor>
2004+
\\ }
2005+
\\ };
2006+
\\}
2007+
, &.{
2008+
.{ .label = "error.E1", .kind = .Constant },
2009+
.{ .label = "error.E2", .kind = .Constant },
2010+
});
2011+
}
2012+
2013+
test "switch on error set - completion inside catch statement" {
2014+
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
2015+
try testCompletion(
2016+
\\fn idk() error{ E1, E2 }!void {}
2017+
\\test {
2018+
\\ idk() catch |err| switch (err) {
2019+
\\ error.<cursor>
2020+
\\ };
2021+
\\}
2022+
, &.{
2023+
.{ .label = "error.E1", .kind = .Constant },
2024+
.{ .label = "error.E2", .kind = .Constant },
2025+
});
2026+
}
2027+
2028+
test "switch on error set - Works in a function of a container" {
2029+
if (true) return error.SkipZigTest; // TODO un-skip after https://github.com/zigtools/zls/issues/1535
2030+
try testCompletion(
2031+
\\fn idk() error{ E1, E2 }!void {}
2032+
\\pub const Manager = struct {
2033+
\\ pub fn testIt() void {
2034+
\\ _ = idk() catch |err| {
2035+
\\ switch(err) {
2036+
\\ error.<cursor>
2037+
\\ }
2038+
\\ };
2039+
\\ }
2040+
\\};
2041+
, &.{
2042+
.{ .label = "error.E1", .kind = .Constant },
2043+
.{ .label = "error.E2", .kind = .Constant },
2044+
});
2045+
}
2046+
19282047
test "error set" {
19292048
try testCompletion(
19302049
\\const E = error {

0 commit comments

Comments
 (0)