diff --git a/.release-notes/4464.md b/.release-notes/4464.md new file mode 100644 index 0000000000..84168da527 --- /dev/null +++ b/.release-notes/4464.md @@ -0,0 +1,40 @@ +## Disallow `return` at the end of a `with` block + +When we reimplemented `with` blocks in 2022, we allowed the usage of `return` at the end of the block. Recent analysis of the generated LLVM flagged incorrect LLVM IR that was generated from such code. + +Upon review, we realized that `return` at the end of a `with` block should be disallowed in the same way that `return` at the end of a method is disallowed. + +This a breaking change. If you had code such as: + +```pony +actor Main + new create(env: Env) => + with d = Disposble do + d.set_exit(10) + return + end +``` + +You'll need to update it to: + +```pony +actor Main + new create(env: Env) => + with d = Disposble do + d.set_exit(10) + end +``` + +The above examples are semantically the same. This also fixes a compiler crash if you had something along the lines of: + +```pony +actor Main + new create(env: Env) => + with d = Disposble do + d.set_exit(10) + return + end + let x = "foo" +``` + +Where you had code "after the return" which would be unexpected by the compiler. diff --git a/src/libponyc/expr/control.c b/src/libponyc/expr/control.c index 72f3f255b0..40dd2f442a 100644 --- a/src/libponyc/expr/control.c +++ b/src/libponyc/expr/control.c @@ -444,6 +444,13 @@ bool expr_return(pass_opt_t* opt, ast_t* ast) return false; } + if(ast_id(parent) == TK_DISPOSING_BLOCK) + { + ast_error(opt->check.errors, ast, + "use return only to exit early from a with block, not at the end"); + return false; + } + ast_t* type = ast_childidx(opt->check.frame->method, 4); ast_t* body = ast_child(ast); diff --git a/test/full-program-tests/with-return/expected-exit-code.txt b/test/full-program-tests/with-return/expected-exit-code.txt deleted file mode 100644 index f599e28b8a..0000000000 --- a/test/full-program-tests/with-return/expected-exit-code.txt +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/test/full-program-tests/with-return/main.pony b/test/full-program-tests/with-return/main.pony deleted file mode 100644 index d234b723ed..0000000000 --- a/test/full-program-tests/with-return/main.pony +++ /dev/null @@ -1,20 +0,0 @@ -use @pony_exitcode[None](code: I32) - -class Disposble - var _exit_code: I32 - - new create() => - _exit_code = 0 - - fun ref set_exit(code: I32) => - _exit_code = code - - fun dispose() => - @pony_exitcode(_exit_code) - -actor Main - new create(env: Env) => - with d = Disposble do - d.set_exit(10) - return - end diff --git a/test/libponyc/CMakeLists.txt b/test/libponyc/CMakeLists.txt index c3590aa2e7..2ccaa7ad90 100644 --- a/test/libponyc/CMakeLists.txt +++ b/test/libponyc/CMakeLists.txt @@ -52,6 +52,7 @@ add_executable(libponyc.tests use.cc util.cc verify.cc + with.cc ) target_include_directories(libponyc.tests diff --git a/test/libponyc/with.cc b/test/libponyc/with.cc new file mode 100644 index 0000000000..a7980db89c --- /dev/null +++ b/test/libponyc/with.cc @@ -0,0 +1,33 @@ +#include +#include + +#include "util.h" + + +#define TEST_COMPILE(src) DO(test_compile(src, "expr")) + +#define TEST_ERRORS_1(src, err1) \ + { const char* errs[] = {err1, NULL}; \ + DO(test_expected_errors(src, "expr", errs)); } + +class WithTest : public PassTest +{}; + +TEST_F(WithTest, NoEarlyReturnFromWith) +{ + // From issue #4464 + const char* src = + "class Disposable\n" + " new create() => None\n" + " fun ref set_exit(x: U32) => None\n" + " fun dispose() => None\n" + "actor Main\n" + " new create(env: Env) =>\n" + " with d = Disposable do\n" + " d.set_exit(10)\n" + " return\n" + " end"; + + TEST_ERRORS_1(src, + "use return only to exit early from a with block, not at the end"); +}