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
This commit is contained in:
mlugg
2025-01-05 05:27:48 +00:00
parent 3f95003d4c
commit e9bd2d45d4
36 changed files with 842 additions and 1221 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,5 @@ export fn entry() i32 {
}
// error
// backend=stage2
// target=native
//
// :2:14: error: comptime call of extern function

View File

@@ -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

View File

@@ -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

View File

@@ -7,7 +7,5 @@ export fn f() void {
}
// error
// backend=stage2
// target=native
//
// :2:16: error: comptime call of extern function

View File

@@ -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

View File

@@ -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

View File

@@ -9,7 +9,5 @@ export fn entry() usize {
}
// error
// backend=stage2
// target=native
//
// :4:27: error: comptime call of extern function

View File

@@ -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

View File

@@ -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

View File

@@ -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