Commit 107b65ec5d

mlugg <mlugg@mlugg.co.uk>
2025-01-29 07:39:29
Sema: explain why we tried to call an `extern fn` at comptime
I recently saw a user hit the "comptime call of extern function" error, and get confused because they didn't know why the scope was `comptime`. So, use `explainWhyBlockIsComptime` on this and related errors to add all the relevant notes. The added test case shows the motivating situation.
1 parent 484f723
lib/std/zig.zig
@@ -749,7 +749,6 @@ pub const SimpleComptimeReason = enum(u32) {
     atomic_order,
     array_mul_factor,
     slice_cat_operand,
-    comptime_call_target,
     inline_call_target,
     generic_call_target,
     wasm_memory_index,
@@ -830,7 +829,6 @@ pub const SimpleComptimeReason = enum(u32) {
             .atomic_order         => "atomic order must be comptime-known",
             .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",
src/Sema.zig
@@ -8021,26 +8021,49 @@ fn analyzeCall(
 
     // 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 (func_ty_info.is_noinline and !block.isComptime()) {
+        return sema.fail(block, call_src, "inline call of noinline function", .{});
+    }
 
+    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});
-    }
-    if (func_ty_info.is_noinline and !block.isComptime()) {
-        return sema.fail(block, call_src, "{s} call of noinline function", .{call_type});
+        const msg, const fail_block = msg: {
+            const msg = try sema.errMsg(call_src, "cannot perform {s} call with 'never_inline' modifier", .{call_type});
+            errdefer msg.destroy(gpa);
+            const fail_block = if (block.isComptime()) b: {
+                break :b try block.explainWhyBlockIsComptime(msg);
+            } else block;
+            break :msg .{ msg, fail_block };
+        };
+        return sema.failWithOwnedErrorMsg(fail_block, msg);
     }
     if (func_ty_info.is_var_args) {
-        return sema.fail(block, call_src, "{s} call of variadic function", .{call_type});
+        const msg, const fail_block = msg: {
+            const msg = try sema.errMsg(call_src, "{s} call of variadic function", .{call_type});
+            errdefer msg.destroy(gpa);
+            const fail_block = if (block.isComptime()) b: {
+                break :b try block.explainWhyBlockIsComptime(msg);
+            } else block;
+            break :msg .{ msg, fail_block };
+        };
+        return sema.failWithOwnedErrorMsg(fail_block, msg);
     }
-
     if (func_val == null) {
         if (func_is_extern) {
-            return sema.fail(block, call_src, "{s} call of extern function", .{call_type});
+            const msg, const fail_block = msg: {
+                const msg = try sema.errMsg(call_src, "{s} call of extern function", .{call_type});
+                errdefer msg.destroy(gpa);
+                const fail_block = if (block.isComptime()) b: {
+                    break :b try block.explainWhyBlockIsComptime(msg);
+                } else block;
+                break :msg .{ msg, fail_block };
+            };
+            return sema.failWithOwnedErrorMsg(fail_block, msg);
         }
         return sema.failWithNeededComptime(
             block,
             func_src,
-            .{ .simple = if (block.isComptime()) .comptime_call_target else .inline_call_target },
+            if (block.isComptime()) null else .{ .simple = .inline_call_target },
         );
     }
 
test/cases/compile_errors/complex_comptime_call_of_extern_function.zig
@@ -0,0 +1,40 @@
+extern fn next_id() u32;
+
+const Foo = struct {
+    bar: Bar,
+
+    fn init() Foo {
+        return .{ .bar = .init() };
+    }
+};
+const Bar = struct {
+    qux: ?Qux,
+    id: u32,
+
+    fn init() Bar {
+        return .{
+            .qux = null,
+            .id = next_id(),
+        };
+    }
+};
+const Qux = struct {
+    handleThing: fn () void,
+};
+
+export fn entry() void {
+    const foo: Foo = .init();
+    _ = foo;
+}
+
+// error
+//
+// :17:26: error: comptime call of extern function
+// :7:31: note: called at comptime from here
+// :26:27: note: called at comptime from here
+// :26:27: note: call to function with comptime-only return type 'tmp.Foo' is evaluated at comptime
+// :6:15: note: return type declared here
+// :4:10: note: struct requires comptime because of this field
+// :11:10: note: struct requires comptime because of this field
+// :22:18: note: struct requires comptime because of this field
+// :22:18: note: use '*const fn () void' for a function pointer type
test/cases/compile_errors/comptime_call_of_function_pointer.zig
@@ -6,4 +6,4 @@ export fn entry() void {
 // error
 //
 // :3:14: error: unable to resolve comptime value
-// :3:14: note: function being called at comptime must be comptime-known
+// :3:5: note: 'comptime' keyword forces comptime evaluation
test/cases/compile_errors/global_variable_initializer_must_be_constant_expression.zig
@@ -7,3 +7,4 @@ export fn entry() i32 {
 // error
 //
 // :2:14: error: comptime call of extern function
+// :2:14: note: initializer of container-level variable must be comptime-known
test/cases/compile_errors/invalid_extern_function_call.zig
@@ -11,4 +11,5 @@ export fn entry1() void {
 // error
 //
 // :4:15: error: comptime call of extern function
+// :4:5: note: 'comptime' keyword forces comptime evaluation
 // :8:5: error: inline call of extern function
test/cases/compile_errors/invalid_pointer_for_var_type.zig
@@ -9,3 +9,4 @@ export fn f() void {
 // error
 //
 // :2:16: error: comptime call of extern function
+// :2:12: note: types must be comptime-known
test/cases/compile_errors/non-const_expression_in_struct_literal_outside_function.zig
@@ -11,3 +11,4 @@ export fn entry() usize {
 // error
 //
 // :4:27: error: comptime call of extern function
+// :4:14: note: initializer of container-level variable must be comptime-known