Skip to content

Commit

Permalink
Merge pull request #1779 from zigtools/techatrix/error-union-represen…
Browse files Browse the repository at this point in the history
…tation

rework error union representation
  • Loading branch information
llogick authored Feb 18, 2024
2 parents 3d8c14d + 91b7e33 commit 1a7a1fa
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/DocumentScope.zig
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ noinline fn walkCatchNode(
try walkNode(context, tree, data[node_idx].lhs);

const catch_token = main_tokens[node_idx] + 2;
if (token_tags.len > catch_token and
if (catch_token < tree.tokens.len and
token_tags[catch_token - 1] == .pipe and
token_tags[catch_token] == .identifier)
{
Expand Down
163 changes: 79 additions & 84 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -749,12 +749,21 @@ pub fn resolveReturnType(analyser: *Analyser, fn_decl: Ast.full.FnProto, handle:
const ret = .{ .node = return_type, .handle = handle };
const child_type = (try analyser.resolveTypeOfNodeInternal(ret)) orelse
return null;
if (!child_type.is_type_val) return null;

if (ast.hasInferredError(tree, fn_decl)) {
const child_type_ptr = try analyser.arena.allocator().create(Type);
child_type_ptr.* = try child_type.instanceTypeVal(analyser) orelse return null;
return Type{ .data = .{ .error_union = child_type_ptr }, .is_type_val = false };
} else return try child_type.instanceTypeVal(analyser);
child_type_ptr.* = child_type;
return Type{
.data = .{ .error_union = .{
.error_set = null,
.payload = child_type_ptr,
} },
.is_type_val = false,
};
}

return try child_type.instanceTypeVal(analyser);
}

/// `optional.?`
Expand Down Expand Up @@ -789,32 +798,16 @@ pub fn resolveAddressOf(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type
return Type{ .data = .{ .pointer = .{ .size = .One, .is_const = false, .elem_ty = base_type_ptr } }, .is_type_val = false };
}

pub fn resolveUnwrapErrorUnionType(analyser: *Analyser, rhs: Type, side: ErrorUnionSide) error{OutOfMemory}!?Type {
const rhs_node_handle = switch (rhs.data) {
.other => |n| n,
.error_union => |t| return switch (side) {
.left => null,
.right => t.*,
pub const ErrorUnionSide = enum { error_set, payload };

pub fn resolveUnwrapErrorUnionType(analyser: *Analyser, ty: Type, side: ErrorUnionSide) error{OutOfMemory}!?Type {
return switch (ty.data) {
.error_union => |info| switch (side) {
.error_set => try (info.error_set orelse return null).instanceTypeVal(analyser),
.payload => try info.payload.instanceTypeVal(analyser),
},
else => return null,
};
const rhs_node = rhs_node_handle.node;
const rhs_handle = rhs_node_handle.handle;
const tree = rhs_handle.tree;
if (tree.nodes.items(.tag)[rhs_node] == .error_union) {
const data = tree.nodes.items(.data)[rhs_node];
const node_with_handle = NodeWithHandle{
.node = switch (side) {
.left => data.lhs,
.right => data.rhs,
},
.handle = rhs_handle,
};
const ty = try analyser.resolveTypeOfNodeInternal(node_with_handle) orelse return null;
return try ty.instanceTypeVal(analyser);
}

return null;
}

fn resolveTaggedUnionFieldType(analyser: *Analyser, ty: Type, symbol: []const u8) error{OutOfMemory}!?Type {
Expand Down Expand Up @@ -1460,8 +1453,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
.unwrap_optional => try analyser.resolveOptionalUnwrap(base_type),
.array_access => try analyser.resolveBracketAccessType(base_type, .Single),
.@"orelse" => try analyser.resolveOptionalUnwrap(base_type),
.@"catch" => try analyser.resolveUnwrapErrorUnionType(base_type, .right),
.@"try" => try analyser.resolveUnwrapErrorUnionType(base_type, .right),
.@"catch" => try analyser.resolveUnwrapErrorUnionType(base_type, .payload),
.@"try" => try analyser.resolveUnwrapErrorUnionType(base_type, .payload),
.address_of => try analyser.resolveAddressOf(base_type),
else => unreachable,
};
Expand Down Expand Up @@ -1580,8 +1573,29 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
.is_type_val = false,
};
},
.error_union => {
const error_set = try analyser.resolveTypeOfNodeInternal(.{ .node = datas[node].lhs, .handle = handle }) orelse return null;
if (!error_set.is_type_val) return null;

const payload = try analyser.resolveTypeOfNodeInternal(.{ .node = datas[node].rhs, .handle = handle }) orelse return null;
if (!payload.is_type_val) return null;

const error_set_ptr = try analyser.arena.allocator().create(Type);
error_set_ptr.* = error_set;

const payload_ptr = try analyser.arena.allocator().create(Type);
payload_ptr.* = payload;

return Type{
.data = .{ .error_union = .{
.error_set = error_set_ptr,
.payload = payload_ptr,
} },
.is_type_val = true,
};
},

.anyframe_type,
.error_union,
.error_set_decl,
.merge_error_sets,
.container_decl,
Expand Down Expand Up @@ -2117,8 +2131,12 @@ pub const Type = struct {
/// ?T
optional: *Type,

/// Return type of `fn foo() !Foo`
error_union: *Type,
/// `error_set!payload`
error_union: struct {
/// `null` if inferred error
error_set: ?*Type,
payload: *Type,
},

/// `Foo` in `Foo.bar` where `Foo = union(enum) { bar }`
union_tag: *Type,
Expand Down Expand Up @@ -2175,10 +2193,13 @@ pub const Type = struct {
std.hash.autoHash(hasher, info.sentinel);
info.elem_ty.hashWithHasher(hasher);
},
.optional,
.error_union,
.union_tag,
=> |t| t.hashWithHasher(hasher),
.optional, .union_tag => |t| t.hashWithHasher(hasher),
.error_union => |info| {
if (info.error_set) |error_set| {
error_set.hashWithHasher(hasher);
}
info.payload.hashWithHasher(hasher);
},
.other, .compile_error => |node_handle| {
std.hash.autoHash(hasher, node_handle.node);
hasher.update(node_handle.handle.uri);
Expand Down Expand Up @@ -2213,12 +2234,19 @@ pub const Type = struct {
if (!a_type.elem_ty.eql(b_type.elem_ty.*)) return false;
},
inline .optional,
.error_union,
.union_tag,
=> |a_type, name| {
const b_type = @field(b.data, @tagName(name));
if (!a_type.eql(b_type.*)) return false;
},
.error_union => |info| {
const b_info = b.data.error_union;
if (!info.payload.eql(b_info.payload.*)) return false;
if ((info.error_set == null) != (b_info.error_set == null)) return false;
if (info.error_set) |a_error_set| {
if (!a_error_set.eql(b_info.error_set.?.*)) return false;
}
},
.other => |a_node_handle| return a_node_handle.eql(b.data.other),
.compile_error => |a_node_handle| return a_node_handle.eql(b.data.compile_error),
.either => |a_entries| {
Expand Down Expand Up @@ -2566,7 +2594,12 @@ pub const Type = struct {
try writer.print("{}", .{info.elem_ty.fmtTypeVal(analyser)});
},
.optional => |child_ty| try writer.print("?{}", .{child_ty.fmtTypeVal(analyser)}),
.error_union => |t| try writer.print("!{}", .{t.fmtTypeVal(analyser)}),
.error_union => |info| {
if (info.error_set) |error_set| {
try writer.print("{}", .{error_set.fmtTypeVal(analyser)});
}
try writer.print("!{}", .{info.payload.fmtTypeVal(analyser)});
},
.union_tag => |t| try writer.print("@typeInfo({}).Union.tag_type.?", .{t.fmtTypeVal(analyser)}),
.other => |node_handle| switch (node_handle.handle.tree.nodes.items(.tag)[node_handle.node]) {
.root => {
Expand Down Expand Up @@ -3205,8 +3238,6 @@ pub const TokenWithHandle = struct {
handle: *DocumentStore.Handle,
};

pub const ErrorUnionSide = enum { left, right };

pub const Declaration = union(enum) {
/// Index of the ast node
ast_node: Ast.Node.Index,
Expand Down Expand Up @@ -3547,14 +3578,14 @@ pub const DeclWithHandle = struct {
.node = pay.condition,
.handle = self.handle,
})) orelse return null,
.right,
.payload,
),
.error_union_error => |pay| try analyser.resolveUnwrapErrorUnionType(
(try analyser.resolveTypeOfNodeInternal(.{
.node = if (pay.condition == 0) return null else pay.condition,
.handle = self.handle,
})) orelse return null,
.left,
.error_set,
),
.array_payload => |pay| try analyser.resolveBracketAccessType(
(try analyser.resolveTypeOfNodeInternal(.{
Expand Down Expand Up @@ -4000,7 +4031,7 @@ pub fn lookupSymbolFieldInit(
nodes[1..],
)) orelse return null;

if (try analyser.resolveUnwrapErrorUnionType(container_type, .right)) |unwrapped|
if (try analyser.resolveUnwrapErrorUnionType(container_type, .payload)) |unwrapped|
container_type = unwrapped;

if (try analyser.resolveOptionalUnwrap(container_type)) |unwrapped|
Expand Down Expand Up @@ -4520,7 +4551,12 @@ fn addReferencedTypes(
.pointer => |info| try analyser.addReferencedTypes(info.elem_ty.*, ReferencedType.Collector.init(referenced_types)),
.array => |info| try analyser.addReferencedTypes(info.elem_ty.*, ReferencedType.Collector.init(referenced_types)),
.optional => |child_ty| try analyser.addReferencedTypes(child_ty.*, ReferencedType.Collector.init(referenced_types)),
.error_union => |t| try analyser.addReferencedTypes(t.*, ReferencedType.Collector.init(referenced_types)),
.error_union => |info| {
if (info.error_set) |error_set| {
try analyser.addReferencedTypes(error_set.*, ReferencedType.Collector.init(referenced_types));
}
try analyser.addReferencedTypes(info.payload.*, ReferencedType.Collector.init(referenced_types));
},
.union_tag => |t| try analyser.addReferencedTypes(t.*, ReferencedType.Collector.init(referenced_types)),

.other => |node_handle| switch (node_handle.handle.tree.nodes.items(.tag)[node_handle.node]) {
Expand Down Expand Up @@ -4615,47 +4651,6 @@ fn addReferencedTypes(
referenced_types,
);
},

.array_type,
.array_type_sentinel,
=> unreachable,

.ptr_type,
.ptr_type_aligned,
.ptr_type_bit_range,
.ptr_type_sentinel,
=> unreachable,

.optional_type => unreachable,

.error_union => {
const node = node_handle.node;
const handle = node_handle.handle;
const tree = handle.tree;

try analyser.addReferencedTypesFromNode(
.{ .node = tree.nodes.items(.data)[node].lhs, .handle = handle },
referenced_types,
);

try analyser.addReferencedTypesFromNode(
.{ .node = tree.nodes.items(.data)[node].rhs, .handle = handle },
referenced_types,
);
},

.multiline_string_literal => unreachable,

.string_literal => unreachable,

.number_literal, .char_literal => unreachable,

.enum_literal => unreachable,

.error_value => unreachable,

.identifier => {},

else => {}, // TODO: Implement more "other" type expressions; better safe than sorry
},

Expand Down
6 changes: 3 additions & 3 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ fn collectVarAccessContainerNodes(
const has_body = fn_proto_handle.tree.nodes.items(.tag)[fn_proto_node] == .fn_decl;
const body = fn_proto_handle.tree.nodes.items(.data)[fn_proto_node].rhs;
var node_type = try analyser.resolveReturnType(full_fn_proto, fn_proto_handle, if (has_body) body else null) orelse return;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .right)) |unwrapped| node_type = unwrapped;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
return;
}
Expand Down Expand Up @@ -1360,7 +1360,7 @@ fn collectFieldAccessContainerNodes(
const name_loc = Analyser.identifierLocFromPosition(loc.end, handle) orelse {
const result = try analyser.getFieldAccessType(handle, loc.end, loc) orelse return;
const container = try analyser.resolveDerefType(result) orelse result;
if (try analyser.resolveUnwrapErrorUnionType(container, .right)) |unwrapped| {
if (try analyser.resolveUnwrapErrorUnionType(container, .payload)) |unwrapped| {
if (unwrapped.isEnumType() or unwrapped.isUnionType()) {
try types_with_handles.append(arena, unwrapped);
return;
Expand Down Expand Up @@ -1388,7 +1388,7 @@ fn collectFieldAccessContainerNodes(
const has_body = fn_proto_handle.tree.nodes.items(.tag)[fn_proto_node] == .fn_decl;
const body = fn_proto_handle.tree.nodes.items(.data)[fn_proto_node].rhs;
node_type = try analyser.resolveReturnType(full_fn_proto, fn_proto_handle, if (has_body) body else null) orelse continue;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .right)) |unwrapped| node_type = unwrapped;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
continue;
}
Expand Down
12 changes: 12 additions & 0 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ fn writeNodeInlayHint(
) error{OutOfMemory}!void {
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);

const tag = node_tags[node];

Expand Down Expand Up @@ -426,6 +427,17 @@ fn writeNodeInlayHint(
const full_case = builder.handle.tree.fullSwitchCase(node).?;
if (full_case.payload_token) |token| try inferAppendTypeStr(builder, token);
},
.@"catch" => {
if (!builder.config.inlay_hints_show_variable_type_hints) return;

const catch_token = main_tokens[node] + 2;
if (catch_token < tree.tokens.len and
token_tags[catch_token - 1] == .pipe and
token_tags[catch_token] == .identifier)
{
try inferAppendTypeStr(builder, catch_token);
}
},
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
Expand Down
11 changes: 11 additions & 0 deletions tests/lsp_features/completion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,17 @@ test "completion - error union" {
.{ .label = "alpha", .kind = .Field, .detail = "u32" },
});

try testCompletion(
\\const S = struct { alpha: u32 };
\\fn foo() !S {}
\\fn bar() !void {
\\ const baz = try foo();
\\ baz.<cursor>
\\}
, &.{
.{ .label = "alpha", .kind = .Field, .detail = "u32" },
});

// try testCompletion(
// \\const S = struct { alpha: u32 };
// \\fn foo() error{Foo}!S {}
Expand Down
Loading

0 comments on commit 1a7a1fa

Please sign in to comment.