Skip to content

Commit

Permalink
refactor some function analysis code
Browse files Browse the repository at this point in the history
  • Loading branch information
Techatrix committed Dec 22, 2024
1 parent 25ac14e commit 015f619
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 112 deletions.
80 changes: 32 additions & 48 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -811,28 +811,36 @@ fn findReturnStatement(tree: Ast, body: Ast.Node.Index) ?Ast.Node.Index {
return findReturnStatementInternal(tree, body, &already_found);
}

pub fn resolveReturnType(analyser: *Analyser, fn_decl: Ast.full.FnProto, handle: *DocumentStore.Handle, fn_body: ?Ast.Node.Index) error{OutOfMemory}!?Type {
const tree = handle.tree;
if (isTypeFunction(tree, fn_decl) and fn_body != null) {
pub fn resolveReturnType(analyser: *Analyser, func_type: Type) error{OutOfMemory}!?Type {
const func_node_handle = func_type.data.other; // this assumes that function types can only be Ast nodes
const tree = func_node_handle.handle.tree;
const func_node = func_node_handle.node;

var buf: [1]Ast.Node.Index = undefined;
const fn_proto = tree.fullFnProto(&buf, func_node).?;
const has_body = tree.nodes.items(.tag)[func_node] == .fn_decl;

if (isTypeFunction(tree, fn_proto) and has_body) {
const body = tree.nodes.items(.data)[func_node].rhs;
// If this is a type function and it only contains a single return statement that returns
// a container declaration, we will return that declaration.
const ret = findReturnStatement(tree, fn_body.?) orelse return null;
const ret = findReturnStatement(tree, body) orelse return null;
const data = tree.nodes.items(.data)[ret];
if (data.lhs != 0) {
return try analyser.resolveTypeOfNodeInternal(.{ .node = data.lhs, .handle = handle });
return try analyser.resolveTypeOfNodeInternal(.{ .node = data.lhs, .handle = func_node_handle.handle });
}

return null;
}

if (fn_decl.ast.return_type == 0) return null;
const return_type = fn_decl.ast.return_type;
const ret: NodeWithHandle = .{ .node = return_type, .handle = handle };
if (fn_proto.ast.return_type == 0) return null;
const return_type = fn_proto.ast.return_type;
const ret: NodeWithHandle = .{ .node = return_type, .handle = func_node_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)) {
if (ast.hasInferredError(tree, fn_proto)) {
const child_type_ptr = try analyser.arena.allocator().create(Type);
child_type_ptr.* = child_type;
return Type{
Expand Down Expand Up @@ -1542,11 +1550,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
}, argument_type);
}

const has_body = func_tree.nodes.items(.tag)[func_node] == .fn_decl;
const body = func_tree.nodes.items(.data)[func_node].rhs;
if (try analyser.resolveReturnType(fn_proto, func_handle, if (has_body) body else null)) |ret| {
return ret;
}
return try analyser.resolveReturnType(func_ty);
},
.container_field,
.container_field_init,
Expand Down Expand Up @@ -2646,6 +2650,10 @@ pub const Type = struct {
}
}

pub fn isContainerType(self: Type) bool {
return self.data == .container;
}

