Sema: refactor generic calls to interleave argument analysis and parameter type resolution

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
This commit is contained in:
mlugg 2023-08-01 22:42:01 +01:00
parent 93e53d1e00
commit f2c8fa769a
No known key found for this signature in database
GPG Key ID: 58978E823BDE3EF9
4 changed files with 606 additions and 449 deletions

View File

@ -1528,11 +1528,13 @@ pub fn refToInterned(ref: Inst.Ref) ?InternPool.Index {
}
pub fn internedToRef(ip_index: InternPool.Index) Inst.Ref {
assert(@intFromEnum(ip_index) >> 31 == 0);
return switch (ip_index) {
.var_args_param_type => .var_args_param_type,
.none => .none,
else => @enumFromInt(@as(u31, @intCast(@intFromEnum(ip_index)))),
else => {
assert(@intFromEnum(ip_index) >> 31 == 0);
return @enumFromInt(@as(u31, @intCast(@intFromEnum(ip_index))));
},
};
}

View File

@ -5938,35 +5938,6 @@ pub fn paramSrc(
unreachable;
}
pub fn argSrc(
mod: *Module,
call_node_offset: i32,
decl: *Decl,
start_arg_i: usize,
bound_arg_src: ?LazySrcLoc,
) LazySrcLoc {
@setCold(true);
const gpa = mod.gpa;
if (start_arg_i == 0 and bound_arg_src != null) return bound_arg_src.?;
const arg_i = start_arg_i - @intFromBool(bound_arg_src != null);
const tree = decl.getFileScope(mod).getTree(gpa) catch |err| {
// In this case we emit a warning + a less precise source location.
log.warn("unable to load {s}: {s}", .{
decl.getFileScope(mod).sub_file_path, @errorName(err),
});
return LazySrcLoc.nodeOffset(0);
};
const node = decl.relativeToNodeIndex(call_node_offset);
var args: [1]Ast.Node.Index = undefined;
const call_full = tree.fullCall(&args, node) orelse {
assert(tree.nodes.items(.tag)[node] == .builtin_call);
const call_args_node = tree.extra_data[tree.nodes.items(.data)[node].rhs - 1];
const call_args_offset = decl.nodeIndexToRelative(call_args_node);
return mod.initSrc(call_args_offset, decl, arg_i);
};
return LazySrcLoc.nodeOffset(decl.nodeIndexToRelative(call_full.ast.params[arg_i]));
}
pub fn initSrc(
mod: *Module,
init_node_offset: i32,

File diff suppressed because it is too large Load Diff

View File

@ -430,3 +430,64 @@ test "method call as parameter type" {
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();
}