Skip to content

Commit

Permalink
Sema: add error for recursive inline call
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexu committed Dec 20, 2022
1 parent 3db8cff commit f678cf5
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 33 deletions.
46 changes: 32 additions & 14 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pub const Block = struct {
/// It is shared among all the blocks in an inline or comptime called
/// function.
pub const Inlining = struct {
func: ?*Module.Fn,
comptime_result: Air.Inst.Ref,
merges: Merges,
};
Expand Down Expand Up @@ -6429,7 +6430,6 @@ fn analyzeCall(
}),
else => unreachable,
};
if (!is_comptime_call and module_fn.state == .sema_failure) return error.AnalysisFail;
if (func_ty_info.is_var_args) {
return sema.fail(block, call_src, "{s} call of variadic function", .{
@as([]const u8, if (is_comptime_call) "comptime" else "inline"),
Expand All @@ -6449,6 +6449,7 @@ fn analyzeCall(
// This one is shared among sub-blocks within the same callee, but not
// shared among the entire inline/comptime call stack.
var inlining: Block.Inlining = .{
.func = null,
.comptime_result = undefined,
.merges = .{
.results = .{},
Expand Down Expand Up @@ -6535,6 +6536,7 @@ fn analyzeCall(
const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
try sema.inst_map.ensureSpaceForInstructions(sema.gpa, fn_info.param_body);

var has_comptime_args = false;
var arg_i: usize = 0;
for (fn_info.param_body) |inst| {
sema.analyzeInlineCallArg(
Expand All @@ -6550,6 +6552,7 @@ fn analyzeCall(
memoized_call_key,
func_ty_info.param_types,
func,
&has_comptime_args,
) catch |err| switch (err) {
error.NeededSourceLocation => {
_ = sema.inst_map.remove(inst);
Expand All @@ -6567,13 +6570,27 @@ fn analyzeCall(
memoized_call_key,
func_ty_info.param_types,
func,
&has_comptime_args,
);
unreachable;
},
else => |e| return e,
};
}

if (!has_comptime_args and module_fn.state == .sema_failure) return error.AnalysisFail;

const recursive_msg = "inline call is recursive";
var head = if (!has_comptime_args) block else null;
while (head) |some| {
const parent_inlining = some.inlining orelse break;
if (parent_inlining.func == module_fn) {
return sema.fail(block, call_src, recursive_msg, .{});
}
head = some.parent;
}
if (!has_comptime_args) inlining.func = module_fn;

// In case it is a generic function with an expression for the return type that depends
// on parameters, we must now do the same for the return type as we just did with
// each of the parameters, resolving the return type and providing it to the child
Expand Down Expand Up @@ -6658,6 +6675,7 @@ fn analyzeCall(
error.ComptimeReturn => break :result inlining.comptime_result,
error.AnalysisFail => {
const err_msg = sema.err orelse return err;
if (std.mem.eql(u8, err_msg.msg, recursive_msg)) return err;
try sema.errNote(block, call_src, err_msg, "called from here", .{});
err_msg.clearTrace(sema.gpa);
return err;
Expand Down Expand Up @@ -6815,8 +6833,13 @@ fn analyzeInlineCallArg(
memoized_call_key: Module.MemoizedCall.Key,
raw_param_types: []const Type,
func_inst: Air.Inst.Ref,
has_comptime_args: *bool,
) !void {
const zir_tags = sema.code.instructions.items(.tag);
switch (zir_tags[inst]) {
.param_comptime, .param_anytype_comptime => has_comptime_args.* = true,
else => {},
}
switch (zir_tags[inst]) {
.param, .param_comptime => {
// Evaluate the parameter type expression now that previous ones have
Expand Down Expand Up @@ -6871,23 +6894,20 @@ fn analyzeInlineCallArg(
.ty = param_ty,
.val = arg_val,
};
} else if (zir_tags[inst] == .param_comptime or try sema.typeRequiresComptime(param_ty)) {
sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
} else if (try sema.resolveMaybeUndefVal(casted_arg)) |val| {
// We have a comptime value but we need a runtime value to preserve inlining semantics,
const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
} else {
sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
}

if (try sema.resolveMaybeUndefVal(casted_arg)) |_| {
has_comptime_args.* = true;
}

arg_i.* += 1;
},
.param_anytype, .param_anytype_comptime => {
// No coercion needed.
const uncasted_arg = uncasted_args[arg_i.*];
new_fn_info.param_types[arg_i.*] = sema.typeOf(uncasted_arg);
const param_ty = sema.typeOf(uncasted_arg);

if (is_comptime_call) {
sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
Expand All @@ -6913,16 +6933,14 @@ fn analyzeInlineCallArg(
.ty = sema.typeOf(uncasted_arg),
.val = arg_val,
};
} else if (zir_tags[inst] == .param_anytype_comptime or try sema.typeRequiresComptime(param_ty)) {
sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
} else if (try sema.resolveMaybeUndefVal(uncasted_arg)) |val| {
// We have a comptime value but we need a runtime value to preserve inlining semantics,
const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
} else {
sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
}

if (try sema.resolveMaybeUndefVal(uncasted_arg)) |_| {
has_comptime_args.* = true;
}

arg_i.* += 1;
},
else => {},
Expand Down
1 change: 0 additions & 1 deletion test/behavior.zig
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ test {
_ = @import("behavior/bugs/13112.zig");
_ = @import("behavior/bugs/13128.zig");
_ = @import("behavior/bugs/13159.zig");
_ = @import("behavior/bugs/13164.zig");
_ = @import("behavior/bugs/13171.zig");
_ = @import("behavior/bugs/13209.zig");
_ = @import("behavior/bugs/13285.zig");
Expand Down
18 changes: 0 additions & 18 deletions test/behavior/bugs/13164.zig

This file was deleted.

14 changes: 14 additions & 0 deletions test/behavior/call.zig
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,17 @@ test "inline call doesn't re-evaluate non generic struct" {
try @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
comptime try @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
}

test "recursive inline call with comptime known argument" {
const S = struct {
inline fn foo(x: i32) i32 {
if (x <= 0) {
return 0;
} else {
return x * 2 + foo(x - 1);
}
}
};

try expect(S.foo(4) == 20);
}
18 changes: 18 additions & 0 deletions test/cases/compile_errors/recursive_inline_fn.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
inline fn foo(x: i32) i32 {
if (x <= 0) {
return 0;
} else {
return x * 2 + foo(x - 1);
}
}

pub export fn entry() void {
var x: i32 = 4;
_ = foo(x) == 20;
}

// error
// backend=stage2
// target=native
//
// :5:27: error: inline call is recursive

0 comments on commit f678cf5

Please sign in to comment.