From 1cc5d4e758a95be373756e7c32f9bb46d21633c9 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Tue, 28 Sep 2021 12:00:35 -0500 Subject: [PATCH] Stage 2: Support inst.func() syntax (#9827) * Merge call zir instructions to make space for field_call * Fix bug with comptime known anytype args * Delete the param_type zir instruction * Move some passing tests to stage 2 * Implement a.b() function calls * Add field_call_bind support for call and field builtins --- src/AstGen.zig | 161 +++++++++++----- src/Sema.zig | 294 ++++++++++++++++++++++-------- src/Zir.zig | 77 ++++---- src/print_zir.zig | 42 +++-- src/type.zig | 62 ++++++- src/value.zig | 20 ++ test/behavior.zig | 1 + test/behavior/eval.zig | 28 +++ test/behavior/eval_stage1.zig | 28 --- test/behavior/generics.zig | 16 ++ test/behavior/generics_stage1.zig | 16 -- test/behavior/member_func.zig | 103 +++++++++++ 12 files changed, 629 insertions(+), 219 deletions(-) create mode 100644 test/behavior/member_func.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 469e77037a..15594ac27c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -56,6 +56,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { u32 => @field(extra, field.name), Zir.Inst.Ref => @enumToInt(@field(extra, field.name)), i32 => @bitCast(u32, @field(extra, field.name)), + Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)), else => @compileError("bad field type"), }); } @@ -1934,11 +1935,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner // in the above while loop. const zir_tags = gz.astgen.instructions.items(.tag); switch (zir_tags[inst]) { - // For some instructions, swap in a slightly different ZIR tag + // For some instructions, modify the zir data // so we can avoid a separate ensure_result_used instruction. - .call_chkused => unreachable, .call => { - zir_tags[inst] = .call_chkused; + 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.*); + flags.ensure_result_used = true; + slot.* = @bitCast(u32, flags); break :b true; }, @@ -1976,9 +1980,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .bool_br_and, .bool_br_or, .bool_not, - .call_compile_time, - .call_nosuspend, - .call_async, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1996,8 +1997,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .elem_val_node, .field_ptr, .field_val, + .field_call_bind, .field_ptr_named, .field_val_named, + .field_call_bind_named, .func, .func_inferred, .int, @@ -2012,7 +2015,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .mod_rem, .mul, .mulwrap, - .param_type, .ref, .shl, .shr, @@ -4968,6 +4970,21 @@ fn fieldAccess( scope: *Scope, rl: ResultLoc, node: Ast.Node.Index, +) InnerError!Zir.Inst.Ref { + if (rl == .ref) { + return addFieldAccess(.field_ptr, gz, scope, .ref, node); + } else { + const access = try addFieldAccess(.field_val, gz, scope, .none_or_ref, node); + return rvalue(gz, rl, access, node); + } +} + +fn addFieldAccess( + tag: Zir.Inst.Tag, + gz: *GenZir, + scope: *Scope, + lhs_rl: ResultLoc, + node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; const tree = astgen.tree; @@ -4978,16 +4995,11 @@ fn fieldAccess( const dot_token = main_tokens[node]; const field_ident = dot_token + 1; const str_index = try astgen.identAsString(field_ident); - switch (rl) { - .ref => return gz.addPlNode(.field_ptr, node, Zir.Inst.Field{ - .lhs = try expr(gz, scope, .ref, object_node), - .field_name_start = str_index, - }), - else => return rvalue(gz, rl, try gz.addPlNode(.field_val, node, Zir.Inst.Field{ - .lhs = try expr(gz, scope, .none_or_ref, object_node), - .field_name_start = str_index, - }), node), - } + + return gz.addPlNode(tag, node, Zir.Inst.Field{ + .lhs = try expr(gz, scope, lhs_rl, object_node), + .field_name_start = str_index, + }); } fn arrayAccess( @@ -7169,16 +7181,15 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .field => { - const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); if (rl == .ref) { return gz.addPlNode(.field_ptr_named, node, Zir.Inst.FieldNamed{ .lhs = try expr(gz, scope, .ref, params[0]), - .field_name = field_name, + .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]), }); } const result = try gz.addPlNode(.field_val_named, node, Zir.Inst.FieldNamed{ .lhs = try expr(gz, scope, .none, params[0]), - .field_name = field_name, + .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]), }); return rvalue(gz, rl, result, node); }, @@ -7554,7 +7565,7 @@ fn builtinCall( }, .call => { const options = try comptimeExpr(gz, scope, .{ .ty = .call_options_type }, params[0]); - const callee = try expr(gz, scope, .none, params[1]); + const callee = try calleeExpr(gz, scope, params[1]); const args = try expr(gz, scope, .none, params[2]); const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{ .options = options, @@ -7897,20 +7908,16 @@ fn callExpr( call: Ast.full.Call, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; - const lhs = try expr(gz, scope, .none, call.ast.fn_expr); + + const callee = try calleeExpr(gz, scope, call.ast.fn_expr); const args = try astgen.gpa.alloc(Zir.Inst.Ref, call.ast.params.len); defer astgen.gpa.free(args); for (call.ast.params) |param_node, i| { - const param_type = try gz.add(.{ - .tag = .param_type, - .data = .{ .param_type = .{ - .callee = lhs, - .param_index = @intCast(u32, i), - } }, - }); - args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node); + // Parameters are always temporary values, they have no + // meaningful result location. Sema will coerce them. + args[i] = try expr(gz, scope, .none, param_node); } const modifier: std.builtin.CallOptions.Modifier = blk: { @@ -7925,20 +7932,72 @@ fn callExpr( } break :blk .auto; }; - const result: Zir.Inst.Ref = res: { - const tag: Zir.Inst.Tag = switch (modifier) { - .auto => .call, - .async_kw => .call_async, - .never_tail => unreachable, - .never_inline => unreachable, - .no_async => .call_nosuspend, - .always_tail => unreachable, - .always_inline => unreachable, - .compile_time => .call_compile_time, - }; - break :res try gz.addCall(tag, lhs, args, node); - }; - return rvalue(gz, rl, result, node); // TODO function call with result location + const call_inst = try gz.addCall(modifier, callee, args, node); + return rvalue(gz, rl, 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. +fn calleeExpr( + gz: *GenZir, + scope: *Scope, + node: Ast.Node.Index, +) InnerError!Zir.Inst.Ref { + 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, .ref, node), + + .builtin_call_two, + .builtin_call_two_comma, + .builtin_call, + .builtin_call_comma, + => { + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(builtin_token); + + 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], + + .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, .ref, params[0]); + const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); + return gz.addPlNode(.field_call_bind_named, node, Zir.Inst.FieldNamed{ + .lhs = lhs, + .field_name = field_name, + }); + } + + return builtinCall(gz, scope, .none, node, params); + }, + else => return expr(gz, scope, .none, node), + } } pub const simple_types = std.ComptimeStringMap(Zir.Inst.Ref, .{ @@ -9607,7 +9666,7 @@ const GenZir = struct { fn addCall( gz: *GenZir, - tag: Zir.Inst.Tag, + modifier: std.builtin.CallOptions.Modifier, callee: Zir.Inst.Ref, args: []const Zir.Inst.Ref, /// Absolute node index. This function does the conversion to offset from Decl. @@ -9616,20 +9675,24 @@ const GenZir = struct { assert(callee != .none); assert(src_node != 0); const gpa = gz.astgen.gpa; + const Call = Zir.Inst.Call; try gz.instructions.ensureUnusedCapacity(gpa, 1); try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); - try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Call).Struct.fields.len + + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Call).Struct.fields.len + args.len); - const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Call{ + const payload_index = gz.astgen.addExtraAssumeCapacity(Call{ .callee = callee, - .args_len = @intCast(u32, args.len), + .flags = .{ + .packed_modifier = @intCast(Call.Flags.PackedModifier, @enumToInt(modifier)), + .args_len = @intCast(Call.Flags.PackedArgsLen, args.len), + }, }); gz.astgen.appendRefsAssumeCapacity(args); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ - .tag = tag, + .tag = .call, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(src_node), .payload_index = payload_index, diff --git a/src/Sema.zig b/src/Sema.zig index b669cdb979..35a434eb35 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -185,11 +185,7 @@ pub fn analyzeBody( .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, .auto, false), - .call_chkused => try sema.zirCall(block, inst, .auto, true), - .call_compile_time => try sema.zirCall(block, inst, .compile_time, false), - .call_nosuspend => try sema.zirCall(block, inst, .no_async, false), - .call_async => try sema.zirCall(block, inst, .async_kw, false), + .call => try sema.zirCall(block, inst), .closure_get => try sema.zirClosureGet(block, inst), .cmp_lt => try sema.zirCmp(block, inst, .lt), .cmp_lte => try sema.zirCmp(block, inst, .lte), @@ -223,6 +219,8 @@ pub fn analyzeBody( .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), + .field_call_bind_named => try sema.zirFieldCallBindNamed(block, inst), .func => try sema.zirFunc(block, inst, false), .func_inferred => try sema.zirFunc(block, inst, true), .import => try sema.zirImport(block, inst), @@ -244,7 +242,6 @@ pub fn analyzeBody( .optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false), .optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false), .optional_type => try sema.zirOptionalType(block, inst), - .param_type => try sema.zirParamType(block, inst), .ptr_type => try sema.zirPtrType(block, inst), .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst), .ref => try sema.zirRef(block, inst), @@ -2031,45 +2028,6 @@ fn zirStoreNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE return sema.storePtr(block, src, ptr, value); } -fn zirParamType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const src = sema.src; - const fn_inst_src = sema.src; - - const inst_data = sema.code.instructions.items(.data)[inst].param_type; - const fn_inst = sema.resolveInst(inst_data.callee); - const fn_inst_ty = sema.typeOf(fn_inst); - const param_index = inst_data.param_index; - - const fn_ty: Type = switch (fn_inst_ty.zigTypeTag()) { - .Fn => fn_inst_ty, - .BoundFn => { - return sema.mod.fail(&block.base, fn_inst_src, "TODO implement zirParamType for method call syntax", .{}); - }, - else => { - return sema.mod.fail(&block.base, fn_inst_src, "expected function, found '{}'", .{fn_inst_ty}); - }, - }; - - const param_count = fn_ty.fnParamLen(); - if (param_index >= param_count) { - if (fn_ty.fnIsVarArgs()) { - return sema.addType(Type.initTag(.var_args_param)); - } - return sema.mod.fail(&block.base, src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{ - param_index, - fn_ty, - param_count, - }); - } - - // TODO support generic functions - const param_type = fn_ty.fnParamType(param_index); - return sema.addType(param_type); -} - fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -2786,8 +2744,6 @@ fn zirCall( sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, - modifier: std.builtin.CallOptions.Modifier, - ensure_result_used: bool, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -2796,14 +2752,31 @@ fn zirCall( const func_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 args = sema.code.refSlice(extra.end, extra.data.args_len); + const args = sema.code.refSlice(extra.end, extra.data.flags.args_len); - const func = sema.resolveInst(extra.data.callee); - // TODO handle function calls of generic functions - const resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len); - for (args) |zir_arg, i| { - // the args are already casted to the result of a param type instruction. - resolved_args[i] = sema.resolveInst(zir_arg); + const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier); + const ensure_result_used = extra.data.flags.ensure_result_used; + + var func = sema.resolveInst(extra.data.callee); + var resolved_args: []Air.Inst.Ref = undefined; + + const func_type = sema.typeOf(func); + + // Desugar bound functions here + if (func_type.tag() == .bound_fn) { + const bound_func = try sema.resolveValue(block, func_src, 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[0] = bound_data.arg0_inst; + for (args) |zir_arg, i| { + resolved_args[i + 1] = sema.resolveInst(zir_arg); + } + } else { + resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len); + for (args) |zir_arg, i| { + resolved_args[i] = sema.resolveInst(zir_arg); + } } return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args); @@ -3334,14 +3307,16 @@ fn analyzeCall( } const arg_src = call_src; // TODO: better source location const arg = uncasted_args[arg_i]; - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else if (is_comptime) { - return sema.failWithNeededComptime(block, arg_src); + if (is_comptime) { + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + return sema.failWithNeededComptime(block, arg_src); + } } else if (is_anytype) { // We insert into the map an instruction which is runtime-known - // but has the type of the comptime argument. + // but has the type of the argument. const child_arg = try child_block.addArg(sema.typeOf(arg), 0); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } @@ -4558,6 +4533,19 @@ fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } +fn zirFieldCallBind(sema: *Sema, block: *Scope.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 = sema.resolveInst(extra.lhs); + return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); +} + fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -4584,6 +4572,19 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } +fn zirFieldCallBindNamed(sema: *Sema, block: *Scope.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_builtin_call_arg1 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; + const object_ptr = sema.resolveInst(extra.lhs); + const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name); + return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); +} + fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -9484,6 +9485,148 @@ fn fieldPtr( return mod.fail(&block.base, src, "type '{}' does not support field access", .{object_ty}); } +fn fieldCallBind( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + raw_ptr: Air.Inst.Ref, + field_name: []const u8, + field_name_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + // When editing this function, note that there is corresponding logic to be edited + // in `fieldVal`. This function takes a pointer and returns a pointer. + + const mod = sema.mod; + const raw_ptr_src = src; // TODO better source location + const raw_ptr_ty = sema.typeOf(raw_ptr); + const inner_ty = if (raw_ptr_ty.zigTypeTag() == .Pointer and raw_ptr_ty.ptrSize() == .One) + raw_ptr_ty.childType() + else + return mod.fail(&block.base, raw_ptr_src, "expected single pointer, found '{}'", .{raw_ptr_ty}); + + // Optionally dereference a second pointer to get the concrete type. + const is_double_ptr = inner_ty.zigTypeTag() == .Pointer and inner_ty.ptrSize() == .One; + const concrete_ty = if (is_double_ptr) inner_ty.childType() else inner_ty; + const ptr_ty = if (is_double_ptr) inner_ty else raw_ptr_ty; + const object_ptr = if (is_double_ptr) + try sema.analyzeLoad(block, src, raw_ptr, src) + else + raw_ptr; + + const arena = sema.arena; + find_field: { + switch (concrete_ty.zigTypeTag()) { + .Struct => { + const struct_ty = try sema.resolveTypeFields(block, src, concrete_ty); + const struct_obj = struct_ty.castTag(.@"struct").?.data; + + const field_index = struct_obj.fields.getIndex(field_name) orelse + break :find_field; + const field = struct_obj.fields.values()[field_index]; + + const ptr_field_ty = try Type.ptr(arena, .{ + .pointee_type = field.ty, + .mutable = ptr_ty.ptrIsMutable(), + .@"addrspace" = ptr_ty.ptrAddressSpace(), + }); + + if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| { + const pointer = try sema.addConstant( + ptr_field_ty, + try Value.Tag.field_ptr.create(arena, .{ + .container_ptr = struct_ptr_val, + .field_index = field_index, + }), + ); + return sema.analyzeLoad(block, src, pointer, src); + } + + try sema.requireRuntimeBlock(block, src); + const ptr_inst = ptr_inst: { + const tag: Air.Inst.Tag = switch (field_index) { + 0 => .struct_field_ptr_index_0, + 1 => .struct_field_ptr_index_1, + 2 => .struct_field_ptr_index_2, + 3 => .struct_field_ptr_index_3, + else => { + break :ptr_inst try block.addInst(.{ + .tag = .struct_field_ptr, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(ptr_field_ty), + .payload = try sema.addExtra(Air.StructField{ + .struct_operand = object_ptr, + .field_index = @intCast(u32, field_index), + }), + } }, + }); + }, + }; + break :ptr_inst try block.addInst(.{ + .tag = tag, + .data = .{ .ty_op = .{ + .ty = try sema.addType(ptr_field_ty), + .operand = object_ptr, + } }, + }); + }; + return sema.analyzeLoad(block, src, ptr_inst, src); + }, + .Union => return sema.mod.fail(&block.base, src, "TODO implement field calls on unions", .{}), + .Type => { + const namespace = try sema.analyzeLoad(block, src, object_ptr, src); + return sema.fieldVal(block, src, namespace, field_name, field_name_src); + }, + else => {}, + } + } + + // If we get here, we need to look for a decl in the struct type instead. + switch (concrete_ty.zigTypeTag()) { + .Struct, .Opaque, .Union, .Enum => { + if (concrete_ty.getNamespace()) |namespace| { + if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { + const decl_val = try sema.analyzeLoad(block, src, inst, src); + const decl_type = sema.typeOf(decl_val); + if (decl_type.zigTypeTag() == .Fn and + decl_type.fnParamLen() >= 1) + { + const first_param_type = decl_type.fnParamType(0); + const first_param_tag = first_param_type.tag(); + // zig fmt: off + if (first_param_tag == .var_args_param or + first_param_tag == .generic_poison or ( + first_param_type.zigTypeTag() == .Pointer and + first_param_type.ptrSize() == .One and + first_param_type.childType().eql(concrete_ty))) + { + // zig fmt: on + // 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, .{ + .func_inst = decl_val, + .arg0_inst = object_ptr, + }); + return sema.addConstant(ty, value); + } else if (first_param_type.eql(concrete_ty)) { + var 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, .{ + .func_inst = decl_val, + .arg0_inst = deref, + }); + return sema.addConstant(ty, value); + } + } + } + } + }, + else => {}, + } + + return mod.fail(&block.base, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty, field_name }); +} + fn namespaceLookup( sema: *Sema, block: *Scope.Block, @@ -9850,14 +9993,14 @@ fn coerce( if (dest_type.eql(inst_ty)) return inst; - const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false); + const mod = sema.mod; + const arena = sema.arena; + + const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false, mod.getTarget()); if (in_memory_result == .ok) { return sema.bitcast(block, dest_type, inst, inst_src); } - const mod = sema.mod; - const arena = sema.arena; - // undefined to anything if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { if (val.isUndef() or inst_ty.zigTypeTag() == .Undefined) { @@ -9898,7 +10041,7 @@ fn coerce( if (inst_ty.ptrAddressSpace() != dest_type.ptrAddressSpace()) break :src_array_ptr; const dst_elem_type = dest_type.elemType(); - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) { + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, mod.getTarget())) { .ok => {}, .no_match => break :src_array_ptr, } @@ -10024,7 +10167,7 @@ const InMemoryCoercionResult = enum { /// * sentinel-terminated pointers can coerce into `[*]` /// TODO improve this function to report recursive compile errors like it does in stage1. /// look at the function types_match_const_cast_only -fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InMemoryCoercionResult { +fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult { if (dest_type.eql(src_type)) return .ok; @@ -10034,7 +10177,7 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM const dest_info = dest_type.ptrInfo().data; const src_info = src_type.ptrInfo().data; - const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable); + const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); if (child == .no_match) { return child; } @@ -10081,11 +10224,19 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM return .no_match; } - assert(src_info.@"align" != 0); - assert(dest_info.@"align" != 0); + // If both pointers have alignment 0, it means they both want ABI alignment. + // In this case, if they share the same child type, no need to resolve + // pointee type alignment. Otherwise both pointee types must have their alignment + // resolved and we compare the alignment numerically. + if (src_info.@"align" != 0 or dest_info.@"align" != 0 or + !dest_info.pointee_type.eql(src_info.pointee_type)) + { + const src_align = src_type.ptrAlignment(target); + const dest_align = dest_type.ptrAlignment(target); - if (dest_info.@"align" > src_info.@"align") { - return .no_match; + if (dest_align > src_align) { + return .no_match; + } } return .ok; @@ -11606,6 +11757,7 @@ fn typeHasOnePossibleValue( .single_const_pointer, .single_mut_pointer, .pointer, + .bound_fn, => return null, .@"struct" => { diff --git a/src/Zir.zig b/src/Zir.zig index 83c43fd7f2..483880c9b6 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -70,6 +70,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en u32 => code.extra[i], Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]), i32 => @bitCast(i32, code.extra[i]), + Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]), else => @compileError("bad field type"), }; i += 1; @@ -222,17 +223,9 @@ pub const Inst = struct { break_inline, /// Uses the `node` union field. breakpoint, - /// Function call with modifier `.auto`. + /// Function call. /// Uses `pl_node`. AST node is the function call. Payload is `Call`. call, - /// Same as `call` but it also does `ensure_result_used` on the return value. - call_chkused, - /// Same as `call` but with modifier `.compile_time`. - call_compile_time, - /// Same as `call` but with modifier `.no_suspend`. - call_nosuspend, - /// Same as `call` but with modifier `.async_kw`. - call_async, /// `<` /// Uses the `pl_node` union field. Payload is `Bin`. cmp_lt, @@ -327,6 +320,15 @@ 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. @@ -335,6 +337,15 @@ pub const Inst = struct { /// The field name is a comptime instruction. Used by @field. /// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed. field_val_named, + /// 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_named, /// Returns a function type, or a function instance, depending on whether /// the body_len is 0. Calling convention is auto. /// Uses the `pl_node` union field. `payload_index` points to a `Func`. @@ -395,14 +406,6 @@ pub const Inst = struct { /// Twos complement wrapping integer multiplication. /// Uses the `pl_node` union field. Payload is `Bin`. mulwrap, - /// Given a reference to a function and a parameter index, returns the - /// type of the parameter. The only usage of this instruction is for the - /// result location of parameters of function calls. In the case of a function's - /// parameter type being `anytype`, it is the type coercion's job to detect this - /// scenario and skip the coercion, so that semantic analysis of this instruction - /// is not in a position where it must create an invalid type. - /// Uses the `param_type` union field. - param_type, /// Turns an R-Value into a const L-Value. In other words, it takes a value, /// stores it in a memory location, and returns a const pointer to it. If the value /// is `comptime`, the memory location is global static constant data. Otherwise, @@ -988,10 +991,6 @@ pub const Inst = struct { .breakpoint, .fence, .call, - .call_chkused, - .call_compile_time, - .call_nosuspend, - .call_async, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1017,8 +1016,10 @@ pub const Inst = struct { .export_value, .field_ptr, .field_val, + .field_call_bind, .field_ptr_named, .field_val_named, + .field_call_bind_named, .func, .func_inferred, .has_decl, @@ -1034,7 +1035,6 @@ pub const Inst = struct { .mod_rem, .mul, .mulwrap, - .param_type, .ref, .shl, .shr, @@ -1247,10 +1247,6 @@ pub const Inst = struct { .break_inline = .@"break", .breakpoint = .node, .call = .pl_node, - .call_chkused = .pl_node, - .call_compile_time = .pl_node, - .call_nosuspend = .pl_node, - .call_async = .pl_node, .cmp_lt = .pl_node, .cmp_lte = .pl_node, .cmp_eq = .pl_node, @@ -1282,6 +1278,8 @@ pub const Inst = struct { .field_val = .pl_node, .field_ptr_named = .pl_node, .field_val_named = .pl_node, + .field_call_bind = .pl_node, + .field_call_bind_named = .pl_node, .func = .pl_node, .func_inferred = .pl_node, .import = .str_tok, @@ -1301,7 +1299,6 @@ pub const Inst = struct { .mod_rem = .pl_node, .mul = .pl_node, .mulwrap = .pl_node, - .param_type = .param_type, .ref = .un_tok, .ret_node = .un_node, .ret_load = .un_node, @@ -2170,10 +2167,6 @@ pub const Inst = struct { /// Points to a `Block`. payload_index: u32, }, - param_type: struct { - callee: Ref, - param_index: u32, - }, @"unreachable": struct { /// Offset from Decl AST node index. /// `Tag` determines which kind of AST node this points to. @@ -2244,7 +2237,6 @@ pub const Inst = struct { ptr_type, int_type, bool_br, - param_type, @"unreachable", @"break", switch_capture, @@ -2372,8 +2364,27 @@ pub const Inst = struct { /// Stored inside extra, with trailing arguments according to `args_len`. /// Each argument is a `Ref`. pub const Call = struct { + // Note: Flags *must* come first so that unusedResultExpr + // can find it when it goes to modify them. + flags: Flags, callee: Ref, - args_len: u32, + + pub const Flags = packed struct { + /// std.builtin.CallOptions.Modifier in packed form + pub const PackedModifier = u3; + pub const PackedArgsLen = u28; + + packed_modifier: PackedModifier, + ensure_result_used: bool = false, + args_len: PackedArgsLen, + + comptime { + if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32) + @compileError("Layout of Call.Flags needs to be updated!"); + if (@bitSizeOf(std.builtin.CallOptions.Modifier) != @bitSizeOf(PackedModifier)) + @compileError("Call.Flags.PackedModifier needs to be updated!"); + } + }; }; pub const BuiltinCall = struct { diff --git a/src/print_zir.zig b/src/print_zir.zig index 6ae218ed22..3834a694e9 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -179,7 +179,6 @@ const Writer = struct { => try self.writeBoolBr(stream, inst), .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), - .param_type => try self.writeParamType(stream, inst), .ptr_type_simple => try self.writePtrTypeSimple(stream, inst), .ptr_type => try self.writePtrType(stream, inst), .int => try self.writeInt(stream, inst), @@ -195,8 +194,6 @@ const Writer = struct { .elem_ptr_node, .elem_val_node, - .field_ptr_named, - .field_val_named, .slice_start, .slice_end, .slice_sentinel, @@ -288,12 +285,7 @@ const Writer = struct { .@"export" => try self.writePlNodeExport(stream, inst), .export_value => try self.writePlNodeExportValue(stream, inst), - .call, - .call_chkused, - .call_compile_time, - .call_nosuspend, - .call_async, - => try self.writePlNodeCall(stream, inst), + .call => try self.writePlNodeCall(stream, inst), .block, .block_inline, @@ -328,8 +320,14 @@ const Writer = struct { .field_ptr, .field_val, + .field_call_bind, => try self.writePlNodeField(stream, inst), + .field_ptr_named, + .field_val_named, + .field_call_bind_named, + => try self.writePlNodeFieldNamed(stream, inst), + .as_node => try self.writeAs(stream, inst), .breakpoint, @@ -481,16 +479,6 @@ const Writer = struct { try stream.writeAll("TODO)"); } - fn writeParamType( - self: *Writer, - stream: anytype, - inst: Zir.Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].param_type; - try self.writeInstRef(stream, inst_data.callee); - try stream.print(", {d})", .{inst_data.param_index}); - } - fn writePtrTypeSimple( self: *Writer, stream: anytype, @@ -881,8 +869,12 @@ const Writer = struct { fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !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 args = self.code.refSlice(extra.end, extra.data.args_len); + const args = self.code.refSlice(extra.end, extra.data.flags.args_len); + if (extra.data.flags.ensure_result_used) { + try stream.writeAll("nodiscard "); + } + try stream.print(".{s}, ", .{@tagName(@intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier))}); try self.writeInstRef(stream, extra.data.callee); try stream.writeAll(", ["); for (args) |arg, i| { @@ -1637,6 +1629,16 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writePlNodeFieldNamed(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.field_name); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeAs(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.As, inst_data.payload_index).data; diff --git a/src/type.zig b/src/type.zig index 781fe74d45..e13ede852a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -138,6 +138,7 @@ pub const Type = extern union { .type_info, => return .Union, + .bound_fn => unreachable, .var_args_param => unreachable, // can be any type } } @@ -771,6 +772,7 @@ pub const Type = extern union { .type_info, .@"anyframe", .generic_poison, + .bound_fn, => unreachable, .array_u8, @@ -936,6 +938,7 @@ pub const Type = extern union { .comptime_float, .noreturn, .var_args_param, + .bound_fn, => return writer.writeAll(@tagName(t)), .enum_literal => return writer.writeAll("@Type(.EnumLiteral)"), @@ -1248,6 +1251,7 @@ pub const Type = extern union { .var_args_param => unreachable, .inferred_alloc_mut => unreachable, .inferred_alloc_const => unreachable, + .bound_fn => unreachable, .array_u8, .array_u8_sentinel_0, @@ -1479,6 +1483,7 @@ pub const Type = extern union { .empty_struct_literal, .@"opaque", .type_info, + .bound_fn, => false, .inferred_alloc_const => unreachable, @@ -1489,7 +1494,9 @@ pub const Type = extern union { } pub fn isNoReturn(self: Type) bool { - const definitely_correct_result = self.zigTypeTag() == .NoReturn; + const definitely_correct_result = + self.tag_if_small_enough != .bound_fn and + self.zigTypeTag() == .NoReturn; const fast_result = self.tag_if_small_enough == Tag.noreturn; assert(fast_result == definitely_correct_result); return fast_result; @@ -1736,6 +1743,7 @@ pub const Type = extern union { .@"opaque", .var_args_param, .type_info, + .bound_fn, => unreachable, .generic_poison => unreachable, @@ -1768,6 +1776,7 @@ pub const Type = extern union { .var_args_param => unreachable, .generic_poison => unreachable, .type_info => unreachable, + .bound_fn => unreachable, .@"struct" => { const s = self.castTag(.@"struct").?.data; @@ -1951,6 +1960,7 @@ pub const Type = extern union { .@"opaque" => unreachable, .var_args_param => unreachable, .generic_poison => unreachable, + .bound_fn => unreachable, .@"struct" => { @panic("TODO bitSize struct"); @@ -2353,6 +2363,51 @@ pub const Type = extern union { } } + /// Returns if type can be used for a runtime variable + pub fn isValidVarType(self: Type, is_extern: bool) bool { + var ty = self; + while (true) switch (ty.zigTypeTag()) { + .Bool, + .Int, + .Float, + .ErrorSet, + .Enum, + .Frame, + .AnyFrame, + => return true, + + .Opaque => return is_extern, + .BoundFn, + .ComptimeFloat, + .ComptimeInt, + .EnumLiteral, + .NoReturn, + .Type, + .Void, + .Undefined, + .Null, + => return false, + + .Optional => { + var buf: Payload.ElemType = undefined; + return ty.optionalChild(&buf).isValidVarType(is_extern); + }, + .Pointer, .Array, .Vector => ty = ty.elemType(), + .ErrorUnion => ty = ty.errorUnionPayload(), + + .Fn => @panic("TODO fn isValidVarType"), + .Struct => { + // TODO this is not always correct; introduce lazy value mechanism + // and here we need to force a resolve of "type requires comptime". + return true; + }, + .Union => @panic("TODO union isValidVarType"), + }; + } + + /// For *[N]T, returns [N]T. + /// For *T, returns T. + /// For [*]T, returns T. pub fn childType(ty: Type) Type { return switch (ty.tag()) { .vector => ty.castTag(.vector).?.data.elem_type, @@ -2934,6 +2989,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .pointer, + .bound_fn, => return null, .@"struct" => { @@ -3480,6 +3536,7 @@ 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, @@ -3518,7 +3575,7 @@ pub const Type = extern union { enum_full, enum_nonexhaustive, - pub const last_no_payload_tag = Tag.inferred_alloc_const; + pub const last_no_payload_tag = Tag.bound_fn; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; pub fn Type(comptime t: Tag) type { @@ -3585,6 +3642,7 @@ pub const Type = extern union { .extern_options, .type_info, .@"anyframe", + .bound_fn, => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), .array_u8, diff --git a/src/value.zig b/src/value.zig index 69f8945e01..336f5f9cf7 100644 --- a/src/value.zig +++ b/src/value.zig @@ -159,6 +159,10 @@ 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, pub const last_no_payload_tag = Tag.empty_array; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -279,6 +283,7 @@ pub const Value = extern union { .inferred_alloc => Payload.InferredAlloc, .@"struct" => Payload.Struct, .@"union" => Payload.Union, + .bound_fn => Payload.BoundFn, }; } @@ -422,6 +427,7 @@ pub const Value = extern union { .extern_options_type, .type_info_type, .generic_poison, + .bound_fn, => unreachable, .ty => { @@ -716,6 +722,10 @@ pub const Value = extern union { try out_stream.writeAll("(opt_payload_ptr)"); val = val.castTag(.opt_payload_ptr).?.data; }, + .bound_fn => { + const bound_func = val.castTag(.bound_fn).?.data; + return out_stream.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst }); + }, }; } @@ -2199,6 +2209,16 @@ 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 diff --git a/test/behavior.zig b/test/behavior.zig index 3a5c0fe589..4bfd947fcf 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -10,6 +10,7 @@ test { _ = @import("behavior/eval.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); + _ = @import("behavior/member_func.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/struct.zig"); diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 0d103bc49a..6acdf15e89 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -155,3 +155,31 @@ fn MakeType(comptime T: type) type { field: T, }; } + +test "try to trick eval with runtime if" { + try expect(testTryToTrickEvalWithRuntimeIf(true) == 10); +} + +fn testTryToTrickEvalWithRuntimeIf(b: bool) usize { + comptime var i: usize = 0; + inline while (i < 10) : (i += 1) { + const result = if (b) false else true; + _ = result; + } + comptime { + return i; + } +} + +test "@setEvalBranchQuota" { + comptime { + // 1001 for the loop and then 1 more for the expect fn call + @setEvalBranchQuota(1002); + var i = 0; + var sum = 0; + while (i < 1001) : (i += 1) { + sum += i; + } + try expect(sum == 500500); + } +} diff --git a/test/behavior/eval_stage1.zig b/test/behavior/eval_stage1.zig index 743f7af69e..4e945d7af0 100644 --- a/test/behavior/eval_stage1.zig +++ b/test/behavior/eval_stage1.zig @@ -109,21 +109,6 @@ test "const slice" { } } -test "try to trick eval with runtime if" { - try expect(testTryToTrickEvalWithRuntimeIf(true) == 10); -} - -fn testTryToTrickEvalWithRuntimeIf(b: bool) usize { - comptime var i: usize = 0; - inline while (i < 10) : (i += 1) { - const result = if (b) false else true; - _ = result; - } - comptime { - return i; - } -} - test "inlined loop has array literal with elided runtime scope on first iteration but not second iteration" { var runtime = [1]i32{3}; comptime var i: usize = 0; @@ -276,19 +261,6 @@ fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) !void { try expect(ptr1 == ptr2); } -test "@setEvalBranchQuota" { - comptime { - // 1001 for the loop and then 1 more for the expect fn call - @setEvalBranchQuota(1002); - var i = 0; - var sum = 0; - while (i < 1001) : (i += 1) { - sum += i; - } - try expect(sum == 500500); - } -} - test "float literal at compile time not lossy" { try expect(16777216.0 + 1.0 == 16777217.0); try expect(9007199254740992.0 + 1.0 == 9007199254740993.0); diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index f642028e54..76ad5cede7 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -118,3 +118,19 @@ pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type { prealloc_items: [STATIC_SIZE]T, }; } + +test "const decls in struct" { + try expect(GenericDataThing(3).count_plus_one == 4); +} +fn GenericDataThing(comptime count: isize) type { + return struct { + const count_plus_one = count + 1; + }; +} + +test "use generic param in generic param" { + try expect(aGenericFn(i32, 3, 4) == 7); +} +fn aGenericFn(comptime T: type, comptime a: T, b: T) T { + return a + b; +} diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 4fa52f5377..c4b9687aa6 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -26,22 +26,6 @@ fn GenNode(comptime T: type) type { }; } -test "const decls in struct" { - try expect(GenericDataThing(3).count_plus_one == 4); -} -fn GenericDataThing(comptime count: isize) type { - return struct { - const count_plus_one = count + 1; - }; -} - -test "use generic param in generic param" { - try expect(aGenericFn(i32, 3, 4) == 7); -} -fn aGenericFn(comptime T: type, comptime a: T, b: T) T { - return a + b; -} - test "generic fn with implicit cast" { try expect(getFirstByte(u8, &[_]u8{13}) == 13); try expect(getFirstByte(u16, &[_]u16{ diff --git a/test/behavior/member_func.zig b/test/behavior/member_func.zig new file mode 100644 index 0000000000..092a691901 --- /dev/null +++ b/test/behavior/member_func.zig @@ -0,0 +1,103 @@ +const expect = @import("std").testing.expect; + +const HasFuncs = struct { + state: u32, + func_field: fn (u32) u32, + + fn inc(self: *HasFuncs) void { + self.state += 1; + } + + fn get(self: HasFuncs) u32 { + return self.state; + } + + fn getPtr(self: *const HasFuncs) *const u32 { + return &self.state; + } + + fn one(_: u32) u32 { + return 1; + } + fn two(_: u32) u32 { + return 2; + } +}; + +test "standard field calls" { + try expect(HasFuncs.one(0) == 1); + try expect(HasFuncs.two(0) == 2); + + var v: HasFuncs = undefined; + v.state = 0; + v.func_field = HasFuncs.one; + + const pv = &v; + const pcv: *const HasFuncs = pv; + + try expect(v.get() == 0); + v.inc(); + try expect(v.state == 1); + try expect(v.get() == 1); + + pv.inc(); + try expect(v.state == 2); + try expect(pv.get() == 2); + try expect(v.getPtr().* == 2); + try expect(pcv.get() == 2); + try expect(pcv.getPtr().* == 2); + + v.func_field = HasFuncs.one; + try expect(v.func_field(0) == 1); + try expect(pv.func_field(0) == 1); + try expect(pcv.func_field(0) == 1); + + try expect(pcv.func_field(blk: { + pv.func_field = HasFuncs.two; + break :blk 0; + }) == 1); + + v.func_field = HasFuncs.two; + try expect(v.func_field(0) == 2); + try expect(pv.func_field(0) == 2); + try expect(pcv.func_field(0) == 2); +} + +test "@field field calls" { + try expect(@field(HasFuncs, "one")(0) == 1); + try expect(@field(HasFuncs, "two")(0) == 2); + + var v: HasFuncs = undefined; + v.state = 0; + v.func_field = HasFuncs.one; + + 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); + try expect(@field(pcv, "func_field")(0) == 1); + + try expect(@field(pcv, "func_field")(blk: { + pv.func_field = HasFuncs.two; + break :blk 0; + }) == 1); + + v.func_field = HasFuncs.two; + try expect(@field(v, "func_field")(0) == 2); + try expect(@field(pv, "func_field")(0) == 2); + try expect(@field(pcv, "func_field")(0) == 2); +}