From bae35bdf2d8919b60dee9a0af3afbdd93dd72b59 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 25 Mar 2022 18:27:10 -0700 Subject: [PATCH] stage2: result location types for function call arguments * AstGen: restore the param_type ZIR instruction and pass it to the expression for function call arguments. This does not solve the problem for generic function parameters, but it catches stage2 up to stage1 which also does not solve the problem for generic function parameters. - Most of the enhancements in this commit will still be needed for a more sophisticated further improvement to handle generic function types. - In Sema, handling of `as` coercion recognizes the `var_args_param` Type Tag and passes the operand through doing no coercion. - That was the last ZIR tag and we are now using all 256 ZIR tags. * AstGen: array init and struct init expressions use the anon form even when the result location has a type. Prevents the type system incorrectly believing, for example, that a tuple is actually an array when the result location is a param_type of a function with `anytype` parameter. * Sema: add missing coercion in `unionInit` to coerce the init to the corresponding union field type. * `Value.fieldValue` now takes a type and does not take an allocator. closes #11293 After this commit, stage2 passes all the parser tests. --- src/AstGen.zig | 41 ++++++++++++++++++++++++++++------------- src/Sema.zig | 42 ++++++++++++++++++++++++++++++++++++++++-- src/Zir.zig | 15 +++++++++++++++ src/print_zir.zig | 11 +++++++++++ src/type.zig | 2 ++ src/value.zig | 15 +++++++++++---- test/behavior/call.zig | 20 ++++++++++++++++++++ 7 files changed, 127 insertions(+), 19 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index e0f4028cbae887f7bde18d811f04a2791fc43319..4141b73e9a226a852a7091be6412db31f4b4a620 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1318,13 +1318,13 @@ fn arrayInitExpr( return arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon); } }, - .ty, .coerced_ty => |ty_inst| { + .ty, .coerced_ty => { if (types.array != .none) { const result = try arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, types.elem, types.sentinel, false); return rvalue(gz, rl, result, node); } else { - const elem_type = try gz.addUnNode(.elem_type, ty_inst, node); - return arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, elem_type, types.sentinel, false); + const result = try arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon); + return rvalue(gz, rl, result, node); } }, .ptr => |ptr_inst| { @@ -1559,7 +1559,7 @@ fn structInitExpr( _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init_ref); } else { - return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon_ref); + return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon_ref); } }, .none => { @@ -1568,12 +1568,13 @@ fn structInitExpr( _ = try gz.addUnNode(.validate_struct_init_ty, ty_inst, node); return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); } else { - return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon); + return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); } }, .ty, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { - return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); + const result = try structInitExprRlNone(gz, scope, node, struct_init, ty_inst, .struct_init_anon); + return rvalue(gz, rl, result, node); } const inner_ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); _ = try gz.addUnNode(.validate_struct_init_ty, inner_ty_inst, node); @@ -1586,7 +1587,7 @@ fn structInitExpr( // We treat this case differently so that we don't get a crash when // analyzing field_base_ptr against an alloc_inferred_mut. // See corresponding logic in arrayInitExpr. - const result = try structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon); + const result = try structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); return rvalue(gz, rl, result, node); } else { return structInitExprRlPtr(gz, scope, rl, node, struct_init, ptr_inst); @@ -1596,7 +1597,7 @@ fn structInitExpr( // This condition is here for the same reason as the above condition in `inferred_ptr`. // See corresponding logic in arrayInitExpr. if (struct_init.ast.type_expr == 0 and astgen.isInferred(block_gz.rl_ptr)) { - const result = try structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon); + const result = try structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); return rvalue(gz, rl, result, node); } @@ -1610,6 +1611,7 @@ fn structInitExprRlNone( scope: *Scope, node: Ast.Node.Index, struct_init: Ast.full.StructInit, + ty_inst: Zir.Inst.Ref, tag: Zir.Inst.Tag, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; @@ -1624,9 +1626,16 @@ fn structInitExprRlNone( for (struct_init.ast.fields) |field_init| { const name_token = tree.firstToken(field_init) - 2; const str_index = try astgen.identAsString(name_token); + const sub_rl: ResultLoc = if (ty_inst != .none) + ResultLoc{ .ty = try gz.addPlNode(.field_type, field_init, Zir.Inst.FieldType{ + .container_type = ty_inst, + .name_start = str_index, + }) } + else + .none; setExtra(astgen, extra_index, Zir.Inst.StructInitAnon.Item{ .field_name = str_index, - .init = try expr(gz, scope, .none, field_init), + .init = try expr(gz, scope, sub_rl, field_init), }); extra_index += field_size; } @@ -2350,6 +2359,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .closure_get, .array_base_ptr, .field_base_ptr, + .param_type, => break :b false, // ZIR instructions that are always `noreturn`. @@ -7846,10 +7856,15 @@ fn callExpr( }); var extra_index = try reserveExtra(astgen, call.ast.params.len); - for (call.ast.params) |param_node| { - // Parameters are always temporary values, they have no - // meaningful result location. Sema will coerce them. - const arg_ref = try expr(gz, scope, .none, param_node); + for (call.ast.params) |param_node, i| { + const param_type = try gz.add(.{ + .tag = .param_type, + .data = .{ .param_type = .{ + .callee = callee, + .param_index = @intCast(u32, i), + } }, + }); + const arg_ref = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node); astgen.extra.items[extra_index] = @enumToInt(arg_ref); extra_index += 1; } diff --git a/src/Sema.zig b/src/Sema.zig index e1a8d6f09a04a95ad271effd26b1dad9497d96ee..428ad71da1133f0eb0f9246c0f6515efed2a0a72 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -738,6 +738,7 @@ fn analyzeBodyInner( .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), @@ -3638,6 +3639,39 @@ fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!v return sema.storePtr(block, src, ptr, operand); } +fn zirParamType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const callee_src = sema.src; + + const inst_data = sema.code.instructions.items(.data)[inst].param_type; + const callee = sema.resolveInst(inst_data.callee); + const callee_ty = sema.typeOf(callee); + var param_index = inst_data.param_index; + + const fn_ty = if (callee_ty.tag() == .bound_fn) fn_ty: { + const bound_fn_val = try sema.resolveConstValue(block, callee_src, callee); + const bound_fn = bound_fn_val.castTag(.bound_fn).?.data; + const fn_ty = sema.typeOf(bound_fn.func_inst); + param_index += 1; + break :fn_ty fn_ty; + } else callee_ty; + + const fn_info = if (fn_ty.zigTypeTag() == .Pointer) + fn_ty.childType().fnInfo() + else + fn_ty.fnInfo(); + + if (param_index >= fn_info.param_types.len) { + assert(fn_info.is_var_args); + return sema.addType(Type.initTag(.var_args_param)); + } + + if (fn_info.param_types[param_index].tag() == .generic_poison) { + return sema.addType(Type.initTag(.var_args_param)); + } + + return sema.addType(fn_info.param_types[param_index]); +} + fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -6613,6 +6647,7 @@ fn analyzeAs( ) CompileError!Air.Inst.Ref { const dest_ty = try sema.resolveType(block, src, zir_dest_type); const operand = sema.resolveInst(zir_operand); + if (dest_ty.tag() == .var_args_param) return operand; return sema.coerce(block, dest_ty, operand, src); } @@ -12140,7 +12175,7 @@ fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A fn unionInit( sema: *Sema, block: *Block, - init: Air.Inst.Ref, + uncasted_init: Air.Inst.Ref, init_src: LazySrcLoc, union_ty: Type, union_ty_src: LazySrcLoc, @@ -12148,6 +12183,8 @@ fn unionInit( field_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src); + const field = union_ty.unionFields().values()[field_index]; + const init = try sema.coerce(block, field.ty, uncasted_init, init_src); if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); @@ -12620,6 +12657,7 @@ fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const ty_src = inst_data.src(); const field_src = inst_data.src(); const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); + if (aggregate_ty.tag() == .var_args_param) return sema.addType(aggregate_ty); const field_name = sema.code.nullTerminatedString(extra.name_start); return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); } @@ -18964,7 +19002,7 @@ fn beginComptimePtrLoad( if (coerce_in_mem_ok) { deref.pointee = TypedValue{ .ty = field_ty, - .val = try tv.val.fieldValue(sema.arena, field_index), + .val = tv.val.fieldValue(tv.ty, field_index), }; break :blk deref; } diff --git a/src/Zir.zig b/src/Zir.zig index d6e8486ea75e050fc91a3f4916ca2e3bbc52fb38..d895281141bdb7f8ede1b6b5557e663d4b23fcd6 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -464,6 +464,14 @@ pub const Inst = struct { /// Merge two error sets into one, `E1 || E2`. /// Uses the `pl_node` field with payload `Bin`. merge_error_sets, + /// 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, @@ -1077,6 +1085,7 @@ pub const Inst = struct { .mul, .mulwrap, .mul_sat, + .param_type, .ref, .shl, .shl_sat, @@ -1266,6 +1275,7 @@ pub const Inst = struct { .mulwrap = .pl_node, .mul_sat = .pl_node, + .param_type = .param_type, .param = .pl_tok, .param_comptime = .pl_tok, .param_anytype = .str_tok, @@ -2213,6 +2223,10 @@ 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. @@ -2288,6 +2302,7 @@ pub const Inst = struct { ptr_type, int_type, bool_br, + param_type, @"unreachable", @"break", switch_capture, diff --git a/src/print_zir.zig b/src/print_zir.zig index 12aad8698410ec9798b851128fc1ef6373c29241..81562dc7bc9835b08ccc5a3038f50d4145577163 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -252,6 +252,7 @@ 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), @@ -558,6 +559,16 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + 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, diff --git a/src/type.zig b/src/type.zig index 17c8d4d1113fa5b4371b36ecf447dc6db1bd80c6..a7064830035f071c3085305d695480f3f9d75d3a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3724,6 +3724,8 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), .pointer => ty.castTag(.pointer).?.data.pointee_type, + .var_args_param => ty, + else => unreachable, }; } diff --git a/src/value.zig b/src/value.zig index be5794d6ae07d04b71ac6acca2ef3874da32bfe6..80d76f6bce4ee818e04e98d1d7a8bd017a8fe0d9 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2659,8 +2659,7 @@ pub const Value = extern union { }; } - pub fn fieldValue(val: Value, allocator: Allocator, index: usize) error{OutOfMemory}!Value { - _ = allocator; + pub fn fieldValue(val: Value, ty: Type, index: usize) Value { switch (val.tag()) { .aggregate => { const field_values = val.castTag(.aggregate).?.data; @@ -2671,8 +2670,16 @@ pub const Value = extern union { // TODO assert the tag is correct return payload.val; }, - // Structs which have only one possible value need to consist of members which have only one possible value. - .the_only_possible_value => return val, + + .the_only_possible_value => return ty.onePossibleValue().?, + + .empty_struct_value => { + if (ty.isTupleOrAnonStruct()) { + const tuple = ty.tupleFields(); + return tuple.values[index]; + } + unreachable; + }, else => unreachable, } diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 57bc0fb3f7eba294c50840060d5ab0c8d58d1486..119dc289b1fe40d7f3f2a1d123b63c3e1e4e0f09 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -98,3 +98,23 @@ test "comptime call with bound function as parameter" { var inst: S = undefined; try expectEqual(?i32, S.ReturnType(inst.call_me_maybe)); } + +test "result location of function call argument through runtime condition and struct init" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + const E = enum { a, b }; + const S = struct { + e: E, + }; + const namespace = struct { + fn foo(s: S) !void { + try expect(s.e == .b); + } + }; + var runtime = true; + try namespace.foo(.{ + .e = if (!runtime) .a else .b, + }); +} -- 2.54.0