From b9f01bc39452042be1609b63f3066cfcac82f273 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 7 Jul 2022 20:54:10 +0300 Subject: Sema: add detailed error notes to `coerceInMemoryAllowed` --- src/Sema.zig | 599 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 526 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index 0e7c1c5937..6f1259ed82 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19931,7 +19931,7 @@ fn coerce( if (inst_ty.ptrAddressSpace() != dest_info.@"addrspace") break :single_item; switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) { .ok => {}, - .no_match => break :single_item, + else => break :single_item, } return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } @@ -19951,7 +19951,7 @@ fn coerce( const dst_elem_type = dest_info.pointee_type; switch (try sema.coerceInMemoryAllowed(block, dst_elem_type, array_elem_type, dest_is_mut, target, dest_ty_src, inst_src)) { .ok => {}, - .no_match => break :src_array_ptr, + else => break :src_array_ptr, } switch (dest_info.size) { @@ -19990,7 +19990,7 @@ fn coerce( const dst_elem_type = dest_info.pointee_type; switch (try sema.coerceInMemoryAllowed(block, dst_elem_type, src_elem_ty, dest_is_mut, target, dest_ty_src, inst_src)) { .ok => {}, - .no_match => break :src_c_ptr, + else => break :src_c_ptr, } // TODO add safety check for null pointer return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); @@ -20034,7 +20034,7 @@ fn coerce( inst_src, )) { .ok => {}, - .no_match => break :p, + else => break :p, } if (inst_info.size == .Slice) { if (dest_info.sentinel == null or inst_info.sentinel == null or @@ -20124,7 +20124,7 @@ fn coerce( inst_src, )) { .ok => {}, - .no_match => break :p, + else => break :p, } if (dest_info.sentinel == null or inst_info.sentinel == null or @@ -20356,14 +20356,348 @@ fn coerce( return sema.addConstUndef(dest_ty); } - return sema.fail(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); + const msg = msg: { + const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); + errdefer msg.destroy(sema.gpa); + + try in_memory_result.report(sema, block, inst_src, msg); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } -const InMemoryCoercionResult = enum { +const InMemoryCoercionResult = union(enum) { ok, - no_match, + no_match: Pair, + int_mismatch: Int, + error_union_payload: PairAndChild, + array_len: IntPair, + array_sentinel: Sentinel, + array_elem: PairAndChild, + vector_len: IntPair, + vector_elem: PairAndChild, + optional_shape: Pair, + optional_child: PairAndChild, + from_anyerror, + missing_error: []const []const u8, + /// true if wanted is var args + fn_var_args: bool, + /// true if wanted is generic + fn_generic: bool, + fn_param_count: IntPair, + fn_param_noalias: IntPair, + fn_param_comptime: ComptimeParam, + fn_param: Param, + fn_cc: CC, + fn_return_type: PairAndChild, + ptr_child: PairAndChild, + ptr_addrspace: AddressSpace, + ptr_sentinel: Sentinel, + ptr_size: Size, + ptr_qualifiers: Qualifiers, + ptr_allowzero: Pair, + ptr_bit_range: BitRange, + ptr_alignment: IntPair, + + const Pair = struct { + actual: Type, + wanted: Type, + }; + + const PairAndChild = struct { + child: *InMemoryCoercionResult, + actual: Type, + wanted: Type, + }; + + const Param = struct { + child: *InMemoryCoercionResult, + actual: Type, + wanted: Type, + index: u64, + }; + + const ComptimeParam = struct { + index: u64, + wanted: bool, + }; + + const Sentinel = struct { + // unreachable_value indicates no sentinel + actual: Value, + wanted: Value, + ty: Type, + }; + + const Int = struct { + actual_signedness: std.builtin.Signedness, + wanted_signedness: std.builtin.Signedness, + actual_bits: u16, + wanted_bits: u16, + }; + + const IntPair = struct { + actual: u64, + wanted: u64, + }; + + const Size = struct { + actual: std.builtin.Type.Pointer.Size, + wanted: std.builtin.Type.Pointer.Size, + }; + + const Qualifiers = struct { + actual_const: bool, + wanted_const: bool, + actual_volatile: bool, + wanted_volatile: bool, + }; + + const AddressSpace = struct { + actual: std.builtin.AddressSpace, + wanted: std.builtin.AddressSpace, + }; + + const CC = struct { + actual: std.builtin.CallingConvention, + wanted: std.builtin.CallingConvention, + }; + + const BitRange = struct { + actual_host: u16, + wanted_host: u16, + actual_offset: u16, + wanted_offset: u16, + }; + + fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) !*InMemoryCoercionResult { + const res = try arena.create(InMemoryCoercionResult); + res.* = child.*; + return res; + } + + fn report(res: *const InMemoryCoercionResult, sema: *Sema, block: *Block, src: LazySrcLoc, msg: *Module.ErrorMsg) !void { + var cur = res; + while (true) switch (cur.*) { + .ok => unreachable, + .no_match => |types| { + try sema.addDeclaredHereNote(msg, types.wanted); + try sema.addDeclaredHereNote(msg, types.actual); + break; + }, + .int_mismatch => |int| { + try sema.errNote(block, src, msg, "{s} {d}-bit int cannot represent all possible {s} {d}-bit values", .{ + @tagName(int.wanted_signedness), int.wanted_bits, @tagName(int.actual_signedness), int.actual_bits, + }); + break; + }, + .error_union_payload => |pair| { + try sema.errNote(block, src, msg, "error union payload '{}' cannot cast into error union payload '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .array_len => |lens| { + try sema.errNote(block, src, msg, "array of length {d} cannot cast into an array of length {d}", .{ + lens.actual, lens.wanted, + }); + break; + }, + .array_sentinel => |sentinel| { + if (sentinel.actual.tag() != .unreachable_value) { + try sema.errNote(block, src, msg, "array sentinel '{}' cannot cast into array sentinel '{}'", .{ + sentinel.actual.fmtValue(sentinel.ty, sema.mod), sentinel.wanted.fmtValue(sentinel.ty, sema.mod), + }); + } else { + try sema.errNote(block, src, msg, "destination array requires '{}' sentinel", .{ + sentinel.wanted.fmtValue(sentinel.ty, sema.mod), + }); + } + break; + }, + .array_elem => |pair| { + try sema.errNote(block, src, msg, "array element type '{}' cannot cast into array element type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .vector_len => |lens| { + try sema.errNote(block, src, msg, "vector of length {d} cannot cast into a vector of length {d}", .{ + lens.actual, lens.wanted, + }); + break; + }, + .vector_elem => |pair| { + try sema.errNote(block, src, msg, "vector element type '{}' cannot cast into vector element type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .optional_shape => |pair| { + try sema.errNote(block, src, msg, "optional type child '{}' cannot cast into optional type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + break; + }, + .optional_child => |pair| { + try sema.errNote(block, src, msg, "optional type child '{}' cannot cast into optional type child '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .from_anyerror => { + try sema.errNote(block, src, msg, "global error set cannot cast into a smaller set", .{}); + break; + }, + .missing_error => |missing_errors| { + for (missing_errors) |err| { + try sema.errNote(block, src, msg, "'error.{s}' not a member of destination error set", .{err}); + } + break; + }, + .fn_var_args => |wanted_var_args| { + if (wanted_var_args) { + try sema.errNote(block, src, msg, "non-variadic function cannot cast into a variadic function", .{}); + } else { + try sema.errNote(block, src, msg, "variadic function cannot cast into a non-variadic function", .{}); + } + break; + }, + .fn_generic => |wanted_generic| { + if (wanted_generic) { + try sema.errNote(block, src, msg, "non-generic function cannot cast into a generic function", .{}); + } else { + try sema.errNote(block, src, msg, "generic function cannot cast into a non-generic function", .{}); + } + break; + }, + .fn_param_count => |lens| { + try sema.errNote(block, src, msg, "function with {d} parameters cannot cast into a function with {d} parameters", .{ + lens.actual, lens.wanted, + }); + break; + }, + .fn_param_noalias => |param| { + var index: u6 = 0; + var actual_noalias = false; + while (true) : (index += 1) { + if (param.actual << index != param.wanted << index) { + actual_noalias = (param.actual << index) == (1 << 31); + } + } + if (!actual_noalias) { + try sema.errNote(block, src, msg, "regular paramter {d} cannot cast into a noalias paramter", .{index}); + } else { + try sema.errNote(block, src, msg, "noalias paramter {d} cannot cast into a regular paramter", .{index}); + } + break; + }, + .fn_param_comptime => |param| { + if (param.wanted) { + try sema.errNote(block, src, msg, "non-comptime paramter {d} cannot cast into a comptime paramter", .{param.index}); + } else { + try sema.errNote(block, src, msg, "comptime paramter {d} cannot cast into a non-comptime paramter", .{param.index}); + } + break; + }, + .fn_param => |param| { + try sema.errNote(block, src, msg, "parameter {d} '{}' cannot cast into '{}'", .{ + param.index, param.actual.fmt(sema.mod), param.wanted.fmt(sema.mod), + }); + cur = param.child; + }, + .fn_cc => |cc| { + try sema.errNote(block, src, msg, "calling convention {s} cannot cast into calling convention {s}", .{ @tagName(cc.actual), @tagName(cc.wanted) }); + break; + }, + .fn_return_type => |pair| { + try sema.errNote(block, src, msg, "return type '{}' cannot cast into return type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .ptr_child => |pair| { + try sema.errNote(block, src, msg, "pointer type child '{}' cannot cast into pointer type child '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + cur = pair.child; + }, + .ptr_addrspace => |@"addrspace"| { + try sema.errNote(block, src, msg, "address space '{s}' cannot cast into address space '{s}'", .{ @tagName(@"addrspace".actual), @tagName(@"addrspace".wanted) }); + break; + }, + .ptr_sentinel => |sentinel| { + if (sentinel.actual.tag() != .unreachable_value) { + try sema.errNote(block, src, msg, "pointer sentinel '{}' cannot cast into pointer sentinel '{}'", .{ + sentinel.actual.fmtValue(sentinel.ty, sema.mod), sentinel.wanted.fmtValue(sentinel.ty, sema.mod), + }); + } else { + try sema.errNote(block, src, msg, "destination pointer requires '{}' sentinel", .{ + sentinel.wanted.fmtValue(sentinel.ty, sema.mod), + }); + } + break; + }, + .ptr_size => |size| { + try sema.errNote(block, src, msg, "a {s} pointer cannot cast into a {s} pointer", .{ pointerSizeString(size.actual), pointerSizeString(size.wanted) }); + break; + }, + .ptr_qualifiers => |qualifiers| { + const ok_const = !qualifiers.actual_const or qualifiers.wanted_const; + const ok_volatile = !qualifiers.actual_volatile or qualifiers.wanted_volatile; + if (!ok_const) { + try sema.errNote(block, src, msg, "cast discards const qualifier", .{}); + } else if (!ok_volatile) { + try sema.errNote(block, src, msg, "cast discards volatile qualifier", .{}); + } + break; + }, + .ptr_allowzero => |pair| { + const wanted_allow_zero = pair.wanted.ptrAllowsZero(); + const actual_allow_zero = pair.actual.ptrAllowsZero(); + if (actual_allow_zero and !wanted_allow_zero) { + try sema.errNote(block, src, msg, "'{}' could have null values which are illegal in type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + } else { + try sema.errNote(block, src, msg, "mutable '{}' allows illegal null values stored to type '{}'", .{ + pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + }); + } + break; + }, + .ptr_bit_range => |bit_range| { + if (bit_range.actual_host != bit_range.wanted_host) { + try sema.errNote(block, src, msg, "pointer host size '{}' cannot cast into pointer host size '{}'", .{ + bit_range.actual_host, bit_range.wanted_host, + }); + } + if (bit_range.actual_offset != bit_range.wanted_offset) { + try sema.errNote(block, src, msg, "pointer bit offset '{}' cannot cast into pointer bit offset '{}'", .{ + bit_range.actual_offset, bit_range.wanted_offset, + }); + } + break; + }, + .ptr_alignment => |pair| { + try sema.errNote(block, src, msg, "pointer alignment '{}' cannot cast into pointer alignment '{}'", .{ + pair.actual, pair.wanted, + }); + break; + }, + }; + } }; +fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 { + return switch (size) { + .One => "single", + .Many => "many", + .C => "C", + .Slice => unreachable, + }; +} + /// If pointers have the same representation in runtime memory, a bitcast AIR instruction /// may be used for the coercion. /// * `const` attribute can be gained @@ -20373,8 +20707,6 @@ const InMemoryCoercionResult = enum { /// * bit offset attributes must match exactly /// * `*`/`[*]` must match exactly, but `[*c]` matches either one /// * 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( sema: *Sema, block: *Block, @@ -20392,11 +20724,17 @@ fn coerceInMemoryAllowed( if (dest_ty.zigTypeTag() == .Int and src_ty.zigTypeTag() == .Int) { const dest_info = dest_ty.intInfo(target); const src_info = src_ty.intInfo(target); - if (dest_info.signedness == src_info.signedness and - dest_info.bits == src_info.bits) + if (dest_info.signedness != src_info.signedness or + dest_info.bits != src_info.bits) { - return .ok; + return InMemoryCoercionResult{ .int_mismatch = .{ + .actual_signedness = src_info.signedness, + .wanted_signedness = dest_info.signedness, + .actual_bits = src_info.bits, + .wanted_bits = dest_info.bits, + } }; } + return .ok; } // Differently-named floats with the same number of bits. @@ -20434,9 +20772,15 @@ fn coerceInMemoryAllowed( // Error Unions if (dest_tag == .ErrorUnion and src_tag == .ErrorUnion) { - const child = try sema.coerceInMemoryAllowed(block, dest_ty.errorUnionPayload(), src_ty.errorUnionPayload(), dest_is_mut, target, dest_src, src_src); - if (child == .no_match) { - return child; + const dest_payload = dest_ty.errorUnionPayload(); + const src_payload = src_ty.errorUnionPayload(); + const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src); + if (child != .ok) { + return InMemoryCoercionResult{ .error_union_payload = .{ + .child = try child.dupe(sema.arena), + .actual = src_payload, + .wanted = dest_payload, + } }; } return try sema.coerceInMemoryAllowed(block, dest_ty.errorUnionSet(), src_ty.errorUnionSet(), dest_is_mut, target, dest_src, src_src); } @@ -20447,57 +20791,89 @@ fn coerceInMemoryAllowed( } // Arrays - if (dest_tag == .Array and src_tag == .Array) arrays: { + if (dest_tag == .Array and src_tag == .Array) { const dest_info = dest_ty.arrayInfo(); const src_info = src_ty.arrayInfo(); - if (dest_info.len != src_info.len) break :arrays; + if (dest_info.len != src_info.len) { + return InMemoryCoercionResult{ .array_len = .{ + .actual = src_info.len, + .wanted = dest_info.len, + } }; + } const child = try sema.coerceInMemoryAllowed(block, dest_info.elem_type, src_info.elem_type, dest_is_mut, target, dest_src, src_src); - if (child == .no_match) { - return child; + if (child != .ok) { + return InMemoryCoercionResult{ .array_elem = .{ + .child = try child.dupe(sema.arena), + .actual = src_info.elem_type, + .wanted = dest_info.elem_type, + } }; } const ok_sent = dest_info.sentinel == null or (src_info.sentinel != null and dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.elem_type, sema.mod)); if (!ok_sent) { - return .no_match; + return InMemoryCoercionResult{ .array_sentinel = .{ + .actual = src_info.sentinel orelse Value.initTag(.unreachable_value), + .wanted = dest_info.sentinel orelse Value.initTag(.unreachable_value), + .ty = dest_info.elem_type, + } }; } return .ok; } // Vectors - if (dest_tag == .Vector and src_tag == .Vector) vectors: { + if (dest_tag == .Vector and src_tag == .Vector) { const dest_len = dest_ty.vectorLen(); const src_len = src_ty.vectorLen(); - if (dest_len != src_len) break :vectors; + if (dest_len != src_len) { + return InMemoryCoercionResult{ .vector_len = .{ + .actual = src_len, + .wanted = dest_len, + } }; + } const dest_elem_ty = dest_ty.scalarType(); const src_elem_ty = src_ty.scalarType(); const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src); - if (child == .no_match) break :vectors; + if (child != .ok) { + return InMemoryCoercionResult{ .vector_elem = .{ + .child = try child.dupe(sema.arena), + .actual = src_elem_ty, + .wanted = dest_elem_ty, + } }; + } return .ok; } // Optionals - if (dest_tag == .Optional and src_tag == .Optional) optionals: { + if (dest_tag == .Optional and src_tag == .Optional) { if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) { - // TODO "optional type child '{}' cannot cast into optional type '{}'" - return .no_match; + return InMemoryCoercionResult{ .optional_shape = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; } const dest_child_type = dest_ty.optionalChild(&dest_buf); const src_child_type = src_ty.optionalChild(&src_buf); const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src); - if (child == .no_match) { - // TODO "optional type child '{}' cannot cast into optional type child '{}'" - break :optionals; + if (child != .ok) { + return InMemoryCoercionResult{ .optional_child = .{ + .child = try child.dupe(sema.arena), + .actual = src_child_type, + .wanted = dest_child_type, + } }; } return .ok; } - return .no_match; + return InMemoryCoercionResult{ .no_match = .{ + .actual = dest_ty, + .wanted = src_ty, + } }; } fn coerceInMemoryAllowedErrorSets( @@ -20564,6 +20940,9 @@ fn coerceInMemoryAllowedErrorSets( } } + var missing_error_buf = std.ArrayList([]const u8).init(sema.gpa); + defer missing_error_buf.deinit(); + switch (src_ty.tag()) { .error_set_inferred => { const src_data = src_ty.castTag(.error_set_inferred).?.data; @@ -20572,15 +20951,21 @@ fn coerceInMemoryAllowedErrorSets( // src anyerror status might have changed after the resolution. if (src_ty.isAnyError()) { // dest_ty.isAnyError() == true is already checked for at this point. - return .no_match; + return .from_anyerror; } for (src_data.errors.keys()) |key| { if (!dest_ty.errorSetHasField(key)) { - return .no_match; + try missing_error_buf.append(key); } } + if (missing_error_buf.items.len != 0) { + return InMemoryCoercionResult{ + .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items), + }; + } + return .ok; }, .error_set_single => { @@ -20588,37 +20973,52 @@ fn coerceInMemoryAllowedErrorSets( if (dest_ty.errorSetHasField(name)) { return .ok; } + const list = try sema.arena.alloc([]const u8, 1); + list[0] = name; + return InMemoryCoercionResult{ .missing_error = list }; }, .error_set_merged => { const names = src_ty.castTag(.error_set_merged).?.data.keys(); for (names) |name| { if (!dest_ty.errorSetHasField(name)) { - return .no_match; + try missing_error_buf.append(name); } } + if (missing_error_buf.items.len != 0) { + return InMemoryCoercionResult{ + .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items), + }; + } + return .ok; }, .error_set => { const names = src_ty.castTag(.error_set).?.data.names.keys(); for (names) |name| { if (!dest_ty.errorSetHasField(name)) { - return .no_match; + try missing_error_buf.append(name); } } + if (missing_error_buf.items.len != 0) { + return InMemoryCoercionResult{ + .missing_error = try sema.arena.dupe([]const u8, missing_error_buf.items), + }; + } + return .ok; }, .anyerror => switch (dest_ty.tag()) { - .error_set_inferred => return .no_match, // Caught by dest.isAnyError() above. - .error_set_single, .error_set_merged, .error_set => {}, + .error_set_inferred => unreachable, // Caught by dest_ty.isAnyError() above. + .error_set_single, .error_set_merged, .error_set => return .from_anyerror, .anyerror => unreachable, // Filtered out above. else => unreachable, }, else => unreachable, } - return .no_match; + unreachable; } fn coerceInMemoryAllowedFns( @@ -20634,44 +21034,67 @@ fn coerceInMemoryAllowedFns( const src_info = src_ty.fnInfo(); if (dest_info.is_var_args != src_info.is_var_args) { - return .no_match; + return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args }; } if (dest_info.is_generic != src_info.is_generic) { - return .no_match; + return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic }; + } + + if (dest_info.cc != src_info.cc) { + return InMemoryCoercionResult{ .fn_cc = .{ + .actual = src_info.cc, + .wanted = dest_info.cc, + } }; } if (!src_info.return_type.isNoReturn()) { const rt = try sema.coerceInMemoryAllowed(block, dest_info.return_type, src_info.return_type, false, target, dest_src, src_src); - if (rt == .no_match) { - return rt; + if (rt != .ok) { + return InMemoryCoercionResult{ .fn_return_type = .{ + .child = try rt.dupe(sema.arena), + .actual = src_info.return_type, + .wanted = dest_info.return_type, + } }; } } if (dest_info.param_types.len != src_info.param_types.len) { - return .no_match; + return InMemoryCoercionResult{ .fn_param_count = .{ + .actual = dest_info.param_types.len, + .wanted = dest_info.param_types.len, + } }; + } + + if (dest_info.noalias_bits != src_info.noalias_bits) { + return InMemoryCoercionResult{ .fn_param_noalias = .{ + .actual = dest_info.noalias_bits, + .wanted = dest_info.noalias_bits, + } }; } for (dest_info.param_types) |dest_param_ty, i| { const src_param_ty = src_info.param_types[i]; if (dest_info.comptime_params[i] != src_info.comptime_params[i]) { - return .no_match; + return InMemoryCoercionResult{ .fn_param_comptime = .{ + .index = i, + .wanted = dest_info.comptime_params[i], + } }; } - // TODO: noalias - // Note: Cast direction is reversed here. const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, false, target, dest_src, src_src); - if (param == .no_match) { - return param; + if (param != .ok) { + return InMemoryCoercionResult{ .fn_param = .{ + .child = try param.dupe(sema.arena), + .actual = src_param_ty, + .wanted = dest_param_ty, + .index = i, + } }; } } - if (dest_info.cc != src_info.cc) { - return .no_match; - } - return .ok; } @@ -20690,26 +21113,13 @@ fn coerceInMemoryAllowedPtrs( const dest_info = dest_ptr_ty.ptrInfo().data; const src_info = src_ptr_ty.ptrInfo().data; - const child = try sema.coerceInMemoryAllowed(block, dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target, dest_src, src_src); - if (child == .no_match) { - return child; - } - - if (dest_info.@"addrspace" != src_info.@"addrspace") { - return .no_match; - } - - const ok_sent = dest_info.sentinel == null or src_info.size == .C or - (src_info.sentinel != null and - dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type, sema.mod)); - if (!ok_sent) { - return .no_match; - } - const ok_ptr_size = src_info.size == dest_info.size or src_info.size == .C or dest_info.size == .C; if (!ok_ptr_size) { - return .no_match; + return InMemoryCoercionResult{ .ptr_size = .{ + .actual = src_info.size, + .wanted = dest_info.size, + } }; } const ok_cv_qualifiers = @@ -20717,7 +21127,28 @@ fn coerceInMemoryAllowedPtrs( (!src_info.@"volatile" or dest_info.@"volatile"); if (!ok_cv_qualifiers) { - return .no_match; + return InMemoryCoercionResult{ .ptr_qualifiers = .{ + .actual_const = !src_info.mutable, + .wanted_const = !dest_info.mutable, + .actual_volatile = src_info.@"volatile", + .wanted_volatile = dest_info.@"volatile", + } }; + } + + if (dest_info.@"addrspace" != src_info.@"addrspace") { + return InMemoryCoercionResult{ .ptr_addrspace = .{ + .actual = src_info.@"addrspace", + .wanted = dest_info.@"addrspace", + } }; + } + + const child = try sema.coerceInMemoryAllowed(block, dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target, dest_src, src_src); + if (child != .ok) { + return InMemoryCoercionResult{ .ptr_child = .{ + .child = try child.dupe(sema.arena), + .actual = src_info.pointee_type, + .wanted = dest_info.pointee_type, + } }; } const dest_allow_zero = dest_ty.ptrAllowsZero(); @@ -20727,13 +21158,32 @@ fn coerceInMemoryAllowedPtrs( (src_allow_zero or !dest_is_mut)) or (!dest_allow_zero and !src_allow_zero); if (!ok_allows_zero) { - return .no_match; + return InMemoryCoercionResult{ .ptr_allowzero = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; } if (src_info.host_size != dest_info.host_size or src_info.bit_offset != dest_info.bit_offset) { - return .no_match; + return InMemoryCoercionResult{ .ptr_bit_range = .{ + .actual_host = src_info.host_size, + .wanted_host = dest_info.host_size, + .actual_offset = src_info.bit_offset, + .wanted_offset = dest_info.bit_offset, + } }; + } + + const ok_sent = dest_info.sentinel == null or src_info.size == .C or + (src_info.sentinel != null and + dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type, sema.mod)); + if (!ok_sent) { + return InMemoryCoercionResult{ .ptr_sentinel = .{ + .actual = src_info.sentinel orelse Value.initTag(.unreachable_value), + .wanted = dest_info.sentinel orelse Value.initTag(.unreachable_value), + .ty = dest_info.pointee_type, + } }; } // If both pointers have alignment 0, it means they both want ABI alignment. @@ -20758,7 +21208,10 @@ fn coerceInMemoryAllowedPtrs( dest_info.pointee_type.abiAlignment(target); if (dest_align > src_align) { - return .no_match; + return InMemoryCoercionResult{ .ptr_alignment = .{ + .actual = src_align, + .wanted = dest_align, + } }; } break :alignment; -- cgit v1.2.3 From 34fe2b4f4be29efa8f4ba4b9f32b22373fdddc22 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 10 Jul 2022 16:51:10 +0300 Subject: Sema: prefer original error message in `coerce` --- src/Sema.zig | 66 ++++++++++++++++++---- .../any_typed_null_to_any_typed_optional.zig | 10 ++-- ...between_optional_T_where_T_is_not_a_pointer.zig | 16 ++++++ .../implicit_cast_to_c_ptr_from_int.zig | 15 +++++ ...between_optional_T_where_T_is_not_a_pointer.zig | 15 ----- .../type_mismatch_in_C_prototype_with_varargs.zig | 2 +- 6 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig create mode 100644 test/cases/compile_errors/implicit_cast_to_c_ptr_from_int.zig delete mode 100644 test/cases/compile_errors/stage1/test/cast_between_optional_T_where_T_is_not_a_pointer.zig (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index 6f1259ed82..bb14943e22 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19853,6 +19853,26 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { + return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; +} + +const CoersionError = CompileError || error{ + /// When coerce is called recursively, this error should be returned instead of using `fail` + /// to ensure correct types in compile errors. + NotCoercible, +}; + +fn coerceExtra( + sema: *Sema, + block: *Block, + dest_ty_unresolved: Type, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, + report_err: bool, +) CoersionError!Air.Inst.Ref { switch (dest_ty_unresolved.tag()) { .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), .generic_poison => return inst, @@ -19869,7 +19889,7 @@ fn coerce( const arena = sema.arena; const maybe_inst_val = try sema.resolveMaybeUndefVal(block, inst_src, inst); - const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src); + var in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src); if (in_memory_result == .ok) { if (maybe_inst_val) |val| { // Keep the comptime Value representation; take the new type. @@ -19882,7 +19902,7 @@ fn coerce( const is_undef = if (maybe_inst_val) |val| val.isUndef() else false; switch (dest_ty.zigTypeTag()) { - .Optional => { + .Optional => optional: { // undefined sets the optional bit also to undefined. if (is_undef) { return sema.addConstUndef(dest_ty); @@ -19903,10 +19923,19 @@ fn coerce( // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); - const intermediate = try sema.coerce(block, child_type, inst, inst_src); - return sema.wrapOptional(block, dest_ty, intermediate, inst_src); + const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false) catch |err| switch (err) { + error.NotCoercible => { + if (in_memory_result == .no_match) { + // Try to give more useful notes + in_memory_result = try sema.coerceInMemoryAllowed(block, child_type, inst_ty, false, target, dest_ty_src, inst_src); + } + break :optional; + }, + else => |e| return e, + }; + return try sema.wrapOptional(block, dest_ty, intermediate, inst_src); }, - .Pointer => { + .Pointer => pointer: { const dest_info = dest_ty.ptrInfo().data; // Function body to function pointer. @@ -20011,16 +20040,26 @@ fn coerce( return sema.addConstant(dest_ty, Value.@"null"); }, .ComptimeInt => { - const addr = try sema.coerce(block, Type.usize, inst, inst_src); - return sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src); + const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false) catch |err| switch (err) { + error.NotCoercible => break :pointer, + else => |e| return e, + }; + return try sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src); }, .Int => { const ptr_size_ty = switch (inst_ty.intInfo(target).signedness) { .signed => Type.isize, .unsigned => Type.usize, }; - const addr = try sema.coerce(block, ptr_size_ty, inst, inst_src); - return sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src); + const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false) catch |err| switch (err) { + error.NotCoercible => { + // Try to give more useful notes + in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src); + break :pointer; + }, + else => |e| return e, + }; + return try sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src); }, .Pointer => p: { const inst_info = inst_ty.ptrInfo().data; @@ -20155,6 +20194,7 @@ fn coerce( if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { // comptime known integer to other number if (!(try sema.intFitsInType(block, inst_src, val, dest_ty, null))) { + if (!report_err) return error.NotCoercible; return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }); } return try sema.addConstant(dest_ty, val); @@ -20356,6 +20396,8 @@ fn coerce( return sema.addConstUndef(dest_ty); } + if (!report_err) return error.NotCoercible; + const msg = msg: { const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); errdefer msg.destroy(sema.gpa); @@ -20534,8 +20576,10 @@ const InMemoryCoercionResult = union(enum) { cur = pair.child; }, .optional_shape => |pair| { - try sema.errNote(block, src, msg, "optional type child '{}' cannot cast into optional type '{}'", .{ - pair.actual.fmt(sema.mod), pair.wanted.fmt(sema.mod), + var buf_actual: Type.Payload.ElemType = undefined; + var buf_wanted: Type.Payload.ElemType = undefined; + try sema.errNote(block, src, msg, "optional type child '{}' cannot cast into optional type child '{}'", .{ + pair.actual.optionalChild(&buf_actual).fmt(sema.mod), pair.wanted.optionalChild(&buf_wanted).fmt(sema.mod), }); break; }, diff --git a/test/cases/compile_errors/any_typed_null_to_any_typed_optional.zig b/test/cases/compile_errors/any_typed_null_to_any_typed_optional.zig index ad6427d224..6b3ffaca2f 100644 --- a/test/cases/compile_errors/any_typed_null_to_any_typed_optional.zig +++ b/test/cases/compile_errors/any_typed_null_to_any_typed_optional.zig @@ -1,11 +1,11 @@ -pub fn main() void { +pub export fn entry() void { var a: ?*anyopaque = undefined; a = @as(?usize, null); } // error -// output_mode=Exe -// backend=stage2,llvm -// target=x86_64-linux,x86_64-macos +// backend=stage2 +// target=native // -// :3:21: error: expected type '*anyopaque', found '?usize' +// :3:21: error: expected type '?*anyopaque', found '?usize' +// :3:21: note: optional type child 'usize' cannot cast into optional type child '*anyopaque' diff --git a/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig b/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig new file mode 100644 index 0000000000..a09059eb3e --- /dev/null +++ b/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig @@ -0,0 +1,16 @@ +pub const fnty1 = ?*const fn (i8) void; +pub const fnty2 = ?*const fn (u64) void; +export fn entry() void { + var a: fnty1 = undefined; + var b: fnty2 = undefined; + a = b; +} + +// error +// backend=stage2 +// target=native +// +// :6:9: error: expected type '?*const fn(i8) void', found '?*const fn(u64) void' +// :6:9: note: pointer type child 'fn(u64) void' cannot cast into pointer type child 'fn(i8) void' +// :6:9: note: parameter 0 'u64' cannot cast into 'i8' +// :6:9: note: unsigned 64-bit int cannot represent all possible signed 8-bit values diff --git a/test/cases/compile_errors/implicit_cast_to_c_ptr_from_int.zig b/test/cases/compile_errors/implicit_cast_to_c_ptr_from_int.zig new file mode 100644 index 0000000000..bcf15569ff --- /dev/null +++ b/test/cases/compile_errors/implicit_cast_to_c_ptr_from_int.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +export fn entry1() void { + _ = @as([*c]u8, @as(u65, std.math.maxInt(u65))); +} +export fn entry2() void { + _ = @as([*c]u8, std.math.maxInt(u65)); +} + +// error +// backend=stage2 +// target=native +// +// :3:21: error: expected type '[*c]u8', found 'u65' +// :3:21: note: unsigned 64-bit int cannot represent all possible unsigned 65-bit values +// :6:36: error: expected type '[*c]u8', found 'comptime_int' diff --git a/test/cases/compile_errors/stage1/test/cast_between_optional_T_where_T_is_not_a_pointer.zig b/test/cases/compile_errors/stage1/test/cast_between_optional_T_where_T_is_not_a_pointer.zig deleted file mode 100644 index b8392ff5db..0000000000 --- a/test/cases/compile_errors/stage1/test/cast_between_optional_T_where_T_is_not_a_pointer.zig +++ /dev/null @@ -1,15 +0,0 @@ -pub const fnty1 = ?fn (i8) void; -pub const fnty2 = ?fn (u64) void; -export fn entry() void { - var a: fnty1 = undefined; - var b: fnty2 = undefined; - a = b; -} - -// error -// backend=stage1 -// target=native -// is_test=1 -// -// tmp.zig:6:9: error: expected type '?fn(i8) void', found '?fn(u64) void' -// tmp.zig:6:9: note: optional type child 'fn(u64) void' cannot cast into optional type child 'fn(i8) void' diff --git a/test/cases/compile_errors/type_mismatch_in_C_prototype_with_varargs.zig b/test/cases/compile_errors/type_mismatch_in_C_prototype_with_varargs.zig index 1e7acb55b8..66256fc9e9 100644 --- a/test/cases/compile_errors/type_mismatch_in_C_prototype_with_varargs.zig +++ b/test/cases/compile_errors/type_mismatch_in_C_prototype_with_varargs.zig @@ -10,6 +10,6 @@ export fn main() void { // backend=stage2 // target=native // -// :5:22: error: expected type 'fn([*c]u8, ...) callconv(.C) void', found 'fn([*:0]u8, ...) callconv(.C) void' +// :5:22: error: expected type '?fn([*c]u8, ...) callconv(.C) void', found 'fn([*:0]u8, ...) callconv(.C) void' // :5:22: note: parameter 0 '[*:0]u8' cannot cast into '[*c]u8' // :5:22: note: '[*c]u8' could have null values which are illegal in type '[*:0]u8' -- cgit v1.2.3 From e644a2ab6a99951ac8d367f7a6bac985cf16f9cc Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 10 Jul 2022 16:58:25 +0300 Subject: Compilation: do not repeat same source line for notes --- lib/std/zig.zig | 4 ++++ src/Compilation.zig | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 0d3c94d37b..b8f75f649e 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -49,6 +49,10 @@ pub const Loc = struct { column: usize, /// Does not include the trailing newline. source_line: []const u8, + + pub fn eql(a: Loc, b: Loc) bool { + return a.line == b.line and a.column == b.column and std.mem.eql(u8, a.source_line, b.source_line); + } }; pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc { diff --git a/src/Compilation.zig b/src/Compilation.zig index b00f135813..48a287d40b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -505,6 +505,9 @@ pub const AllErrors = struct { Message.HashContext, std.hash_map.default_max_load_percentage, ).init(allocator); + const err_source = try module_err_msg.src_loc.file_scope.getSource(module.gpa); + const err_byte_offset = try module_err_msg.src_loc.byteOffset(module.gpa); + const err_loc = std.zig.findLineColumn(err_source.bytes, err_byte_offset); for (module_err_msg.notes) |module_note| { const source = try module_note.src_loc.file_scope.getSource(module.gpa); @@ -519,7 +522,7 @@ pub const AllErrors = struct { .byte_offset = byte_offset, .line = @intCast(u32, loc.line), .column = @intCast(u32, loc.column), - .source_line = try allocator.dupe(u8, loc.source_line), + .source_line = if (err_loc.eql(loc)) null else try allocator.dupe(u8, loc.source_line), }, }; const gop = try seen_notes.getOrPut(note); @@ -537,19 +540,16 @@ pub const AllErrors = struct { }); return; } - const source = try module_err_msg.src_loc.file_scope.getSource(module.gpa); - const byte_offset = try module_err_msg.src_loc.byteOffset(module.gpa); - const loc = std.zig.findLineColumn(source.bytes, byte_offset); const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator); try errors.append(.{ .src = .{ .src_path = file_path, .msg = try allocator.dupe(u8, module_err_msg.msg), - .byte_offset = byte_offset, - .line = @intCast(u32, loc.line), - .column = @intCast(u32, loc.column), + .byte_offset = err_byte_offset, + .line = @intCast(u32, err_loc.line), + .column = @intCast(u32, err_loc.column), .notes = notes_buf[0..note_i], - .source_line = try allocator.dupe(u8, loc.source_line), + .source_line = try allocator.dupe(u8, err_loc.source_line), }, }); } -- cgit v1.2.3 From 0370006c1f5a23e5e2d0993fb71f825791d64957 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 11 Jul 2022 11:38:04 +0300 Subject: Sema: only add note about int mismatch if not coercible `unsigned 64-bit int cannot represent all possible unsigned 63-bit values` is nonsensical. --- src/Sema.zig | 20 ++++++++++++++------ ...t_between_optional_T_where_T_is_not_a_pointer.zig | 12 +++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index bb14943e22..95b8d8acdf 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20411,7 +20411,7 @@ fn coerceExtra( const InMemoryCoercionResult = union(enum) { ok, no_match: Pair, - int_mismatch: Int, + int_not_coercible: Int, error_union_payload: PairAndChild, array_len: IntPair, array_sentinel: Sentinel, @@ -20527,7 +20527,7 @@ const InMemoryCoercionResult = union(enum) { try sema.addDeclaredHereNote(msg, types.actual); break; }, - .int_mismatch => |int| { + .int_not_coercible => |int| { try sema.errNote(block, src, msg, "{s} {d}-bit int cannot represent all possible {s} {d}-bit values", .{ @tagName(int.wanted_signedness), int.wanted_bits, @tagName(int.actual_signedness), int.actual_bits, }); @@ -20768,17 +20768,25 @@ fn coerceInMemoryAllowed( if (dest_ty.zigTypeTag() == .Int and src_ty.zigTypeTag() == .Int) { const dest_info = dest_ty.intInfo(target); const src_info = src_ty.intInfo(target); - if (dest_info.signedness != src_info.signedness or - dest_info.bits != src_info.bits) + + if (dest_info.signedness == src_info.signedness and + dest_info.bits == src_info.bits) + { + return .ok; + } + + if ((src_info.signedness == dest_info.signedness and dest_info.bits < src_info.bits) or + // small enough unsigned ints can get casted to large enough signed ints + (dest_info.signedness == .signed and (src_info.signedness == .unsigned or dest_info.bits <= src_info.bits)) or + (dest_info.signedness == .unsigned and src_info.signedness == .signed)) { - return InMemoryCoercionResult{ .int_mismatch = .{ + return InMemoryCoercionResult{ .int_not_coercible = .{ .actual_signedness = src_info.signedness, .wanted_signedness = dest_info.signedness, .actual_bits = src_info.bits, .wanted_bits = dest_info.bits, } }; } - return .ok; } // Differently-named floats with the same number of bits. diff --git a/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig b/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig index a09059eb3e..dcccd0fb8a 100644 --- a/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig +++ b/test/cases/compile_errors/cast_between_optional_T_where_T_is_not_a_pointer.zig @@ -1,11 +1,18 @@ pub const fnty1 = ?*const fn (i8) void; pub const fnty2 = ?*const fn (u64) void; -export fn entry() void { +export fn entry1() void { var a: fnty1 = undefined; var b: fnty2 = undefined; a = b; } +pub const fnty3 = ?*const fn (u63) void; +export fn entry2() void { + var a: fnty3 = undefined; + var b: fnty2 = undefined; + a = b; +} + // error // backend=stage2 // target=native @@ -14,3 +21,6 @@ export fn entry() void { // :6:9: note: pointer type child 'fn(u64) void' cannot cast into pointer type child 'fn(i8) void' // :6:9: note: parameter 0 'u64' cannot cast into 'i8' // :6:9: note: unsigned 64-bit int cannot represent all possible signed 8-bit values +// :13:9: error: expected type '?*const fn(u63) void', found '?*const fn(u64) void' +// :13:9: note: pointer type child 'fn(u64) void' cannot cast into pointer type child 'fn(u63) void' +// :13:9: note: parameter 0 'u64' cannot cast into 'u63' -- cgit v1.2.3 From 2a41b1449b724aa10290b9ec735c89405b627960 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 11 Jul 2022 14:10:25 +0300 Subject: Compilation: do not repeat AstGen error source line for notes --- src/Compilation.zig | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/Compilation.zig b/src/Compilation.zig index 48a287d40b..9678bc27b9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -572,6 +572,16 @@ pub const AllErrors = struct { while (item_i < items_len) : (item_i += 1) { const item = file.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index); extra_index = item.end; + const err_byte_offset = blk: { + const token_starts = file.tree.tokens.items(.start); + if (item.data.node != 0) { + const main_tokens = file.tree.nodes.items(.main_token); + const main_token = main_tokens[item.data.node]; + break :blk token_starts[main_token]; + } + break :blk token_starts[item.data.token] + item.data.byte_offset; + }; + const err_loc = std.zig.findLineColumn(file.source, err_byte_offset); var notes: []Message = &[0]Message{}; if (item.data.notes != 0) { @@ -600,33 +610,22 @@ pub const AllErrors = struct { .line = @intCast(u32, loc.line), .column = @intCast(u32, loc.column), .notes = &.{}, // TODO rework this function to be recursive - .source_line = try arena.dupe(u8, loc.source_line), + .source_line = if (loc.eql(err_loc)) null else try arena.dupe(u8, loc.source_line), }, }; } } const msg = file.zir.nullTerminatedString(item.data.msg); - const byte_offset = blk: { - const token_starts = file.tree.tokens.items(.start); - if (item.data.node != 0) { - const main_tokens = file.tree.nodes.items(.main_token); - const main_token = main_tokens[item.data.node]; - break :blk token_starts[main_token]; - } - break :blk token_starts[item.data.token] + item.data.byte_offset; - }; - const loc = std.zig.findLineColumn(file.source, byte_offset); - try errors.append(.{ .src = .{ .src_path = try file.fullPath(arena), .msg = try arena.dupe(u8, msg), - .byte_offset = byte_offset, - .line = @intCast(u32, loc.line), - .column = @intCast(u32, loc.column), + .byte_offset = err_byte_offset, + .line = @intCast(u32, err_loc.line), + .column = @intCast(u32, err_loc.column), .notes = notes, - .source_line = try arena.dupe(u8, loc.source_line), + .source_line = try arena.dupe(u8, err_loc.source_line), }, }); } -- cgit v1.2.3 From c9e1360cdba2bc0c20dc04a3d22fbc0002bcd70b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 11 Jul 2022 14:17:22 +0300 Subject: Sema: add "cannot convert to payload type" error notes --- src/Sema.zig | 37 ++++++++++++++++++++-- ...ls_with_a_function_that_returns_an_optional.zig | 23 ++++++++++++++ .../compile_errors/discarding_error_value.zig | 3 +- .../ignored_deferred_function_call.zig | 3 +- .../ignored_expression_in_while_continuation.zig | 9 ++++-- ...f_optional_anyopaque_to_anyopaque_must_fail.zig | 14 ++++++++ ...erly_when_assigning_a_value_within_a_struct.zig | 23 ++++++++++++++ ...n_incompatibility_mismatching_handle_is_ptr.zig | 20 ++++++++++++ ...lity_mismatching_handle_is_ptr_generic_call.zig | 20 ++++++++++++ ...ls_with_a_function_that_returns_an_optional.zig | 21 ------------ ...f_optional_anyopaque_to_anyopaque_must_fail.zig | 11 ------- ...erly_when_assigning_a_value_within_a_struct.zig | 21 ------------ ...n_incompatibility_mismatching_handle_is_ptr.zig | 18 ----------- ...lity_mismatching_handle_is_ptr_generic_call.zig | 18 ----------- 14 files changed, 145 insertions(+), 96 deletions(-) create mode 100644 test/cases/compile_errors/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig create mode 100644 test/cases/compile_errors/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig create mode 100644 test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig create mode 100644 test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr.zig create mode 100644 test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig delete mode 100644 test/cases/compile_errors/stage1/obj/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig delete mode 100644 test/cases/compile_errors/stage1/obj/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig delete mode 100644 test/cases/compile_errors/stage1/obj/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig delete mode 100644 test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr.zig delete mode 100644 test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index 95b8d8acdf..a10695df1f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2749,7 +2749,15 @@ fn ensureResultUsed( const operand_ty = sema.typeOf(operand); switch (operand_ty.zigTypeTag()) { .Void, .NoReturn => return, - .ErrorSet, .ErrorUnion => return sema.fail(block, src, "error is ignored. consider using `try`, `catch`, or `if`", .{}), + .ErrorSet, .ErrorUnion => { + const msg = msg: { + const msg = try sema.errMsg(block, src, "error is ignored", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "consider using `try`, `catch`, or `if`", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, else => return sema.fail(block, src, "expression value is ignored", .{}), } } @@ -2763,7 +2771,15 @@ fn zirEnsureResultNonError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const src = inst_data.src(); const operand_ty = sema.typeOf(operand); switch (operand_ty.zigTypeTag()) { - .ErrorSet, .ErrorUnion => return sema.fail(block, src, "error is discarded. consider using `try`, `catch`, or `if`", .{}), + .ErrorSet, .ErrorUnion => { + const msg = msg: { + const msg = try sema.errMsg(block, src, "error is discarded", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "consider using `try`, `catch`, or `if`", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, else => return, } } @@ -20402,6 +20418,23 @@ fn coerceExtra( const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); errdefer msg.destroy(sema.gpa); + // E!T to T + if (inst_ty.zigTypeTag() == .ErrorUnion and + (try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) + { + try sema.errNote(block, inst_src, msg, "cannot convert error union to payload type", .{}); + try sema.errNote(block, inst_src, msg, "consider using `try`, `catch`, or `if`", .{}); + } + + // ?T to T + var buf: Type.Payload.ElemType = undefined; + if (inst_ty.zigTypeTag() == .Optional and + (try sema.coerceInMemoryAllowed(block, inst_ty.optionalChild(&buf), dest_ty, false, target, dest_ty_src, inst_src)) == .ok) + { + try sema.errNote(block, inst_src, msg, "cannot convert optional to payload type", .{}); + try sema.errNote(block, inst_src, msg, "consider using `.?`, `orelse`, or `if`", .{}); + } + try in_memory_result.report(sema, block, inst_src, msg); break :msg msg; }; diff --git a/test/cases/compile_errors/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig b/test/cases/compile_errors/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig new file mode 100644 index 0000000000..762eb284f2 --- /dev/null +++ b/test/cases/compile_errors/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig @@ -0,0 +1,23 @@ +fn maybe(is: bool) ?u8 { + if (is) return @as(u8, 10) else return null; +} +const U = union { + Ye: u8, +}; +const S = struct { + num: u8, +}; +export fn entry() void { + var u = U{ .Ye = maybe(false) }; + var s = S{ .num = maybe(false) }; + _ = u; + _ = s; +} + +// error +// backend=stage2 +// target=native +// +// :11:27: error: expected type 'u8', found '?u8' +// :11:27: note: cannot convert optional to payload type +// :11:27: note: consider using `.?`, `orelse`, or `if` diff --git a/test/cases/compile_errors/discarding_error_value.zig b/test/cases/compile_errors/discarding_error_value.zig index f74fc4ea29..6dfe0be231 100644 --- a/test/cases/compile_errors/discarding_error_value.zig +++ b/test/cases/compile_errors/discarding_error_value.zig @@ -9,4 +9,5 @@ fn foo() !void { // backend=stage2 // target=native // -// :2:12: error: error is discarded. consider using `try`, `catch`, or `if` +// :2:12: error: error is discarded +// :2:12: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/ignored_deferred_function_call.zig b/test/cases/compile_errors/ignored_deferred_function_call.zig index 69df8b0498..05c4373705 100644 --- a/test/cases/compile_errors/ignored_deferred_function_call.zig +++ b/test/cases/compile_errors/ignored_deferred_function_call.zig @@ -7,4 +7,5 @@ fn bar() anyerror!i32 { return 0; } // backend=stage2 // target=native // -// :2:14: error: error is ignored. consider using `try`, `catch`, or `if` +// :2:14: error: error is ignored +// :2:14: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/ignored_expression_in_while_continuation.zig b/test/cases/compile_errors/ignored_expression_in_while_continuation.zig index d295d476ab..d7de0aac57 100644 --- a/test/cases/compile_errors/ignored_expression_in_while_continuation.zig +++ b/test/cases/compile_errors/ignored_expression_in_while_continuation.zig @@ -17,6 +17,9 @@ fn bad() anyerror!void { // backend=stage2 // target=native // -// :2:24: error: error is ignored. consider using `try`, `catch`, or `if` -// :6:25: error: error is ignored. consider using `try`, `catch`, or `if` -// :10:25: error: error is ignored. consider using `try`, `catch`, or `if` +// :2:24: error: error is ignored +// :2:24: note: consider using `try`, `catch`, or `if` +// :6:25: error: error is ignored +// :6:25: note: consider using `try`, `catch`, or `if` +// :10:25: error: error is ignored +// :10:25: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig b/test/cases/compile_errors/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig new file mode 100644 index 0000000000..f4716bc24d --- /dev/null +++ b/test/cases/compile_errors/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig @@ -0,0 +1,14 @@ +export fn foo() void { + var u: ?*anyopaque = null; + var v: *anyopaque = undefined; + v = u; +} + +// error +// backend=stage2 +// target=native +// +// :4:9: error: expected type '*anyopaque', found '?*anyopaque' +// :4:9: note: cannot convert optional to payload type +// :4:9: note: consider using `.?`, `orelse`, or `if` +// :4:9: note: '?*anyopaque' could have null values which are illegal in type '*anyopaque' diff --git a/test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig b/test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig new file mode 100644 index 0000000000..09c496211a --- /dev/null +++ b/test/cases/compile_errors/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig @@ -0,0 +1,23 @@ +const Foo = struct { + ptr: ?*usize, + uval: u32, +}; +fn get_uval(x: u32) !u32 { + _ = x; + return error.NotFound; +} +export fn entry() void { + const afoo = Foo{ + .ptr = null, + .uval = get_uval(42), + }; + _ = afoo; +} + +// error +// backend=stage2 +// target=native +// +// :12:25: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.get_uval)).Fn.return_type.?).ErrorUnion.error_set!u32' +// :12:25: note: cannot convert error union to payload type +// :12:25: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr.zig b/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr.zig new file mode 100644 index 0000000000..cc1d2c976a --- /dev/null +++ b/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr.zig @@ -0,0 +1,20 @@ +export fn entry() void { + var damn = Container{ + .not_optional = getOptional(), + }; + _ = damn; +} +pub fn getOptional() ?i32 { + return 0; +} +pub const Container = struct { + not_optional: i32, +}; + +// error +// backend=stage2 +// target=native +// +// :3:36: error: expected type 'i32', found '?i32' +// :3:36: note: cannot convert optional to payload type +// :3:36: note: consider using `.?`, `orelse`, or `if` diff --git a/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig b/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig new file mode 100644 index 0000000000..897675d448 --- /dev/null +++ b/test/cases/compile_errors/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig @@ -0,0 +1,20 @@ +export fn entry() void { + var damn = Container{ + .not_optional = getOptional(i32), + }; + _ = damn; +} +pub fn getOptional(comptime T: type) ?T { + return 0; +} +pub const Container = struct { + not_optional: i32, +}; + +// error +// backend=stage2 +// target=native +// +// :3:36: error: expected type 'i32', found '?i32' +// :3:36: note: cannot convert optional to payload type +// :3:36: note: consider using `.?`, `orelse`, or `if` diff --git a/test/cases/compile_errors/stage1/obj/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig b/test/cases/compile_errors/stage1/obj/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig deleted file mode 100644 index 8285991c05..0000000000 --- a/test/cases/compile_errors/stage1/obj/assigning_to_struct_or_union_fields_that_are_not_optionals_with_a_function_that_returns_an_optional.zig +++ /dev/null @@ -1,21 +0,0 @@ -fn maybe(is: bool) ?u8 { - if (is) return @as(u8, 10) else return null; -} -const U = union { - Ye: u8, -}; -const S = struct { - num: u8, -}; -export fn entry() void { - var u = U{ .Ye = maybe(false) }; - var s = S{ .num = maybe(false) }; - _ = u; - _ = s; -} - -// error -// backend=stage1 -// target=native -// -// tmp.zig:11:27: error: cannot convert optional to payload type. consider using `.?`, `orelse`, or `if`. expected type 'u8', found '?u8' diff --git a/test/cases/compile_errors/stage1/obj/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig b/test/cases/compile_errors/stage1/obj/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig deleted file mode 100644 index 9ba9910838..0000000000 --- a/test/cases/compile_errors/stage1/obj/issue_5618_coercion_of_optional_anyopaque_to_anyopaque_must_fail.zig +++ /dev/null @@ -1,11 +0,0 @@ -export fn foo() void { - var u: ?*anyopaque = null; - var v: *anyopaque = undefined; - v = u; -} - -// error -// backend=stage1 -// target=native -// -// tmp.zig:4:9: error: cannot convert optional to payload type. consider using `.?`, `orelse`, or `if`. expected type '*anyopaque', found '?*anyopaque' diff --git a/test/cases/compile_errors/stage1/obj/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig b/test/cases/compile_errors/stage1/obj/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig deleted file mode 100644 index 8be44fbe05..0000000000 --- a/test/cases/compile_errors/stage1/obj/regression_test_2980_base_type_u32_is_not_type_checked_properly_when_assigning_a_value_within_a_struct.zig +++ /dev/null @@ -1,21 +0,0 @@ -const Foo = struct { - ptr: ?*usize, - uval: u32, -}; -fn get_uval(x: u32) !u32 { - _ = x; - return error.NotFound; -} -export fn entry() void { - const afoo = Foo{ - .ptr = null, - .uval = get_uval(42), - }; - _ = afoo; -} - -// error -// backend=stage1 -// target=native -// -// tmp.zig:12:25: error: cannot convert error union to payload type. consider using `try`, `catch`, or `if`. expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(get_uval)).Fn.return_type.?).ErrorUnion.error_set!u32' diff --git a/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr.zig b/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr.zig deleted file mode 100644 index 18dd6382fc..0000000000 --- a/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr.zig +++ /dev/null @@ -1,18 +0,0 @@ -export fn entry() void { - var damn = Container{ - .not_optional = getOptional(), - }; - _ = damn; -} -pub fn getOptional() ?i32 { - return 0; -} -pub const Container = struct { - not_optional: i32, -}; - -// error -// backend=stage1 -// target=native -// -// tmp.zig:3:36: error: cannot convert optional to payload type. consider using `.?`, `orelse`, or `if`. expected type 'i32', found '?i32' diff --git a/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig b/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig deleted file mode 100644 index 75811c58e1..0000000000 --- a/test/cases/compile_errors/stage1/obj/result_location_incompatibility_mismatching_handle_is_ptr_generic_call.zig +++ /dev/null @@ -1,18 +0,0 @@ -export fn entry() void { - var damn = Container{ - .not_optional = getOptional(i32), - }; - _ = damn; -} -pub fn getOptional(comptime T: type) ?T { - return 0; -} -pub const Container = struct { - not_optional: i32, -}; - -// error -// backend=stage1 -// target=native -// -// tmp.zig:3:36: error: cannot convert optional to payload type. consider using `.?`, `orelse`, or `if`. expected type 'i32', found '?i32' -- cgit v1.2.3 From 20d4f7213dde1ffabe0880bbee46a1de44d586fc Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 11 Jul 2022 15:39:21 +0300 Subject: Sema: add notes about function return type --- src/Sema.zig | 87 +++++++++++++++++----- .../aarch64-macos/hello_world_with_updates.1.zig | 3 +- .../compile_errors/address_of_number_literal.zig | 1 + .../compile_errors/incompatible_sentinels.zig | 2 + .../cases/compile_errors/incorrect_return_type.zig | 1 + .../invalid_address_space_coercion.zig | 1 + ...ss_space_when_taking_address_of_dereference.zig | 1 + .../pointer_with_different_address_spaces.zig | 1 + .../pointers_with_different_address_spaces.zig | 1 + .../compile_errors/slice_sentinel_mismatch-2.zig | 1 + .../test/helpful_return_type_error_message.zig | 22 +++--- .../try_in_function_with_non_error_return_type.zig | 1 + .../compile_errors/unreachable_with_return.zig | 3 +- .../compile_errors/variable_has_wrong_type.zig | 1 + .../x86_64-linux/hello_world_with_updates.1.zig | 3 +- .../x86_64-macos/hello_world_with_updates.1.zig | 3 +- 16 files changed, 100 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/Sema.zig b/src/Sema.zig index a10695df1f..8356f06159 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4135,23 +4135,24 @@ fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!v const ptr = try sema.resolveInst(extra.lhs); const operand = try sema.resolveInst(extra.rhs); + const is_ret = if (Zir.refToIndex(extra.lhs)) |ptr_index| + zir_tags[ptr_index] == .ret_ptr + else + false; + // Check for the possibility of this pattern: // %a = ret_ptr // %b = store(%a, %c) // Where %c is an error union or error set. In such case we need to add // to the current function's inferred error set, if any. - if ((sema.typeOf(operand).zigTypeTag() == .ErrorUnion or + if (is_ret and (sema.typeOf(operand).zigTypeTag() == .ErrorUnion or sema.typeOf(operand).zigTypeTag() == .ErrorSet) and sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { - if (Zir.refToIndex(extra.lhs)) |ptr_index| { - if (zir_tags[ptr_index] == .ret_ptr) { - try sema.addToInferredErrorSet(operand); - } - } + try sema.addToInferredErrorSet(operand); } - return sema.storePtr(block, src, ptr, operand); + return sema.storePtr2(block, src, ptr, src, operand, src, if (is_ret) .ret_ptr else .store); } fn zirParamType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -5543,7 +5544,7 @@ fn analyzeCall( try sema.resolveBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst) else try sema.resolveInst(fn_info.ret_ty_ref); - const ret_ty_src = func_src; // TODO better source location + const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); // Create a fresh inferred error set type for inline/comptime calls. const fn_ret_ty = blk: { @@ -6885,7 +6886,7 @@ fn zirFunc( const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); const target = sema.mod.getTarget(); - const ret_ty_src = inst_data.src(); // TODO better source location + const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = inst_data.src_node }; var extra_index = extra.end; @@ -7467,13 +7468,20 @@ fn analyzeAs( zir_dest_type: Zir.Inst.Ref, zir_operand: Zir.Inst.Ref, ) CompileError!Air.Inst.Ref { + const is_ret = if (Zir.refToIndex(zir_dest_type)) |ptr_index| + sema.code.instructions.items(.tag)[ptr_index] == .ret_type + else + false; const dest_ty = try sema.resolveType(block, src, zir_dest_type); const operand = try sema.resolveInst(zir_operand); if (dest_ty.tag() == .var_args_param) return operand; if (dest_ty.zigTypeTag() == .NoReturn) { return sema.fail(block, src, "cannot cast to noreturn", .{}); } - return sema.coerce(block, dest_ty, operand, src); + return sema.coerceExtra(block, dest_ty, operand, src, true, is_ret) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; } fn zirPtrToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -13656,7 +13664,10 @@ fn analyzeRet( if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { try sema.addToInferredErrorSet(uncasted_operand); } - const operand = try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src); + const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, true, true) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; if (block.inlining) |inlining| { if (block.is_comptime) { @@ -19869,7 +19880,7 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true, false) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -19888,6 +19899,7 @@ fn coerceExtra( inst: Air.Inst.Ref, inst_src: LazySrcLoc, report_err: bool, + is_ret: bool, ) CoersionError!Air.Inst.Ref { switch (dest_ty_unresolved.tag()) { .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), @@ -19939,7 +19951,7 @@ fn coerceExtra( // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); - const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false) catch |err| switch (err) { + const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false, is_ret) catch |err| switch (err) { error.NotCoercible => { if (in_memory_result == .no_match) { // Try to give more useful notes @@ -20056,7 +20068,7 @@ fn coerceExtra( return sema.addConstant(dest_ty, Value.@"null"); }, .ComptimeInt => { - const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false) catch |err| switch (err) { + const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false, is_ret) catch |err| switch (err) { error.NotCoercible => break :pointer, else => |e| return e, }; @@ -20067,7 +20079,7 @@ fn coerceExtra( .signed => Type.isize, .unsigned => Type.usize, }; - const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false) catch |err| switch (err) { + const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false, is_ret) catch |err| switch (err) { error.NotCoercible => { // Try to give more useful notes in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src); @@ -20414,6 +20426,19 @@ fn coerceExtra( if (!report_err) return error.NotCoercible; + if (is_ret and dest_ty.zigTypeTag() == .NoReturn) { + const msg = msg: { + const msg = try sema.errMsg(block, inst_src, "function declared 'noreturn' returns", .{}); + errdefer msg.destroy(sema.gpa); + + const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; + const src_decl = sema.mod.declPtr(sema.func.?.owner_decl); + try sema.mod.errNoteNonLazy(ret_ty_src.toSrcLoc(src_decl), msg, "'noreturn' declared here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + const msg = msg: { const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); errdefer msg.destroy(sema.gpa); @@ -20436,6 +20461,20 @@ fn coerceExtra( } try in_memory_result.report(sema, block, inst_src, msg); + + // Add notes about function return type + if (is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { + const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; + const src_decl = sema.mod.declPtr(sema.func.?.owner_decl); + if (inst_ty.isError() and !dest_ty.isError()) { + try sema.mod.errNoteNonLazy(ret_ty_src.toSrcLoc(src_decl), msg, "function cannot return an error", .{}); + } else { + try sema.mod.errNoteNonLazy(ret_ty_src.toSrcLoc(src_decl), msg, "function return type declared here", .{}); + } + } + + // TODO maybe add "cannot store an error in type '{}'" note + break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); @@ -21372,6 +21411,8 @@ fn storePtr2( // TODO do the same thing for anon structs as for tuples above. // However, beware of the need to handle missing/extra fields. + const is_ret = air_tag == .ret_ptr; + // Detect if we are storing an array operand to a bitcasted vector pointer. // If so, we instead reach through the bitcasted pointer to the vector pointer, // bitcast the array operand to a vector, and then lower this as a store of @@ -21380,12 +21421,18 @@ fn storePtr2( // https://github.com/ziglang/zig/issues/11154 if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| { const vector_ty = sema.typeOf(vector_ptr).childType(); - const vector = try sema.coerce(block, vector_ty, uncasted_operand, operand_src); + const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; try sema.storePtr2(block, src, vector_ptr, ptr_src, vector, operand_src, .store); return; } - const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); + const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand); const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { @@ -21415,7 +21462,11 @@ fn storePtr2( try sema.requireRuntimeBlock(block, runtime_src); try sema.queueFullTypeResolution(elem_ty); - _ = try block.addBinOp(air_tag, ptr, operand); + if (is_ret) { + _ = try block.addBinOp(.store, ptr, operand); + } else { + _ = try block.addBinOp(air_tag, ptr, operand); + } } /// Traverse an arbitrary number of bitcasted pointers and return the underyling vector diff --git a/test/cases/aarch64-macos/hello_world_with_updates.1.zig b/test/cases/aarch64-macos/hello_world_with_updates.1.zig index 435747c2ee..e18a4c6a1e 100644 --- a/test/cases/aarch64-macos/hello_world_with_updates.1.zig +++ b/test/cases/aarch64-macos/hello_world_with_updates.1.zig @@ -2,4 +2,5 @@ pub export fn main() noreturn {} // error // -// :1:32: error: expected type 'noreturn', found 'void' +// :1:32: error: function declared 'noreturn' returns +// :1:22: note: 'noreturn' declared here diff --git a/test/cases/compile_errors/address_of_number_literal.zig b/test/cases/compile_errors/address_of_number_literal.zig index 2000561207..c6b41a1495 100644 --- a/test/cases/compile_errors/address_of_number_literal.zig +++ b/test/cases/compile_errors/address_of_number_literal.zig @@ -9,3 +9,4 @@ export fn entry() usize { return @sizeOf(@TypeOf(&foo)); } // // :3:30: error: expected type '*const i32', found '*const comptime_int' // :3:30: note: pointer type child 'comptime_int' cannot cast into pointer type child 'i32' +// :3:10: note: function return type declared here diff --git a/test/cases/compile_errors/incompatible_sentinels.zig b/test/cases/compile_errors/incompatible_sentinels.zig index a2ed320e18..821a0a8c69 100644 --- a/test/cases/compile_errors/incompatible_sentinels.zig +++ b/test/cases/compile_errors/incompatible_sentinels.zig @@ -21,8 +21,10 @@ export fn entry4() void { // // :4:12: error: expected type '[*:0]u8', found '[*:255]u8' // :4:12: note: pointer sentinel '255' cannot cast into pointer sentinel '0' +// :3:35: note: function return type declared here // :7:12: error: expected type '[*:0]u8', found '[*]u8' // :7:12: note: destination pointer requires '0' sentinel +// :6:31: note: function return type declared here // :10:35: error: expected type '[2:0]u8', found '[2:255]u8' // :10:35: note: array sentinel '255' cannot cast into array sentinel '0' // :14:31: error: expected type '[2:0]u8', found '[2]u8' diff --git a/test/cases/compile_errors/incorrect_return_type.zig b/test/cases/compile_errors/incorrect_return_type.zig index 7b678b68e3..57cf54a023 100644 --- a/test/cases/compile_errors/incorrect_return_type.zig +++ b/test/cases/compile_errors/incorrect_return_type.zig @@ -21,3 +21,4 @@ // :8:16: error: expected type 'tmp.A', found 'tmp.B' // :10:12: note: struct declared here // :4:12: note: struct declared here +// :7:11: note: function return type declared here diff --git a/test/cases/compile_errors/invalid_address_space_coercion.zig b/test/cases/compile_errors/invalid_address_space_coercion.zig index 1c42260a0e..4633b12e0f 100644 --- a/test/cases/compile_errors/invalid_address_space_coercion.zig +++ b/test/cases/compile_errors/invalid_address_space_coercion.zig @@ -12,3 +12,4 @@ pub fn main() void { // // :2:12: error: expected type '*i32', found '*addrspace(.gs) i32' // :2:12: note: address space 'gs' cannot cast into address space 'generic' +// :1:34: note: function return type declared here diff --git a/test/cases/compile_errors/invalid_pointer_keeps_address_space_when_taking_address_of_dereference.zig b/test/cases/compile_errors/invalid_pointer_keeps_address_space_when_taking_address_of_dereference.zig index fb8e0d5fd4..4d7b3c627b 100644 --- a/test/cases/compile_errors/invalid_pointer_keeps_address_space_when_taking_address_of_dereference.zig +++ b/test/cases/compile_errors/invalid_pointer_keeps_address_space_when_taking_address_of_dereference.zig @@ -12,3 +12,4 @@ pub fn main() void { // // :2:12: error: expected type '*i32', found '*addrspace(.gs) i32' // :2:12: note: address space 'gs' cannot cast into address space 'generic' +// :1:34: note: function return type declared here diff --git a/test/cases/compile_errors/pointer_with_different_address_spaces.zig b/test/cases/compile_errors/pointer_with_different_address_spaces.zig index 3f8c961174..2bbea3d3b6 100644 --- a/test/cases/compile_errors/pointer_with_different_address_spaces.zig +++ b/test/cases/compile_errors/pointer_with_different_address_spaces.zig @@ -12,3 +12,4 @@ export fn entry2() void { // // :2:12: error: expected type '*addrspace(.fs) i32', found '*addrspace(.gs) i32' // :2:12: note: address space 'gs' cannot cast into address space 'fs' +// :1:34: note: function return type declared here diff --git a/test/cases/compile_errors/pointers_with_different_address_spaces.zig b/test/cases/compile_errors/pointers_with_different_address_spaces.zig index 2a7698d0d9..e952da2af5 100644 --- a/test/cases/compile_errors/pointers_with_different_address_spaces.zig +++ b/test/cases/compile_errors/pointers_with_different_address_spaces.zig @@ -12,3 +12,4 @@ pub fn main() void { // // :2:13: error: expected type '*i32', found '*addrspace(.gs) i32' // :2:13: note: address space 'gs' cannot cast into address space 'generic' +// :1:35: note: function return type declared here diff --git a/test/cases/compile_errors/slice_sentinel_mismatch-2.zig b/test/cases/compile_errors/slice_sentinel_mismatch-2.zig index b1ff905e26..3cc5ac4c39 100644 --- a/test/cases/compile_errors/slice_sentinel_mismatch-2.zig +++ b/test/cases/compile_errors/slice_sentinel_mismatch-2.zig @@ -10,3 +10,4 @@ comptime { _ = foo; } // // :3:12: error: expected type '[:0]u8', found '[]u8' // :3:12: note: destination pointer requires '0' sentinel +// :1:10: note: function return type declared here diff --git a/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig b/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig index 0e97c09de2..b8e48036de 100644 --- a/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig +++ b/test/cases/compile_errors/stage1/test/helpful_return_type_error_message.zig @@ -16,15 +16,17 @@ export fn quux() u32 { } // error -// backend=stage1 +// backend=stage2 // target=native -// is_test=1 // -// tmp.zig:2:17: error: expected type 'u32', found 'error{Ohno}' -// tmp.zig:1:17: note: function cannot return an error -// tmp.zig:8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(bar)).Fn.return_type.?).ErrorUnion.error_set' -// tmp.zig:7:17: note: function cannot return an error -// tmp.zig:11:15: error: cannot convert error union to payload type. consider using `try`, `catch`, or `if`. expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// tmp.zig:10:17: note: function cannot return an error -// tmp.zig:15:14: error: cannot convert error union to payload type. consider using `try`, `catch`, or `if`. expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(bar)).Fn.return_type.?).ErrorUnion.error_set!u32' -// tmp.zig:14:5: note: cannot store an error in type 'u32' +// :2:18: error: expected type 'u32', found 'error{Ohno}' +// :1:17: note: function cannot return an error +// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set' +// :7:17: note: function cannot return an error +// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' +// :10:17: note: function cannot return an error +// :11:15: note: cannot convert error union to payload type +// :11:15: note: consider using `try`, `catch`, or `if` +// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32' +// :15:14: note: cannot convert error union to payload type +// :15:14: note: consider using `try`, `catch`, or `if` diff --git a/test/cases/compile_errors/try_in_function_with_non_error_return_type.zig b/test/cases/compile_errors/try_in_function_with_non_error_return_type.zig index 2107c2a397..44d8b9ee56 100644 --- a/test/cases/compile_errors/try_in_function_with_non_error_return_type.zig +++ b/test/cases/compile_errors/try_in_function_with_non_error_return_type.zig @@ -8,3 +8,4 @@ fn something() anyerror!void { } // target=native // // :2:5: error: expected type 'void', found 'anyerror' +// :1:15: note: function cannot return an error diff --git a/test/cases/compile_errors/unreachable_with_return.zig b/test/cases/compile_errors/unreachable_with_return.zig index cc6439ca0f..5293734c82 100644 --- a/test/cases/compile_errors/unreachable_with_return.zig +++ b/test/cases/compile_errors/unreachable_with_return.zig @@ -5,4 +5,5 @@ export fn entry() void { a(); } // backend=stage2 // target=native // -// :1:18: error: expected type 'noreturn', found 'void' +// :1:18: error: function declared 'noreturn' returns +// :1:8: note: 'noreturn' declared here diff --git a/test/cases/compile_errors/variable_has_wrong_type.zig b/test/cases/compile_errors/variable_has_wrong_type.zig index ec21c610e0..e99921db9f 100644 --- a/test/cases/compile_errors/variable_has_wrong_type.zig +++ b/test/cases/compile_errors/variable_has_wrong_type.zig @@ -8,3 +8,4 @@ export fn f() i32 { // target=native // // :3:12: error: expected type 'i32', found '*const [1:0]u8' +// :1:15: note: function return type declared here diff --git a/test/cases/x86_64-linux/hello_world_with_updates.1.zig b/test/cases/x86_64-linux/hello_world_with_updates.1.zig index 9be388ab63..1f1a6a9682 100644 --- a/test/cases/x86_64-linux/hello_world_with_updates.1.zig +++ b/test/cases/x86_64-linux/hello_world_with_updates.1.zig @@ -2,4 +2,5 @@ pub export fn _start() noreturn {} // error // -// :1:34: error: expected type 'noreturn', found 'void' +// :1:34: error: function declared 'noreturn' returns +// :1:24: note: 'noreturn' declared here diff --git a/test/cases/x86_64-macos/hello_world_with_updates.1.zig b/test/cases/x86_64-macos/hello_world_with_updates.1.zig index 435747c2ee..e18a4c6a1e 100644 --- a/test/cases/x86_64-macos/hello_world_with_updates.1.zig +++ b/test/cases/x86_64-macos/hello_world_with_updates.1.zig @@ -2,4 +2,5 @@ pub export fn main() noreturn {} // error // -// :1:32: error: expected type 'noreturn', found 'void' +// :1:32: error: function declared 'noreturn' returns +// :1:22: note: 'noreturn' declared here -- cgit v1.2.3