diff --git a/.release-notes/5077.md b/.release-notes/5077.md new file mode 100644 index 0000000000..f5cb6612fe --- /dev/null +++ b/.release-notes/5077.md @@ -0,0 +1,5 @@ +## Fix `with` tuple: process every binding in dispose generation + +The compiler walked only the first element of a tuple pattern in `with` when building dispose calls and checking for invalid `_` bindings. Later elements were skipped, so some programs compiled incorrectly (missing dispose for later bindings, or no error for `_` in a later position). + +Tuple patterns now walk every element. Programs using multi-binding `with` get correct cleanup and diagnostics. diff --git a/src/libponyc/pass/sugar.c b/src/libponyc/pass/sugar.c index 799c40bfaf..c26b5272de 100644 --- a/src/libponyc/pass/sugar.c +++ b/src/libponyc/pass/sugar.c @@ -592,7 +592,8 @@ static bool build_with_dispose(pass_opt_t* opt, ast_t* dispose_clause, ast_t* id { pony_assert(ast_id(p) == TK_SEQ); ast_t* let = ast_child(p); - return build_with_dispose(opt, dispose_clause, let); + if(!build_with_dispose(opt, dispose_clause, let)) + return false; } return true; diff --git a/test/libponyc/with.cc b/test/libponyc/with.cc index a7980db89c..dea460fba6 100644 --- a/test/libponyc/with.cc +++ b/test/libponyc/with.cc @@ -31,3 +31,40 @@ TEST_F(WithTest, NoEarlyReturnFromWith) TEST_ERRORS_1(src, "use return only to exit early from a with block, not at the end"); } + +TEST_F(WithTest, DontcareInTupleSecondBindingIsRejected) +{ + // Regression: without the tuple-loop fix in build_with_dispose (sugar.c), + // this compiles with 0 errors (wrong). With the fix, exactly one error. + // build_with_dispose must recurse through every tuple element; a prior bug + // returned after the first binding only, so `_` in a later position was not + // diagnosed. + const char* src = + "class D\n" + " new create() => None\n" + " fun dispose() => None\n" + "actor Main\n" + " new create(env: Env) =>\n" + " with (a, _) = (D.create(), D.create()) do\n" + " None\n" + " end"; + + TEST_ERRORS_1(src, "_ isn't allowed for a variable in a with block"); +} + +TEST_F(WithTest, TwoElementTupleWithCompiles) +{ + // Happy path: both bindings get dispose calls; tuple loop must process every + // element (regression for early-return bug that only handled the first). + const char* src = + "class D\n" + " new create() => None\n" + " fun dispose() => None\n" + "actor Main\n" + " new create(env: Env) =>\n" + " with (a, b) = (D.create(), D.create()) do\n" + " None\n" + " end"; + + TEST_COMPILE(src); +}