mirror of
https://github.com/ziglang/zig.git
synced 2025-12-22 06:03:16 +00:00
AstGen provides all function call arguments with a result location, referenced through the call instruction index. The idea is that this should be the parameter type, but for `anytype` parameters, we use generic poison, which is required to be handled correctly. Previously, generic instantiations and inline calls worked by evaluating all args in advance, before resolving generic parameter types. This means any generic parameter (not just `anytype` ones) had generic poison result types. This caused missing result locations in some cases. Additionally, the generic instantiation logic caused `zirParam` to analyze the argument types a second time before coercion. This meant that for nominal types (struct/enum/etc), a *new* type was created, distinct to the result type which was previously forwarded to the argument expression. This commit fixes both of these issues. Generic parameter type resolution is now interleaved with argument analysis, so that we don't have unnecessary generic poison types, and generic instantiation logic now handles parameters itself rather than falling through to the standard zirParam logic, so avoids duplicating the types. Resolves: #16566 Resolves: #16258 Resolves: #16753
494 lines
15 KiB
Zig
494 lines
15 KiB
Zig
const builtin = @import("builtin");
|
|
const std = @import("std");
|
|
const expect = std.testing.expect;
|
|
const expectEqual = std.testing.expectEqual;
|
|
|
|
test "super basic invocations" {
|
|
const foo = struct {
|
|
fn foo() i32 {
|
|
return 1234;
|
|
}
|
|
}.foo;
|
|
try expect(@call(.auto, foo, .{}) == 1234);
|
|
try comptime expect(@call(.always_inline, foo, .{}) == 1234);
|
|
{
|
|
// comptime call without comptime keyword
|
|
const result = @call(.compile_time, foo, .{}) == 1234;
|
|
try comptime expect(result);
|
|
}
|
|
}
|
|
|
|
test "basic invocations" {
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const foo = struct {
|
|
fn foo() i32 {
|
|
return 1234;
|
|
}
|
|
}.foo;
|
|
try expect(@call(.auto, foo, .{}) == 1234);
|
|
comptime {
|
|
// modifiers that allow comptime calls
|
|
try expect(@call(.auto, foo, .{}) == 1234);
|
|
try expect(@call(.no_async, foo, .{}) == 1234);
|
|
try expect(@call(.always_tail, foo, .{}) == 1234);
|
|
try expect(@call(.always_inline, foo, .{}) == 1234);
|
|
}
|
|
{
|
|
// comptime call without comptime keyword
|
|
const result = @call(.compile_time, foo, .{}) == 1234;
|
|
try comptime expect(result);
|
|
}
|
|
{
|
|
// call of non comptime-known function
|
|
var alias_foo = &foo;
|
|
try expect(@call(.no_async, alias_foo, .{}) == 1234);
|
|
try expect(@call(.never_tail, alias_foo, .{}) == 1234);
|
|
try expect(@call(.never_inline, alias_foo, .{}) == 1234);
|
|
}
|
|
}
|
|
|
|
test "tuple parameters" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const add = struct {
|
|
fn add(a: i32, b: i32) i32 {
|
|
return a + b;
|
|
}
|
|
}.add;
|
|
var a: i32 = 12;
|
|
var b: i32 = 34;
|
|
try expect(@call(.auto, add, .{ a, 34 }) == 46);
|
|
try expect(@call(.auto, add, .{ 12, b }) == 46);
|
|
try expect(@call(.auto, add, .{ a, b }) == 46);
|
|
try expect(@call(.auto, add, .{ 12, 34 }) == 46);
|
|
if (false) {
|
|
try comptime expect(@call(.auto, add, .{ 12, 34 }) == 46); // TODO
|
|
}
|
|
try expect(comptime @call(.auto, add, .{ 12, 34 }) == 46);
|
|
{
|
|
const separate_args0 = .{ a, b };
|
|
const separate_args1 = .{ a, 34 };
|
|
const separate_args2 = .{ 12, 34 };
|
|
const separate_args3 = .{ 12, b };
|
|
try expect(@call(.always_inline, add, separate_args0) == 46);
|
|
try expect(@call(.always_inline, add, separate_args1) == 46);
|
|
try expect(@call(.always_inline, add, separate_args2) == 46);
|
|
try expect(@call(.always_inline, add, separate_args3) == 46);
|
|
}
|
|
}
|
|
|
|
test "result location of function call argument through runtime condition and struct init" {
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const E = enum { a, b };
|
|
const S = struct {
|
|
e: E,
|
|
};
|
|
const namespace = struct {
|
|
fn foo(s: S) !void {
|
|
try expect(s.e == .b);
|
|
}
|
|
};
|
|
var runtime = true;
|
|
try namespace.foo(.{
|
|
.e = if (!runtime) .a else .b,
|
|
});
|
|
}
|
|
|
|
test "function call with 40 arguments" {
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest(thirty_nine: i32) !void {
|
|
const result = add(
|
|
0,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
7,
|
|
8,
|
|
9,
|
|
10,
|
|
11,
|
|
12,
|
|
13,
|
|
14,
|
|
15,
|
|
16,
|
|
17,
|
|
18,
|
|
19,
|
|
20,
|
|
21,
|
|
22,
|
|
23,
|
|
24,
|
|
25,
|
|
26,
|
|
27,
|
|
28,
|
|
29,
|
|
30,
|
|
31,
|
|
32,
|
|
33,
|
|
34,
|
|
35,
|
|
36,
|
|
37,
|
|
38,
|
|
thirty_nine,
|
|
40,
|
|
);
|
|
try expect(result == 820);
|
|
try expect(thirty_nine == 39);
|
|
}
|
|
|
|
fn add(
|
|
a0: i32,
|
|
a1: i32,
|
|
a2: i32,
|
|
a3: i32,
|
|
a4: i32,
|
|
a5: i32,
|
|
a6: i32,
|
|
a7: i32,
|
|
a8: i32,
|
|
a9: i32,
|
|
a10: i32,
|
|
a11: i32,
|
|
a12: i32,
|
|
a13: i32,
|
|
a14: i32,
|
|
a15: i32,
|
|
a16: i32,
|
|
a17: i32,
|
|
a18: i32,
|
|
a19: i32,
|
|
a20: i32,
|
|
a21: i32,
|
|
a22: i32,
|
|
a23: i32,
|
|
a24: i32,
|
|
a25: i32,
|
|
a26: i32,
|
|
a27: i32,
|
|
a28: i32,
|
|
a29: i32,
|
|
a30: i32,
|
|
a31: i32,
|
|
a32: i32,
|
|
a33: i32,
|
|
a34: i32,
|
|
a35: i32,
|
|
a36: i32,
|
|
a37: i32,
|
|
a38: i32,
|
|
a39: i32,
|
|
a40: i32,
|
|
) i32 {
|
|
return a0 +
|
|
a1 +
|
|
a2 +
|
|
a3 +
|
|
a4 +
|
|
a5 +
|
|
a6 +
|
|
a7 +
|
|
a8 +
|
|
a9 +
|
|
a10 +
|
|
a11 +
|
|
a12 +
|
|
a13 +
|
|
a14 +
|
|
a15 +
|
|
a16 +
|
|
a17 +
|
|
a18 +
|
|
a19 +
|
|
a20 +
|
|
a21 +
|
|
a22 +
|
|
a23 +
|
|
a24 +
|
|
a25 +
|
|
a26 +
|
|
a27 +
|
|
a28 +
|
|
a29 +
|
|
a30 +
|
|
a31 +
|
|
a32 +
|
|
a33 +
|
|
a34 +
|
|
a35 +
|
|
a36 +
|
|
a37 +
|
|
a38 +
|
|
a39 +
|
|
a40;
|
|
}
|
|
};
|
|
try S.doTheTest(39);
|
|
}
|
|
|
|
test "arguments to comptime parameters generated in comptime blocks" {
|
|
const S = struct {
|
|
fn fortyTwo() i32 {
|
|
return 42;
|
|
}
|
|
|
|
fn foo(comptime x: i32) void {
|
|
if (x != 42) @compileError("bad");
|
|
}
|
|
};
|
|
S.foo(S.fortyTwo());
|
|
}
|
|
|
|
test "forced tail call" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
if (builtin.zig_backend == .stage2_llvm) {
|
|
// Only attempt this test on targets we know have tail call support in LLVM.
|
|
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
|
return error.SkipZigTest;
|
|
}
|
|
}
|
|
|
|
const S = struct {
|
|
fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
|
|
if (n == 0) return a;
|
|
if (n == 1) return b;
|
|
return @call(
|
|
.always_tail,
|
|
fibonacciTailInternal,
|
|
.{ n - 1, b, a + b },
|
|
);
|
|
}
|
|
|
|
fn fibonacciTail(n: u16) u16 {
|
|
return fibonacciTailInternal(n, 0, 1);
|
|
}
|
|
};
|
|
try expect(S.fibonacciTail(10) == 55);
|
|
}
|
|
|
|
test "inline call preserves tail call" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
if (builtin.zig_backend == .stage2_llvm) {
|
|
// Only attempt this test on targets we know have tail call support in LLVM.
|
|
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
|
return error.SkipZigTest;
|
|
}
|
|
}
|
|
|
|
const max = std.math.maxInt(u16);
|
|
const S = struct {
|
|
var a: u16 = 0;
|
|
fn foo() void {
|
|
return bar();
|
|
}
|
|
|
|
inline fn bar() void {
|
|
if (a == max) return;
|
|
// Stack overflow if not tail called
|
|
var buf: [max]u16 = undefined;
|
|
buf[a] = a;
|
|
a += 1;
|
|
return @call(.always_tail, foo, .{});
|
|
}
|
|
};
|
|
S.foo();
|
|
try expect(S.a == std.math.maxInt(u16));
|
|
}
|
|
|
|
test "inline call doesn't re-evaluate non generic struct" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn foo(f: struct { a: u8, b: u8 }) !void {
|
|
try expect(f.a == 123);
|
|
try expect(f.b == 45);
|
|
}
|
|
};
|
|
const ArgTuple = std.meta.ArgsTuple(@TypeOf(S.foo));
|
|
try @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
|
|
try comptime @call(.always_inline, S.foo, ArgTuple{.{ .a = 123, .b = 45 }});
|
|
}
|
|
|
|
test "Enum constructed by @Type passed as generic argument" {
|
|
const S = struct {
|
|
const E = std.meta.FieldEnum(struct {
|
|
prev_pos: bool,
|
|
pos: bool,
|
|
vel: bool,
|
|
damp_vel: bool,
|
|
acc: bool,
|
|
rgba: bool,
|
|
prev_scale: bool,
|
|
scale: bool,
|
|
prev_rotation: bool,
|
|
rotation: bool,
|
|
angular_vel: bool,
|
|
alive: bool,
|
|
});
|
|
fn foo(comptime a: E, b: u32) !void {
|
|
try expect(@intFromEnum(a) == b);
|
|
}
|
|
};
|
|
inline for (@typeInfo(S.E).Enum.fields, 0..) |_, i| {
|
|
try S.foo(@as(S.E, @enumFromInt(i)), i);
|
|
}
|
|
}
|
|
|
|
test "generic function with generic function parameter" {
|
|
const S = struct {
|
|
fn f(comptime a: fn (anytype) anyerror!void, b: anytype) anyerror!void {
|
|
try a(b);
|
|
}
|
|
fn g(a: anytype) anyerror!void {
|
|
try expect(a == 123);
|
|
}
|
|
};
|
|
try S.f(S.g, 123);
|
|
}
|
|
|
|
test "recursive inline call with comptime known argument" {
|
|
const S = struct {
|
|
inline fn foo(x: i32) i32 {
|
|
if (x <= 0) {
|
|
return 0;
|
|
} else {
|
|
return x * 2 + foo(x - 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
try expect(S.foo(4) == 20);
|
|
}
|
|
|
|
test "inline while with @call" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn inc(a: *u32) void {
|
|
a.* += 1;
|
|
}
|
|
};
|
|
var a: u32 = 0;
|
|
comptime var i = 0;
|
|
inline while (i < 10) : (i += 1) {
|
|
@call(.auto, S.inc, .{&a});
|
|
}
|
|
try expect(a == 10);
|
|
}
|
|
|
|
test "method call as parameter type" {
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn foo(x: anytype, y: @TypeOf(x).Inner()) @TypeOf(y) {
|
|
return y;
|
|
}
|
|
fn Inner() type {
|
|
return u64;
|
|
}
|
|
};
|
|
try expectEqual(@as(u64, 123), S.foo(S{}, 123));
|
|
try expectEqual(@as(u64, 500), S.foo(S{}, 500));
|
|
}
|
|
|
|
test "non-anytype generic parameters provide result type" {
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn f(comptime T: type, y: T) !void {
|
|
try expectEqual(@as(T, 123), y);
|
|
}
|
|
|
|
fn g(x: anytype, y: @TypeOf(x)) !void {
|
|
try expectEqual(@as(@TypeOf(x), 0x222), y);
|
|
}
|
|
};
|
|
|
|
var rt_u16: u16 = 123;
|
|
var rt_u32: u32 = 0x10000222;
|
|
|
|
try S.f(u8, @intCast(rt_u16));
|
|
try S.f(u8, @intCast(123));
|
|
|
|
try S.g(rt_u16, @truncate(rt_u32));
|
|
try S.g(rt_u16, @truncate(0x10000222));
|
|
|
|
try comptime S.f(u8, @intCast(123));
|
|
try comptime S.g(@as(u16, undefined), @truncate(0x99990222));
|
|
}
|
|
|
|
test "argument to generic function has correct result type" {
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn foo(_: anytype, e: enum { a, b }) bool {
|
|
return e == .b;
|
|
}
|
|
|
|
fn doTheTest() !void {
|
|
var t = true;
|
|
|
|
// Since the enum literal passes through a runtime conditional here, these can only
|
|
// compile if RLS provides the correct result type to the argument
|
|
try expect(foo({}, if (!t) .a else .b));
|
|
try expect(!foo("dummy", if (t) .a else .b));
|
|
try expect(foo({}, if (t) .b else .a));
|
|
try expect(!foo(123, if (t) .a else .a));
|
|
try expect(foo(123, if (t) .b else .b));
|
|
}
|
|
};
|
|
|
|
try S.doTheTest();
|
|
try comptime S.doTheTest();
|
|
}
|