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
69 changes: 49 additions & 20 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -529,16 +529,23 @@ fn resolveUnwrapOptionalType(analyser: *Analyser, opt: TypeWithHandle) !?TypeWit
return null;
}

fn resolveUnwrapErrorType(analyser: *Analyser, rhs: TypeWithHandle) !?TypeWithHandle {
fn resolveUnwrapErrorUnionType(analyser: *Analyser, rhs: TypeWithHandle, side: ErrorUnionSide) !?TypeWithHandle {
const rhs_node = switch (rhs.type.data) {
.other => |n| n,
.error_union => |t| return t.*,
.error_union => |t| return switch (side) {
.left => null,
.right => t.*,
},
.primitive, .slice, .pointer, .array_index, .@"comptime", .either => return null,
};

if (rhs.handle.tree.nodes.items(.tag)[rhs_node] == .error_union) {
const data = rhs.handle.tree.nodes.items(.data)[rhs_node];
return ((try analyser.resolveTypeOfNodeInternal(.{
.node = rhs.handle.tree.nodes.items(.data)[rhs_node].rhs,
.node = switch (side) {
.left => data.lhs,
.right => data.rhs,
},
.handle = rhs.handle,
})) orelse return null).instanceTypeVal();
}
Expand Down Expand Up @@ -910,8 +917,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
.unwrap_optional => try analyser.resolveUnwrapOptionalType(base_type),
.array_access => try analyser.resolveBracketAccessType(base_type, .Single),
.@"orelse" => try analyser.resolveUnwrapOptionalType(base_type),
.@"catch" => try analyser.resolveUnwrapErrorType(base_type),
.@"try" => try analyser.resolveUnwrapErrorType(base_type),
.@"catch" => try analyser.resolveUnwrapErrorUnionType(base_type, .right),
.@"try" => try analyser.resolveUnwrapErrorUnionType(base_type, .right),
.address_of => {
const base_type_ptr = try analyser.arena.allocator().create(TypeWithHandle);
base_type_ptr.* = base_type;
Expand Down Expand Up @@ -956,6 +963,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
.ptr_type_bit_range,
.error_union,
.error_set_decl,
.merge_error_sets,
.container_decl,
.container_decl_arg,
.container_decl_arg_trailing,
Expand Down Expand Up @@ -1986,6 +1994,8 @@ pub fn getPositionContext(
return if (tok.tag == .identifier) PositionContext{ .var_access = tok.loc } else .empty;
}

pub const ErrorUnionSide = enum { left, right };

pub const Declaration = union(enum) {
/// Index of the ast node
ast_node: Ast.Node.Index,
Expand All @@ -1999,6 +2009,11 @@ pub const Declaration = union(enum) {
name: Ast.TokenIndex,
condition: Ast.Node.Index,
},
error_union_payload: struct {
name: Ast.TokenIndex,
condition: ?Ast.Node.Index,
side: ErrorUnionSide,
},
array_payload: struct {
identifier: Ast.TokenIndex,
array_expr: Ast.Node.Index,
Expand Down Expand Up @@ -2037,6 +2052,7 @@ pub const DeclWithHandle = struct {
.ast_node => |n| getDeclNameToken(tree, n).?,
.param_payload => |pp| pp.param.name_token.?,
.pointer_payload => |pp| pp.name,
.error_union_payload => |ep| ep.name,
.array_payload => |ap| ap.identifier,
.array_index => |ai| ai,
.switch_payload => |sp| sp.node,
Expand Down Expand Up @@ -2146,6 +2162,13 @@ pub const DeclWithHandle = struct {
.handle = self.handle,
})) orelse return null,
),
.error_union_payload => |pay| try analyser.resolveUnwrapErrorUnionType(
(try analyser.resolveTypeOfNodeInternal(.{
.node = pay.condition orelse return null,
.handle = self.handle,
})) orelse return null,
pay.side,
),
.array_payload => |pay| try analyser.resolveBracketAccessType(
(try analyser.resolveTypeOfNodeInternal(.{
.node = pay.array_expr,
Expand Down Expand Up @@ -3008,19 +3031,21 @@ fn makeScopeAt(
std.debug.assert(token_tags[name_token] == .identifier);

const name = tree.tokenSlice(name_token);
try context.putDecl(
then_scope,
name,
.{ .pointer_payload = .{ .name = name_token, .condition = if_node.ast.cond_expr } },
);
const decl: Declaration = if (if_node.ast.else_expr != 0)
.{ .error_union_payload = .{ .name = name_token, .condition = if_node.ast.cond_expr, .side = .right } }
else
.{ .pointer_payload = .{ .name = name_token, .condition = if_node.ast.cond_expr } };
try context.putDecl(then_scope, name, decl);
}

if (if_node.ast.else_expr != 0) {
const else_start = if_node.error_token orelse tree.firstToken(if_node.ast.else_expr);
const else_scope = (try makeBlockScopeAt(context, tree, if_node.ast.else_expr, else_start)).?;
if (if_node.error_token) |err_token| {
const name = tree.tokenSlice(err_token);
try context.putDecl(else_scope, name, .{ .ast_node = if_node.ast.else_expr });
try context.putDecl(else_scope, name, .{
.error_union_payload = .{ .name = err_token, .condition = if_node.ast.cond_expr, .side = .left },
});
}
}
},
Expand All @@ -3034,7 +3059,9 @@ fn makeScopeAt(
{
const expr_scope = (try makeBlockScopeAt(context, tree, data[node_idx].rhs, catch_token)).?;
const name = tree.tokenSlice(catch_token);
try context.putDecl(expr_scope, name, .{ .ast_node = data[node_idx].rhs });
try context.putDecl(expr_scope, name, .{
.error_union_payload = .{ .name = catch_token, .condition = data[node_idx].lhs, .side = .left },
});
} else {
try makeScopeInternal(context, tree, data[node_idx].rhs);
}
Expand Down Expand Up @@ -3071,12 +3098,10 @@ fn makeScopeAt(
std.debug.assert(token_tags[name_token] == .identifier);

const name = tree.tokenSlice(name_token);
const decl: Declaration = .{
.pointer_payload = .{
.name = name_token,
.condition = while_node.ast.cond_expr,
},
};
const decl: Declaration = if (while_node.error_token != null)
.{ .error_union_payload = .{ .name = name_token, .condition = while_node.ast.cond_expr, .side = .right } }
else
.{ .pointer_payload = .{ .name = name_token, .condition = while_node.ast.cond_expr } };
if (cont_scope) |index| {
try context.putDecl(index, name, decl);
}
Expand All @@ -3086,7 +3111,9 @@ fn makeScopeAt(
if (while_node.error_token) |err_token| {
std.debug.assert(token_tags[err_token] == .identifier);
const name = tree.tokenSlice(err_token);
try context.putDecl(else_scope.?, name, .{ .ast_node = while_node.ast.else_expr });
try context.putDecl(else_scope.?, name, .{
.error_union_payload = .{ .name = err_token, .condition = while_node.ast.cond_expr, .side = .left },
});
}
},
.@"for",
Expand Down Expand Up @@ -3165,7 +3192,9 @@ fn makeScopeAt(

if (payload_token != 0) {
const name = tree.tokenSlice(payload_token);
try context.putDecl(expr_scope, name, .{ .ast_node = data[node_idx].rhs });
try context.putDecl(expr_scope, name, .{
.error_union_payload = .{ .name = payload_token, .condition = null, .side = .left },
});
}
},
else => {
Expand Down
14 changes: 14 additions & 0 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fn nodeToCompletion(
const handle = node_handle.handle;
const tree = handle.tree;
const node_tags = tree.nodes.items(.tag);
const datas = tree.nodes.items(.data);
const token_tags = tree.tokens.items(.tag);

const doc_kind: types.MarkupKind = if (server.client_capabilities.completion_doc_supports_md)
Expand Down Expand Up @@ -145,6 +146,18 @@ fn nodeToCompletion(
);
}

switch (node_tags[node]) {
.merge_error_sets => {
if (try server.analyser.resolveTypeOfNode(.{ .node = datas[node].lhs, .handle = handle })) |ty| {
try typeToCompletion(server, list, .{ .original = ty }, orig_handle, either_descriptor);
}
if (try server.analyser.resolveTypeOfNode(.{ .node = datas[node].rhs, .handle = handle })) |ty| {
try typeToCompletion(server, list, .{ .original = ty }, orig_handle, either_descriptor);
}
},
else => {},
}

if (is_type_val) return;

switch (node_tags[node]) {
Expand Down Expand Up @@ -367,6 +380,7 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: Analyser.Decl
});
},
.pointer_payload,
.error_union_payload,
.array_payload,
.array_index,
.switch_payload,
Expand Down
1 change: 1 addition & 0 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup
break :def ast.paramSlice(tree, param);
},
.pointer_payload,
.error_union_payload,
.array_payload,
.array_index,
.switch_payload,
Expand Down
1 change: 1 addition & 0 deletions src/features/references.zig
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ pub fn symbolReferences(
switch (decl_handle.decl.*) {
.ast_node,
.pointer_payload,
.error_union_payload,
.switch_payload,
.array_payload,
.array_index,
Expand Down
86 changes: 85 additions & 1 deletion tests/lsp_features/completion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,52 @@ test "completion - captures" {
// , &.{
// .{ .label = "alpha", .kind = .Field, .detail = "alpha: u32" },
// });

try testCompletion(
\\const E = error{ X, Y };
\\const S = struct { alpha: u32 };
\\fn foo() E!S { return undefined; }
\\fn bar() void {
\\ const baz = foo() catch |err| {
\\ _ = err;
\\ return;
\\ };
\\ baz.<cursor>
\\}
, &.{
.{ .label = "alpha", .kind = .Field, .detail = "alpha: u32" },
});

try testCompletion(
\\const E = error{ X, Y };
\\const S = struct { alpha: u32 };
\\fn foo() E!S { return undefined; }
\\fn bar() void {
\\ if (foo()) |baz| {
\\ baz.<cursor>
\\ } else |err| {
\\ _ = err;
\\ }
\\}
, &.{
.{ .label = "alpha", .kind = .Field, .detail = "alpha: u32" },
});

// TODO fix error union capture with while loop
// try testCompletion(
// \\const E = error{ X, Y };
// \\const S = struct { alpha: u32 };
// \\fn foo() E!S { return undefined; }
// \\fn bar() void {
// \\ while (foo()) |baz| {
// \\ baz.<cursor>
// \\ } else |err| {
// \\ _ = err;
// \\ }
// \\}
// , &.{
// .{ .label = "alpha", .kind = .Field, .detail = "alpha: u32" },
// });
}

test "completion - struct" {
Expand Down Expand Up @@ -331,7 +377,7 @@ test "completion - enum" {
});
}

test "completion - error union" {
test "completion - error set" {
try testCompletion(
\\const E = error {
\\ Foo,
Expand All @@ -353,7 +399,45 @@ test "completion - error union" {
.{ .label = "foo", .kind = .Constant, .detail = "error.foo" },
.{ .label = "bar", .kind = .Constant, .detail = "error.bar" },
});
}

test "completion - merged error sets" {
try testCompletion(
\\const FirstSet = error{
\\ X,
\\ Y,
\\};
\\const SecondSet = error{
\\ Foo,
\\ Bar,
\\} || FirstSet;
\\const e = error.<cursor>
, &.{
.{ .label = "X", .kind = .Constant, .detail = "error.X" },
.{ .label = "Y", .kind = .Constant, .detail = "error.Y" },
.{ .label = "Foo", .kind = .Constant, .detail = "error.Foo" },
.{ .label = "Bar", .kind = .Constant, .detail = "error.Bar" },
});

try testCompletion(
\\const FirstSet = error{
\\ x,
\\ y,
\\};
\\const SecondSet = error{
\\ foo,
\\ bar,
\\} || FirstSet;
\\const e = SecondSet.<cursor>
, &.{
.{ .label = "x", .kind = .Constant, .detail = "error.x" },
.{ .label = "y", .kind = .Constant, .detail = "error.y" },
.{ .label = "foo", .kind = .Constant, .detail = "error.foo" },
.{ .label = "bar", .kind = .Constant, .detail = "error.bar" },
});
}

test "completion - error union" {
try testCompletion(
\\const S = struct { alpha: u32 };
\\fn foo() error{Foo}!S {}
Expand Down