Commit f20e449fd6

Veikka Tuominen <git@vexu.eu>
2022-12-02 19:56:40
Sema: improve error for mismatched type in implicit return
Closes #2653
1 parent e2509dd
src/AstGen.zig
@@ -2632,7 +2632,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
             .compile_error,
             .ret_node,
             .ret_load,
-            .ret_tok,
+            .ret_implicit,
             .ret_err_value,
             .@"unreachable",
             .repeat,
@@ -3914,9 +3914,8 @@ fn fnDecl(
             // As our last action before the return, "pop" the error trace if needed
             _ = try gz.addRestoreErrRetIndex(.ret, .always);
 
-            // Since we are adding the return instruction here, we must handle the coercion.
-            // We do this by using the `ret_tok` instruction.
-            _ = try fn_gz.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
+            // Add implicit return at end of function.
+            _ = try fn_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node));
         }
 
         break :func try decl_gz.addFunc(.{
@@ -4334,9 +4333,8 @@ fn testDecl(
         // As our last action before the return, "pop" the error trace if needed
         _ = try gz.addRestoreErrRetIndex(.ret, .always);
 
-        // Since we are adding the return instruction here, we must handle the coercion.
-        // We do this by using the `ret_tok` instruction.
-        _ = try fn_block.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
+        // Add implicit return at end of function.
+        _ = try fn_block.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node));
     }
 
     const func_inst = try decl_block.addFunc(.{
src/print_zir.zig
@@ -235,7 +235,7 @@ const Writer = struct {
             => try self.writeUnNode(stream, inst),
 
             .ref,
-            .ret_tok,
+            .ret_implicit,
             .closure_capture,
             .switch_capture_tag,
             => try self.writeUnTok(stream, inst),
src/Sema.zig
@@ -1098,7 +1098,7 @@ fn analyzeBodyInner(
             // These functions match the return type of analyzeBody so that we can
             // tail call them here.
             .compile_error  => break sema.zirCompileError(block, inst),
-            .ret_tok        => break sema.zirRetTok(block, inst),
+            .ret_implicit   => break sema.zirRetImplicit(block, inst),
             .ret_node       => break sema.zirRetNode(block, inst),
             .ret_load       => break sema.zirRetLoad(block, inst),
             .ret_err_value  => break sema.zirRetErrValue(block, inst),
@@ -16546,7 +16546,7 @@ fn zirRetErrValue(
     return sema.analyzeRet(block, result_inst, src);
 }
 
-fn zirRetTok(
+fn zirRetImplicit(
     sema: *Sema,
     block: *Block,
     inst: Zir.Inst.Index,
@@ -16556,9 +16556,33 @@ fn zirRetTok(
 
     const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
     const operand = try sema.resolveInst(inst_data.operand);
-    const src = inst_data.src();
 
-    return sema.analyzeRet(block, operand, src);
+    const r_brace_src = inst_data.src();
+    const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 };
+    const base_tag = sema.fn_ret_ty.baseZigTypeTag();
+    if (base_tag == .NoReturn) {
+        const msg = msg: {
+            const msg = try sema.errMsg(block, ret_ty_src, "function declared '{}' implicitly returns", .{
+                sema.fn_ret_ty.fmt(sema.mod),
+            });
+            errdefer msg.destroy(sema.gpa);
+            try sema.errNote(block, r_brace_src, msg, "control flow reaches end of body here", .{});
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(msg);
+    } else if (base_tag != .Void) {
+        const msg = msg: {
+            const msg = try sema.errMsg(block, ret_ty_src, "function with non-void return type '{}' implicitly returns", .{
+                sema.fn_ret_ty.fmt(sema.mod),
+            });
+            errdefer msg.destroy(sema.gpa);
+            try sema.errNote(block, r_brace_src, msg, "control flow reaches end of body here", .{});
+            break :msg msg;
+        };
+        return sema.failWithOwnedErrorMsg(msg);
+    }
+
+    return sema.analyzeRet(block, operand, .unneeded);
 }
 
 fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
src/type.zig
@@ -160,6 +160,17 @@ pub const Type = extern union {
         }
     }
 
+    pub fn baseZigTypeTag(self: Type) std.builtin.TypeId {
+        return switch (self.zigTypeTag()) {
+            .ErrorUnion => self.errorUnionPayload().baseZigTypeTag(),
+            .Optional => {
+                var buf: Payload.ElemType = undefined;
+                return self.optionalChild(&buf).baseZigTypeTag();
+            },
+            else => |t| t,
+        };
+    }
+
     pub fn isSelfComparable(ty: Type, is_equality_cmp: bool) bool {
         return switch (ty.zigTypeTag()) {
             .Int,
src/Zir.zig
@@ -519,7 +519,7 @@ pub const Inst = struct {
         /// Includes an operand as the return value.
         /// Includes a token source location.
         /// Uses the `un_tok` union field.
-        ret_tok,
+        ret_implicit,
         /// Sends control flow back to the function's callee.
         /// The return operand is `error.foo` where `foo` is given by the string.
         /// If the current function has an inferred error set, the error given by the
@@ -1256,7 +1256,7 @@ pub const Inst = struct {
                 .compile_error,
                 .ret_node,
                 .ret_load,
-                .ret_tok,
+                .ret_implicit,
                 .ret_err_value,
                 .@"unreachable",
                 .repeat,
@@ -1530,7 +1530,7 @@ pub const Inst = struct {
                 .compile_error,
                 .ret_node,
                 .ret_load,
-                .ret_tok,
+                .ret_implicit,
                 .ret_err_value,
                 .ret_ptr,
                 .ret_type,
@@ -1659,7 +1659,7 @@ pub const Inst = struct {
                 .ref = .un_tok,
                 .ret_node = .un_node,
                 .ret_load = .un_node,
-                .ret_tok = .un_tok,
+                .ret_implicit = .un_tok,
                 .ret_err_value = .str_tok,
                 .ret_err_value_code = .str_tok,
                 .ret_ptr = .node,
test/cases/aarch64-macos/hello_world_with_updates.1.zig
@@ -2,5 +2,5 @@ pub export fn main() noreturn {}
 
 // error
 //
-// :1:32: error: function declared 'noreturn' returns
-// :1:22: note: 'noreturn' declared here
+// :1:22: error: function declared 'noreturn' implicitly returns
+// :1:32: note: control flow reaches end of body here
test/cases/compile_errors/control_reaches_end_of_non-void_function.zig
@@ -1,9 +0,0 @@
-fn a() i32 {}
-export fn entry() void { _ = a(); }
-
-// error
-// backend=stage2
-// target=native
-//
-// :1:13: error: expected type 'i32', found 'void'
-// :1:8: note: function return type declared here
test/cases/compile_errors/type_error_in_implicit_return.zig
@@ -0,0 +1,17 @@
+fn f1(x: bool) u32 {
+    if (x) return 1;
+}
+fn f2() noreturn {}
+pub export fn entry() void {
+    _ = f1(true);
+    _ = f2();
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :1:16: error: function with non-void return type 'u32' implicitly returns
+// :3:1: note: control flow reaches end of body here
+// :4:9: error: function declared 'noreturn' implicitly returns
+// :4:19: note: control flow reaches end of body here
test/cases/x86_64-linux/hello_world_with_updates.1.zig
@@ -1,6 +1,6 @@
-pub export fn _start() noreturn {}
+pub export fn main() noreturn {}
 
 // error
 //
-// :1:34: error: function declared 'noreturn' returns
-// :1:24: note: 'noreturn' declared here
+// :1:22: error: function declared 'noreturn' implicitly returns
+// :1:32: note: control flow reaches end of body here
test/cases/x86_64-macos/hello_world_with_updates.1.zig
@@ -2,5 +2,5 @@ pub export fn main() noreturn {}
 
 // error
 //
-// :1:32: error: function declared 'noreturn' returns
-// :1:22: note: 'noreturn' declared here
+// :1:22: error: function declared 'noreturn' implicitly returns
+// :1:32: note: control flow reaches end of body here
test/cases/x86_64-windows/hello_world_with_updates.1.zig
@@ -2,5 +2,5 @@ pub export fn main() noreturn {}
 
 // error
 //
-// :1:32: error: function declared 'noreturn' returns
-// :1:22: note: 'noreturn' declared here
+// :1:22: error: function declared 'noreturn' implicitly returns
+// :1:32: note: control flow reaches end of body here