diff --git a/src/Sema.zig b/src/Sema.zig index aa02288df7..f3ddf21206 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3087,7 +3087,7 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro const candidate = block.instructions.items[search_index]; switch (air_tags[candidate]) { - .dbg_stmt => continue, + .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue, .store => break candidate, else => break :ct, } @@ -3099,7 +3099,7 @@ fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro const candidate = block.instructions.items[search_index]; switch (air_tags[candidate]) { - .dbg_stmt => continue, + .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue, .alloc => { if (Air.indexToRef(candidate) != alloc) break :ct; break; @@ -3317,7 +3317,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const candidate = block.instructions.items[search_index]; switch (air_tags[candidate]) { - .dbg_stmt => continue, + .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue, .store => break candidate, else => break :ct, } @@ -3329,7 +3329,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const candidate = block.instructions.items[search_index]; switch (air_tags[candidate]) { - .dbg_stmt => continue, + .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue, .bitcast => break candidate, else => break :ct, } @@ -3341,7 +3341,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const candidate = block.instructions.items[search_index]; switch (air_tags[candidate]) { - .dbg_stmt => continue, + .dbg_stmt, .dbg_block_begin, .dbg_block_end => continue, .constant => break candidate, else => break :ct, } @@ -3615,8 +3615,6 @@ fn validateUnionInit( union_ptr: Air.Inst.Ref, is_comptime: bool, ) CompileError!void { - const union_obj = union_ty.cast(Type.Payload.Union).?.data; - if (instrs.len != 1) { const msg = msg: { const msg = try sema.errMsg( @@ -3650,7 +3648,8 @@ fn validateUnionInit( const field_src: LazySrcLoc = .{ .node_offset_initializer = field_ptr_data.src_node }; const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); - const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src); + // Validate the field access but ignore the index since we want the tag enum field index. + _ = try sema.unionFieldIndex(block, union_ty, field_name, field_src); const air_tags = sema.air_instructions.items(.tag); const air_datas = sema.air_instructions.items(.data); const field_ptr_air_ref = sema.inst_map.get(field_ptr).?; @@ -3709,7 +3708,9 @@ fn validateUnionInit( break; } - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const tag_ty = union_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); if (init_val) |val| { // Our task is to delete all the `field_ptr` and `store` instructions, and insert @@ -3726,7 +3727,7 @@ fn validateUnionInit( } try sema.requireFunctionBlock(block, init_src); - const new_tag = try sema.addConstant(union_obj.tag_ty, tag_val); + const new_tag = try sema.addConstant(tag_ty, tag_val); _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag); } @@ -5643,6 +5644,37 @@ const GenericCallAdapter = struct { } }; +fn addComptimeReturnTypeNote( + sema: *Sema, + block: *Block, + func: Air.Inst.Ref, + func_src: LazySrcLoc, + return_ty: Type, + parent: *Module.ErrorMsg, + requires_comptime: bool, +) !void { + if (!requires_comptime) return; + + const src_loc = if (try sema.funcDeclSrc(block, func_src, func)) |capture| blk: { + var src_loc = capture; + src_loc.lazy = .{ .node_offset_fn_type_ret_ty = 0 }; + break :blk src_loc; + } else blk: { + const src_decl = sema.mod.declPtr(block.src_decl); + break :blk func_src.toSrcLoc(src_decl); + }; + if (return_ty.tag() == .generic_poison) { + return sema.mod.errNoteNonLazy(src_loc, parent, "generic function is instantiated with a comptime only return type", .{}); + } + try sema.mod.errNoteNonLazy( + src_loc, + parent, + "function is being called at comptime because it returns a comptime only type '{}'", + .{return_ty.fmt(sema.mod)}, + ); + try sema.explainWhyTypeIsComptime(block, func_src, parent, src_loc, return_ty); +} + fn analyzeCall( sema: *Sema, block: *Block, @@ -5733,9 +5765,11 @@ fn analyzeCall( var is_generic_call = func_ty_info.is_generic; var is_comptime_call = block.is_comptime or modifier == .compile_time; + var comptime_only_ret_ty = false; if (!is_comptime_call) { if (sema.typeRequiresComptime(block, func_src, func_ty_info.return_type)) |ct| { is_comptime_call = ct; + comptime_only_ret_ty = ct; } else |err| switch (err) { error.GenericPoison => is_generic_call = true, else => |e| return e, @@ -5764,6 +5798,7 @@ fn analyzeCall( error.ComptimeReturn => { is_inline_call = true; is_comptime_call = true; + comptime_only_ret_ty = true; }, else => |e| return e, } @@ -5774,8 +5809,12 @@ fn analyzeCall( } const result: Air.Inst.Ref = if (is_inline_call) res: { - // TODO explain why function is being called at comptime - const func_val = try sema.resolveConstValue(block, func_src, func, "function being called at comptime must be comptime known"); + const func_val = sema.resolveConstValue(block, func_src, func, "function being called at comptime must be comptime known") catch |err| { + if (err == error.AnalysisFail and sema.err != null) { + try sema.addComptimeReturnTypeNote(block, func, func_src, func_ty_info.return_type, sema.err.?, comptime_only_ret_ty); + } + return err; + }; const module_fn = switch (func_val.tag()) { .decl_ref => mod.declPtr(func_val.castTag(.decl_ref).?.data).val.castTag(.function).?.data, .function => func_val.castTag(.function).?.data, @@ -5887,6 +5926,11 @@ fn analyzeCall( is_comptime_call, &should_memoize, memoized_call_key, + // last 4 arguments are only used when reporting errors + undefined, + undefined, + undefined, + undefined, ) catch |err| switch (err) { error.NeededSourceLocation => { sema.inst_map.clearRetainingCapacity(); @@ -5904,6 +5948,10 @@ fn analyzeCall( is_comptime_call, &should_memoize, memoized_call_key, + func, + func_src, + func_ty_info.return_type, + comptime_only_ret_ty, ); return error.AnalysisFail; }, @@ -6119,6 +6167,10 @@ fn analyzeInlineCallArg( is_comptime_call: bool, should_memoize: *bool, memoized_call_key: Module.MemoizedCall.Key, + func: Air.Inst.Ref, + func_src: LazySrcLoc, + ret_ty: Type, + comptime_only_ret_ty: bool, ) !void { const zir_tags = sema.code.instructions.items(.tag); switch (zir_tags[inst]) { @@ -6134,14 +6186,23 @@ fn analyzeInlineCallArg( new_fn_info.param_types[arg_i.*] = param_ty; const uncasted_arg = uncasted_args[arg_i.*]; if (try sema.typeRequiresComptime(arg_block, arg_src, param_ty)) { - _ = try sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to parameter with comptime only type must be comptime known"); + _ = sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to parameter with comptime only type must be comptime known") catch |err| { + if (err == error.AnalysisFail and sema.err != null) { + try sema.addComptimeReturnTypeNote(arg_block, func, func_src, ret_ty, sema.err.?, comptime_only_ret_ty); + } + return err; + }; } const casted_arg = try sema.coerce(arg_block, param_ty, uncasted_arg, arg_src); try sema.inst_map.putNoClobber(sema.gpa, inst, casted_arg); if (is_comptime_call) { - // TODO explain why function is being called at comptime - const arg_val = try sema.resolveConstMaybeUndefVal(arg_block, arg_src, casted_arg, "argument to function being called at comptime must be comptime known"); + const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, casted_arg, "argument to function being called at comptime must be comptime known") catch |err| { + if (err == error.AnalysisFail and sema.err != null) { + try sema.addComptimeReturnTypeNote(arg_block, func, func_src, ret_ty, sema.err.?, comptime_only_ret_ty); + } + return err; + }; switch (arg_val.tag()) { .generic_poison, .generic_poison_type => { // This function is currently evaluated as part of an as-of-yet unresolvable @@ -6171,8 +6232,12 @@ fn analyzeInlineCallArg( try sema.inst_map.putNoClobber(sema.gpa, inst, uncasted_arg); if (is_comptime_call) { - // TODO explain why function is being called at comptime - const arg_val = try sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to function being called at comptime must be comptime known"); + const arg_val = sema.resolveConstMaybeUndefVal(arg_block, arg_src, uncasted_arg, "argument to function being called at comptime must be comptime known") catch |err| { + if (err == error.AnalysisFail and sema.err != null) { + try sema.addComptimeReturnTypeNote(arg_block, func, func_src, ret_ty, sema.err.?, comptime_only_ret_ty); + } + return err; + }; switch (arg_val.tag()) { .generic_poison, .generic_poison_type => { // This function is currently evaluated as part of an as-of-yet unresolvable @@ -8774,13 +8839,11 @@ fn zirSwitchCapture( switch (operand_ty.zigTypeTag()) { .Union => { const union_obj = operand_ty.cast(Type.Payload.Union).?.data; - const enum_ty = union_obj.tag_ty; - const first_item = try sema.resolveInst(items[0]); // Previous switch validation ensured this will succeed const first_item_val = sema.resolveConstValue(block, .unneeded, first_item, undefined) catch unreachable; - const first_field_index = @intCast(u32, enum_ty.enumTagFieldIndex(first_item_val, sema.mod).?); + const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, sema.mod).?); const first_field = union_obj.fields.values()[first_field_index]; for (items[1..]) |item, i| { @@ -8788,7 +8851,7 @@ fn zirSwitchCapture( // Previous switch validation ensured this will succeed const item_val = sema.resolveConstValue(block, .unneeded, item_ref, undefined) catch unreachable; - const field_index = enum_ty.enumTagFieldIndex(item_val, sema.mod).?; + const field_index = operand_ty.unionTagFieldIndex(item_val, sema.mod).?; const field = union_obj.fields.values()[field_index]; if (!field.ty.eql(first_field.ty, sema.mod)) { const msg = msg: { @@ -15521,7 +15584,9 @@ fn unionInit( 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); + const tag_ty = union_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); return sema.addConstant(union_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = init_val, @@ -15619,7 +15684,9 @@ fn zirStructInit( const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src); - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const tag_ty = resolved_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const init_inst = try sema.resolveInst(item.data.init); if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { @@ -16384,9 +16451,8 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src); const val = try sema.resolveConstValue(block, operand_src, type_info, "operand to @Type must be comptime known"); const union_val = val.cast(Value.Payload.Union).?.data; - const tag_ty = type_info_ty.unionTagType().?; const target = mod.getTarget(); - const tag_index = tag_ty.enumTagFieldIndex(union_val.tag, mod).?; + const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag, mod).?; if (union_val.val.anyUndef()) return sema.failWithUseOfUndef(block, src); switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, @@ -25091,8 +25157,7 @@ fn coerceEnumToUnion( const enum_tag = try sema.coerce(block, tag_ty, inst, inst_src); if (try sema.resolveDefinedValue(block, inst_src, enum_tag)) |val| { - const union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.tag_ty.enumTagFieldIndex(val, sema.mod) orelse { + const field_index = union_ty.unionTagFieldIndex(val, sema.mod) orelse { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "union '{}' has no tag with value '{}'", .{ union_ty.fmt(sema.mod), val.fmtValue(tag_ty, sema.mod), @@ -25103,6 +25168,8 @@ fn coerceEnumToUnion( }; return sema.failWithOwnedErrorMsg(msg); }; + + const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field = union_obj.fields.values()[field_index]; const field_ty = try sema.resolveTypeFields(block, inst_src, field.ty); if (field_ty.zigTypeTag() == .NoReturn) { diff --git a/src/codegen.zig b/src/codegen.zig index 025decdb4b..f5340458a5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -607,7 +607,7 @@ pub fn generateSymbol( const union_ty = typed_value.ty.cast(Type.Payload.Union).?.data; const mod = bin_file.options.module.?; - const field_index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag, mod).?; + const field_index = typed_value.ty.unionTagFieldIndex(union_obj.tag, mod).?; assert(union_ty.haveFieldTypes()); const field_ty = union_ty.fields.values()[field_index].ty; if (!field_ty.hasRuntimeBits()) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 81a892183f..4a09c09cc9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -835,7 +835,6 @@ pub const DeclGen = struct { }, .Union => { const union_obj = val.castTag(.@"union").?.data; - const union_ty = ty.cast(Type.Payload.Union).?.data; const layout = ty.unionGetLayout(target); try writer.writeAll("("); @@ -851,7 +850,7 @@ pub const DeclGen = struct { try writer.writeAll(".payload = {"); } - const index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag, dg.module).?; + const index = ty.unionTagFieldIndex(union_obj.tag, dg.module).?; const field_ty = ty.unionFields().values()[index].ty; const field_name = ty.unionFields().keys()[index]; if (field_ty.hasRuntimeBits()) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d50b463606..5c537cd5bc 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3502,7 +3502,7 @@ pub const DeclGen = struct { }); } const union_obj = tv.ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.tag_ty.enumTagFieldIndex(tag_and_val.tag, dg.module).?; + const field_index = tv.ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?; assert(union_obj.haveFieldTypes()); // Sometimes we must make an unnamed struct because LLVM does diff --git a/src/type.zig b/src/type.zig index f6afa33df1..d2885f537f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4285,11 +4285,18 @@ pub const Type = extern union { pub fn unionFieldType(ty: Type, enum_tag: Value, mod: *Module) Type { const union_obj = ty.cast(Payload.Union).?.data; - const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag, mod).?; + const index = ty.unionTagFieldIndex(enum_tag, mod).?; assert(union_obj.haveFieldTypes()); return union_obj.fields.values()[index].ty; } + pub fn unionTagFieldIndex(ty: Type, enum_tag: Value, mod: *Module) ?usize { + const union_obj = ty.cast(Payload.Union).?.data; + const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag, mod) orelse return null; + const name = union_obj.tag_ty.enumFieldName(index); + return union_obj.fields.getIndex(name); + } + pub fn unionHasAllZeroBitFieldTypes(ty: Type) bool { return ty.cast(Payload.Union).?.data.hasAllZeroBitFieldTypes(); } diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 8a872f6378..49183227b5 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -1310,3 +1310,18 @@ test "repeated value is correctly expanded" { } }, res); } } + +test "value in if block is comptime known" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + + const first = blk: { + const s = if (false) "a" else "b"; + break :blk "foo" ++ s; + }; + const second = blk: { + const S = struct { str: []const u8 }; + const s = if (false) S{ .str = "a" } else S{ .str = "b" }; + break :blk "foo" ++ s.str; + }; + comptime try expect(std.mem.eql(u8, first, second)); +} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index efca75af30..92f277b946 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1301,3 +1301,27 @@ test "noreturn field in union" { } try expect(count == 5); } + +test "union and enum field order doesn't match" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const MyTag = enum(u32) { + b = 1337, + a = 1666, + }; + const MyUnion = union(MyTag) { + a: f32, + b: void, + }; + var x: MyUnion = .{ .a = 666 }; + switch (x) { + .a => |my_f32| { + try expect(@TypeOf(my_f32) == f32); + }, + .b => unreachable, + } + x = .b; + try expect(x == .b); +} diff --git a/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig new file mode 100644 index 0000000000..7ec539dcd1 --- /dev/null +++ b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig @@ -0,0 +1,23 @@ +const S = struct { + fnPtr: fn () void, + a: u8, +}; +fn bar() void {} + +fn foo(a: u8) S { + return .{ .fnPtr = bar, .a = a }; +} +pub export fn entry() void { + var a: u8 = 1; + _ = foo(a); +} + +// error +// backend=stage2 +// target=native +// +// :12:13: error: unable to resolve comptime value +// :12:13: note: argument to function being called at comptime must be comptime known +// :7:15: note: function is being called at comptime because it returns a comptime only type 'tmp.S' +// :2:12: note: struct requires comptime because of this field +// :2:12: note: use '*const fn() void' for a function pointer type diff --git a/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig b/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig new file mode 100644 index 0000000000..ccd828bd5c --- /dev/null +++ b/test/cases/compile_errors/explain_why_generic_fn_is_called_at_comptime.zig @@ -0,0 +1,22 @@ +fn S(comptime PtrTy: type) type { + return struct { + fnPtr: PtrTy, + a: u8, + }; +} +fn bar() void {} + +fn foo(a: u8, comptime PtrTy: type) S(PtrTy) { + return .{ .fnPtr = bar, .a = a }; +} +pub export fn entry() void { + var a: u8 = 1; + _ = foo(a, fn () void); +} +// error +// backend=stage2 +// target=native +// +// :14:13: error: unable to resolve comptime value +// :14:13: note: argument to function being called at comptime must be comptime known +// :9:38: note: generic function is instantiated with a comptime only return type diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 180cf74bcb..f8ede21a9d 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -204,6 +204,7 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":3:12: error: unable to resolve comptime value", ":3:12: note: argument to function being called at comptime must be comptime known", + ":2:55: note: generic function is instantiated with a comptime only return type", }); }