From e9bd2d45d4bbaf7eff7e95bc3ef7a0123b66a103 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 5 Jan 2025 05:27:48 +0000 Subject: [PATCH 1/2] Sema: rewrite semantic analysis of function calls This rewrite improves some error messages, hugely simplifies the logic, and fixes several bugs. One of these bugs is technically a new rule which Andrew and I agreed on: if a parameter has a comptime-only type but is not declared `comptime`, then the corresponding call argument should not be *evaluated* at comptime; only resolved. Implementing this required changing how function types work a little, which in turn required allowing a new kind of function coercion for some generic use cases: function coercions are now allowed to implicitly *remove* `comptime` annotations from parameters with comptime-only types. This is okay because removing the annotation affects only the call site. Resolves: #22262 --- lib/compiler/aro_translate_c.zig | 1 + lib/std/math/log_int.zig | 1 + lib/std/os/windows.zig | 1 + lib/std/zig.zig | 6 +- lib/std/zig/AstGen.zig | 3 - lib/std/zig/Zir.zig | 11 +- src/Sema.zig | 1732 +++++++---------- src/Zcu.zig | 24 + src/translate_c.zig | 1 + test/behavior/eval.zig | 2 +- test/behavior/generics.zig | 2 +- test/behavior/struct.zig | 2 +- test/behavior/typename.zig | 2 +- ...only_type_is_not_evaluated_at_comptime.zig | 36 + .../compile_errors/bad_usage_of_call.zig | 6 +- .../comptime_call_of_function_pointer.zig | 5 +- .../condition_comptime_reason_explained.zig | 6 +- .../compile_errors/dereference_anyopaque.zig | 53 +- .../explain_why_fn_is_called_at_comptime.zig | 3 +- ...n_why_generic_fn_is_called_at_comptime.zig | 3 +- ..._instance_with_non-constant_expression.zig | 7 +- ...antiation_inherits_parent_branch_quota.zig | 3 +- ...ailure_in_generic_function_return_type.zig | 1 + ...generic_method_call_with_invalid_param.zig | 7 +- ...nitializer_must_be_constant_expression.zig | 2 - ...e_call_runtime_value_to_comptime_param.zig | 3 +- .../invalid_extern_function_call.zig | 6 +- .../invalid_pointer_for_var_type.zig | 2 - ...d_generic_function_param_type_mismatch.zig | 4 +- ..._call_of_inline_fn_with_comptime_param.zig | 4 +- ...ion_in_struct_literal_outside_function.zig | 2 - ...on_comptime_param_in_comptime_function.zig | 5 +- .../compile_errors/recursive_inline_fn.zig | 6 +- .../runtime_operation_in_comptime_scope.zig | 5 +- test/compile_errors.zig | 15 +- .../standalone/simple/std_enums_big_enums.zig | 1 + 36 files changed, 797 insertions(+), 1176 deletions(-) create mode 100644 test/cases/compile_errors/arg_to_non_comptime_param_with_comptime_only_type_is_not_evaluated_at_comptime.zig diff --git a/lib/compiler/aro_translate_c.zig b/lib/compiler/aro_translate_c.zig index 6be33a196fac..ffb133dad50c 100644 --- a/lib/compiler/aro_translate_c.zig +++ b/lib/compiler/aro_translate_c.zig @@ -168,6 +168,7 @@ pub fn translate( context.pattern_list.deinit(gpa); } + @setEvalBranchQuota(2000); inline for (@typeInfo(std.zig.c_builtins).@"struct".decls) |decl| { const builtin_fn = try ZigTag.pub_var_simple.create(arena, .{ .name = decl.name, diff --git a/lib/std/math/log_int.zig b/lib/std/math/log_int.zig index 376aa69a4d66..96227639d0b7 100644 --- a/lib/std/math/log_int.zig +++ b/lib/std/math/log_int.zig @@ -61,6 +61,7 @@ pub fn log_int(comptime T: type, base: T, x: T) Log2Int(T) { } test "log_int" { + @setEvalBranchQuota(2000); // Test all unsigned integers with 2, 3, ..., 64 bits. // We cannot test 0 or 1 bits since base must be > 1. inline for (2..64 + 1) |bits| { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c56e27d8ec0c..ceed0618d1f1 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1468,6 +1468,7 @@ fn mountmgrIsVolumeName(name: []const u16) bool { } test mountmgrIsVolumeName { + @setEvalBranchQuota(2000); const L = std.unicode.utf8ToUtf16LeStringLiteral; try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 7af8bacb3459..6fec585326f4 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -749,6 +749,8 @@ pub const SimpleComptimeReason = enum(u32) { array_mul_factor, slice_cat_operand, comptime_call_target, + inline_call_target, + generic_call_target, wasm_memory_index, work_group_dim_index, @@ -791,7 +793,6 @@ pub const SimpleComptimeReason = enum(u32) { struct_field_default_value, enum_field_tag_value, slice_single_item_ptr_bounds, - comptime_param_arg, stored_to_comptime_field, stored_to_comptime_var, casted_to_comptime_enum, @@ -828,6 +829,8 @@ pub const SimpleComptimeReason = enum(u32) { .array_mul_factor => "array multiplication factor must be comptime-known", .slice_cat_operand => "slice being concatenated must be comptime-known", .comptime_call_target => "function being called at comptime must be comptime-known", + .inline_call_target => "function being called inline must be comptime-known", + .generic_call_target => "generic function being called must be comptime-known", .wasm_memory_index => "wasm memory index must be comptime-known", .work_group_dim_index => "work group dimension index must be comptime-known", @@ -865,7 +868,6 @@ pub const SimpleComptimeReason = enum(u32) { .struct_field_default_value => "struct field default value must be comptime-known", .enum_field_tag_value => "enum field tag value must be comptime-known", .slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds", - .comptime_param_arg => "argument to comptime parameter must be comptime-known", .stored_to_comptime_field => "value stored to a comptime field must be comptime-known", .stored_to_comptime_var => "value stored to a comptime variable must be comptime-known", .casted_to_comptime_enum => "value casted to enum with 'comptime_int' tag type must be comptime-known", diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index d318a9f3e505..1355c7cc77bc 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -10209,9 +10209,6 @@ fn callExpr( const callee = try calleeExpr(gz, scope, ri.rl, call.ast.fn_expr); const modifier: std.builtin.CallModifier = blk: { - if (gz.is_comptime) { - break :blk .compile_time; - } if (call.async_token != null) { break :blk .async_kw; } diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 21f59f8e8398..3b27f1cf5b6c 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -4735,6 +4735,7 @@ pub const FnInfo = struct { body: []const Inst.Index, ret_ty_ref: Zir.Inst.Ref, total_params_len: u32, + inferred_error_set: bool, }; pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const Zir.Inst.Index { @@ -4774,8 +4775,9 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { body: []const Inst.Index, ret_ty_ref: Inst.Ref, ret_ty_body: []const Inst.Index, + ies: bool, } = switch (tags[@intFromEnum(fn_inst)]) { - .func, .func_inferred => blk: { + .func, .func_inferred => |tag| blk: { const inst_data = datas[@intFromEnum(fn_inst)].pl_node; const extra = zir.extraData(Inst.Func, inst_data.payload_index); @@ -4805,6 +4807,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { .ret_ty_ref = ret_ty_ref, .ret_ty_body = ret_ty_body, .body = body, + .ies = tag == .func_inferred, }; }, .func_fancy => blk: { @@ -4812,7 +4815,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index); var extra_index: usize = extra.end; - var ret_ty_ref: Inst.Ref = .void_type; + var ret_ty_ref: Inst.Ref = .none; var ret_ty_body: []const Inst.Index = &.{}; if (extra.data.bits.has_cc_body) { @@ -4828,6 +4831,8 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { } else if (extra.data.bits.has_ret_ty_ref) { ret_ty_ref = @enumFromInt(zir.extra[extra_index]); extra_index += 1; + } else { + ret_ty_ref = .void_type; } extra_index += @intFromBool(extra.data.bits.has_any_noalias); @@ -4839,6 +4844,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { .ret_ty_ref = ret_ty_ref, .ret_ty_body = ret_ty_body, .body = body, + .ies = extra.data.bits.is_inferred_error, }; }, else => unreachable, @@ -4860,6 +4866,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { .ret_ty_ref = info.ret_ty_ref, .body = info.body, .total_params_len = total_params_len, + .inferred_error_set = info.ies, }; } diff --git a/src/Sema.zig b/src/Sema.zig index 081e51af36c6..c27fd7321e9f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -46,21 +46,6 @@ branch_count: u32 = 0, /// Populated when returning `error.ComptimeBreak`. Used to communicate the /// break instruction up the stack to find the corresponding Block. comptime_break_inst: Zir.Inst.Index = undefined, -/// When doing a generic function instantiation, this array collects a value -/// for each parameter of the generic owner. `none` for non-comptime parameters. -/// This is a separate array from `block.params` so that it can be passed -/// directly to `comptime_args` when calling `InternPool.getFuncInstance`. -/// This memory is allocated by a parent `Sema` in the temporary arena, and is -/// used only to add a `func_instance` into the `InternPool`. -comptime_args: []InternPool.Index = &.{}, -/// Used to communicate from a generic function instantiation to the logic that -/// creates a generic function instantiation value in `funcCommon`. -generic_owner: InternPool.Index = .none, -/// When `generic_owner` is not none, this contains the generic function -/// instantiation callsite so that compile errors on the parameter types of the -/// instantiation can point back to the instantiation site in addition to the -/// declaration site. -generic_call_src: LazySrcLoc = LazySrcLoc.unneeded, /// These are lazily created runtime blocks from block_inline instructions. /// They are created when an break_inline passes through a runtime condition, because /// Sema must convert comptime control flow to runtime control flow, which means @@ -862,12 +847,29 @@ const ComptimeReason = union(enum) { union_init, struct_init, tuple_init, - param_ty_arg, - ret_ty_call, - ret_ty_generic_call, }, }, + /// Like `comptime_only`, but for a parameter type. + /// Includes a "parameter type declared here" note. + comptime_only_param_ty: struct { + ty: Type, + param_ty_src: LazySrcLoc, + }, + + /// Like `comptime_only`, but for a return type. + /// Includes a "return type declared here" note. + comptime_only_ret_ty: struct { + ty: Type, + is_generic_inst: bool, + ret_ty_src: LazySrcLoc, + }, + + /// Evaluating at comptime because we're evaluating an argument to a parameter marked `comptime`. + comptime_param: struct { + comptime_src: LazySrcLoc, + }, + fn explain(reason: ComptimeReason, sema: *Sema, src: LazySrcLoc, err_msg: *Zcu.ErrorMsg) !void { switch (reason) { .simple => |simple| { @@ -878,13 +880,25 @@ const ComptimeReason = union(enum) { .union_init => .{ "initializer of comptime-only union", "must be comptime-known" }, .struct_init => .{ "initializer of comptime-only struct", "must be comptime-known" }, .tuple_init => .{ "initializer of comptime-only tuple", "must be comptime-known" }, - .param_ty_arg => .{ "argument to parameter with comptime-only type", "must be comptime-known" }, - .ret_ty_call => .{ "function with comptime-only return type", "is evaluated at comptime" }, - .ret_ty_generic_call => .{ "generic function instantiated with comptime-only return type", "is evaluated at comptime" }, }; try sema.errNote(src, err_msg, "{s} '{}' {s}", .{ pre, co.ty.fmt(sema.pt), post }); try sema.explainWhyTypeIsComptime(err_msg, src, co.ty); }, + .comptime_only_param_ty => |co| { + try sema.errNote(src, err_msg, "argument to parameter with comptime-only type '{}' must be comptime-known", .{co.ty.fmt(sema.pt)}); + try sema.errNote(co.param_ty_src, err_msg, "parameter type declared here", .{}); + try sema.explainWhyTypeIsComptime(err_msg, src, co.ty); + }, + .comptime_only_ret_ty => |co| { + const function_with: []const u8 = if (co.is_generic_inst) "generic function instantiated with" else "function with"; + try sema.errNote(src, err_msg, "call to {s} comptime-only return type '{}' is evaluated at comptime", .{ function_with, co.ty.fmt(sema.pt) }); + try sema.errNote(co.ret_ty_src, err_msg, "return type declared here", .{}); + try sema.explainWhyTypeIsComptime(err_msg, src, co.ty); + }, + .comptime_param => |cp| { + try sema.errNote(src, err_msg, "argument to comptime parameter must be comptime-known", .{}); + try sema.errNote(cp.comptime_src, err_msg, "parameter declared comptime here", .{}); + }, } } }; @@ -7423,8 +7437,9 @@ const CallArgsInfo = union(enum) { /// Analyzes the arg at `arg_index` and coerces it to `param_ty`. /// `param_ty` may be `generic_poison`. A value of `null` indicates a varargs parameter. - /// `func_ty_info` may be the type before instantiation, even if a generic - /// instantiation has been partially completed. + /// `func_ty_info` may be the type before instantiation, even if a generic instantiation is in progress. + /// Emits a compile error if the argument is not comptime-known despite either `block.isComptime()` or + /// the parameter being marked `comptime`. fn analyzeArg( cai: CallArgsInfo, sema: *Sema, @@ -7433,6 +7448,7 @@ const CallArgsInfo = union(enum) { maybe_param_ty: ?Type, func_ty_info: InternPool.Key.FuncType, func_inst: Air.Inst.Ref, + maybe_func_src_inst: ?InternPool.TrackedInst.Index, ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; @@ -7460,11 +7476,22 @@ const CallArgsInfo = union(enum) { const parent_comptime = block.comptime_reason; defer block.comptime_reason = parent_comptime; // Note that we are indexing into parameters, not arguments, so use `arg_index` instead of `real_arg_idx` - if (arg_index < @min(param_count, 32) and func_ty_info.paramIsComptime(@intCast(arg_index))) { - block.comptime_reason = .{ .reason = .{ - .src = cai.argSrc(block, arg_index), - .r = .{ .simple = .comptime_param_arg }, - } }; + if (std.math.cast(u5, arg_index)) |i| { + if (i < param_count and func_ty_info.paramIsComptime(i)) { + block.comptime_reason = .{ + .reason = .{ + .src = cai.argSrc(block, arg_index), + .r = .{ + .comptime_param = .{ + .comptime_src = if (maybe_func_src_inst) |src_inst| .{ + .base_node_inst = src_inst, + .offset = .{ .func_decl_param_comptime = @intCast(arg_index) }, + } else unreachable, // should be non-null because the function is generic + }, + }, + }, + }; + } } // Give the arg its result type const provide_param_ty = if (maybe_param_ty) |t| t else Type.generic_poison; @@ -7472,6 +7499,10 @@ const CallArgsInfo = union(enum) { // Resolve the arg! const uncoerced_arg = try sema.resolveInlineBody(block, arg_body, zir_call.call_inst); + if (block.isComptime() and !try sema.isComptimeKnown(uncoerced_arg)) { + return sema.failWithNeededComptime(block, cai.argSrc(block, arg_index), null); + } + if (sema.typeOf(uncoerced_arg).zigTypeTag(zcu) == .noreturn) { // This terminates resolution of arguments. The caller should // propagate this. @@ -7507,104 +7538,10 @@ const CallArgsInfo = union(enum) { } }; -/// While performing an inline call, we need to switch between two Sema states a few times: the -/// state for the caller (with the callee's `code`, `fn_ret_ty`, etc), and the state for the callee. -/// These cannot be two separate Sema instances as they must share AIR. -/// Therefore, this struct acts as a helper to switch between the two. -/// This switching is required during argument evaluation, where function argument analysis must be -/// interleaved with resolving generic parameter types. -const InlineCallSema = struct { - sema: *Sema, - cur: enum { - caller, - callee, - }, - - other_code: Zir, - other_func_index: InternPool.Index, - other_fn_ret_ty: Type, - other_fn_ret_ty_ies: ?*InferredErrorSet, - other_inst_map: InstMap, - other_error_return_trace_index_on_fn_entry: Air.Inst.Ref, - other_generic_owner: InternPool.Index, - other_generic_call_src: LazySrcLoc, - - /// Sema should currently be set up for the caller (i.e. unchanged yet). This init will not - /// change that. The other parameters contain data for the callee Sema. The other modified - /// Sema fields are all initialized to default values for the callee. - /// Must call deinit on the result. - fn init( - sema: *Sema, - callee_code: Zir, - callee_func_index: InternPool.Index, - callee_error_return_trace_index_on_fn_entry: Air.Inst.Ref, - ) InlineCallSema { - return .{ - .sema = sema, - .cur = .caller, - .other_code = callee_code, - .other_func_index = callee_func_index, - .other_fn_ret_ty = Type.void, - .other_fn_ret_ty_ies = null, - .other_inst_map = .{}, - .other_error_return_trace_index_on_fn_entry = callee_error_return_trace_index_on_fn_entry, - .other_generic_owner = .none, - .other_generic_call_src = LazySrcLoc.unneeded, - }; - } - - /// Switch back to the caller Sema if necessary and free all temporary state of the callee Sema. - fn deinit(ics: *InlineCallSema) void { - switch (ics.cur) { - .caller => {}, - .callee => ics.swap(), - } - // Callee Sema owns the inst_map memory - ics.other_inst_map.deinit(ics.sema.gpa); - ics.* = undefined; - } - - /// Returns a Sema instance suitable for usage from the caller context. - fn caller(ics: *InlineCallSema) *Sema { - switch (ics.cur) { - .caller => {}, - .callee => ics.swap(), - } - return ics.sema; - } - - /// Returns a Sema instance suitable for usage from the callee context. - fn callee(ics: *InlineCallSema) *Sema { - switch (ics.cur) { - .caller => ics.swap(), - .callee => {}, - } - return ics.sema; - } - - /// Internal use only. Swaps to the other Sema state. - fn swap(ics: *InlineCallSema) void { - ics.cur = switch (ics.cur) { - .caller => .callee, - .callee => .caller, - }; - // zig fmt: off - std.mem.swap(Zir, &ics.sema.code, &ics.other_code); - std.mem.swap(InternPool.Index, &ics.sema.func_index, &ics.other_func_index); - std.mem.swap(Type, &ics.sema.fn_ret_ty, &ics.other_fn_ret_ty); - std.mem.swap(?*InferredErrorSet, &ics.sema.fn_ret_ty_ies, &ics.other_fn_ret_ty_ies); - std.mem.swap(InstMap, &ics.sema.inst_map, &ics.other_inst_map); - std.mem.swap(InternPool.Index, &ics.sema.generic_owner, &ics.other_generic_owner); - std.mem.swap(LazySrcLoc, &ics.sema.generic_call_src, &ics.other_generic_call_src); - std.mem.swap(Air.Inst.Ref, &ics.sema.error_return_trace_index_on_fn_entry, &ics.other_error_return_trace_index_on_fn_entry); - // zig fmt: on - } -}; - fn analyzeCall( sema: *Sema, block: *Block, - func: Air.Inst.Ref, + callee: Air.Inst.Ref, func_ty: Type, func_src: LazySrcLoc, call_src: LazySrcLoc, @@ -7616,983 +7553,696 @@ fn analyzeCall( ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; + const arena = sema.arena; + + if (modifier == .async_kw) { + return sema.failWithUseOfAsync(block, call_src); + } + + const maybe_func_inst = try sema.funcDeclSrcInst(callee); + const func_ret_ty_src: LazySrcLoc = if (maybe_func_inst) |fn_decl_inst| .{ + .base_node_inst = fn_decl_inst, + .offset = .{ .node_offset_fn_type_ret_ty = 0 }, + } else func_src; - const callee_ty = sema.typeOf(func); const func_ty_info = zcu.typeToFunc(func_ty).?; - const cc = func_ty_info.cc; - if (try sema.resolveValue(func)) |func_val| - if (func_val.isUndef(zcu)) - return sema.failWithUseOfUndef(block, call_src); - if (!callConvIsCallable(cc)) { - const maybe_func_inst = try sema.funcDeclSrcInst(func); - const msg = msg: { + if (!callConvIsCallable(func_ty_info.cc)) { + return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg( func_src, "unable to call function with calling convention '{s}'", - .{@tagName(cc)}, + .{@tagName(func_ty_info.cc)}, ); - errdefer msg.destroy(sema.gpa); - + errdefer msg.destroy(gpa); if (maybe_func_inst) |func_inst| try sema.errNote(.{ .base_node_inst = func_inst, - .offset = LazySrcLoc.Offset.nodeOffset(0), + .offset = .nodeOffset(0), }, msg, "function declared here", .{}); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }); } - const call_tag: Air.Inst.Tag = switch (modifier) { - .auto, - .always_inline, - .compile_time, - .no_async, - => Air.Inst.Tag.call, - - .never_tail => Air.Inst.Tag.call_never_tail, - .never_inline => Air.Inst.Tag.call_never_inline, - .always_tail => Air.Inst.Tag.call_always_tail, - - .async_kw => return sema.failWithUseOfAsync(block, call_src), - }; + // We need this value in a few code paths. + const callee_val = try sema.resolveDefinedValue(block, call_src, callee); + // If the callee is a comptime-known *non-extern* function, `func_val` is populated. + // If it is a comptime-known extern function, `func_is_extern` is set instead. + // If it is not comptime-known, neither is set. + const func_val: ?Value, const func_is_extern: bool = if (callee_val) |c| switch (ip.indexToKey(c.toIntern())) { + .func => .{ c, false }, + .ptr => switch (try sema.pointerDerefExtra(block, func_src, c)) { + .runtime_load, .needed_well_defined, .out_of_bounds => .{ null, false }, + .val => |pointee| switch (ip.indexToKey(pointee.toIntern())) { + .func => .{ pointee, false }, + .@"extern" => .{ null, true }, + else => unreachable, + }, + }, + .@"extern" => .{ null, true }, + else => unreachable, + } else .{ null, false }; - if (modifier == .never_inline and func_ty_info.cc == .@"inline") { - return sema.fail(block, call_src, "'never_inline' call of inline function", .{}); + if (func_ty_info.is_generic and func_val == null) { + return sema.failWithNeededComptime(block, func_src, .{ .simple = .generic_call_target }); } - if (modifier == .always_inline and func_ty_info.is_noinline) { - return sema.fail(block, call_src, "'always_inline' call of noinline function", .{}); - } - - const gpa = sema.gpa; - const func_ret_ty_src: LazySrcLoc = if (try sema.funcDeclSrcInst(func)) |fn_decl_inst| .{ - .base_node_inst = fn_decl_inst, - .offset = .{ .node_offset_fn_type_ret_ty = 0 }, - } else func_src; + const inline_requested = func_ty_info.cc == .@"inline" or modifier == .always_inline; - // If this is not `null`, the call is comptime. - var comptime_call_reason: ?BlockComptimeReason = cr: { - if (block.comptime_reason) |r| break :cr r; - if (modifier == .compile_time) break :cr .{ .reason = .{ - .src = call_src, - .r = .{ .simple = .comptime_call_modifier }, - } }; - break :cr null; - }; - - const is_generic_call = func_ty_info.is_generic; - var is_inline_call = comptime_call_reason != null or modifier == .always_inline or func_ty_info.cc == .@"inline"; - if (!is_inline_call) { - if (try Type.fromInterned(func_ty_info.return_type).comptimeOnlySema(pt)) { - is_inline_call = true; - comptime_call_reason = .{ .reason = .{ - .src = func_ret_ty_src, - .r = .{ .comptime_only = .{ - .ty = .fromInterned(func_ty_info.return_type), - .msg = .ret_ty_call, - } }, + // If the modifier is `.compile_time`, or if the return type is non-generic and comptime-only, + // then we need to enter a comptime scope *now* to make sure the args are comptime-eval'd. + const old_block_comptime_reason = block.comptime_reason; + defer block.comptime_reason = old_block_comptime_reason; + if (!block.isComptime()) { + if (modifier == .compile_time) { + block.comptime_reason = .{ .reason = .{ + .src = call_src, + .r = .{ .simple = .comptime_call_modifier }, } }; - } - } - - if (sema.func_is_naked and !is_inline_call) { - const msg = msg: { - const msg = try sema.errMsg(call_src, "runtime {s} not allowed in naked function", .{@tagName(operation)}); - errdefer msg.destroy(sema.gpa); - - switch (operation) { - .call, .@"@call", .@"@panic", .@"error return" => {}, - .@"safety check" => try sema.errNote(call_src, msg, "use @setRuntimeSafety to disable runtime safety", .{}), - } - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - - if (!is_inline_call and is_generic_call) { - var comptime_ret_ty: Type = undefined; - if (sema.instantiateGenericCall( - block, - func, - func_src, - call_src, - ensure_result_used, - args_info, - call_tag, - call_dbg_node, - &comptime_ret_ty, - )) |some| { - return some; - } else |err| switch (err) { - error.GenericPoison => { - is_inline_call = true; - }, - error.ComptimeReturn => { - is_inline_call = true; - comptime_call_reason = .{ .reason = .{ - .src = func_ret_ty_src, + } else if (!inline_requested and try Type.fromInterned(func_ty_info.return_type).comptimeOnlySema(pt)) { + block.comptime_reason = .{ + .reason = .{ + .src = call_src, .r = .{ - .comptime_only = .{ - .ty = comptime_ret_ty, - .msg = .ret_ty_generic_call, + .comptime_only_ret_ty = .{ + .ty = .fromInterned(func_ty_info.return_type), + .is_generic_inst = false, + .ret_ty_src = func_ret_ty_src, }, }, - } }; - }, - else => |e| return e, + }, + }; } } - const is_comptime_call = comptime_call_reason != null; - // `comptime_call_reason` shouldn't be mutated again - defer assert(is_comptime_call == (comptime_call_reason != null)); + // These values are undefined if `func_val == null`. + const fn_nav: InternPool.Nav, const fn_zir: Zir, const fn_tracked_inst: InternPool.TrackedInst.Index, const fn_zir_inst: Zir.Inst.Index, const fn_zir_info: Zir.FnInfo = if (func_val) |f| b: { + const info = ip.indexToKey(f.toIntern()).func; + const nav = ip.getNav(info.owner_nav); + const resolved_func_inst = info.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(resolved_func_inst.file); + assert(file.zir_loaded); + const zir_info = file.zir.getFnInfo(resolved_func_inst.inst); + break :b .{ nav, file.zir, info.zir_body_inst, resolved_func_inst.inst, zir_info }; + } else .{ undefined, undefined, undefined, undefined, undefined }; + + // This is the `inst_map` used when evaluating generic parameters and return types. + var generic_inst_map: InstMap = .{}; + defer generic_inst_map.deinit(gpa); + if (func_ty_info.is_generic) { + try generic_inst_map.ensureSpaceForInstructions(gpa, fn_zir_info.param_body); + } + + // This exists so that `generic_block` below can include a "called from here" note back to this + // call site when analyzing generic parameter/return types. + var generic_inlining: Block.Inlining = if (func_ty_info.is_generic) .{ + .call_block = block, + .call_src = call_src, + .has_comptime_args = false, // unused by error reporting + .func = .none, // unused by error reporting + .comptime_result = .none, // unused by error reporting + .merges = undefined, // unused because we'll never `return` + } else undefined; - if (is_comptime_call and modifier == .never_inline) { - return sema.fail(block, call_src, "unable to perform 'never_inline' call at compile-time", .{}); - } + // This is the block in which we evaluate generic function components: that is, generic parameter + // types and the generic return type. This must not be used if the function is not generic. + // `comptime_reason` is set as needed. + var generic_block: Block = if (func_ty_info.is_generic) .{ + .parent = null, + .sema = sema, + .namespace = fn_nav.analysis.?.namespace, + .instructions = .{}, + .inlining = &generic_inlining, + .src_base_inst = fn_nav.analysis.?.zir_index, + .type_name_ctx = fn_nav.fqn, + } else undefined; + defer if (func_ty_info.is_generic) generic_block.instructions.deinit(gpa); + + if (func_ty_info.is_generic) { + // We certainly depend on the generic owner's signature! + try sema.declareDependency(.{ .src_hash = fn_tracked_inst }); + } + + const args = try arena.alloc(Air.Inst.Ref, args_info.count()); + for (args, 0..) |*arg, arg_idx| { + const param_ty: ?Type = if (arg_idx < func_ty_info.param_types.len) ty: { + const raw = func_ty_info.param_types.get(ip)[arg_idx]; + if (raw != .generic_poison_type) break :ty .fromInterned(raw); + + // We must discover the generic parameter type. + assert(func_ty_info.is_generic); + const param_inst_idx = fn_zir_info.param_body[arg_idx]; + const param_inst = fn_zir.instructions.get(@intFromEnum(param_inst_idx)); + switch (param_inst.tag) { + .param_anytype, .param_anytype_comptime => break :ty .generic_poison, + .param, .param_comptime => {}, + else => unreachable, + } - const result: Air.Inst.Ref = if (is_inline_call) res: { - const old_comptime_reason = block.comptime_reason; - block.comptime_reason = comptime_call_reason; - defer block.comptime_reason = old_comptime_reason; + // Evaluate the generic parameter type. We need to switch out `sema.code` and `sema.inst_map`, because + // the function definition may be in a different file to the call site. + const old_code = sema.code; + const old_inst_map = sema.inst_map; + defer { + generic_inst_map = sema.inst_map; + sema.code = old_code; + sema.inst_map = old_inst_map; + } + sema.code = fn_zir; + sema.inst_map = generic_inst_map; - const func_val = try sema.resolveConstDefinedValue(block, func_src, func, .{ .simple = .comptime_call_target }); - const module_fn_index = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { - .@"extern" => return sema.fail(block, call_src, "{s} call of extern function", .{ - @as([]const u8, if (is_comptime_call) "comptime" else "inline"), - }), - .func => func_val.toIntern(), - .ptr => |ptr| blk: { - switch (ptr.base_addr) { - .nav => |nav_index| if (ptr.byte_offset == 0) { - try sema.ensureNavResolved(call_src, nav_index, .fully); - const nav = ip.getNav(nav_index); - if (nav.getExtern(ip) != null) - return sema.fail(block, call_src, "{s} call of extern function pointer", .{ - if (is_comptime_call) "comptime" else "inline", - }); - break :blk nav.status.fully_resolved.val; - }, - else => {}, - } - assert(callee_ty.isPtrAtRuntime(zcu)); - return sema.fail(block, call_src, "{s} call of function pointer", .{ - if (is_comptime_call) "comptime" else "inline", - }); - }, - else => unreachable, - }; - if (func_ty_info.is_var_args) { - return sema.fail(block, call_src, "{s} call of variadic function", .{ - if (is_comptime_call) "comptime" else "inline", - }); - } + const extra = sema.code.extraData(Zir.Inst.Param, param_inst.data.pl_tok.payload_index); + const param_src = generic_block.tokenOffset(param_inst.data.pl_tok.src_tok); + const body = sema.code.bodySlice(extra.end, extra.data.body_len); - // Analyze the ZIR. The same ZIR gets analyzed into a runtime function - // or an inlined call depending on what union tag the `label` field is - // set to in the `Block`. - // This block instruction will be used to capture the return value from the - // inlined function. - const need_debug_scope = !is_comptime_call and !block.is_typeof and !block.ownerModule().strip; - const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); - try sema.air_instructions.append(gpa, .{ - .tag = if (need_debug_scope) .dbg_inline_block else .block, - .data = undefined, - }); - // 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 = .{ - .call_block = block, - .call_src = call_src, - .has_comptime_args = false, - .func = module_fn_index, - .comptime_result = undefined, - .merges = .{ - .src_locs = .{}, - .results = .{}, - .br_list = .{}, - .block_inst = block_inst, - }, - }; + generic_block.comptime_reason = .{ .reason = .{ + .r = .{ .simple = .function_parameters }, + .src = param_src, + } }; - const module_fn = zcu.funcInfo(module_fn_index); + const ty_ref = try sema.resolveInlineBody(&generic_block, body, param_inst_idx); + const param_ty = try sema.analyzeAsType(&generic_block, param_src, ty_ref); - // The call site definitely depends on the function's signature. - try sema.declareDependency(.{ .src_hash = module_fn.zir_body_inst }); + if (!param_ty.isValidParamType(zcu)) { + const opaque_str = if (param_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else ""; + return sema.fail(block, param_src, "parameter of {s}type '{}' not allowed", .{ + opaque_str, param_ty.fmt(pt), + }); + } - // This is not a function instance, so the function's `Nav` has analysis - // state -- we don't need to check `generic_owner`. - const fn_nav = ip.getNav(module_fn.owner_nav); + break :ty param_ty; + } else null; // vararg - // We effectively want a child Sema here, but can't literally do that, because we need AIR - // to be shared. InlineCallSema is a wrapper which handles this for us. While `ics` is in - // scope, we should use its `caller`/`callee` methods rather than using `sema` directly - // whenever performing an operation where the difference matters. - var ics = InlineCallSema.init( - sema, - zcu.navFileScope(module_fn.owner_nav).zir, - module_fn_index, - block.error_return_trace_index, - ); - defer ics.deinit(); + arg.* = try args_info.analyzeArg(sema, block, arg_idx, param_ty, func_ty_info, callee, maybe_func_inst); + const arg_ty = sema.typeOf(arg.*); + if (arg_ty.zigTypeTag(zcu) == .noreturn) { + return arg.*; // terminate analysis here + } + + if (func_ty_info.is_generic) { + // We need to put the argument into `generic_inst_map` so that other parameters can refer to it. + const param_inst_idx = fn_zir_info.param_body[arg_idx]; + const declared_comptime = if (std.math.cast(u5, arg_idx)) |i| func_ty_info.paramIsComptime(i) else false; + const param_is_comptime = declared_comptime or try arg_ty.comptimeOnlySema(pt); + if (param_is_comptime) { + if (!try sema.isComptimeKnown(arg.*)) { + assert(!declared_comptime); // `analyzeArg` handles this + const arg_src = args_info.argSrc(block, arg_idx); + const param_ty_src: LazySrcLoc = .{ + .base_node_inst = maybe_func_inst.?, // the function is generic + .offset = .{ .func_decl_param_ty = @intCast(arg_idx) }, + }; + return sema.failWithNeededComptime( + block, + arg_src, + .{ .comptime_only_param_ty = .{ .ty = arg_ty, .param_ty_src = param_ty_src } }, + ); + } + generic_inst_map.putAssumeCapacityNoClobber(param_inst_idx, arg.*); + } else { + // We need a dummy instruction with this type. It doesn't actually need to be in any block, + // since it will never be referenced at runtime! + const dummy: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); + try sema.air_instructions.append(gpa, .{ .tag = .alloc, .data = .{ .ty = arg_ty } }); + generic_inst_map.putAssumeCapacityNoClobber(param_inst_idx, dummy.toRef()); + } + } + } - var child_block: Block = .{ - .parent = null, - .sema = sema, - // The function body exists in the same namespace as the corresponding function declaration. - .namespace = fn_nav.analysis.?.namespace, - .instructions = .{}, - .label = null, - .inlining = &inlining, - .is_typeof = block.is_typeof, - .comptime_reason = if (is_comptime_call) .inlining_parent else null, - .error_return_trace_index = block.error_return_trace_index, - .runtime_cond = block.runtime_cond, - .runtime_loop = block.runtime_loop, - .runtime_index = block.runtime_index, - .src_base_inst = fn_nav.analysis.?.zir_index, - .type_name_ctx = fn_nav.fqn, - }; + // This return type is never generic poison. + // However, if it has an IES, it is always associated with the callee value. + // This is not correct for inline calls (where it should be an ad-hoc IES), nor for generic + // calls (where it should be the IES of the instantiation). However, it's how we print this + // in error messages. + const resolved_ret_ty: Type = ret_ty: { + if (!func_ty_info.is_generic) break :ret_ty .fromInterned(func_ty_info.return_type); - const merges = &child_block.inlining.?.merges; + const maybe_poison_bare = if (fn_zir_info.inferred_error_set) maybe_poison: { + break :maybe_poison ip.errorUnionPayload(func_ty_info.return_type); + } else func_ty_info.return_type; - defer child_block.instructions.deinit(gpa); - defer merges.deinit(gpa); + if (maybe_poison_bare != .generic_poison_type) break :ret_ty .fromInterned(func_ty_info.return_type); - try sema.emitBackwardBranch(block, call_src); + // Evaluate the generic return type. As with generic parameters, we switch out `sema.code` and `sema.inst_map`. - // Whether this call should be memoized, set to false if the call can - // mutate comptime state. - // TODO: comptime call memoization is currently not supported under incremental compilation - // since dependencies are not marked on callers. If we want to keep this around (we should - // check that it's worthwhile first!), each memoized call needs an `AnalUnit`. - var should_memoize = !zcu.comp.incremental; - - // If it's a comptime function call, we need to memoize it as long as no external - // comptime memory is mutated. - const memoized_arg_values = try sema.arena.alloc(InternPool.Index, func_ty_info.param_types.len); - - const owner_info = zcu.typeToFunc(Type.fromInterned(module_fn.ty)).?; - const new_param_types = try sema.arena.alloc(InternPool.Index, owner_info.param_types.len); - var new_fn_info: InternPool.GetFuncTypeKey = .{ - .param_types = new_param_types, - .return_type = owner_info.return_type, - .noalias_bits = owner_info.noalias_bits, - .cc = owner_info.cc, - .is_var_args = owner_info.is_var_args, - .is_noinline = owner_info.is_noinline, - .is_generic = owner_info.is_generic, - }; + assert(func_ty_info.is_generic); - // This will have return instructions analyzed as break instructions to - // the block_inst above. Here we are performing "comptime/inline semantic analysis" - // for a function body, which means we must map the parameter ZIR instructions to - // the AIR instructions of the callsite. The callee could be a generic function - // which means its parameter type expressions must be resolved in order and used - // to successively coerce the arguments. - const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); - try ics.callee().inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); - - var arg_i: u32 = 0; - for (fn_info.param_body) |inst| { - const opt_noreturn_ref = try analyzeInlineCallArg( - &ics, - block, - &child_block, - inst, - new_param_types, - &arg_i, - args_info, - is_comptime_call, - &should_memoize, - memoized_arg_values, - func_ty_info, - func, - ); - if (opt_noreturn_ref) |ref| { - // Analyzing this argument gave a ref of a noreturn type. Terminate argument analysis here. - return ref; - } + const old_code = sema.code; + const old_inst_map = sema.inst_map; + defer { + generic_inst_map = sema.inst_map; + sema.code = old_code; + sema.inst_map = old_inst_map; } + sema.code = fn_zir; + sema.inst_map = generic_inst_map; - // From here, we only really need to use the callee Sema. Make it the active one, then we - // can just use `sema` directly. - _ = ics.callee(); + generic_block.comptime_reason = .{ .reason = .{ + .r = .{ .simple = .function_ret_ty }, + .src = func_ret_ty_src, + } }; - if (!inlining.has_comptime_args) { - var block_it = block; - while (block_it.inlining) |parent_inlining| { - if (!parent_inlining.has_comptime_args and parent_inlining.func == module_fn_index) { - const err_msg = try sema.errMsg(call_src, "inline call is recursive", .{}); - return sema.failWithOwnedErrorMsg(null, err_msg); - } - block_it = parent_inlining.call_block; - } + const bare_ty = if (fn_zir_info.ret_ty_ref != .none) bare: { + assert(fn_zir_info.ret_ty_body.len == 0); + break :bare try sema.resolveType(&generic_block, func_ret_ty_src, fn_zir_info.ret_ty_ref); + } else bare: { + assert(fn_zir_info.ret_ty_body.len != 0); + const ty_ref = try sema.resolveInlineBody(&generic_block, fn_zir_info.ret_ty_body, fn_zir_inst); + break :bare try sema.analyzeAsType(&generic_block, func_ret_ty_src, ty_ref); + }; + assert(bare_ty.toIntern() != .generic_poison_type); + + const full_ty = if (fn_zir_info.inferred_error_set) full: { + try sema.validateErrorUnionPayloadType(block, bare_ty, func_ret_ty_src); + const set = ip.errorUnionSet(func_ty_info.return_type); + break :full try pt.errorUnionType(.fromInterned(set), bare_ty); + } else bare_ty; + + if (!full_ty.isValidReturnType(zcu)) { + const opaque_str = if (full_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else ""; + return sema.fail(block, func_ret_ty_src, "{s}return type '{}' not allowed", .{ + opaque_str, full_ty.fmt(pt), + }); } - // 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 - // `Sema` so that it can be used for the `ret_ptr` instruction. - const ret_ty_src: LazySrcLoc = .{ .base_node_inst = module_fn.zir_body_inst, .offset = .{ .node_offset_fn_type_ret_ty = 0 } }; - const ret_ty_inst = if (fn_info.ret_ty_body.len != 0) r: { - const old_child_comptime_reason = child_block.comptime_reason; - defer child_block.comptime_reason = old_child_comptime_reason; - child_block.comptime_reason = .{ .reason = .{ - .src = ret_ty_src, - .r = .{ .simple = .function_ret_ty }, - } }; - break :r try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); - } else try sema.resolveInst(fn_info.ret_ty_ref); - sema.fn_ret_ty = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); - if (module_fn.analysisUnordered(ip).inferred_error_set) { - // Create a fresh inferred error set type for inline/comptime calls. - const ies = try sema.arena.create(InferredErrorSet); - ies.* = .{ .func = .none }; - sema.fn_ret_ty_ies = ies; - sema.fn_ret_ty = Type.fromInterned(try pt.intern(.{ .error_union_type = .{ - .error_set_type = .adhoc_inferred_error_set_type, - .payload_type = sema.fn_ret_ty.toIntern(), - } })); - } + break :ret_ty full_ty; + }; - memoize: { - if (!should_memoize) break :memoize; - if (!is_comptime_call) break :memoize; - const memoized_call_index = ip.getIfExists(.{ - .memoized_call = .{ - .func = module_fn_index, - .arg_values = memoized_arg_values, - .result = undefined, // ignored by hash+eql - .branch_count = undefined, // ignored by hash+eql + // If we've discovered after evaluating arguments that a generic function instantiation is + // comptime-only, then we can mark the block as comptime *now*. + if (!inline_requested and !block.isComptime() and try resolved_ret_ty.comptimeOnlySema(pt)) { + block.comptime_reason = .{ + .reason = .{ + .src = call_src, + .r = .{ + .comptime_only_ret_ty = .{ + .ty = resolved_ret_ty, + .is_generic_inst = true, + .ret_ty_src = func_ret_ty_src, + }, }, - }) orelse break :memoize; - const memoized_call = ip.indexToKey(memoized_call_index).memoized_call; - if (sema.branch_count + memoized_call.branch_count > sema.branch_quota) { - // Let the call play out se we get the correct source location for the - // "evaluation exceeded X backwards branches" error. - break :memoize; - } - sema.branch_count += memoized_call.branch_count; - break :res Air.internedToRef(memoized_call.result); - } + }, + }; + } - // Since we're doing an inline call, we depend on the source code of the whole - // function declaration. - try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index }); + if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); - new_fn_info.return_type = sema.fn_ret_ty.toIntern(); - if (!is_comptime_call and !block.is_typeof) { - const zir_tags = sema.code.instructions.items(.tag); - for (fn_info.param_body) |param| switch (zir_tags[@intFromEnum(param)]) { - .param, .param_comptime => { - const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].pl_tok; - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); - const param_name = sema.code.nullTerminatedString(extra.data.name); - const inst = sema.inst_map.get(param).?; - - try sema.addDbgVar(&child_block, inst, .dbg_arg_inline, param_name); - }, - .param_anytype, .param_anytype_comptime => { - const inst_data = sema.code.instructions.items(.data)[@intFromEnum(param)].str_tok; - const param_name = inst_data.get(sema.code); - const inst = sema.inst_map.get(param).?; + const is_inline_call = block.isComptime() or inline_requested; - try sema.addDbgVar(&child_block, inst, .dbg_arg_inline, param_name); - }, - else => continue, - }; + if (!is_inline_call) { + if (sema.func_is_naked) return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(call_src, "runtime {s} not allowed in naked function", .{@tagName(operation)}); + errdefer msg.destroy(gpa); + switch (operation) { + .call, .@"@call", .@"@panic", .@"error return" => {}, + .@"safety check" => try sema.errNote(call_src, msg, "use @setRuntimeSafety to disable runtime safety", .{}), + } + break :msg msg; + }); + for (args, 0..) |arg, arg_idx| { + try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_idx), arg); } + const runtime_func: Air.Inst.Ref, const runtime_args: []const Air.Inst.Ref = func: { + if (!func_ty_info.is_generic) break :func .{ callee, args }; - if (is_comptime_call and ensure_result_used) { - try sema.ensureResultUsed(block, sema.fn_ret_ty, call_src); - } + // Instantiate the generic function! - if (is_comptime_call or block.is_typeof) { - // Save the error trace as our first action in the function - // to match the behavior of runtime function calls. - const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block); - sema.error_return_trace_index_on_fn_entry = error_return_trace_index; - child_block.error_return_trace_index = error_return_trace_index; - } + // This may be an overestimate, but it's definitely sufficient. + const max_runtime_args = args_info.count() - @popCount(func_ty_info.comptime_bits); + var runtime_args: std.ArrayListUnmanaged(Air.Inst.Ref) = try .initCapacity(arena, max_runtime_args); + var runtime_param_tys: std.ArrayListUnmanaged(InternPool.Index) = try .initCapacity(arena, max_runtime_args); - // We temporarily set `allow_memoize` to `true` to track this comptime call. - // It is restored after this call finishes analysis, so that a caller may - // know whether an in-progress call (containing this call) may be memoized. - const old_allow_memoize = sema.allow_memoize; - defer sema.allow_memoize = old_allow_memoize and sema.allow_memoize; - sema.allow_memoize = true; + const comptime_args = try arena.alloc(InternPool.Index, args_info.count()); - // Store the current eval branch count so we can find out how many eval branches - // the comptime call caused. - const old_branch_count = sema.branch_count; + var noalias_bits: u32 = 0; - const result = result: { - sema.analyzeFnBody(&child_block, fn_info.body) catch |err| switch (err) { - error.ComptimeReturn => break :result inlining.comptime_result, - else => |e| return e, - }; - break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, merges, need_debug_scope); - }; + for (args, comptime_args, 0..) |arg, *comptime_arg, arg_idx| { + const arg_ty = sema.typeOf(arg); - if (is_comptime_call) { - const result_val = try sema.resolveConstValue(block, LazySrcLoc.unneeded, result, undefined); - const result_interned = result_val.toIntern(); - - // Transform ad-hoc inferred error set types into concrete error sets. - const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_interned); - - // If the result can mutate comptime vars, we must not memoize it, as it contains - // a reference to `comptime_allocs` so is not stable across instances of `Sema`. - // TODO: check whether any external comptime memory was mutated by the - // comptime function call. If so, then do not memoize the call here. - if (should_memoize and sema.allow_memoize and !Value.fromInterned(result_interned).canMutateComptimeVarState(zcu)) { - _ = try pt.intern(.{ .memoized_call = .{ - .func = module_fn_index, - .arg_values = memoized_arg_values, - .result = result_transformed, - .branch_count = sema.branch_count - old_branch_count, - } }); + const is_comptime = c: { + if (std.math.cast(u5, arg_idx)) |i| { + if (func_ty_info.paramIsComptime(i)) { + break :c true; + } + } + break :c try arg_ty.comptimeOnlySema(pt); + }; + const is_noalias = if (std.math.cast(u5, arg_idx)) |i| func_ty_info.paramIsNoalias(i) else false; + + if (is_comptime) { + // We already emitted an error if the argument isn't comptime-known. + comptime_arg.* = (try sema.resolveValue(arg)).?.toIntern(); + } else { + comptime_arg.* = .none; + if (is_noalias) { + const runtime_idx = runtime_args.items.len; + noalias_bits |= @as(u32, 1) << @intCast(runtime_idx); + } + runtime_args.appendAssumeCapacity(arg); + runtime_param_tys.appendAssumeCapacity(arg_ty.toIntern()); + } } - break :res Air.internedToRef(result_transformed); - } + const bare_ret_ty = if (fn_zir_info.inferred_error_set) t: { + break :t resolved_ret_ty.errorUnionPayload(zcu); + } else resolved_ret_ty; - if (try sema.resolveValue(result)) |result_val| { - const result_transformed = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern()); - break :res Air.internedToRef(result_transformed); - } + // We now need to actually create the function instance. + const func_instance = try ip.getFuncInstance(gpa, pt.tid, .{ + .param_types = runtime_param_tys.items, + .noalias_bits = noalias_bits, + .bare_return_type = bare_ret_ty.toIntern(), + .is_noinline = func_ty_info.is_noinline, + .inferred_error_set = fn_zir_info.inferred_error_set, + .generic_owner = func_val.?.toIntern(), + .comptime_args = comptime_args, + }); - const new_ty = try sema.resolveAdHocInferredErrorSetTy(block, call_src, sema.typeOf(result).toIntern()); - if (new_ty != .none) { - // TODO: mutate in place the previous instruction if possible - // rather than adding a bitcast instruction. - break :res try block.addBitCast(Type.fromInterned(new_ty), result); - } + // This call is problematic as it breaks guarantees about order-independency of semantic analysis. + // These guarantees are necessary for incremental compilation and parallel semantic analysis. + // See: #22410 + zcu.funcInfo(func_instance).maxBranchQuota(ip, sema.branch_quota); - break :res result; - } else res: { - assert(!func_ty_info.is_generic); + break :func .{ Air.internedToRef(func_instance), runtime_args.items }; + }; - const args = try sema.arena.alloc(Air.Inst.Ref, args_info.count()); - for (args, 0..) |*arg_out, arg_idx| { - // Non-generic, so param types are already resolved - const param_ty: ?Type = if (arg_idx < func_ty_info.param_types.len) ty: { - break :ty Type.fromInterned(func_ty_info.param_types.get(ip)[arg_idx]); - } else null; - if (param_ty) |t| assert(!t.isGenericPoison()); - arg_out.* = try args_info.analyzeArg(sema, block, arg_idx, param_ty, func_ty_info, func); - try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_idx), arg_out.*); - if (sema.typeOf(arg_out.*).zigTypeTag(zcu) == .noreturn) { - return arg_out.*; - } + ref_func: { + const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func; + if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func; + try sema.addReferenceEntry(call_src, .wrap(.{ .func = runtime_func_val.toIntern() })); + try zcu.ensureFuncBodyAnalysisQueued(runtime_func_val.toIntern()); } - if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); - switch (sema.owner.unwrap()) { .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {}, - .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { + .func => |owner_func| if (resolved_ret_ty.isError(zcu)) { ip.funcSetCallsOrAwaitsErrorableFn(owner_func); }, } - if (try sema.resolveValue(func)) |func_val| { - if (zcu.intern_pool.isFuncBody(func_val.toIntern())) { - try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = func_val.toIntern() })); - try zcu.ensureFuncBodyAnalysisQueued(func_val.toIntern()); - } - } + const call_tag: Air.Inst.Tag = switch (modifier) { + .auto, .no_async => .call, + .never_tail => .call_never_tail, + .never_inline => .call_never_inline, + .always_tail => .call_always_tail, - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + - args.len); - const func_inst = try block.addInst(.{ + .always_inline, + .compile_time, + .async_kw, + => unreachable, + }; + + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + runtime_args.len); + const result = try block.addInst(.{ .tag = call_tag, .data = .{ .pl_op = .{ - .operand = func, + .operand = runtime_func, .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = @intCast(args.len), + .args_len = @intCast(runtime_args.len), }), } }, }); - sema.appendRefsAssumeCapacity(args); + sema.appendRefsAssumeCapacity(runtime_args); + + if (ensure_result_used) { + try sema.ensureResultUsed(block, sema.typeOf(result), call_src); + } if (call_tag == .call_always_tail) { - if (ensure_result_used) { - try sema.ensureResultUsed(block, sema.typeOf(func_inst), call_src); - } - return sema.handleTailCall(block, call_src, func_ty, func_inst); - } - if (block.wantSafety() and func_ty_info.return_type == .noreturn_type) skip_safety: { - // Function pointers and extern functions aren't guaranteed to - // actually be noreturn so we add a safety check for them. - if (try sema.resolveValue(func)) |func_val| { - switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { - .func => break :skip_safety, - .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) { - .nav => |nav| { - try sema.ensureNavResolved(call_src, nav, .fully); - if (ip.getNav(nav).getExtern(ip) == null) break :skip_safety; - }, - else => {}, - }, - else => {}, - } - } - try sema.safetyPanic(block, call_src, .noreturn_returned); - return .unreachable_value; + return sema.handleTailCall(block, call_src, sema.typeOf(runtime_func), result); } - if (func_ty_info.return_type == .noreturn_type) { - _ = try block.addNoOp(.unreach); + + if (resolved_ret_ty.toIntern() == .noreturn_type) { + const want_check = c: { + if (!block.wantSafety()) break :c false; + if (func_val != null) break :c false; + break :c true; + }; + if (want_check) { + try sema.safetyPanic(block, call_src, .noreturn_returned); + } else { + _ = try block.addNoOp(.unreach); + } return .unreachable_value; } - break :res func_inst; - }; - if (ensure_result_used) { - try sema.ensureResultUsed(block, sema.typeOf(result), call_src); + return result; } - return result; -} -fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { - const pt = sema.pt; - const zcu = pt.zcu; - const target = zcu.getTarget(); - const backend = zcu.comp.getZigBackend(); - if (!target_util.supportsTailCall(target, backend)) { - return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{ - @tagName(backend), @tagName(target.cpu.arch), - }); + // This is an inline call. The function must be comptime-known. We will analyze its body directly using this `Sema`. + + const call_type: []const u8 = if (block.isComptime()) "comptime" else "inline"; + + if (modifier == .never_inline) { + return sema.fail(block, call_src, "cannot perform {s} call with 'never_inline' modifier", .{call_type}); } - const owner_func_ty = Type.fromInterned(zcu.funcInfo(sema.owner.unwrap().func).ty); - if (owner_func_ty.toIntern() != func_ty.toIntern()) { - return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ - func_ty.fmt(pt), owner_func_ty.fmt(pt), - }); + if (func_ty_info.is_noinline and !block.isComptime()) { + return sema.fail(block, call_src, "{s} call of noinline function", .{call_type}); } - _ = try block.addUnOp(.ret, result); - return .unreachable_value; -} - -/// Usually, returns null. If an argument was noreturn, returns that ref (which should become the call result). -fn analyzeInlineCallArg( - ics: *InlineCallSema, - arg_block: *Block, - param_block: *Block, - inst: Zir.Inst.Index, - new_param_types: []InternPool.Index, - arg_i: *u32, - args_info: CallArgsInfo, - is_comptime_call: bool, - should_memoize: *bool, - memoized_arg_values: []InternPool.Index, - func_ty_info: InternPool.Key.FuncType, - func_inst: Air.Inst.Ref, -) !?Air.Inst.Ref { - const zcu = ics.sema.pt.zcu; - const ip = &zcu.intern_pool; - const zir_tags = ics.callee().code.instructions.items(.tag); - switch (zir_tags[@intFromEnum(inst)]) { - .param_comptime, .param_anytype_comptime => param_block.inlining.?.has_comptime_args = true, - else => {}, + if (func_ty_info.is_var_args) { + return sema.fail(block, call_src, "{s} call of variadic function", .{call_type}); } - switch (zir_tags[@intFromEnum(inst)]) { - .param, .param_comptime => { - // Evaluate the parameter type expression now that previous ones have - // been mapped, and coerce the corresponding argument to it. - const pl_tok = ics.callee().code.instructions.items(.data)[@intFromEnum(inst)].pl_tok; - const param_src = param_block.tokenOffset(pl_tok.src_tok); - const extra = ics.callee().code.extraData(Zir.Inst.Param, pl_tok.payload_index); - const param_body = ics.callee().code.bodySlice(extra.end, extra.data.body_len); - const param_ty = param_ty: { - const raw_param_ty = func_ty_info.param_types.get(ip)[arg_i.*]; - if (raw_param_ty != .generic_poison_type) break :param_ty raw_param_ty; - const param_ty_inst = try ics.callee().resolveInlineBody(param_block, param_body, inst); - const param_ty = try ics.callee().analyzeAsType(param_block, param_src, param_ty_inst); - break :param_ty param_ty.toIntern(); - }; - new_param_types[arg_i.*] = param_ty; - const casted_arg = try args_info.analyzeArg(ics.caller(), arg_block, arg_i.*, Type.fromInterned(param_ty), func_ty_info, func_inst); - if (ics.caller().typeOf(casted_arg).zigTypeTag(zcu) == .noreturn) { - return casted_arg; - } - const arg_src = args_info.argSrc(arg_block, arg_i.*); - if (zir_tags[@intFromEnum(inst)] == .param_comptime) { - _ = try ics.caller().resolveConstValue(arg_block, arg_src, casted_arg, .{ .simple = .comptime_param_arg }); - } else if (!is_comptime_call and try Type.fromInterned(param_ty).comptimeOnlySema(ics.callee().pt)) { - _ = try ics.caller().resolveConstValue(arg_block, arg_src, casted_arg, .{ .comptime_only = .{ - .ty = .fromInterned(param_ty), - .msg = .param_ty_arg, - } }); - } - if (is_comptime_call) { - ics.callee().inst_map.putAssumeCapacityNoClobber(inst, casted_arg); - const arg_val = try ics.caller().resolveConstValue(arg_block, arg_src, casted_arg, null); - switch (arg_val.toIntern()) { - .generic_poison, .generic_poison_type => { - // This function is currently evaluated as part of an as-of-yet unresolvable - // parameter or return type. - return error.GenericPoison; - }, - else => {}, - } - // Needed so that lazy values do not trigger - // assertion due to type not being resolved - // when the hash function is called. - const resolved_arg_val = try ics.caller().resolveLazyValue(arg_val); - should_memoize.* = should_memoize.* and !resolved_arg_val.canMutateComptimeVarState(zcu); - memoized_arg_values[arg_i.*] = resolved_arg_val.toIntern(); - } else { - ics.callee().inst_map.putAssumeCapacityNoClobber(inst, casted_arg); - } - - if (try ics.caller().resolveValue(casted_arg)) |_| { - param_block.inlining.?.has_comptime_args = true; - } - - arg_i.* += 1; - }, - .param_anytype, .param_anytype_comptime => { - // No coercion needed. - const uncasted_arg = try args_info.analyzeArg(ics.caller(), arg_block, arg_i.*, Type.generic_poison, func_ty_info, func_inst); - if (ics.caller().typeOf(uncasted_arg).zigTypeTag(zcu) == .noreturn) { - return uncasted_arg; - } - const arg_src = args_info.argSrc(arg_block, arg_i.*); - new_param_types[arg_i.*] = ics.caller().typeOf(uncasted_arg).toIntern(); - - if (is_comptime_call) { - ics.callee().inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg); - const arg_val = try ics.caller().resolveConstValue(arg_block, arg_src, uncasted_arg, null); - switch (arg_val.toIntern()) { - .generic_poison, .generic_poison_type => { - // This function is currently evaluated as part of an as-of-yet unresolvable - // parameter or return type. - return error.GenericPoison; - }, - else => {}, - } - // Needed so that lazy values do not trigger - // assertion due to type not being resolved - // when the hash function is called. - const resolved_arg_val = try ics.caller().resolveLazyValue(arg_val); - should_memoize.* = should_memoize.* and !resolved_arg_val.canMutateComptimeVarState(zcu); - memoized_arg_values[arg_i.*] = resolved_arg_val.toIntern(); - } else { - if (zir_tags[@intFromEnum(inst)] == .param_anytype_comptime) { - _ = try ics.caller().resolveConstValue(arg_block, arg_src, uncasted_arg, .{ .simple = .comptime_param_arg }); - } - ics.callee().inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg); - } + if (func_val == null) { + if (func_is_extern) { + return sema.fail(block, call_src, "{s} call of extern function", .{call_type}); + } + return sema.failWithNeededComptime( + block, + func_src, + .{ .simple = if (block.isComptime()) .comptime_call_target else .inline_call_target }, + ); + } - if (try ics.caller().resolveValue(uncasted_arg)) |_| { - param_block.inlining.?.has_comptime_args = true; + if (block.isComptime()) { + for (args, 0..) |arg, arg_idx| { + if (!try sema.isComptimeKnown(arg)) { + const arg_src = args_info.argSrc(block, arg_idx); + return sema.failWithNeededComptime(block, arg_src, null); } - - arg_i.* += 1; - }, - else => {}, + } } - return null; -} - -fn instantiateGenericCall( - sema: *Sema, - block: *Block, - func: Air.Inst.Ref, - func_src: LazySrcLoc, - call_src: LazySrcLoc, - ensure_result_used: bool, - args_info: CallArgsInfo, - call_tag: Air.Inst.Tag, - call_dbg_node: ?Zir.Inst.Index, - /// Populated when `error.ComptimeReturn` is returned. - comptime_ret_ty: *Type, -) CompileError!Air.Inst.Ref { - const pt = sema.pt; - const zcu = pt.zcu; - const gpa = sema.gpa; - const ip = &zcu.intern_pool; - - // Generic function pointers are comptime-only types, so `func` is definitely comptime-known. - const func_val = (sema.resolveValue(func) catch unreachable).?; - if (func_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, func_src); + // For an inline call, we depend on the source code of the whole function definition. + try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index }); - const generic_owner = switch (zcu.intern_pool.indexToKey(func_val.toIntern())) { - .func => func_val.toIntern(), - .ptr => |ptr| ip.getNav(ptr.base_addr.nav).status.fully_resolved.val, - else => unreachable, - }; - const generic_owner_func = zcu.intern_pool.indexToKey(generic_owner).func; - const generic_owner_ty_info = zcu.typeToFunc(Type.fromInterned(generic_owner_func.ty)).?; + try sema.emitBackwardBranch(block, call_src); - try sema.declareDependency(.{ .src_hash = generic_owner_func.zir_body_inst }); - - // Even though there may already be a generic instantiation corresponding - // to this callsite, we must evaluate the expressions of the generic - // function signature with the values of the callsite plugged in. - // Importantly, this may include type coercions that determine whether the - // instantiation is a match of a previous instantiation. - // The actual monomorphization happens via adding `func_instance` to - // `InternPool`. - - // Since we are looking at the generic owner here, it has analysis state. - const fn_nav = ip.getNav(generic_owner_func.owner_nav); - const fn_zir = zcu.navFileScope(generic_owner_func.owner_nav).zir; - const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); + const want_memoize = m: { + // TODO: comptime call memoization is currently not supported under incremental compilation + // since dependencies are not marked on callers. If we want to keep this around (we should + // check that it's worthwhile first!), each memoized call needs an `AnalUnit`. + if (zcu.comp.incremental) break :m false; + if (!block.isComptime()) break :m false; + for (args) |a| { + const val = (try sema.resolveValue(a)).?; + if (val.canMutateComptimeVarState(zcu)) break :m false; + } + break :m true; + }; + const memoized_arg_values: []const InternPool.Index = if (want_memoize) arg_vals: { + const vals = try sema.arena.alloc(InternPool.Index, args.len); + for (vals, args) |*v, a| v.* = (try sema.resolveValue(a)).?.toIntern(); + break :arg_vals vals; + } else undefined; + if (want_memoize) memoize: { + const memoized_call_index = ip.getIfExists(.{ + .memoized_call = .{ + .func = func_val.?.toIntern(), + .arg_values = memoized_arg_values, + .result = undefined, // ignored by hash+eql + .branch_count = undefined, // ignored by hash+eql + }, + }) orelse break :memoize; + const memoized_call = ip.indexToKey(memoized_call_index).memoized_call; + if (sema.branch_count + memoized_call.branch_count > sema.branch_quota) { + // Let the call play out se we get the correct source location for the + // "evaluation exceeded X backwards branches" error. + break :memoize; + } + sema.branch_count += memoized_call.branch_count; + const result = Air.internedToRef(memoized_call.result); + if (ensure_result_used) { + try sema.ensureResultUsed(block, sema.typeOf(result), call_src); + } + return result; + } - const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); - @memset(comptime_args, .none); + var new_ies: InferredErrorSet = .{ .func = .none }; - // We may overestimate the number of runtime args, but this will definitely be sufficient. - const max_runtime_args = args_info.count() - @popCount(generic_owner_ty_info.comptime_bits); - var runtime_args = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(sema.arena, max_runtime_args); + const old_inst_map = sema.inst_map; + const old_code = sema.code; + const old_func_index = sema.func_index; + const old_fn_ret_ty = sema.fn_ret_ty; + const old_fn_ret_ty_ies = sema.fn_ret_ty_ies; + const old_error_return_trace_index_on_fn_entry = sema.error_return_trace_index_on_fn_entry; + defer { + sema.inst_map.deinit(gpa); + sema.inst_map = old_inst_map; + sema.code = old_code; + sema.func_index = old_func_index; + sema.fn_ret_ty = old_fn_ret_ty; + sema.fn_ret_ty_ies = old_fn_ret_ty_ies; + sema.error_return_trace_index_on_fn_entry = old_error_return_trace_index_on_fn_entry; + } + sema.inst_map = .{}; + sema.code = fn_zir; + sema.func_index = func_val.?.toIntern(); + sema.fn_ret_ty = if (fn_zir_info.inferred_error_set) try pt.errorUnionType( + .fromInterned(.adhoc_inferred_error_set_type), + resolved_ret_ty.errorUnionPayload(zcu), + ) else resolved_ret_ty; + sema.fn_ret_ty_ies = if (fn_zir_info.inferred_error_set) &new_ies else null; + + try sema.inst_map.ensureSpaceForInstructions(gpa, fn_zir_info.param_body); + for (args, 0..) |arg, arg_idx| { + sema.inst_map.putAssumeCapacityNoClobber(fn_zir_info.param_body[arg_idx], arg); + } + + const need_debug_scope = !block.isComptime() and !block.is_typeof and !block.ownerModule().strip; + const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); + try sema.air_instructions.append(gpa, .{ + .tag = if (need_debug_scope) .dbg_inline_block else .block, + .data = undefined, + }); - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .pt = pt, - .gpa = gpa, - .arena = sema.arena, - .code = fn_zir, - // We pass the generic callsite's owner decl here because whatever `Decl` - // dependencies are chased at this point should be attached to the - // callsite, not the `Decl` associated with the `func_instance`. - .owner = sema.owner, - .func_index = sema.func_index, - // This may not be known yet, since the calling convention could be generic, but there - // should be no illegal instructions encountered while creating the function anyway. - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .comptime_args = comptime_args, - .generic_owner = generic_owner, - .generic_call_src = call_src, - .branch_quota = sema.branch_quota, - .branch_count = sema.branch_count, - .comptime_err_ret_trace = sema.comptime_err_ret_trace, + var inlining: Block.Inlining = .{ + .call_block = block, + .call_src = call_src, + .has_comptime_args = for (args) |a| { + if (try sema.isComptimeKnown(a)) break true; + } else false, + .func = func_val.?.toIntern(), + .comptime_result = undefined, + .merges = .{ + .block_inst = block_inst, + .results = .empty, + .br_list = .empty, + .src_locs = .empty, + }, }; - defer child_sema.deinit(); - var child_block: Block = .{ .parent = null, - .sema = &child_sema, + .sema = sema, .namespace = fn_nav.analysis.?.namespace, .instructions = .{}, - .inlining = null, - .comptime_reason = undefined, // set as needed + .inlining = &inlining, + .is_typeof = block.is_typeof, + .comptime_reason = if (block.isComptime()) .inlining_parent else null, + .error_return_trace_index = block.error_return_trace_index, + .runtime_cond = block.runtime_cond, + .runtime_loop = block.runtime_loop, + .runtime_index = block.runtime_index, .src_base_inst = fn_nav.analysis.?.zir_index, .type_name_ctx = fn_nav.fqn, }; - defer child_block.instructions.deinit(gpa); - - try child_sema.inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); - - for (fn_info.param_body[0..args_info.count()], 0..) |param_inst, arg_index| { - const param_tag = fn_zir.instructions.items(.tag)[@intFromEnum(param_inst)]; - const param_ty = switch (generic_owner_ty_info.param_types.get(ip)[arg_index]) { - else => |ty| Type.fromInterned(ty), // parameter is not generic, so type is already resolved - .generic_poison_type => param_ty: { - // We have every parameter before this one, so can resolve this parameter's type now. - // However, first check the param type, since it may be anytype. - switch (param_tag) { - .param_anytype, .param_anytype_comptime => { - // The parameter doesn't have a type. - break :param_ty Type.generic_poison; - }, - .param, .param_comptime => { - // We now know every prior parameter, so can resolve this - // parameter's type. The child sema has these types. - const param_data = fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].pl_tok; - const param_extra = fn_zir.extraData(Zir.Inst.Param, param_data.payload_index); - const param_ty_body = fn_zir.bodySlice(param_extra.end, param_extra.data.body_len); - - // Make sure any nested instructions don't clobber our work. - const prev_params = child_block.params; - const prev_no_partial_func_ty = child_sema.no_partial_func_ty; - const prev_generic_owner = child_sema.generic_owner; - const prev_generic_call_src = child_sema.generic_call_src; - child_block.params = .{}; - child_sema.no_partial_func_ty = true; - child_sema.generic_owner = .none; - child_sema.generic_call_src = LazySrcLoc.unneeded; - defer { - child_block.params = prev_params; - child_sema.no_partial_func_ty = prev_no_partial_func_ty; - child_sema.generic_owner = prev_generic_owner; - child_sema.generic_call_src = prev_generic_call_src; - } + defer child_block.instructions.deinit(gpa); + defer inlining.merges.deinit(gpa); - const param_ty_src = child_block.tokenOffset(param_data.src_tok); - child_block.comptime_reason = .{ .reason = .{ - .src = param_ty_src, - .r = .{ .simple = .type }, - } }; - const param_ty_inst = try child_sema.resolveInlineBody(&child_block, param_ty_body, param_inst); - break :param_ty try child_sema.analyzeAsType(&child_block, param_ty_src, param_ty_inst); - }, - else => unreachable, - } - }, - }; - const arg_ref = try args_info.analyzeArg(sema, block, arg_index, param_ty, generic_owner_ty_info, func); - try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_index), arg_ref); - const arg_ty = sema.typeOf(arg_ref); - if (arg_ty.zigTypeTag(zcu) == .noreturn) { - // This terminates argument analysis. - return arg_ref; + if (!inlining.has_comptime_args) { + var block_it = block; + while (block_it.inlining) |parent_inlining| { + if (!parent_inlining.has_comptime_args and parent_inlining.func == func_val.?.toIntern()) { + return sema.fail(block, call_src, "inline call is recursive", .{}); + } + block_it = parent_inlining.call_block; } + } - const arg_is_comptime = switch (param_tag) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => try arg_ty.comptimeOnlySema(pt), - else => unreachable, + if (!block.isComptime() and !block.is_typeof) { + const zir_tags = sema.code.instructions.items(.tag); + const zir_datas = sema.code.instructions.items(.data); + for (fn_zir_info.param_body) |inst| switch (zir_tags[@intFromEnum(inst)]) { + .param, .param_comptime => { + const extra = sema.code.extraData(Zir.Inst.Param, zir_datas[@intFromEnum(inst)].pl_tok.payload_index); + const param_name = sema.code.nullTerminatedString(extra.data.name); + const air_inst = sema.inst_map.get(inst).?; + try sema.addDbgVar(&child_block, air_inst, .dbg_arg_inline, param_name); + }, + .param_anytype, .param_anytype_comptime => { + const param_name = zir_datas[@intFromEnum(inst)].str_tok.get(sema.code); + const air_inst = sema.inst_map.get(inst).?; + try sema.addDbgVar(&child_block, air_inst, .dbg_arg_inline, param_name); + }, + else => {}, }; - - if (arg_is_comptime) { - if (try sema.resolveValue(arg_ref)) |arg_val| { - comptime_args[arg_index] = arg_val.toIntern(); - child_sema.inst_map.putAssumeCapacityNoClobber( - param_inst, - Air.internedToRef(arg_val.toIntern()), - ); - } else switch (param_tag) { - .param_comptime, - .param_anytype_comptime, - => return sema.failWithOwnedErrorMsg(block, msg: { - const arg_src = args_info.argSrc(block, arg_index); - const msg = try sema.errMsg(arg_src, "runtime-known argument passed to comptime parameter", .{}); - errdefer msg.destroy(sema.gpa); - const param_src = child_block.tokenOffset(switch (param_tag) { - .param_comptime => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].pl_tok.src_tok, - .param_anytype_comptime => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].str_tok.src_tok, - else => unreachable, - }); - try child_sema.errNote(param_src, msg, "declared comptime here", .{}); - break :msg msg; - }), - - .param, - .param_anytype, - => return sema.failWithOwnedErrorMsg(block, msg: { - const arg_src = args_info.argSrc(block, arg_index); - const msg = try sema.errMsg(arg_src, "runtime-known argument passed to parameter of comptime-only type", .{}); - errdefer msg.destroy(sema.gpa); - const param_src = child_block.tokenOffset(switch (param_tag) { - .param => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].pl_tok.src_tok, - .param_anytype => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].str_tok.src_tok, - else => unreachable, - }); - try child_sema.errNote(param_src, msg, "declared here", .{}); - try sema.explainWhyTypeIsComptime(msg, arg_src, arg_ty); - break :msg msg; - }), - - else => unreachable, - } - } else { - // The parameter is runtime-known. - const param_name: Zir.NullTerminatedString = switch (param_tag) { - .param_anytype => fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].str_tok.start, - .param => name: { - const inst_data = fn_zir.instructions.items(.data)[@intFromEnum(param_inst)].pl_tok; - const extra = fn_zir.extraData(Zir.Inst.Param, inst_data.payload_index); - break :name extra.data.name; - }, - else => unreachable, - }; - child_sema.inst_map.putAssumeCapacityNoClobber(param_inst, try child_block.addInst(.{ - .tag = .arg, - .data = .{ .arg = .{ - .ty = Air.internedToRef(arg_ty.toIntern()), - .name = if (child_block.ownerModule().strip) - .none - else - try sema.appendAirString(fn_zir.nullTerminatedString(param_name)), - } }, - })); - try child_block.params.append(sema.arena, .{ - .ty = arg_ty.toIntern(), // This is the type after coercion - .is_comptime = false, // We're adding only runtime args to the instantiation - .name = param_name, - }); - runtime_args.appendAssumeCapacity(arg_ref); - } } - // We've already handled parameters, so don't resolve the whole body. Instead, just - // do the instructions after the params (i.e. the func itself). - child_block.comptime_reason = .{ .reason = .{ - .src = call_src, - .r = .{ .simple = .type }, - } }; - const new_func_inst = try child_sema.resolveInlineBody(&child_block, fn_info.param_body[args_info.count()..], fn_info.param_body_inst); - const callee_index = (child_sema.resolveConstDefinedValue(&child_block, LazySrcLoc.unneeded, new_func_inst, undefined) catch unreachable).toIntern(); + child_block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block); + // Save the error trace as our first action in the function + // to match the behavior of runtime function calls. + const error_return_trace_index_on_parent_fn_entry = sema.error_return_trace_index_on_fn_entry; + sema.error_return_trace_index_on_fn_entry = child_block.error_return_trace_index; + defer sema.error_return_trace_index_on_fn_entry = error_return_trace_index_on_parent_fn_entry; - const callee = zcu.funcInfo(callee_index); - callee.maxBranchQuota(ip, sema.branch_quota); + // We temporarily set `allow_memoize` to `true` to track this comptime call. + // It is restored after the call finishes analysis, so that a caller may + // know whether an in-progress call (containing this call) may be memoized. + const old_allow_memoize = sema.allow_memoize; + defer sema.allow_memoize = old_allow_memoize and sema.allow_memoize; + sema.allow_memoize = true; - // Make a runtime call to the new function, making sure to omit the comptime args. - const func_ty = Type.fromInterned(callee.ty); - const func_ty_info = zcu.typeToFunc(func_ty).?; + // Store the current eval branch count so we can find out how many eval branches + // the comptime call caused. + const old_branch_count = sema.branch_count; - // If the call evaluated to a return type that requires comptime, never mind - // our generic instantiation. Instead we need to perform a comptime call. - if (try Type.fromInterned(func_ty_info.return_type).comptimeOnlySema(pt)) { - comptime_ret_ty.* = .fromInterned(func_ty_info.return_type); - return error.ComptimeReturn; - } - // Similarly, if the call evaluated to a generic type we need to instead - // call it inline. - if (func_ty_info.is_generic or func_ty_info.cc == .@"inline") { - return error.GenericPoison; - } + const result_raw: Air.Inst.Ref = result: { + sema.analyzeFnBody(&child_block, fn_zir_info.body) catch |err| switch (err) { + error.ComptimeReturn => break :result inlining.comptime_result, + else => |e| return e, + }; + break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, &inlining.merges, need_debug_scope); + }; - if (call_dbg_node) |some| try sema.zirDbgStmt(block, some); + const result: Air.Inst.Ref = if (try sema.resolveValue(result_raw)) |result_val| r: { + const val_resolved = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern()); + break :r Air.internedToRef(val_resolved); + } else r: { + const resolved_ty = try sema.resolveAdHocInferredErrorSetTy(block, call_src, sema.typeOf(result_raw).toIntern()); + if (resolved_ty == .none) break :r result_raw; + // TODO: mutate in place the previous instruction if possible + // rather than adding a bitcast instruction. + break :r try block.addBitCast(.fromInterned(resolved_ty), result_raw); + }; - switch (sema.owner.unwrap()) { - .@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {}, - .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) { - ip.funcSetCallsOrAwaitsErrorableFn(owner_func); - }, + if (block.isComptime()) { + const result_val = (try sema.resolveValue(result)).?; + if (want_memoize and sema.allow_memoize and !result_val.canMutateComptimeVarState(zcu)) { + _ = try pt.intern(.{ .memoized_call = .{ + .func = func_val.?.toIntern(), + .arg_values = memoized_arg_values, + .result = result_val.toIntern(), + .branch_count = sema.branch_count - old_branch_count, + } }); + } } - try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = callee_index })); - try zcu.ensureFuncBodyAnalysisQueued(callee_index); - - try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).@"struct".fields.len + runtime_args.items.len); - const result = try block.addInst(.{ - .tag = call_tag, - .data = .{ .pl_op = .{ - .operand = Air.internedToRef(callee_index), - .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = @intCast(runtime_args.items.len), - }), - } }, - }); - sema.appendRefsAssumeCapacity(runtime_args.items); - - // `child_sema` is owned by us, so just take its exports. - try sema.exports.appendSlice(sema.gpa, child_sema.exports.items); - if (ensure_result_used) { try sema.ensureResultUsed(block, sema.typeOf(result), call_src); } - if (call_tag == .call_always_tail) { - return sema.handleTailCall(block, call_src, func_ty, result); + + return result; +} + +fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const target = zcu.getTarget(); + const backend = zcu.comp.getZigBackend(); + if (!target_util.supportsTailCall(target, backend)) { + return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{ + @tagName(backend), @tagName(target.cpu.arch), + }); } - if (func_ty.fnReturnType(zcu).isNoReturn(zcu)) { - _ = try block.addNoOp(.unreach); - return .unreachable_value; + const owner_func_ty = Type.fromInterned(zcu.funcInfo(sema.owner.unwrap().func).ty); + if (owner_func_ty.toIntern() != func_ty.toIntern()) { + return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ + func_ty.fmt(pt), owner_func_ty.fmt(pt), + }); } - return result; + _ = try block.addUnOp(.ret, result); + return .unreachable_value; } fn zirIntType(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -9576,9 +9226,7 @@ fn zirFunc( // the callconv based on whether it is exported. Otherwise, the callconv defaults // to `.auto`. const cc: std.builtin.CallingConvention = if (has_body) cc: { - const func_decl_nav = if (sema.generic_owner != .none) nav: { - break :nav zcu.funcInfo(sema.generic_owner).owner_nav; - } else sema.owner.unwrap().nav_val; + const func_decl_nav = sema.owner.unwrap().nav_val; const fn_is_exported = exported: { const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail; const zir_decl = sema.code.getDeclaration(decl_inst); @@ -9635,17 +9283,11 @@ fn resolveGenericBody( // Make sure any nested param instructions don't clobber our work. const prev_params = block.params; const prev_no_partial_func_type = sema.no_partial_func_ty; - const prev_generic_owner = sema.generic_owner; - const prev_generic_call_src = sema.generic_call_src; block.params = .{}; sema.no_partial_func_ty = true; - sema.generic_owner = .none; - sema.generic_call_src = LazySrcLoc.unneeded; defer { block.params = prev_params; sema.no_partial_func_ty = prev_no_partial_func_type; - sema.generic_owner = prev_generic_owner; - sema.generic_call_src = prev_generic_call_src; } const uncasted = sema.resolveInlineBody(block, body, func_inst) catch |err| break :err err; @@ -9911,16 +9553,10 @@ fn funcCommon( const cc_src = block.src(.{ .node_offset_fn_type_cc = src_node_offset }); const func_src = block.nodeOffset(src_node_offset); - var is_generic = bare_return_type.isGenericPoison(); + if (bare_return_type.isGenericPoison() and sema.no_partial_func_ty) return error.GenericPoison; - if (var_args) { - if (is_generic) { - return sema.fail(block, func_src, "generic function cannot be variadic", .{}); - } - try sema.checkCallConvSupportsVarArgs(block, cc_src, cc); - } - - const is_source_decl = sema.generic_owner == .none; + const ret_ty_requires_comptime = try bare_return_type.comptimeOnlySema(pt); + var is_generic = bare_return_type.isGenericPoison() or ret_ty_requires_comptime; var comptime_bits: u32 = 0; for (block.params.items(.ty), block.params.items(.is_comptime), 0..) |param_ty_ip, param_is_comptime, i| { @@ -9933,16 +9569,21 @@ fn funcCommon( .fn_proto_node_offset = src_node_offset, .param_index = @intCast(i), } }); - const requires_comptime = try param_ty.comptimeOnlySema(pt); - if (param_is_comptime or requires_comptime) { + const param_ty_comptime = try param_ty.comptimeOnlySema(pt); + const param_ty_generic = param_ty.isGenericPoison(); + if (param_ty_generic and sema.no_partial_func_ty) { + return error.GenericPoison; + } + if (param_is_comptime or param_ty_comptime or param_ty_generic) { + is_generic = true; + } + if (param_is_comptime) { comptime_bits |= @as(u32, 1) << @intCast(i); // TODO: handle cast error } - const this_generic = param_ty.isGenericPoison(); - is_generic = is_generic or this_generic; if (param_is_comptime and !target_util.fnCallConvAllowsZigTypes(cc)) { return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } - if (this_generic and !sema.no_partial_func_ty and !target_util.fnCallConvAllowsZigTypes(cc)) { + if (param_ty_generic and !target_util.fnCallConvAllowsZigTypes(cc)) { return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)}); } if (!param_ty.isValidParamType(zcu)) { @@ -9951,7 +9592,7 @@ fn funcCommon( opaque_str, param_ty.fmt(pt), }); } - if (!this_generic and !target_util.fnCallConvAllowsZigTypes(cc) and !try sema.validateExternType(param_ty, .param_ty)) { + if (!param_ty_generic and !target_util.fnCallConvAllowsZigTypes(cc) and !try sema.validateExternType(param_ty, .param_ty)) { const msg = msg: { const msg = try sema.errMsg(param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{ param_ty.fmt(pt), @tagName(cc), @@ -9965,7 +9606,7 @@ fn funcCommon( }; return sema.failWithOwnedErrorMsg(block, msg); } - if (is_source_decl and requires_comptime and !param_is_comptime and has_body and !block.isComptime()) { + if (param_ty_comptime and !param_is_comptime and has_body and !block.isComptime()) { const msg = msg: { const msg = try sema.errMsg(param_src, "parameter of type '{}' must be declared comptime", .{ param_ty.fmt(pt), @@ -9979,7 +9620,7 @@ fn funcCommon( }; return sema.failWithOwnedErrorMsg(block, msg); } - if (is_source_decl and !this_generic and is_noalias and + if (!param_ty_generic and is_noalias and !(param_ty.zigTypeTag(zcu) == .pointer or param_ty.isPtrLikeOptional(zcu))) { return sema.fail(block, param_src, "non-pointer parameter declared noalias", .{}); @@ -10007,48 +9648,17 @@ fn funcCommon( } } - const ret_ty_requires_comptime = try bare_return_type.comptimeOnlySema(pt); + if (var_args) { + if (is_generic) { + return sema.fail(block, func_src, "generic function cannot be variadic", .{}); + } + try sema.checkCallConvSupportsVarArgs(block, cc_src, cc); + } + const ret_poison = bare_return_type.isGenericPoison(); - const final_is_generic = is_generic or comptime_bits != 0 or ret_ty_requires_comptime; const param_types = block.params.items(.ty); - if (!is_source_decl) { - assert(has_body); - assert(!is_generic); - assert(comptime_bits == 0); - assert(!var_args); - if (inferred_error_set) { - try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src); - } - const func_index = try ip.getFuncInstance(gpa, pt.tid, .{ - .param_types = param_types, - .noalias_bits = noalias_bits, - .bare_return_type = bare_return_type.toIntern(), - .is_noinline = is_noinline, - .inferred_error_set = inferred_error_set, - .generic_owner = sema.generic_owner, - .comptime_args = sema.comptime_args, - }); - return finishFunc( - sema, - block, - func_index, - .none, - ret_poison, - bare_return_type, - ret_ty_src, - cc, - is_source_decl, - ret_ty_requires_comptime, - func_inst, - cc_src, - is_noinline, - is_generic, - final_is_generic, - ); - } - if (inferred_error_set) { assert(has_body); if (!ret_poison) @@ -10062,7 +9672,7 @@ fn funcCommon( .bare_return_type = bare_return_type.toIntern(), .cc = cc, .is_var_args = var_args, - .is_generic = final_is_generic, + .is_generic = is_generic, .is_noinline = is_noinline, .zir_body_inst = try block.trackZir(func_inst), @@ -10080,13 +9690,11 @@ fn funcCommon( bare_return_type, ret_ty_src, cc, - is_source_decl, ret_ty_requires_comptime, func_inst, cc_src, is_noinline, is_generic, - final_is_generic, ); } @@ -10097,7 +9705,7 @@ fn funcCommon( .return_type = bare_return_type.toIntern(), .cc = cc, .is_var_args = var_args, - .is_generic = final_is_generic, + .is_generic = is_generic, .is_noinline = is_noinline, }); @@ -10122,13 +9730,11 @@ fn funcCommon( bare_return_type, ret_ty_src, cc, - is_source_decl, ret_ty_requires_comptime, func_inst, cc_src, is_noinline, is_generic, - final_is_generic, ); } @@ -10141,13 +9747,11 @@ fn funcCommon( bare_return_type, ret_ty_src, cc, - is_source_decl, ret_ty_requires_comptime, func_inst, cc_src, is_noinline, is_generic, - final_is_generic, ); } @@ -10160,13 +9764,11 @@ fn finishFunc( bare_return_type: Type, ret_ty_src: LazySrcLoc, cc_resolved: std.builtin.CallingConvention, - is_source_decl: bool, ret_ty_requires_comptime: bool, func_inst: Zir.Inst.Index, cc_src: LazySrcLoc, is_noinline: bool, is_generic: bool, - final_is_generic: bool, ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; @@ -10203,7 +9805,7 @@ fn finishFunc( // If the return type is comptime-only but not dependent on parameters then // all parameter types also need to be comptime. - if (is_source_decl and opt_func_index != .none and ret_ty_requires_comptime and !block.isComptime()) comptime_check: { + if (opt_func_index != .none and ret_ty_requires_comptime and !block.isComptime()) comptime_check: { for (block.params.items(.is_comptime)) |is_comptime| { if (!is_comptime) break; } else break :comptime_check; @@ -10300,8 +9902,7 @@ fn finishFunc( }), } - if (is_generic and sema.no_partial_func_ty) return error.GenericPoison; - if (!final_is_generic and sema.wantErrorReturnTracing(return_type)) { + if (!is_generic and sema.wantErrorReturnTracing(return_type)) { // Make sure that StackTrace's fields are resolved so that the backend can // lower this fn type. const unresolved_stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(0), .StackTrace); @@ -10328,17 +9929,11 @@ fn zirParam( // Make sure any nested param instructions don't clobber our work. const prev_params = block.params; const prev_no_partial_func_type = sema.no_partial_func_ty; - const prev_generic_owner = sema.generic_owner; - const prev_generic_call_src = sema.generic_call_src; block.params = .{}; sema.no_partial_func_ty = true; - sema.generic_owner = .none; - sema.generic_call_src = LazySrcLoc.unneeded; defer { block.params = prev_params; sema.no_partial_func_ty = prev_no_partial_func_type; - sema.generic_owner = prev_generic_owner; - sema.generic_call_src = prev_generic_call_src; } if (sema.resolveInlineBody(block, body, inst)) |param_ty_inst| { @@ -26646,11 +26241,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A break :blk try sema.analyzeValueAsCallconv(block, cc_src, cc_val); } else cc: { if (has_body) { - const func_decl_nav = if (sema.generic_owner != .none) nav: { - // Generic instance -- use the original function declaration to - // look for the `export` syntax. - break :nav zcu.funcInfo(sema.generic_owner).owner_nav; - } else sema.owner.unwrap().nav_val; + const func_decl_nav = sema.owner.unwrap().nav_val; const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail; const zir_decl = sema.code.getDeclaration(func_decl_inst); if (zir_decl.linkage == .@"export") { @@ -31068,9 +30659,16 @@ fn coerceInMemoryAllowedFns( const dest_param_ty: Type = .fromInterned(dest_info.param_types.get(ip)[param_i]); const src_param_ty: Type = .fromInterned(src_info.param_types.get(ip)[param_i]); - const src_is_comptime = src_info.paramIsComptime(@intCast(param_i)); - const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i)); - if (src_is_comptime != dest_is_comptime) { + comptime_param: { + const src_is_comptime = src_info.paramIsComptime(@intCast(param_i)); + const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i)); + if (src_is_comptime == dest_is_comptime) break :comptime_param; + if (!dest_is_mut and src_is_comptime and !dest_is_comptime and try dest_param_ty.comptimeOnlySema(pt)) { + // A parameter which is marked `comptime` can drop that annotation if the type is comptime-only. + // The function remains generic, and the parameter is going to be comptime-resolved either way, + // so this just affects whether or not the argument is comptime-evaluated at the call site. + break :comptime_param; + } return .{ .fn_param_comptime = .{ .index = param_i, .wanted = dest_is_comptime, diff --git a/src/Zcu.zig b/src/Zcu.zig index 60aeffdf4457..338c4c55281c 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1928,6 +1928,24 @@ pub const SrcLoc = struct { }, } }, + .func_decl_param_comptime => |param_idx| { + const tree = try src_loc.file_scope.getTree(gpa); + var buf: [1]Ast.Node.Index = undefined; + const full = tree.fullFnProto(&buf, src_loc.base_node).?; + var param_it = full.iterate(tree); + for (0..param_idx) |_| assert(param_it.next() != null); + const param = param_it.next().?; + return tree.tokenToSpan(param.comptime_noalias.?); + }, + .func_decl_param_ty => |param_idx| { + const tree = try src_loc.file_scope.getTree(gpa); + var buf: [1]Ast.Node.Index = undefined; + const full = tree.fullFnProto(&buf, src_loc.base_node).?; + var param_it = full.iterate(tree); + for (0..param_idx) |_| assert(param_it.next() != null); + const param = param_it.next().?; + return tree.nodeToSpan(param.type_expr); + }, } } }; @@ -2235,6 +2253,12 @@ pub const LazySrcLoc = struct { /// The source location points to the "tag" capture (second capture) of /// a specific case of a `switch`. switch_tag_capture: SwitchCapture, + /// The source location points to the `comptime` token on the given comptime parameter, + /// where the base node is a function declaration. The value is the parameter index. + func_decl_param_comptime: u32, + /// The source location points to the type annotation on the given function parameter, + /// where the base node is a function declaration. The value is the parameter index. + func_decl_param_ty: u32, pub const FnProtoParam = struct { /// The offset of the function prototype AST node. diff --git a/src/translate_c.zig b/src/translate_c.zig index 64a673456a06..c28d2c46b299 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -160,6 +160,7 @@ pub fn translate( context.pattern_list.deinit(gpa); } + @setEvalBranchQuota(2000); inline for (@typeInfo(std.zig.c_builtins).@"struct".decls) |decl| { const builtin = try Tag.pub_var_simple.create(arena, .{ .name = decl.name, diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index f53e05feda4e..dd3de9bb9feb 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -363,7 +363,7 @@ test "comptime modification of const struct field" { } test "refer to the type of a generic function" { - const Func = fn (type) void; + const Func = fn (comptime type) void; const f: Func = doNothingWithType; f(i32); } diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index f7cc179f5e99..d49c53470915 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -427,7 +427,7 @@ test "generic function passed as comptime argument" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const S = struct { - fn doMath(comptime f: fn (type, i32, i32) error{Overflow}!i32, a: i32, b: i32) !void { + fn doMath(comptime f: fn (comptime type, i32, i32) error{Overflow}!i32, a: i32, b: i32) !void { const result = try f(i32, a, b); try expect(result == 11); } diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index b45fa171cb03..1ba81695b8d6 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -1511,7 +1511,7 @@ test "if inside struct init inside if" { test "optional generic function label struct field" { const Options = struct { - isFoo: ?fn (type) u8 = defaultIsFoo, + isFoo: ?fn (comptime type) u8 = defaultIsFoo, fn defaultIsFoo(comptime _: type) u8 { return 123; } diff --git a/test/behavior/typename.zig b/test/behavior/typename.zig index 6682b99f4df2..79de5fd442e0 100644 --- a/test/behavior/typename.zig +++ b/test/behavior/typename.zig @@ -238,7 +238,7 @@ test "comptime parameters not converted to anytype in function type" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - const T = fn (fn (type) void, void) void; + const T = fn (comptime fn (comptime type) void, void) void; try expectEqualStrings("fn (comptime fn (comptime type) void, void) void", @typeName(T)); } diff --git a/test/cases/compile_errors/arg_to_non_comptime_param_with_comptime_only_type_is_not_evaluated_at_comptime.zig b/test/cases/compile_errors/arg_to_non_comptime_param_with_comptime_only_type_is_not_evaluated_at_comptime.zig new file mode 100644 index 000000000000..02a646433944 --- /dev/null +++ b/test/cases/compile_errors/arg_to_non_comptime_param_with_comptime_only_type_is_not_evaluated_at_comptime.zig @@ -0,0 +1,36 @@ +//! Whew, that filename is a bit of a mouthful! +//! To maximise consistency with other parts of the language, function arguments expressions are +//! only *evaluated* at comptime if the parameter is declared `comptime`. If the parameter type is +//! comptime-only, but the parameter is not declared `comptime`, the evaluation happens at runtime, +//! and the value is just comptime-resolved. + +export fn foo() void { + // This function is itself generic, with the comptime-only parameter being generic. + simpleGeneric(type, if (cond()) u8 else u16); +} + +export fn bar() void { + // This function is not generic; once `Wrapper` is called, its parameter type is immediately known. + Wrapper(type).inner(if (cond()) u8 else u16); +} + +fn simpleGeneric(comptime T: type, _: T) void {} + +fn Wrapper(comptime T: type) type { + return struct { + fn inner(_: T) void {} + }; +} + +fn cond() bool { + return true; +} + +// error +// +// :9:25: error: value with comptime-only type 'type' depends on runtime control flow +// :9:33: note: runtime control flow here +// :9:25: note: types are not available at runtime +// :14:25: error: value with comptime-only type 'type' depends on runtime control flow +// :14:33: note: runtime control flow here +// :14:25: note: types are not available at runtime diff --git a/test/cases/compile_errors/bad_usage_of_call.zig b/test/cases/compile_errors/bad_usage_of_call.zig index 3b7abe53f69f..6eca5d7e3d5b 100644 --- a/test/cases/compile_errors/bad_usage_of_call.zig +++ b/test/cases/compile_errors/bad_usage_of_call.zig @@ -42,8 +42,8 @@ noinline fn dummy2() void {} // :2:23: error: expected a tuple, found 'void' // :5:21: error: unable to perform 'never_inline' call at compile-time // :8:21: error: unable to perform 'never_tail' call at compile-time -// :11:5: error: 'never_inline' call of inline function +// :11:5: error: cannot perform inline call with 'never_inline' modifier // :15:26: error: modifier 'compile_time' requires a comptime-known function -// :18:9: error: 'always_inline' call of noinline function -// :21:9: error: 'always_inline' call of noinline function +// :18:9: error: inline call of noinline function +// :21:9: error: inline call of noinline function // :26:27: error: modifier 'always_inline' requires a comptime-known function diff --git a/test/cases/compile_errors/comptime_call_of_function_pointer.zig b/test/cases/compile_errors/comptime_call_of_function_pointer.zig index 574f55e9f3d5..a73f541b5ea3 100644 --- a/test/cases/compile_errors/comptime_call_of_function_pointer.zig +++ b/test/cases/compile_errors/comptime_call_of_function_pointer.zig @@ -4,7 +4,6 @@ export fn entry() void { } // error -// backend=stage2 -// target=native // -// :3:20: error: comptime call of function pointer +// :3:14: error: unable to resolve comptime value +// :3:14: note: function being called at comptime must be comptime-known diff --git a/test/cases/compile_errors/condition_comptime_reason_explained.zig b/test/cases/compile_errors/condition_comptime_reason_explained.zig index d641b6176ba7..4cd12d039bce 100644 --- a/test/cases/compile_errors/condition_comptime_reason_explained.zig +++ b/test/cases/compile_errors/condition_comptime_reason_explained.zig @@ -36,11 +36,13 @@ pub export fn entry2() void { // // :8:9: error: unable to resolve comptime value // :19:15: note: called at comptime from here -// :7:13: note: function with comptime-only return type 'tmp.S' is evaluated at comptime +// :19:15: note: call to function with comptime-only return type 'tmp.S' is evaluated at comptime +// :7:13: note: return type declared here // :2:12: note: struct requires comptime because of this field // :2:12: note: use '*const fn () void' for a function pointer type // :22:13: error: unable to resolve comptime value // :32:19: note: called at comptime from here -// :21:17: note: function with comptime-only return type 'tmp.S' is evaluated at comptime +// :32:19: note: call to function with comptime-only return type 'tmp.S' is evaluated at comptime +// :21:17: note: return type declared here // :2:12: note: struct requires comptime because of this field // :2:12: note: use '*const fn () void' for a function pointer type diff --git a/test/cases/compile_errors/dereference_anyopaque.zig b/test/cases/compile_errors/dereference_anyopaque.zig index c4e464989236..f4ce9efdd8aa 100644 --- a/test/cases/compile_errors/dereference_anyopaque.zig +++ b/test/cases/compile_errors/dereference_anyopaque.zig @@ -1,54 +1,7 @@ -const std = @import("std"); - -const Error = error{Something}; - -fn next() Error!void { - return; -} - -fn parse(comptime T: type, allocator: std.mem.Allocator) !void { - parseFree(T, undefined, allocator); - _ = (try next()) != null; -} - -fn parseFree(comptime T: type, value: T, allocator: std.mem.Allocator) void { - switch (@typeInfo(T)) { - .@"struct" => |structInfo| { - inline for (structInfo.fields) |field| { - if (!field.is_comptime) - parseFree(field.type, undefined, allocator); - } - }, - .pointer => |ptrInfo| { - switch (ptrInfo.size) { - .One => { - parseFree(ptrInfo.child, value.*, allocator); - }, - .Slice => { - for (value) |v| - parseFree(ptrInfo.child, v, allocator); - }, - else => unreachable, - } - }, - else => unreachable, - } -} - -pub export fn entry() void { - const allocator = std.testing.failing_allocator; - _ = parse(std.StringArrayHashMap(bool), allocator) catch return; +export fn foo(ptr: *anyopaque) void { + _ = ptr.*; } // error -// target=native -// backend=llvm // -// :11:22: error: comparison of 'void' with null -// :25:51: error: cannot load opaque type 'anyopaque' -// :25:51: error: values of type 'fn (*anyopaque, usize, u8, usize) ?[*]u8' must be comptime-known, but operand value is runtime-known -// :25:51: note: use '*const fn (*anyopaque, usize, u8, usize) ?[*]u8' for a function pointer type -// :25:51: error: values of type 'fn (*anyopaque, []u8, u8, usize, usize) bool' must be comptime-known, but operand value is runtime-known -// :25:51: note: use '*const fn (*anyopaque, []u8, u8, usize, usize) bool' for a function pointer type -// :25:51: error: values of type 'fn (*anyopaque, []u8, u8, usize) void' must be comptime-known, but operand value is runtime-known -// :25:51: note: use '*const fn (*anyopaque, []u8, u8, usize) void' for a function pointer type +// :2:12: error: cannot load opaque type 'anyopaque' diff --git a/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig index 7901808f3c9d..d1f6599f36d7 100644 --- a/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig +++ b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig @@ -15,6 +15,7 @@ pub export fn entry() void { // error // // :12:13: error: unable to resolve comptime value -// :7:16: note: function with comptime-only return type 'tmp.S' is evaluated at comptime +// :12:12: note: call to function with comptime-only return type 'tmp.S' is evaluated at comptime +// :7:16: note: return type declared here // :2:12: note: struct requires comptime because of this field // :2:12: note: use '*const fn () void' for a function pointer type diff --git a/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig b/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig index cfec89d5a785..f57bd44f88e2 100644 --- a/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig +++ b/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig @@ -17,6 +17,7 @@ pub export fn entry() void { // error // // :15:13: error: unable to resolve comptime value -// :9:38: note: generic function instantiated with comptime-only return type 'tmp.S(fn () void)' is evaluated at comptime +// :15:12: note: call to generic function instantiated with comptime-only return type 'tmp.S(fn () void)' is evaluated at comptime +// :9:38: note: return type declared here // :3:16: note: struct requires comptime because of this field // :3:16: note: use '*const fn () void' for a function pointer type diff --git a/test/cases/compile_errors/generic_function_instance_with_non-constant_expression.zig b/test/cases/compile_errors/generic_function_instance_with_non-constant_expression.zig index 829db19d8705..ebef24d293ae 100644 --- a/test/cases/compile_errors/generic_function_instance_with_non-constant_expression.zig +++ b/test/cases/compile_errors/generic_function_instance_with_non-constant_expression.zig @@ -10,8 +10,7 @@ export fn entry() usize { } // error -// backend=stage2 -// target=native // -// :5:16: error: runtime-known argument passed to comptime parameter -// :1:17: note: declared comptime here +// :5:16: error: unable to resolve comptime value +// :5:16: note: argument to comptime parameter must be comptime-known +// :1:8: note: parameter declared comptime here diff --git a/test/cases/compile_errors/generic_function_instantiation_inherits_parent_branch_quota.zig b/test/cases/compile_errors/generic_function_instantiation_inherits_parent_branch_quota.zig index 1d45ce86db15..4db27f0620e6 100644 --- a/test/cases/compile_errors/generic_function_instantiation_inherits_parent_branch_quota.zig +++ b/test/cases/compile_errors/generic_function_instantiation_inherits_parent_branch_quota.zig @@ -22,9 +22,8 @@ fn Type(comptime n: usize) type { } // error -// backend=stage2 -// target=native // // :21:16: error: evaluation exceeded 1001 backwards branches // :21:16: note: use @setEvalBranchQuota() to raise the branch limit from 1001 // :16:34: note: called from here +// :8:15: note: called from here diff --git a/test/cases/compile_errors/generic_instantiation_failure_in_generic_function_return_type.zig b/test/cases/compile_errors/generic_instantiation_failure_in_generic_function_return_type.zig index a4aaf8eb917c..1be4360317b6 100644 --- a/test/cases/compile_errors/generic_instantiation_failure_in_generic_function_return_type.zig +++ b/test/cases/compile_errors/generic_instantiation_failure_in_generic_function_return_type.zig @@ -40,3 +40,4 @@ pub fn is(comptime id: std.builtin.TypeId) TraitFn { // target=native // // :8:48: error: expected type 'type', found 'bool' +// :5:21: note: called from here diff --git a/test/cases/compile_errors/generic_method_call_with_invalid_param.zig b/test/cases/compile_errors/generic_method_call_with_invalid_param.zig index 6ce431461b78..04eba6e99e9c 100644 --- a/test/cases/compile_errors/generic_method_call_with_invalid_param.zig +++ b/test/cases/compile_errors/generic_method_call_with_invalid_param.zig @@ -22,12 +22,11 @@ const S = struct { }; // error -// backend=stage2 -// target=native // // :3:18: error: expected type 'bool', found 'void' // :19:43: note: parameter type declared here // :8:18: error: expected type 'void', found 'bool' // :20:43: note: parameter type declared here -// :15:26: error: runtime-known argument passed to comptime parameter -// :21:57: note: declared comptime here +// :15:26: error: unable to resolve comptime value +// :15:26: note: argument to comptime parameter must be comptime-known +// :21:48: note: parameter declared comptime here diff --git a/test/cases/compile_errors/global_variable_initializer_must_be_constant_expression.zig b/test/cases/compile_errors/global_variable_initializer_must_be_constant_expression.zig index a87f62839660..b06767111d2c 100644 --- a/test/cases/compile_errors/global_variable_initializer_must_be_constant_expression.zig +++ b/test/cases/compile_errors/global_variable_initializer_must_be_constant_expression.zig @@ -5,7 +5,5 @@ export fn entry() i32 { } // error -// backend=stage2 -// target=native // // :2:14: error: comptime call of extern function diff --git a/test/cases/compile_errors/inline_call_runtime_value_to_comptime_param.zig b/test/cases/compile_errors/inline_call_runtime_value_to_comptime_param.zig index 67079c8a2dad..abeb4bcaae01 100644 --- a/test/cases/compile_errors/inline_call_runtime_value_to_comptime_param.zig +++ b/test/cases/compile_errors/inline_call_runtime_value_to_comptime_param.zig @@ -10,8 +10,7 @@ pub export fn entry() void { } // error -// backend=stage2 -// target=native // // :5:18: error: unable to resolve comptime value // :5:18: note: argument to comptime parameter must be comptime-known +// :1:24: note: parameter declared comptime here diff --git a/test/cases/compile_errors/invalid_extern_function_call.zig b/test/cases/compile_errors/invalid_extern_function_call.zig index c270c3bf48cd..36a8ac3fd7de 100644 --- a/test/cases/compile_errors/invalid_extern_function_call.zig +++ b/test/cases/compile_errors/invalid_extern_function_call.zig @@ -9,8 +9,6 @@ export fn entry1() void { } // error -// backend=stage2 -// target=native // -// :4:15: error: comptime call of extern function pointer -// :8:5: error: inline call of extern function pointer +// :4:15: error: comptime call of extern function +// :8:5: error: inline call of extern function diff --git a/test/cases/compile_errors/invalid_pointer_for_var_type.zig b/test/cases/compile_errors/invalid_pointer_for_var_type.zig index d94c7bd3b811..2d80718b4ff6 100644 --- a/test/cases/compile_errors/invalid_pointer_for_var_type.zig +++ b/test/cases/compile_errors/invalid_pointer_for_var_type.zig @@ -7,7 +7,5 @@ export fn f() void { } // error -// backend=stage2 -// target=native // // :2:16: error: comptime call of extern function diff --git a/test/cases/compile_errors/nested_generic_function_param_type_mismatch.zig b/test/cases/compile_errors/nested_generic_function_param_type_mismatch.zig index 99f4b7a19305..93718771b4d0 100644 --- a/test/cases/compile_errors/nested_generic_function_param_type_mismatch.zig +++ b/test/cases/compile_errors/nested_generic_function_param_type_mismatch.zig @@ -19,6 +19,6 @@ pub export fn entry() void { // backend=llvm // target=native // -// :15:28: error: expected type '*const fn (comptime type, u8, u8) u32', found '*const fn (void, u8, u8) u32' -// :15:28: note: pointer type child 'fn (void, u8, u8) u32' cannot cast into pointer type child 'fn (comptime type, u8, u8) u32' +// :15:28: error: expected type '*const fn (type, u8, u8) u32', found '*const fn (void, u8, u8) u32' +// :15:28: note: pointer type child 'fn (void, u8, u8) u32' cannot cast into pointer type child 'fn (type, u8, u8) u32' // :15:28: note: non-generic function cannot cast into a generic function diff --git a/test/cases/compile_errors/never_inline_call_of_inline_fn_with_comptime_param.zig b/test/cases/compile_errors/never_inline_call_of_inline_fn_with_comptime_param.zig index f1eadb762580..20556f1cf58d 100644 --- a/test/cases/compile_errors/never_inline_call_of_inline_fn_with_comptime_param.zig +++ b/test/cases/compile_errors/never_inline_call_of_inline_fn_with_comptime_param.zig @@ -19,5 +19,5 @@ export fn entry2() void { // error // -// :14:5: error: 'never_inline' call of inline function -// :17:5: error: 'never_inline' call of inline function +// :14:5: error: cannot perform inline call with 'never_inline' modifier +// :17:5: error: cannot perform inline call with 'never_inline' modifier diff --git a/test/cases/compile_errors/non-const_expression_in_struct_literal_outside_function.zig b/test/cases/compile_errors/non-const_expression_in_struct_literal_outside_function.zig index c6d4e04fc1f7..808f94d9bfc0 100644 --- a/test/cases/compile_errors/non-const_expression_in_struct_literal_outside_function.zig +++ b/test/cases/compile_errors/non-const_expression_in_struct_literal_outside_function.zig @@ -9,7 +9,5 @@ export fn entry() usize { } // error -// backend=stage2 -// target=native // // :4:27: error: comptime call of extern function diff --git a/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig b/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig index cfb68bbcf6f5..3f913991a601 100644 --- a/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig +++ b/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig @@ -11,5 +11,6 @@ export fn entry() void { // error // // :8:11: error: unable to resolve comptime value -// :1:20: note: function with comptime-only return type 'type' is evaluated at comptime -// :1:20: note: types are not available at runtime +// :8:10: note: call to function with comptime-only return type 'type' is evaluated at comptime +// :1:20: note: return type declared here +// :8:10: note: types are not available at runtime diff --git a/test/cases/compile_errors/recursive_inline_fn.zig b/test/cases/compile_errors/recursive_inline_fn.zig index 1cecfdbada3c..7f96ad670962 100644 --- a/test/cases/compile_errors/recursive_inline_fn.zig +++ b/test/cases/compile_errors/recursive_inline_fn.zig @@ -29,8 +29,10 @@ pub export fn entry2() void { } // error -// backend=stage2 -// target=native // // :5:27: error: inline call is recursive +// :12:12: note: called from here // :24:10: error: inline call is recursive +// :20:10: note: called from here +// :16:11: note: called from here +// :28:10: note: called from here diff --git a/test/cases/compile_errors/runtime_operation_in_comptime_scope.zig b/test/cases/compile_errors/runtime_operation_in_comptime_scope.zig index 8c1d31adf299..e3576c179b52 100644 --- a/test/cases/compile_errors/runtime_operation_in_comptime_scope.zig +++ b/test/cases/compile_errors/runtime_operation_in_comptime_scope.zig @@ -27,8 +27,9 @@ var rt: u32 = undefined; // :19:5: note: operation is runtime due to this operand // :14:8: note: called at comptime from here // :10:12: note: called at comptime from here -// :13:10: note: function with comptime-only return type 'type' is evaluated at comptime -// :13:10: note: types are not available at runtime +// :10:12: note: call to function with comptime-only return type 'type' is evaluated at comptime +// :13:10: note: return type declared here +// :10:12: note: types are not available at runtime // :2:8: note: called from here // :19:8: error: unable to evaluate comptime expression // :19:5: note: operation is runtime due to this operand diff --git a/test/compile_errors.zig b/test/compile_errors.zig index f566464bac77..13d97ba256a0 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -57,8 +57,9 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\} , &[_][]const u8{ ":3:12: error: unable to resolve comptime value", - ":2:55: note: generic function instantiated with comptime-only return type '?fn () void' is evaluated at comptime", - ":2:55: note: use '*const fn () void' for a function pointer type", + ":3:19: note: call to generic function instantiated with comptime-only return type '?fn () void' is evaluated at comptime", + ":2:55: note: return type declared here", + ":3:19: note: use '*const fn () void' for a function pointer type", }); case.addSourceFile("b.zig", \\pub const ElfDynLib = struct { @@ -193,10 +194,12 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\ import.anytypeFunction(S{ .x = x, .y = u32 }); \\} , &[_][]const u8{ - ":4:33: error: runtime-known argument passed to comptime parameter", - ":1:38: note: declared comptime here", - ":8:36: error: runtime-known argument passed to comptime parameter", - ":2:41: note: declared comptime here", + ":4:33: error: unable to resolve comptime value", + ":4:33: note: argument to comptime parameter must be comptime-known", + ":1:29: note: parameter declared comptime here", + ":8:36: error: unable to resolve comptime value", + ":8:36: note: argument to comptime parameter must be comptime-known", + ":2:32: note: parameter declared comptime here", ":13:32: error: unable to resolve comptime value", ":13:32: note: initializer of comptime-only struct 'tmp.callAnytypeFunctionWithRuntimeComptimeOnlyType.S' must be comptime-known", ":12:35: note: struct requires comptime because of this field", diff --git a/test/standalone/simple/std_enums_big_enums.zig b/test/standalone/simple/std_enums_big_enums.zig index c2358bafc095..1ad24a414713 100644 --- a/test/standalone/simple/std_enums_big_enums.zig +++ b/test/standalone/simple/std_enums_big_enums.zig @@ -31,6 +31,7 @@ pub fn main() void { var bounded_multiset = std.enums.BoundedEnumMultiset(big.Big, u8).init(.{}); _ = &bounded_multiset; + @setEvalBranchQuota(3000); var array = std.enums.EnumArray(big.Big, u8).init(undefined); array = std.enums.EnumArray(big.Big, u8).initDefault(123, .{}); } From 6a837e64cf70651bfa16e0d6090ffb4122a2f76f Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 10 Jan 2025 05:35:03 +0000 Subject: [PATCH 2/2] compiler: bump stack size 32 MiB -> 46 MiB Before the prior commit, the maximum comptime recursion depth on my system was 4062. After the prior commit, it decreased to 2854. This commit increases the compiler's stack size enough so that the recursion depth limit is no less than it was before the `Sema.analyzeCall` rewrite, preventing this from being a breaking change. Specifically, this stack size increases my observed maximum comptime recursion depth to 4105. --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index d79a785c76a2..656ec27bfcc1 100644 --- a/build.zig +++ b/build.zig @@ -11,7 +11,7 @@ const assert = std.debug.assert; const DevEnv = @import("src/dev.zig").Env; const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 14, .patch = 0 }; -const stack_size = 32 * 1024 * 1024; +const stack_size = 46 * 1024 * 1024; pub fn build(b: *std.Build) !void { const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;