Sema: improve error for mismatched type in implicit return

Closes #2653
This commit is contained in:
Veikka Tuominen 2022-12-02 20:56:40 +02:00
parent e2509ddbe6
commit f20e449fd6
11 changed files with 75 additions and 34 deletions

View File

@ -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(.{

View File

@ -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 {

View File

@ -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,

View File

@ -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),

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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