Commit 34fe2b4f4b

Veikka Tuominen <git@vexu.eu>
2022-07-10 15:51:10
Sema: prefer original error message in `coerce`
1 parent b9f01bc
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;
             },
test/cases/compile_errors/stage1/test/cast_between_optional_T_where_T_is_not_a_pointer.zig
@@ -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'
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'
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
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'
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'