diff --git a/src/analysis.zig b/src/analysis.zig index 469037b9a..377604be1 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -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(); } @@ -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; @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -3008,11 +3031,11 @@ 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) { @@ -3020,7 +3043,9 @@ fn makeScopeAt( 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 }, + }); } } }, @@ -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); } @@ -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); } @@ -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", @@ -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 => { diff --git a/src/features/completions.zig b/src/features/completions.zig index 6f1987005..46533a839 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -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) @@ -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]) { @@ -367,6 +380,7 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: Analyser.Decl }); }, .pointer_payload, + .error_union_payload, .array_payload, .array_index, .switch_payload, diff --git a/src/features/hover.zig b/src/features/hover.zig index b2a072a3d..f5a1dc299 100644 --- a/src/features/hover.zig +++ b/src/features/hover.zig @@ -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, diff --git a/src/features/references.zig b/src/features/references.zig index 8465e7ed2..cba0d3d62 100644 --- a/src/features/references.zig +++ b/src/features/references.zig @@ -219,6 +219,7 @@ pub fn symbolReferences( switch (decl_handle.decl.*) { .ast_node, .pointer_payload, + .error_union_payload, .switch_payload, .array_payload, .array_index, diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 76dd8c1d6..cecd84e29 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -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. + \\} + , &.{ + .{ .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. + \\ } 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. + // \\ } else |err| { + // \\ _ = err; + // \\ } + // \\} + // , &.{ + // .{ .label = "alpha", .kind = .Field, .detail = "alpha: u32" }, + // }); } test "completion - struct" { @@ -331,7 +377,7 @@ test "completion - enum" { }); } -test "completion - error union" { +test "completion - error set" { try testCompletion( \\const E = error { \\ Foo, @@ -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. + , &.{ + .{ .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. + , &.{ + .{ .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 {}