mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Sema: if generic function evaluates to another generic function call it inline
```zig
fn foo(a: anytype, b: @TypeOf(a)) void { _ = b; }
test {
// foo evaluates to `fn (type) void` and must be called inline
foo(u32, u32);
}
```
This commit is contained in:
parent
b9f521b402
commit
e0fb0770d1
160
src/Sema.zig
160
src/Sema.zig
@ -4474,8 +4474,26 @@ fn analyzeCall(
|
|||||||
|
|
||||||
const is_comptime_call = block.is_comptime or modifier == .compile_time or
|
const is_comptime_call = block.is_comptime or modifier == .compile_time or
|
||||||
try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type);
|
try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type);
|
||||||
const is_inline_call = is_comptime_call or modifier == .always_inline or
|
var is_inline_call = is_comptime_call or modifier == .always_inline or
|
||||||
func_ty_info.cc == .Inline;
|
func_ty_info.cc == .Inline;
|
||||||
|
|
||||||
|
if (!is_inline_call and func_ty_info.is_generic) {
|
||||||
|
if (sema.instantiateGenericCall(
|
||||||
|
block,
|
||||||
|
func,
|
||||||
|
func_src,
|
||||||
|
call_src,
|
||||||
|
func_ty_info,
|
||||||
|
ensure_result_used,
|
||||||
|
uncasted_args,
|
||||||
|
)) |some| {
|
||||||
|
return some;
|
||||||
|
} else |err| switch (err) {
|
||||||
|
error.GenericPoison => is_inline_call = true,
|
||||||
|
else => |e| return e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result: Air.Inst.Ref = if (is_inline_call) res: {
|
const result: Air.Inst.Ref = if (is_inline_call) res: {
|
||||||
const func_val = try sema.resolveConstValue(block, func_src, func);
|
const func_val = try sema.resolveConstValue(block, func_src, func);
|
||||||
const module_fn = switch (func_val.tag()) {
|
const module_fn = switch (func_val.tag()) {
|
||||||
@ -4728,7 +4746,58 @@ fn analyzeCall(
|
|||||||
try wip_captures.finalize();
|
try wip_captures.finalize();
|
||||||
|
|
||||||
break :res res2;
|
break :res res2;
|
||||||
} else if (func_ty_info.is_generic) res: {
|
} else res: {
|
||||||
|
assert(!func_ty_info.is_generic);
|
||||||
|
try sema.requireRuntimeBlock(block, call_src);
|
||||||
|
|
||||||
|
const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
|
||||||
|
for (uncasted_args) |uncasted_arg, i| {
|
||||||
|
const arg_src = call_src; // TODO: better source location
|
||||||
|
if (i < fn_params_len) {
|
||||||
|
const param_ty = func_ty.fnParamType(i);
|
||||||
|
try sema.resolveTypeFully(block, arg_src, param_ty);
|
||||||
|
args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
|
||||||
|
} else {
|
||||||
|
args[i] = uncasted_arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try sema.resolveTypeFully(block, call_src, func_ty_info.return_type);
|
||||||
|
|
||||||
|
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
|
||||||
|
args.len);
|
||||||
|
const func_inst = try block.addInst(.{
|
||||||
|
.tag = .call,
|
||||||
|
.data = .{ .pl_op = .{
|
||||||
|
.operand = func,
|
||||||
|
.payload = sema.addExtraAssumeCapacity(Air.Call{
|
||||||
|
.args_len = @intCast(u32, args.len),
|
||||||
|
}),
|
||||||
|
} },
|
||||||
|
});
|
||||||
|
sema.appendRefsAssumeCapacity(args);
|
||||||
|
break :res func_inst;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ensure_result_used) {
|
||||||
|
try sema.ensureResultUsed(block, result, call_src);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiateGenericCall(
|
||||||
|
sema: *Sema,
|
||||||
|
block: *Block,
|
||||||
|
func: Air.Inst.Ref,
|
||||||
|
func_src: LazySrcLoc,
|
||||||
|
call_src: LazySrcLoc,
|
||||||
|
func_ty_info: Type.Payload.Function.Data,
|
||||||
|
ensure_result_used: bool,
|
||||||
|
uncasted_args: []const Air.Inst.Ref,
|
||||||
|
) CompileError!Air.Inst.Ref {
|
||||||
|
const mod = sema.mod;
|
||||||
|
const gpa = sema.gpa;
|
||||||
|
|
||||||
const func_val = try sema.resolveConstValue(block, func_src, func);
|
const func_val = try sema.resolveConstValue(block, func_src, func);
|
||||||
const module_fn = switch (func_val.tag()) {
|
const module_fn = switch (func_val.tag()) {
|
||||||
.function => func_val.castTag(.function).?.data,
|
.function => func_val.castTag(.function).?.data,
|
||||||
@ -4789,21 +4858,9 @@ fn analyzeCall(
|
|||||||
.comptime_tvs = comptime_tvs,
|
.comptime_tvs = comptime_tvs,
|
||||||
};
|
};
|
||||||
const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
|
const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter);
|
||||||
if (gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
const callee_func = gop.key_ptr.*;
|
|
||||||
break :res try sema.finishGenericCall(
|
|
||||||
block,
|
|
||||||
call_src,
|
|
||||||
callee_func,
|
|
||||||
func_src,
|
|
||||||
uncasted_args,
|
|
||||||
fn_info,
|
|
||||||
zir_tags,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const new_module_func = try gpa.create(Module.Fn);
|
const new_module_func = try gpa.create(Module.Fn);
|
||||||
gop.key_ptr.* = new_module_func;
|
gop.key_ptr.* = new_module_func;
|
||||||
{
|
|
||||||
errdefer gpa.destroy(new_module_func);
|
errdefer gpa.destroy(new_module_func);
|
||||||
const remove_adapter: GenericRemoveAdapter = .{
|
const remove_adapter: GenericRemoveAdapter = .{
|
||||||
.precomputed_hash = precomputed_hash,
|
.precomputed_hash = precomputed_hash,
|
||||||
@ -4820,6 +4877,7 @@ fn analyzeCall(
|
|||||||
module_fn.owner_decl.name, name_index,
|
module_fn.owner_decl.name, name_index,
|
||||||
});
|
});
|
||||||
const new_decl = try mod.allocateNewDecl(decl_name, namespace, module_fn.owner_decl.src_node, src_decl.src_scope);
|
const new_decl = try mod.allocateNewDecl(decl_name, namespace, module_fn.owner_decl.src_node, src_decl.src_scope);
|
||||||
|
errdefer new_decl.destroy(mod);
|
||||||
new_decl.src_line = module_fn.owner_decl.src_line;
|
new_decl.src_line = module_fn.owner_decl.src_line;
|
||||||
new_decl.is_pub = module_fn.owner_decl.is_pub;
|
new_decl.is_pub = module_fn.owner_decl.is_pub;
|
||||||
new_decl.is_exported = module_fn.owner_decl.is_exported;
|
new_decl.is_exported = module_fn.owner_decl.is_exported;
|
||||||
@ -4828,17 +4886,17 @@ fn analyzeCall(
|
|||||||
new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace";
|
new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace";
|
||||||
new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index;
|
new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index;
|
||||||
new_decl.alive = true; // This Decl is called at runtime.
|
new_decl.alive = true; // This Decl is called at runtime.
|
||||||
new_decl.has_tv = true;
|
|
||||||
new_decl.owns_tv = true;
|
|
||||||
new_decl.analysis = .in_progress;
|
new_decl.analysis = .in_progress;
|
||||||
new_decl.generation = mod.generation;
|
new_decl.generation = mod.generation;
|
||||||
|
|
||||||
namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {});
|
namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {});
|
||||||
|
errdefer assert(namespace.anon_decls.orderedRemove(new_decl));
|
||||||
|
|
||||||
// The generic function Decl is guaranteed to be the first dependency
|
// The generic function Decl is guaranteed to be the first dependency
|
||||||
// of each of its instantiations.
|
// of each of its instantiations.
|
||||||
assert(new_decl.dependencies.keys().len == 0);
|
assert(new_decl.dependencies.keys().len == 0);
|
||||||
try mod.declareDeclDependency(new_decl, module_fn.owner_decl);
|
try mod.declareDeclDependency(new_decl, module_fn.owner_decl);
|
||||||
|
errdefer assert(module_fn.owner_decl.dependants.orderedRemove(new_decl));
|
||||||
|
|
||||||
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
|
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
|
||||||
errdefer new_decl_arena.deinit();
|
errdefer new_decl_arena.deinit();
|
||||||
@ -4973,13 +5031,17 @@ fn analyzeCall(
|
|||||||
|
|
||||||
// Populate the Decl ty/val with the function and its type.
|
// Populate the Decl ty/val with the function and its type.
|
||||||
new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator);
|
new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator);
|
||||||
|
// If the call evaluated to a generic type return errror and call inline.
|
||||||
|
if (new_decl.ty.fnInfo().is_generic) return error.GenericPoison;
|
||||||
|
|
||||||
new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func);
|
new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func);
|
||||||
|
new_decl.has_tv = true;
|
||||||
|
new_decl.owns_tv = true;
|
||||||
new_decl.analysis = .complete;
|
new_decl.analysis = .complete;
|
||||||
|
|
||||||
log.debug("generic function '{s}' instantiated with type {}", .{
|
log.debug("generic function '{s}' instantiated with type {}", .{
|
||||||
new_decl.name, new_decl.ty,
|
new_decl.name, new_decl.ty,
|
||||||
});
|
});
|
||||||
assert(!new_decl.ty.fnInfo().is_generic);
|
|
||||||
|
|
||||||
// Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field
|
// Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field
|
||||||
// will be populated, ensuring it will have `analyzeBody` called with the ZIR
|
// will be populated, ensuring it will have `analyzeBody` called with the ZIR
|
||||||
@ -4990,63 +5052,7 @@ fn analyzeCall(
|
|||||||
try new_decl.finalizeNewArena(&new_decl_arena);
|
try new_decl.finalizeNewArena(&new_decl_arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
break :res try sema.finishGenericCall(
|
const callee = gop.key_ptr.*;
|
||||||
block,
|
|
||||||
call_src,
|
|
||||||
new_module_func,
|
|
||||||
func_src,
|
|
||||||
uncasted_args,
|
|
||||||
fn_info,
|
|
||||||
zir_tags,
|
|
||||||
);
|
|
||||||
} else res: {
|
|
||||||
try sema.requireRuntimeBlock(block, call_src);
|
|
||||||
|
|
||||||
const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
|
|
||||||
for (uncasted_args) |uncasted_arg, i| {
|
|
||||||
const arg_src = call_src; // TODO: better source location
|
|
||||||
if (i < fn_params_len) {
|
|
||||||
const param_ty = func_ty.fnParamType(i);
|
|
||||||
try sema.resolveTypeFully(block, arg_src, param_ty);
|
|
||||||
args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
|
|
||||||
} else {
|
|
||||||
args[i] = uncasted_arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try sema.resolveTypeFully(block, call_src, func_ty_info.return_type);
|
|
||||||
|
|
||||||
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
|
|
||||||
args.len);
|
|
||||||
const func_inst = try block.addInst(.{
|
|
||||||
.tag = .call,
|
|
||||||
.data = .{ .pl_op = .{
|
|
||||||
.operand = func,
|
|
||||||
.payload = sema.addExtraAssumeCapacity(Air.Call{
|
|
||||||
.args_len = @intCast(u32, args.len),
|
|
||||||
}),
|
|
||||||
} },
|
|
||||||
});
|
|
||||||
sema.appendRefsAssumeCapacity(args);
|
|
||||||
break :res func_inst;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ensure_result_used) {
|
|
||||||
try sema.ensureResultUsed(block, result, call_src);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finishGenericCall(
|
|
||||||
sema: *Sema,
|
|
||||||
block: *Block,
|
|
||||||
call_src: LazySrcLoc,
|
|
||||||
callee: *Module.Fn,
|
|
||||||
func_src: LazySrcLoc,
|
|
||||||
uncasted_args: []const Air.Inst.Ref,
|
|
||||||
fn_info: Zir.FnInfo,
|
|
||||||
zir_tags: []const Zir.Inst.Tag,
|
|
||||||
) CompileError!Air.Inst.Ref {
|
|
||||||
const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl);
|
const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl);
|
||||||
|
|
||||||
// Make a runtime call to the new function, making sure to omit the comptime args.
|
// Make a runtime call to the new function, making sure to omit the comptime args.
|
||||||
@ -5106,6 +5112,10 @@ fn finishGenericCall(
|
|||||||
} },
|
} },
|
||||||
});
|
});
|
||||||
sema.appendRefsAssumeCapacity(runtime_args);
|
sema.appendRefsAssumeCapacity(runtime_args);
|
||||||
|
|
||||||
|
if (ensure_result_used) {
|
||||||
|
try sema.ensureResultUsed(block, func_inst, call_src);
|
||||||
|
}
|
||||||
return func_inst;
|
return func_inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,7 @@ test {
|
|||||||
_ = @import("behavior/saturating_arithmetic.zig");
|
_ = @import("behavior/saturating_arithmetic.zig");
|
||||||
_ = @import("behavior/widening.zig");
|
_ = @import("behavior/widening.zig");
|
||||||
_ = @import("behavior/bugs/2114.zig");
|
_ = @import("behavior/bugs/2114.zig");
|
||||||
|
_ = @import("behavior/bugs/3779.zig");
|
||||||
_ = @import("behavior/union_with_members.zig");
|
_ = @import("behavior/union_with_members.zig");
|
||||||
|
|
||||||
if (builtin.zig_backend == .stage1) {
|
if (builtin.zig_backend == .stage1) {
|
||||||
@ -160,7 +161,6 @@ test {
|
|||||||
_ = @import("behavior/bugs/920.zig");
|
_ = @import("behavior/bugs/920.zig");
|
||||||
_ = @import("behavior/bugs/1120.zig");
|
_ = @import("behavior/bugs/1120.zig");
|
||||||
_ = @import("behavior/bugs/1851.zig");
|
_ = @import("behavior/bugs/1851.zig");
|
||||||
_ = @import("behavior/bugs/3779.zig");
|
|
||||||
_ = @import("behavior/bugs/6456.zig");
|
_ = @import("behavior/bugs/6456.zig");
|
||||||
_ = @import("behavior/bugs/6781.zig");
|
_ = @import("behavior/bugs/6781.zig");
|
||||||
_ = @import("behavior/bugs/7027.zig");
|
_ = @import("behavior/bugs/7027.zig");
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const TestEnum = enum { TestEnumValue };
|
const TestEnum = enum { TestEnumValue };
|
||||||
const tag_name = @tagName(TestEnum.TestEnumValue);
|
const tag_name = @tagName(TestEnum.TestEnumValue);
|
||||||
const ptr_tag_name: [*:0]const u8 = tag_name;
|
const ptr_tag_name: [*:0]const u8 = tag_name;
|
||||||
|
|
||||||
test "@tagName() returns a string literal" {
|
test "@tagName() returns a string literal" {
|
||||||
try std.testing.expectEqual([:0]const u8, @TypeOf(tag_name));
|
if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the type wrong
|
||||||
|
try std.testing.expectEqual(*const [13:0]u8, @TypeOf(tag_name));
|
||||||
try std.testing.expectEqualStrings("TestEnumValue", tag_name);
|
try std.testing.expectEqualStrings("TestEnumValue", tag_name);
|
||||||
try std.testing.expectEqualStrings("TestEnumValue", ptr_tag_name[0..tag_name.len]);
|
try std.testing.expectEqualStrings("TestEnumValue", ptr_tag_name[0..tag_name.len]);
|
||||||
}
|
}
|
||||||
@ -15,7 +17,8 @@ const error_name = @errorName(TestError.TestErrorCode);
|
|||||||
const ptr_error_name: [*:0]const u8 = error_name;
|
const ptr_error_name: [*:0]const u8 = error_name;
|
||||||
|
|
||||||
test "@errorName() returns a string literal" {
|
test "@errorName() returns a string literal" {
|
||||||
try std.testing.expectEqual([:0]const u8, @TypeOf(error_name));
|
if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the type wrong
|
||||||
|
try std.testing.expectEqual(*const [13:0]u8, @TypeOf(error_name));
|
||||||
try std.testing.expectEqualStrings("TestErrorCode", error_name);
|
try std.testing.expectEqualStrings("TestErrorCode", error_name);
|
||||||
try std.testing.expectEqualStrings("TestErrorCode", ptr_error_name[0..error_name.len]);
|
try std.testing.expectEqualStrings("TestErrorCode", ptr_error_name[0..error_name.len]);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user