Zir: eliminate field_call_bind and field_call_bind_named

This commit removes the `field_call_bind` and `field_call_bind_named` ZIR
instructions, replacing them with a `field_call` instruction which does the bind
and call in one.

`field_call_bind` is an unfortunate instruction. It's tied into one very
specific usage pattern - its result can only be used as a callee. This means
that it creates a value of a "pseudo-type" of sorts, `bound_fn` - this type used
to exist in Zig, but now we just hide it from the user and have AstGen ensure
it's only used in one way. This is quite silly - `Type` and `Value` should, as
much as possible, reflect real Zig types and values.

It makes sense to instead encode the `a.b()` syntax as its own ZIR instruction,
so that's what we do here. This commit introduces a new instruction,
`field_call`. It's like `call`, but rather than a callee ref, it contains a ref
to the object pointer (`&a` in `a.b()`) and the string field name (`b`). This
eliminates `bound_fn` from the language, and slightly decreases the size of
generated ZIR - stats below.

This commit does remove a few usages which used to be allowed:
- `@field(a, "b")()`
- `@call(.auto, a.b, .{})`
- `@call(.auto, @field(a, "b"), .{})`

These forms used to work just like `a.b()`, but are no longer allowed. I believe
this is the correct choice for a few reasons:
- `a.b()` is a purely *syntactic* form; for instance, `(a.b)()` is not valid.
  This means it is *not* inconsistent to not allow it in these cases; the
  special case here isn't "a field access as a callee", but rather this exact
  syntactic form.
- The second argument to `@call` looks much more visually distinct from the
  callee in standard call syntax. To me, this makes it seem strange for that
  argument to not work like a normal expression in this context.
- A more practical argument: it's confusing! `@field` and `@call` are used in
  very different contexts to standard function calls: the former normally hints
  at some comptime machinery, and the latter that you want more precise control
  over parts of a function call. In these contexts, you don't want implicit
  arguments adding extra confusion: you want to be very explicit about what
  you're doing.

Lastly, some stats. I mentioned before that this change slightly reduces the
size of ZIR - this is due to two instructions (`field_call_bind` then `call`)
being replaced with one (`field_call`). Here are some numbers:

+--------------+----------+----------+--------+
| File         | Before   | After    | Change |
+--------------+----------+----------+--------+
| Sema.zig     | 4.72M    | 4.53M    | -4%    |
| AstGen.zig   | 1.52M    | 1.48M    | -3%    |
| hash_map.zig | 283.9K   | 276.2K   | -3%    |
| math.zig     | 312.6K   | 305.3K   | -2%    |
+--------------+----------+----------+--------+
This commit is contained in:
mlugg 2023-05-13 17:10:05 +01:00 committed by Andrew Kelley
parent 7077e90b3f
commit 38b83d9d93
16 changed files with 231 additions and 288 deletions

View File

@ -169,7 +169,7 @@ const FutexImpl = struct {
}
}
inline fn lockFast(self: *@This(), comptime casFn: []const u8) bool {
inline fn lockFast(self: *@This(), comptime cas_fn_name: []const u8) bool {
// On x86, use `lock bts` instead of `lock cmpxchg` as:
// - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
// - `lock bts` is smaller instruction-wise which makes it better for inlining
@ -180,7 +180,8 @@ const FutexImpl = struct {
// Acquire barrier ensures grabbing the lock happens before the critical section
// and that the previous lock holder's critical section happens before we grab the lock.
return @field(self.state, casFn)(unlocked, locked, .Acquire, .Monotonic) == null;
const casFn = @field(@TypeOf(self.state), cas_fn_name);
return casFn(&self.state, unlocked, locked, .Acquire, .Monotonic) == null;
}
fn lockSlow(self: *@This()) void {

View File

@ -167,8 +167,8 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round
pub fn hash(msg: []const u8, key: *const [key_length]u8) T {
const aligned_len = msg.len - (msg.len % 8);
var c = Self.init(key);
@call(.always_inline, c.update, .{msg[0..aligned_len]});
return @call(.always_inline, c.final, .{msg[aligned_len..]});
@call(.always_inline, update, .{ &c, msg[0..aligned_len] });
return @call(.always_inline, final, .{ &c, msg[aligned_len..] });
}
};
}

View File

@ -64,9 +64,13 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
/// Strategy is provided to determine if pointers should be followed or not.
pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
const Key = @TypeOf(key);
const Hasher = switch (@typeInfo(@TypeOf(hasher))) {
.Pointer => |ptr| ptr.child,
else => @TypeOf(hasher),
};
if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
@call(.always_inline, hasher.update, .{mem.asBytes(&key)});
@call(.always_inline, Hasher.update, .{ hasher, mem.asBytes(&key) });
return;
}
@ -89,12 +93,12 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
// TODO Check if the situation is better after #561 is resolved.
.Int => {
if (comptime meta.trait.hasUniqueRepresentation(Key)) {
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)});
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key) });
} else {
// Take only the part containing the key value, the remaining
// bytes are undefined and must not be hashed!
const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable;
@call(.always_inline, hasher.update, .{std.mem.asBytes(&key)[0..byte_size]});
@call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key)[0..byte_size] });
}
},