fn isContainerKind(self: Type, container_kind_tok: std.zig.Token.Tag) bool {
const scope_handle = switch (self.data) {
.container => |s| s,
Expand Down Expand Up @@ -3231,22 +3239,9 @@ pub fn getFieldAccessType(

// Can't call a function type, we need a function type instance.
if (current_type.?.is_type_val) return null;
// this assumes that function types can only be Ast nodes
const current_type_node_handle = ty.data.other;
const current_type_node = current_type_node_handle.node;
const current_type_handle = current_type_node_handle.handle;

const cur_tree = current_type_handle.tree;
var buf: [1]Ast.Node.Index = undefined;
const func = cur_tree.fullFnProto(&buf, current_type_node).?;
// Check if the function has a body and if so, pass it
// so the type can be resolved if it's a generic function returning
// an anonymous struct
const has_body = cur_tree.nodes.items(.tag)[current_type_node] == .fn_decl;
const body = cur_tree.nodes.items(.data)[current_type_node].rhs;

// TODO Actually bind params here when calling functions instead of just skipping args.
current_type = try analyser.resolveReturnType(func, current_type_handle, if (has_body) body else null) orelse return null;
current_type = try analyser.resolveReturnType(ty) orelse return null;

if (do_unwrap_error_payload) {
if (try analyser.resolveUnwrapErrorUnionType(current_type.?, .payload)) |unwrapped| current_type = unwrapped;
Expand Down Expand Up @@ -4689,27 +4684,16 @@ pub fn resolveExpressionTypeFromAncestors(
if (fn_type.is_type_val) return null;

const fn_node_handle = fn_type.data.other; // this assumes that function types can only be Ast nodes
const fn_node = fn_node_handle.node;
const fn_handle = fn_node_handle.handle;
const fn_tree = fn_handle.tree;

var fn_buf: [1]Ast.Node.Index = undefined;
const fn_proto = fn_tree.fullFnProto(&fn_buf, fn_node).?;

var param_iter = fn_proto.iterate(&fn_tree);
if (try analyser.isInstanceCall(handle, call, fn_type)) {
_ = ast.nextFnParam(&param_iter);
}
const param_decl: Declaration.Param = .{
.param_index = @truncate(arg_index + @intFromBool(try analyser.hasSelfParam(fn_type))),
.func = fn_node_handle.node,
};
const param = param_decl.get(fn_node_handle.handle.tree) orelse return null;

var param_index: usize = 0;
while (ast.nextFnParam(&param_iter)) |param| : (param_index += 1) {
if (param_index == arg_index) {
return try analyser.resolveTypeOfNode(.{
.node = param.type_expr,
.handle = fn_handle,
});
}
}
return try analyser.resolveTypeOfNode(.{
.node = param.type_expr,
.handle = fn_node_handle.handle,
});
},
.assign => {
if (node == datas[ancestors[0]].rhs) {
Expand Down
120 changes: 56 additions & 64 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1467,30 +1467,25 @@ fn collectVarAccessContainerNodes(
const symbol_decl = try analyser.lookupSymbolGlobal(handle, handle.tree.source[loc.start..loc.end], loc.end) orelse return;
const result = try symbol_decl.resolveType(analyser) orelse return;
const type_expr = try analyser.resolveDerefType(result) orelse result;
if (type_expr.isFunc()) {
const fn_proto_node_handle = type_expr.data.other; // this assumes that function types can only be Ast nodes
const fn_proto_node = fn_proto_node_handle.node;
const fn_proto_handle = fn_proto_node_handle.handle;
if (dot_context.likely == .enum_comparison or dot_context.need_ret_type) { // => we need f()'s return type
var buf: [1]Ast.Node.Index = undefined;
const full_fn_proto = fn_proto_handle.tree.fullFnProto(&buf, fn_proto_node).?;
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, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
return;
}
const fn_param_decl = Analyser.Declaration{ .function_parameter = .{
.func = fn_proto_node,
.param_index = @intCast(dot_context.fn_arg_index),
} };
const fn_param_decl_with_handle = Analyser.DeclWithHandle{ .decl = fn_param_decl, .handle = fn_proto_handle };
const param_type = try fn_param_decl_with_handle.resolveType(analyser) orelse return;
try types_with_handles.append(arena, param_type);
if (!type_expr.isFunc()) {
try type_expr.getAllTypesWithHandlesArrayList(arena, types_with_handles);
return;
}
try type_expr.getAllTypesWithHandlesArrayList(arena, types_with_handles);

if (dot_context.likely == .enum_comparison or dot_context.need_ret_type) { // => we need f()'s return type
var node_type = try analyser.resolveReturnType(type_expr) orelse return;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
return;
}
const func_node_handle = type_expr.data.other; // this assumes that function types can only be Ast nodes
const fn_param_decl: Analyser.Declaration = .{ .function_parameter = .{
.func = func_node_handle.node,
.param_index = @intCast(dot_context.fn_arg_index),
} };
const fn_param_decl_with_handle = Analyser.DeclWithHandle{ .decl = fn_param_decl, .handle = func_node_handle.handle };
const param_type = try fn_param_decl_with_handle.resolveType(analyser) orelse return;
try types_with_handles.append(arena, param_type);
}

fn collectFieldAccessContainerNodes(
Expand Down Expand Up @@ -1528,50 +1523,47 @@ fn collectFieldAccessContainerNodes(
if (dot_context.likely == .enum_assignment or dot_context.likely == .struct_field) {
if (try analyser.resolveOptionalUnwrap(node_type)) |unwrapped| node_type = unwrapped;
}
if (node_type.isFunc()) {
const fn_proto_node_handle = node_type.data.other; // this assumes that function types can only be Ast nodes
const fn_proto_node = fn_proto_node_handle.node;
const fn_proto_handle = fn_proto_node_handle.handle;
var buf: [1]Ast.Node.Index = undefined;
const full_fn_proto = fn_proto_handle.tree.fullFnProto(&buf, fn_proto_node).?;
if (dot_context.need_ret_type) { // => we need f()'s return type
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, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
continue;
}
var maybe_fn_param: ?Ast.full.FnProto.Param = undefined;
var fn_param_iter = full_fn_proto.iterate(&fn_proto_handle.tree);
// don't have the luxury of referencing an `Ast.full.Call`
// check if the first symbol is a `T` or an instance_of_T
const additional_index: usize = blk: {
// `loc` points to offsets within `handle`, not `node_type.decl.handle`
const field_access_slice = handle.tree.source[loc.start..loc.end];
if (field_access_slice[0] == '@') break :blk 1; // assume `@import("..").some.Other{.}`
var symbol_iter = std.mem.tokenizeScalar(u8, field_access_slice, '.');
const first_symbol = symbol_iter.next() orelse continue;
const symbol_decl = try analyser.lookupSymbolGlobal(handle, first_symbol, loc.start) orelse continue;
const symbol_type = try symbol_decl.resolveType(analyser) orelse continue;
if (!symbol_type.is_type_val) { // then => instance_of_T
if (try analyser.hasSelfParam(node_type)) break :blk 2;
}
break :blk 1; // is `T`, no SelfParam
};
for (dot_context.fn_arg_index + additional_index) |_| maybe_fn_param = ast.nextFnParam(&fn_param_iter);
const param = maybe_fn_param orelse continue;
if (param.type_expr == 0) continue;
const param_rcts = try collectContainerNodes(
builder,
fn_proto_handle,
offsets.nodeToLoc(fn_proto_handle.tree, param.type_expr).end,
dot_context,
);
for (param_rcts) |prct| try types_with_handles.append(arena, prct);
if (!node_type.isFunc()) {
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
continue;
}
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);

if (dot_context.need_ret_type) { // => we need f()'s return type
node_type = try analyser.resolveReturnType(node_type) orelse continue;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
continue;
}
// don't have the luxury of referencing an `Ast.full.Call`
// check if the first symbol is a `T` or an instance_of_T
const additional_index: usize = blk: {
// `loc` points to offsets within `handle`, not `node_type.decl.handle`
const field_access_slice = handle.tree.source[loc.start..loc.end];
if (field_access_slice[0] == '@') break :blk 0; // assume `@import("..").some.Other{.}`
var symbol_iter = std.mem.tokenizeScalar(u8, field_access_slice, '.');
const first_symbol = symbol_iter.next() orelse continue;
const symbol_decl = try analyser.lookupSymbolGlobal(handle, first_symbol, loc.start) orelse continue;
const symbol_type = try symbol_decl.resolveType(analyser) orelse continue;
if (!symbol_type.is_type_val) { // then => instance_of_T
if (try analyser.hasSelfParam(node_type)) break :blk 1;
}
break :blk 0; // is `T`, no SelfParam
};
const fn_node_handle = node_type.data.other; // this assumes that function types can only be Ast nodes
const param_decl: Analyser.Declaration.Param = .{
.param_index = @truncate(dot_context.fn_arg_index + additional_index),
.func = fn_node_handle.node,
};
const param = param_decl.get(fn_node_handle.handle.tree) orelse continue;

if (param.type_expr == 0) continue;
const param_rcts = try collectContainerNodes(
builder,
fn_node_handle.handle,
offsets.nodeToLoc(fn_node_handle.handle.tree, param.type_expr).end,
dot_context,
);
for (param_rcts) |prct| try types_with_handles.append(arena, prct);
}
}

Expand Down

0 comments on commit 015f619

Please sign in to comment.