Functions cannot "fall off the end" and start executing code outside of their scope, regardless of whether they return a value or not. It's common to allow the final return
statement to be omitted for functions which do not return a result (or in some undisciplined languages even for functions which do return a result), but the function needs to return in all cases.
Given that, the simplest way to produce error messages for non-void functions which fall of the end is:
The parser inserts a return
statement at the end of the function body.
Dead code elimination is used to remove the inserted return
statement in case it is unnecessary.
Typechecking is used to verify that all return
statements, included the inserted one if it hasn't been deleted, are consistent with the return type of the function. (That is, since the generated return
statement has no value, it can only be consistent with a void
function.)
In this scenario, you need to delete unnecessary return
statements before typechecking, or you'll end up with a lot of incorrect error messages. That requires some control flow analysis.
If that's too much work, you can issue the error message at run-time by compiling the no-value return
statement as an error operation in the case that the function returns a value.