View File

@ -65,7 +65,7 @@ const WyhashStateless = struct {
var off: usize = 0;
while (off < b.len) : (off += 32) {
@call(.always_inline, self.round, .{b[off..][0..32]});
@call(.always_inline, round, .{ self, b[off..][0..32] });
}
self.msg_len += b.len;
@ -121,8 +121,8 @@ const WyhashStateless = struct {
const aligned_len = input.len - (input.len % 32);
var c = WyhashStateless.init(seed);
@call(.always_inline, c.update, .{input[0..aligned_len]});
return @call(.always_inline, c.final, .{input[aligned_len..]});
@call(.always_inline, update, .{ &c, input[0..aligned_len] });
return @call(.always_inline, final, .{ &c, input[aligned_len..] });
}
};

View File

@ -2482,7 +2482,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
switch (zir_tags[inst]) {
// For some instructions, modify the zir data
// so we can avoid a separate ensure_result_used instruction.
.call => {
.call, .field_call => {
const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index;
const slot = &gz.astgen.extra.items[extra_index];
var flags = @bitCast(Zir.Inst.Call.Flags, slot.*);
@ -2557,7 +2557,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.field_ptr,
.field_ptr_init,
.field_val,
.field_call_bind,
.field_ptr_named,
.field_val_named,
.func,
@ -8516,7 +8515,7 @@ fn builtinCall(
},
.call => {
const modifier = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = .modifier_type } }, params[0]);
const callee = try calleeExpr(gz, scope, params[1]);
const callee = try expr(gz, scope, .{ .rl = .none }, params[1]);
const args = try expr(gz, scope, .{ .rl = .none }, params[2]);
const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{
.modifier = modifier,
@ -8976,7 +8975,10 @@ fn callExpr(
} });
}
assert(callee != .none);
switch (callee) {
.direct => |obj| assert(obj != .none),
.field => |field| assert(field.obj_ptr != .none),
}
assert(node != 0);
const call_index = @intCast(Zir.Inst.Index, astgen.instructions.len);
@ -9015,89 +9017,98 @@ fn callExpr(
else => false,
};
const payload_index = try addExtra(astgen, Zir.Inst.Call{
.callee = callee,
.flags = .{
.pop_error_return_trace = !propagate_error_trace,
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
switch (callee) {
.direct => |callee_obj| {
const payload_index = try addExtra(astgen, Zir.Inst.Call{
.callee = callee_obj,
.flags = .{
.pop_error_return_trace = !propagate_error_trace,
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
},
});
if (call.ast.params.len != 0) {
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
}
gz.astgen.instructions.set(call_index, .{
.tag = .call,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(node),
.payload_index = payload_index,
} },
});
},
.field => |callee_field| {
const payload_index = try addExtra(astgen, Zir.Inst.FieldCall{
.obj_ptr = callee_field.obj_ptr,
.field_name_start = callee_field.field_name_start,
.flags = .{
.pop_error_return_trace = !propagate_error_trace,
.packed_modifier = @intCast(Zir.Inst.Call.Flags.PackedModifier, @enumToInt(modifier)),
.args_len = @intCast(Zir.Inst.Call.Flags.PackedArgsLen, call.ast.params.len),
},
});
if (call.ast.params.len != 0) {
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
}
gz.astgen.instructions.set(call_index, .{
.tag = .field_call,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(node),
.payload_index = payload_index,
} },
});
},
});
if (call.ast.params.len != 0) {
try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
}
gz.astgen.instructions.set(call_index, .{
.tag = .call,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(node),
.payload_index = payload_index,
} },
});
return rvalue(gz, ri, call_inst, node); // TODO function call with result location
}
/// calleeExpr generates the function part of a call expression (f in f(x)), or the
/// callee argument to the @call() builtin. If the lhs is a field access or the
/// @field() builtin, we need to generate a special field_call_bind instruction
/// instead of the normal field_val or field_ptr. If this is a inst.func() call,
/// this instruction will capture the value of the first argument before evaluating
/// the other arguments. We need to use .ref here to guarantee we will be able to
/// promote an lvalue to an address if the first parameter requires it. This
/// unfortunately also means we need to take a reference to any types on the lhs.
const Callee = union(enum) {
field: struct {
/// A *pointer* to the object the field is fetched on, so that we can
/// promote the lvalue to an address if the first parameter requires it.
obj_ptr: Zir.Inst.Ref,
/// Offset into `string_bytes`.
field_name_start: u32,
},
direct: Zir.Inst.Ref,
};
/// calleeExpr generates the function part of a call expression (f in f(x)), but
/// *not* the callee argument to the @call() builtin. Its purpose is to
/// distinguish between standard calls and method call syntax `a.b()`. Thus, if
/// the lhs is a field access, we return using the `field` union field;
/// otherwise, we use the `direct` union field.
fn calleeExpr(
gz: *GenZir,
scope: *Scope,
node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref {
) InnerError!Callee {
const astgen = gz.astgen;
const tree = astgen.tree;
const tag = tree.nodes.items(.tag)[node];
switch (tag) {
.field_access => return addFieldAccess(.field_call_bind, gz, scope, .{ .rl = .ref }, node),
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
.builtin_call_comma,
=> {
const node_datas = tree.nodes.items(.data);
.field_access => {
const main_tokens = tree.nodes.items(.main_token);
const builtin_token = main_tokens[node];
const builtin_name = tree.tokenSlice(builtin_token);
const node_datas = tree.nodes.items(.data);
const object_node = node_datas[node].lhs;
const dot_token = main_tokens[node];
const field_ident = dot_token + 1;
const str_index = try astgen.identAsString(field_ident);
// Capture the object by reference so we can promote it to an
// address in Sema if needed.
const lhs = try expr(gz, scope, .{ .rl = .ref }, object_node);
var inline_params: [2]Ast.Node.Index = undefined;
var params: []Ast.Node.Index = switch (tag) {
.builtin_call,
.builtin_call_comma,
=> tree.extra_data[node_datas[node].lhs..node_datas[node].rhs],
const cursor = maybeAdvanceSourceCursorToMainToken(gz, node);
try emitDbgStmt(gz, cursor);
.builtin_call_two,
.builtin_call_two_comma,
=> blk: {
inline_params = .{ node_datas[node].lhs, node_datas[node].rhs };
const len: usize = if (inline_params[0] == 0) @as(usize, 0) else if (inline_params[1] == 0) @as(usize, 1) else @as(usize, 2);
break :blk inline_params[0..len];
},
else => unreachable,
};
// If anything is wrong, fall back to builtinCall.
// It will emit any necessary compile errors and notes.
if (std.mem.eql(u8, builtin_name, "@field") and params.len == 2) {
const lhs = try expr(gz, scope, .{ .rl = .ref }, params[0]);
const field_name = try comptimeExpr(gz, scope, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[1]);
return gz.addExtendedPayload(.field_call_bind_named, Zir.Inst.FieldNamedNode{
.node = gz.nodeIndexToRelative(node),
.lhs = lhs,
.field_name = field_name,
});
}
return builtinCall(gz, scope, .{ .rl = .none }, node, params);
return .{ .field = .{
.obj_ptr = lhs,
.field_name_start = str_index,
} };
},
else => return expr(gz, scope, .{ .rl = .none }, node),
else => return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) },
}
}

View File

@ -2141,7 +2141,7 @@ fn walkInstruction(
.expr = .{ .declRef = decl_status },
};
},
.field_val, .field_call_bind, .field_ptr, .field_type => {
.field_val, .field_ptr, .field_type => {
// TODO: field type uses Zir.Inst.FieldType, it just happens to have the
// same layout as Zir.Inst.Field :^)
const pl_node = data[inst_index].pl_node;
@ -2163,7 +2163,6 @@ fn walkInstruction(
const lhs = @enumToInt(lhs_extra.data.lhs) - Ref.typed_value_map.len;
if (tags[lhs] != .field_val and
tags[lhs] != .field_call_bind and
tags[lhs] != .field_ptr and
tags[lhs] != .field_type) break :blk lhs_extra.data.lhs;
@ -2191,7 +2190,7 @@ fn walkInstruction(
const wr = blk: {
if (@enumToInt(lhs_ref) >= Ref.typed_value_map.len) {
const lhs_inst = @enumToInt(lhs_ref) - Ref.typed_value_map.len;
if (tags[lhs_inst] == .call) {
if (tags[lhs_inst] == .call or tags[lhs_inst] == .field_call) {
break :blk DocData.WalkResult{
.expr = .{
.comptimeExpr = 0,

View File

@ -2489,8 +2489,21 @@ pub const SrcLoc = struct {
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const tok_index = switch (node_tags[node]) {
.field_access => node_datas[node].rhs,
.call_one,
.call_one_comma,
.async_call_one,
.async_call_one_comma,
.call,
.call_comma,
.async_call,
.async_call_comma,
=> blk: {
const full = tree.fullCall(&buf, node).?;
break :blk tree.lastToken(full.ast.fn_expr);
},
else => tree.firstToken(node) - 2,
};
const start = tree.tokens.items(.start)[tok_index];
@ -3083,7 +3096,8 @@ pub const LazySrcLoc = union(enum) {
/// The payload is offset from the containing Decl AST node.
/// The source location points to the field name of:
/// * a field access expression (`a.b`), or
/// * the operand ("b" node) of a field initialization expression (`.a = b`)
/// * the callee of a method call (`a.b()`), or
/// * the operand ("b" node) of a field initialization expression (`.a = b`), or
/// The Decl is determined contextually.
node_offset_field_name: i32,
/// The source location points to the pointer of a pointer deref expression,

View File

@ -920,7 +920,8 @@ fn analyzeBodyInner(
.bool_br_and => try sema.zirBoolBr(block, inst, false),
.bool_br_or => try sema.zirBoolBr(block, inst, true),
.c_import => try sema.zirCImport(block, inst),
.call => try sema.zirCall(block, inst),
.call => try sema.zirCall(block, inst, .direct),
.field_call => try sema.zirCall(block, inst, .field),
.closure_get => try sema.zirClosureGet(block, inst),
.cmp_lt => try sema.zirCmp(block, inst, .lt),
.cmp_lte => try sema.zirCmp(block, inst, .lte),
@ -952,7 +953,6 @@ fn analyzeBodyInner(
.field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
.field_val => try sema.zirFieldVal(block, inst),
.field_val_named => try sema.zirFieldValNamed(block, inst),
.field_call_bind => try sema.zirFieldCallBind(block, inst),
.func => try sema.zirFunc(block, inst, false),
.func_inferred => try sema.zirFunc(block, inst, true),
.func_fancy => try sema.zirFuncFancy(block, inst),
@ -1149,7 +1149,6 @@ fn analyzeBodyInner(
.wasm_memory_size => try sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => try sema.zirWasmMemoryGrow( block, extended),
.prefetch => try sema.zirPrefetch( block, extended),
.field_call_bind_named => try sema.zirFieldCallBindNamed(block, extended),
.err_set_cast => try sema.zirErrSetCast( block, extended),
.await_nosuspend => try sema.zirAwaitNosuspend( block, extended),
.select => try sema.zirSelect( block, extended),
@ -6262,38 +6261,50 @@ fn zirCall(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
comptime kind: enum { direct, field },
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
const callee_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
const call_src = inst_data.src();
const extra = sema.code.extraData(Zir.Inst.Call, inst_data.payload_index);
const ExtraType = switch (kind) {
.direct => Zir.Inst.Call,
.field => Zir.Inst.FieldCall,
};
const extra = sema.code.extraData(ExtraType, inst_data.payload_index);
const args_len = extra.data.flags.args_len;
const modifier = @intToEnum(std.builtin.CallModifier, extra.data.flags.packed_modifier);
const ensure_result_used = extra.data.flags.ensure_result_used;
const pop_error_return_trace = extra.data.flags.pop_error_return_trace;
var func = try sema.resolveInst(extra.data.callee);
const callee: ResolvedFieldCallee = switch (kind) {
.direct => .{ .direct = try sema.resolveInst(extra.data.callee) },
.field => blk: {
const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
const field_name = sema.code.nullTerminatedString(extra.data.field_name_start);
const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src);
},
};
var resolved_args: []Air.Inst.Ref = undefined;
var arg_index: u32 = 0;
const func_type = sema.typeOf(func);
// Desugar bound functions here
var bound_arg_src: ?LazySrcLoc = null;
if (func_type.tag() == .bound_fn) {
bound_arg_src = func_src;
const bound_func = try sema.resolveValue(block, .unneeded, func, "");
const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data;
func = bound_data.func_inst;
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len + 1);
resolved_args[arg_index] = bound_data.arg0_inst;
arg_index += 1;
} else {
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len);
var func: Air.Inst.Ref = undefined;
var arg_index: u32 = 0;
switch (callee) {
.direct => |func_inst| {
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len);
func = func_inst;
},
.method => |method| {
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_len + 1);
func = method.func_inst;
resolved_args[0] = method.arg0_inst;
arg_index += 1;
bound_arg_src = callee_src;
},
}
const callee_ty = sema.typeOf(func);
@ -6308,10 +6319,11 @@ fn zirCall(
},
else => {},
}
return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
return sema.fail(block, callee_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
};
const total_args = args_len + @boolToInt(bound_arg_src != null);
try sema.checkCallArgumentCount(block, func, func_src, func_ty, total_args, bound_arg_src != null);
try sema.checkCallArgumentCount(block, func, callee_src, func_ty, total_args, bound_arg_src != null);
const args_body = sema.code.extra[extra.end..];
@ -6369,7 +6381,7 @@ fn zirCall(
!block.is_comptime and !block.is_typeof and (input_is_error or pop_error_return_trace))
{
const call_inst: Air.Inst.Ref = if (modifier == .always_tail) undefined else b: {
break :b try sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
break :b try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
};
const return_ty = sema.typeOf(call_inst);
@ -6398,11 +6410,11 @@ fn zirCall(
}
if (modifier == .always_tail) // Perform the call *after* the restore, so that a tail call is possible.
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
return sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
return call_inst;
} else {
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
return sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, call_dbg_node);
}
}
@ -9467,19 +9479,6 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: b
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing);
}
fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
const field_name = sema.code.nullTerminatedString(extra.field_name_start);
const object_ptr = try sema.resolveInst(extra.lhs);
return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
}
fn zirFieldValNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -9506,18 +9505,6 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
}
fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.FieldNamedNode, extended.operand).data;
const src = LazySrcLoc.nodeOffset(extra.node);
const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
const object_ptr = try sema.resolveInst(extra.lhs);
const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime-known");
return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
}
fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@ -21673,25 +21660,9 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
return sema.fail(block, args_src, "expected a tuple, found '{}'", .{args_ty.fmt(sema.mod)});
}
var resolved_args: []Air.Inst.Ref = undefined;
// Desugar bound functions here
var bound_arg_src: ?LazySrcLoc = null;
if (sema.typeOf(func).tag() == .bound_fn) {
bound_arg_src = func_src;
const bound_func = try sema.resolveValue(block, .unneeded, func, "");
const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data;
func = bound_data.func_inst;
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount() + 1);
resolved_args[0] = bound_data.arg0_inst;
for (resolved_args[1..], 0..) |*resolved, i| {
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
}
} else {
resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount());
for (resolved_args, 0..) |*resolved, i| {
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
}
var resolved_args: []Air.Inst.Ref = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount());
for (resolved_args, 0..) |*resolved, i| {
resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty);
}
const callee_ty = sema.typeOf(func);
@ -21708,10 +21679,10 @@ fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
}
return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)});
};
try sema.checkCallArgumentCount(block, func, func_src, func_ty, resolved_args.len, bound_arg_src != null);
try sema.checkCallArgumentCount(block, func, func_src, func_ty, resolved_args.len, false);
const ensure_result_used = extra.flags.ensure_result_used;
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src, null);
return sema.analyzeCall(block, func, func_ty, func_src, call_src, modifier, ensure_result_used, resolved_args, null, null);
}
fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -24175,6 +24146,16 @@ fn fieldPtr(
return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name);
}
const ResolvedFieldCallee = union(enum) {
/// The LHS of the call was an actual field with this value.
direct: Air.Inst.Ref,
/// This is a method call, with the function and first argument given.
method: struct {
func_inst: Air.Inst.Ref,
arg0_inst: Air.Inst.Ref,
},
};
fn fieldCallBind(
sema: *Sema,
block: *Block,
@ -24182,7 +24163,7 @@ fn fieldCallBind(
raw_ptr: Air.Inst.Ref,
field_name: []const u8,
field_name_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
) CompileError!ResolvedFieldCallee {
// When editing this function, note that there is corresponding logic to be edited
// in `fieldVal`. This function takes a pointer and returns a pointer.
@ -24202,7 +24183,6 @@ fn fieldCallBind(
else
raw_ptr;
const arena = sema.arena;
find_field: {
switch (concrete_ty.zigTypeTag()) {
.Struct => {
@ -24216,7 +24196,7 @@ fn fieldCallBind(
return sema.finishFieldCallBind(block, src, ptr_ty, field.ty, field_index, object_ptr);
} else if (struct_ty.isTuple()) {
if (mem.eql(u8, field_name, "len")) {
return sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount());
return .{ .direct = try sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount()) };
}
if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| {
if (field_index >= struct_ty.structFieldCount()) break :find_field;
@ -24243,7 +24223,7 @@ fn fieldCallBind(
},
.Type => {
const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
return sema.fieldVal(block, src, namespace, field_name, field_name_src);
return .{ .direct = try sema.fieldVal(block, src, namespace, field_name, field_name_src) };
},
else => {},
}
@ -24272,54 +24252,47 @@ fn fieldCallBind(
first_param_type.childType().eql(concrete_ty, sema.mod)))
{
// zig fmt: on
// Note that if the param type is generic poison, we know that it must
// specifically be `anytype` since it's the first parameter, meaning we
// can safely assume it can be a pointer.
// TODO: bound fn calls on rvalues should probably
// generate a by-value argument somehow.
const ty = Type.Tag.bound_fn.init();
const value = try Value.Tag.bound_fn.create(arena, .{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = object_ptr,
});
return sema.addConstant(ty, value);
} };
} else if (first_param_type.eql(concrete_ty, sema.mod)) {
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
const ty = Type.Tag.bound_fn.init();
const value = try Value.Tag.bound_fn.create(arena, .{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
});
return sema.addConstant(ty, value);
} };
} else if (first_param_type.zigTypeTag() == .Optional) {
var opt_buf: Type.Payload.ElemType = undefined;
const child = first_param_type.optionalChild(&opt_buf);
if (child.eql(concrete_ty, sema.mod)) {
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
const ty = Type.Tag.bound_fn.init();
const value = try Value.Tag.bound_fn.create(arena, .{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
});
return sema.addConstant(ty, value);
} };
} else if (child.zigTypeTag() == .Pointer and
child.ptrSize() == .One and
child.childType().eql(concrete_ty, sema.mod))
{
const ty = Type.Tag.bound_fn.init();
const value = try Value.Tag.bound_fn.create(arena, .{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = object_ptr,
});
return sema.addConstant(ty, value);
} };
}
} else if (first_param_type.zigTypeTag() == .ErrorUnion and
first_param_type.errorUnionPayload().eql(concrete_ty, sema.mod))
{
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
const ty = Type.Tag.bound_fn.init();
const value = try Value.Tag.bound_fn.create(arena, .{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
});
return sema.addConstant(ty, value);
} };
}
}
break :found_decl decl_idx;
@ -24351,7 +24324,7 @@ fn finishFieldCallBind(
field_ty: Type,
field_index: u32,
object_ptr: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
) CompileError!ResolvedFieldCallee {
const arena = sema.arena;
const ptr_field_ty = try Type.ptr(arena, sema.mod, .{
.pointee_type = field_ty,
@ -24362,7 +24335,7 @@ fn finishFieldCallBind(
const container_ty = ptr_ty.childType();
if (container_ty.zigTypeTag() == .Struct) {
if (container_ty.structFieldValueComptime(field_index)) |default_val| {
return sema.addConstant(field_ty, default_val);
return .{ .direct = try sema.addConstant(field_ty, default_val) };
}
}
@ -24375,12 +24348,12 @@ fn finishFieldCallBind(
.field_index = field_index,
}),
);
return sema.analyzeLoad(block, src, pointer, src);
return .{ .direct = try sema.analyzeLoad(block, src, pointer, src) };
}
try sema.requireRuntimeBlock(block, src, null);
const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);
return sema.analyzeLoad(block, src, ptr_inst, src);
return .{ .direct = try sema.analyzeLoad(block, src, ptr_inst, src) };
}
fn namespaceLookup(
@ -31281,7 +31254,6 @@ pub fn resolveTypeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
@ -32666,7 +32638,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
.single_const_pointer,
.single_mut_pointer,
.pointer,
.bound_fn,
=> return null,
.optional => {
@ -33308,7 +33279,6 @@ pub fn typeRequiresComptime(sema: *Sema, ty: Type) CompileError!bool {
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,

View File

@ -499,10 +499,6 @@ pub fn print(
// TODO these should not appear in this function
.inferred_alloc => return writer.writeAll("(inferred allocation value)"),
.inferred_alloc_comptime => return writer.writeAll("(inferred comptime allocation value)"),
.bound_fn => {
const bound_func = val.castTag(.bound_fn).?.data;
return writer.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
},
.generic_poison_type => return writer.writeAll("(generic poison type)"),
.generic_poison => return writer.writeAll("(generic poison)"),
.runtime_value => return writer.writeAll("[runtime value]"),

View File

@ -297,6 +297,14 @@ pub const Inst = struct {
/// Uses the `pl_node` union field with payload `Call`.
/// AST node is the function call.
call,
/// Function call using `a.b()` syntax.
/// Uses the named field as the callee. If there is no such field, searches in the type for
/// a decl matching the field name. The decl is resolved and we ensure that it's a function
/// which can accept the object as the first parameter, with one pointer fixup. This
/// function is then used as the callee, with the object as an implicit first parameter.
/// Uses the `pl_node` union field with payload `FieldCall`.
/// AST node is the function call.
field_call,
/// Implements the `@call` builtin.
/// Uses the `pl_node` union field with payload `BuiltinCall`.
/// AST node is the builtin call.
@ -432,15 +440,6 @@ pub const Inst = struct {
/// This instruction also accepts a pointer.
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_val,
/// Given a pointer to a struct or object that contains virtual fields, returns the
/// named field. If there is no named field, searches in the type for a decl that
/// matches the field name. The decl is resolved and we ensure that it's a function
/// which can accept the object as the first parameter, with one pointer fixup. If
/// all of that works, this instruction produces a special "bound function" value
/// which contains both the function and the saved first parameter value.
/// Bound functions may only be used as the function parameter to a `call` or
/// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
field_call_bind,
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field. The field name is a comptime instruction. Used by @field.
/// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
@ -1051,6 +1050,7 @@ pub const Inst = struct {
.bool_br_or,
.bool_not,
.call,
.field_call,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@ -1083,7 +1083,6 @@ pub const Inst = struct {
.field_ptr,
.field_ptr_init,
.field_val,
.field_call_bind,
.field_ptr_named,
.field_val_named,
.func,
@ -1361,6 +1360,7 @@ pub const Inst = struct {
.bool_br_or,
.bool_not,
.call,
.field_call,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@ -1383,7 +1383,6 @@ pub const Inst = struct {
.field_ptr,
.field_ptr_init,
.field_val,
.field_call_bind,
.field_ptr_named,
.field_val_named,
.func,
@ -1601,6 +1600,7 @@ pub const Inst = struct {
.check_comptime_control_flow = .un_node,
.for_len = .pl_node,
.call = .pl_node,
.field_call = .pl_node,
.cmp_lt = .pl_node,
.cmp_lte = .pl_node,
.cmp_eq = .pl_node,
@ -1641,7 +1641,6 @@ pub const Inst = struct {
.field_val = .pl_node,
.field_ptr_named = .pl_node,
.field_val_named = .pl_node,
.field_call_bind = .pl_node,
.func = .pl_node,
.func_inferred = .pl_node,
.func_fancy = .pl_node,
@ -1955,16 +1954,6 @@ pub const Inst = struct {
/// The `@prefetch` builtin.
/// `operand` is payload index to `BinNode`.
prefetch,
/// Given a pointer to a struct or object that contains virtual fields, returns the
/// named field. If there is no named field, searches in the type for a decl that
/// matches the field name. The decl is resolved and we ensure that it's a function
/// which can accept the object as the first parameter, with one pointer fixup. If
/// all of that works, this instruction produces a special "bound function" value
/// which contains both the function and the saved first parameter value.
/// Bound functions may only be used as the function parameter to a `call` or
/// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
/// Uses `pl_node` field. The AST node is the `@field` builtin. Payload is FieldNamedNode.
field_call_bind_named,
/// Implements the `@fence` builtin.
/// `operand` is payload index to `UnNode`.
fence,
@ -2913,6 +2902,19 @@ pub const Inst = struct {
};
};
/// Stored inside extra, with trailing arguments according to `args_len`.
/// Implicit 0. arg_0_start: u32, // always same as `args_len`
/// 1. arg_end: u32, // for each `args_len`
/// arg_N_start is the same as arg_N-1_end
pub const FieldCall = struct {
// Note: Flags *must* come first so that unusedResultExpr
// can find it when it goes to modify them.
flags: Call.Flags,
obj_ptr: Ref,
/// Offset into `string_bytes`.
field_name_start: u32,
};
pub const TypeOfPeer = struct {
src_node: i32,
body_len: u32,
@ -3187,12 +3189,6 @@ pub const Inst = struct {
field_name: Ref,
};
pub const FieldNamedNode = struct {
node: i32,
lhs: Ref,
field_name: Ref,
};
pub const As = struct {
dest_type: Ref,
operand: Ref,

View File

@ -369,7 +369,6 @@ const Writer = struct {
.inferred_alloc_const => try s.writeAll("(inferred_alloc_const)"),
.inferred_alloc_mut => try s.writeAll("(inferred_alloc_mut)"),
.generic_poison => try s.writeAll("(generic_poison)"),
.bound_fn => try s.writeAll("(bound_fn)"),
else => try ty.print(s, w.module),
}
}

View File

@ -362,7 +362,8 @@ const Writer = struct {
.@"export" => try self.writePlNodeExport(stream, inst),
.export_value => try self.writePlNodeExportValue(stream, inst),
.call => try self.writeCall(stream, inst),
.call => try self.writeCall(stream, inst, .direct),
.field_call => try self.writeCall(stream, inst, .field),
.block,
.block_comptime,
@ -392,7 +393,6 @@ const Writer = struct {
.field_ptr,
.field_ptr_init,
.field_val,
.field_call_bind,
=> try self.writePlNodeField(stream, inst),
.field_ptr_named,
@ -543,15 +543,6 @@ const Writer = struct {
try self.writeSrc(stream, src);
},
.field_call_bind_named => {
const extra = self.code.extraData(Zir.Inst.FieldNamedNode, extended.operand).data;
const src = LazySrcLoc.nodeOffset(extra.node);
try self.writeInstRef(stream, extra.lhs);
try stream.writeAll(", ");
try self.writeInstRef(stream, extra.field_name);
try stream.writeAll(") ");
try self.writeSrc(stream, src);
},
.builtin_async_call => try self.writeBuiltinAsyncCall(stream, extended),
.cmpxchg => try self.writeCmpxchg(stream, extended),
}
@ -1176,9 +1167,18 @@ const Writer = struct {
try self.writeSrc(stream, src);
}
fn writeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
fn writeCall(
self: *Writer,
stream: anytype,
inst: Zir.Inst.Index,
comptime kind: enum { direct, field },
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
const ExtraType = switch (kind) {
.direct => Zir.Inst.Call,
.field => Zir.Inst.FieldCall,
};
const extra = self.code.extraData(ExtraType, inst_data.payload_index);
const args_len = extra.data.flags.args_len;
const body = self.code.extra[extra.end..];
@ -1186,7 +1186,14 @@ const Writer = struct {
try stream.writeAll("nodiscard ");
}
try stream.print(".{s}, ", .{@tagName(@intToEnum(std.builtin.CallModifier, extra.data.flags.packed_modifier))});
try self.writeInstRef(stream, extra.data.callee);
switch (kind) {
.direct => try self.writeInstRef(stream, extra.data.callee),
.field => {
const field_name = self.code.nullTerminatedString(extra.data.field_name_start);
try self.writeInstRef(stream, extra.data.obj_ptr);
try stream.print(", {}", .{std.zig.fmtId(field_name)});
},
}
try stream.writeAll(", [");
self.indent += 2;

View File

@ -156,8 +156,6 @@ pub const Type = extern union {
.union_tagged,
.type_info,
=> return .Union,
.bound_fn => unreachable,
}
}
@ -933,7 +931,6 @@ pub const Type = extern union {
// for example, a was resolved into .union_tagged but b was one of these tags.
.type_info => unreachable, // needed to resolve the type before now
.bound_fn => unreachable,
}
}
@ -1242,7 +1239,6 @@ pub const Type = extern union {
// we can't hash these based on tags because they wouldn't match the expanded version.
.type_info => unreachable, // needed to resolve the type before now
.bound_fn => unreachable,
}
}
@ -1349,7 +1345,6 @@ pub const Type = extern union {
.type_info,
.@"anyframe",
.generic_poison,
.bound_fn,
=> unreachable,
.array_u8,
@ -1613,7 +1608,6 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.bound_fn,
=> return writer.writeAll(@tagName(t)),
.enum_literal => return writer.writeAll("@Type(.EnumLiteral)"),
@ -1949,7 +1943,6 @@ pub const Type = extern union {
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
.generic_poison => unreachable,
.bound_fn => unreachable,
// TODO get rid of these Type.Tag values.
.atomic_order => unreachable,
@ -2468,7 +2461,6 @@ pub const Type = extern union {
.enum_literal,
.empty_struct,
.empty_struct_literal,
.bound_fn,
// These are function *bodies*, not pointers.
// Special exceptions have to be made when emitting functions due to
// this returning false.
@ -2703,7 +2695,6 @@ pub const Type = extern union {
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
@ -3182,7 +3173,6 @@ pub const Type = extern union {
.noreturn,
.inferred_alloc_const,
.inferred_alloc_mut,
.bound_fn,
=> unreachable,
.generic_poison => unreachable,
@ -3282,7 +3272,6 @@ pub const Type = extern union {
.fn_ccc_void_no_args => unreachable, // represents machine code; not a pointer
.function => unreachable, // represents machine code; not a pointer
.@"opaque" => unreachable, // no size available
.bound_fn => unreachable,
.noreturn => unreachable,
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
@ -3630,7 +3619,6 @@ pub const Type = extern union {
.inferred_alloc_mut => unreachable,
.@"opaque" => unreachable,
.generic_poison => unreachable,
.bound_fn => unreachable,
.void => return 0,
.bool, .u1 => return 1,
@ -5042,7 +5030,6 @@ pub const Type = extern union {
.single_const_pointer,
.single_mut_pointer,
.pointer,
.bound_fn,
=> return null,
.optional => {
@ -5245,7 +5232,6 @@ pub const Type = extern union {
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
@ -6081,7 +6067,6 @@ pub const Type = extern union {
inferred_alloc_mut,
/// Same as `inferred_alloc_mut` but the local is `var` not `const`.
inferred_alloc_const, // See last_no_payload_tag below.
bound_fn,
// After this, the tag requires a payload.
array_u8,
@ -6126,7 +6111,7 @@ pub const Type = extern union {
enum_full,
enum_nonexhaustive,
pub const last_no_payload_tag = Tag.bound_fn;
pub const last_no_payload_tag = Tag.inferred_alloc_const;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
pub fn Type(comptime t: Tag) type {
@ -6199,7 +6184,6 @@ pub const Type = extern union {
.extern_options,
.type_info,
.@"anyframe",
.bound_fn,
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
.array_u8,

View File

@ -183,10 +183,6 @@ pub const Value = extern union {
/// Used to coordinate alloc_inferred, store_to_inferred_ptr, and resolve_inferred_alloc
/// instructions for comptime code.
inferred_alloc_comptime,
/// Used sometimes as the result of field_call_bind. This value is always temporary,
/// and refers directly to the air. It will never be referenced by the air itself.
/// TODO: This is probably a bad encoding, maybe put temp data in the sema instead.
bound_fn,
/// The ABI alignment of the payload type.
lazy_align,
/// The ABI size of the payload type.
@ -326,7 +322,6 @@ pub const Value = extern union {
.inferred_alloc_comptime => Payload.InferredAllocComptime,
.aggregate => Payload.Aggregate,
.@"union" => Payload.Union,
.bound_fn => Payload.BoundFn,
.comptime_field_ptr => Payload.ComptimeFieldPtr,
};
}
@ -477,7 +472,6 @@ pub const Value = extern union {
.extern_options_type,
.type_info_type,
.generic_poison,
.bound_fn,
=> unreachable,
.ty, .lazy_align, .lazy_size => {
@ -837,10 +831,6 @@ pub const Value = extern union {
try out_stream.writeAll("(opt_payload_ptr)");
val = val.castTag(.opt_payload_ptr).?.data.container_ptr;
},
.bound_fn => {
const bound_func = val.castTag(.bound_fn).?.data;
return out_stream.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
},
};
}
@ -5657,16 +5647,6 @@ pub const Value = extern union {
val: Value,
},
};
pub const BoundFn = struct {
pub const base_tag = Tag.bound_fn;
base: Payload = Payload{ .tag = base_tag },
data: struct {
func_inst: Air.Inst.Ref,
arg0_inst: Air.Inst.Ref,
},
};
};
/// Big enough to fit any non-BigInt value

View File

@ -86,18 +86,6 @@ test "@field field calls" {
const pv = &v;
const pcv: *const HasFuncs = pv;
try expect(@field(v, "get")() == 0);
@field(v, "inc")();
try expect(v.state == 1);
try expect(@field(v, "get")() == 1);
@field(pv, "inc")();
try expect(v.state == 2);
try expect(@field(pv, "get")() == 2);
try expect(@field(v, "getPtr")().* == 2);
try expect(@field(pcv, "get")() == 2);
try expect(@field(pcv, "getPtr")().* == 2);
v.func_field = HasFuncs.one;
try expect(@field(v, "func_field")(0) == 1);
try expect(@field(pv, "func_field")(0) == 1);

View File

@ -6,10 +6,6 @@ pub export fn entry() void {
var s: S = undefined;
s.foo(true);
}
pub export fn entry2() void {
var s: S = undefined;
@call(.auto, s.foo, .{true});
}
// error
// backend=stage2
@ -17,5 +13,3 @@ pub export fn entry2() void {
//
// :7:6: error: member function expected 2 argument(s), found 1
// :3:5: note: function declared here
// :11:19: error: member function expected 2 argument(s), found 1
// :3:5: note: function declared here