Skip to content

Commit eb50157

Browse files
authored
Fix spurious error when assigning to a field on an as cast in a try block (#5070)
The `as` operator desugars to a match with a consume of an internal hygienic variable. The refer pass's `ast_get_child` function searched the entire assignment LHS subtree — including the desugared match's scope — for consumed variable names, producing a false positive that triggered the verify pass error. Stopping the search at scope boundaries prevents descending into match-internal variables that aren't assignment targets. Closes #3604
1 parent 13ff553 commit eb50157

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## Fix spurious error when assigning to a field on an `as` cast in a try block
2+
3+
Assigning to a field on the result of an `as` expression inside a `try` block incorrectly produced an error about consumed identifiers:
4+
5+
```pony
6+
class Wumpus
7+
var hunger: USize = 0
8+
9+
actor Main
10+
new create(env: Env) =>
11+
let a: (Wumpus | None) = Wumpus
12+
try
13+
(a as Wumpus).hunger = 1
14+
end
15+
```
16+
17+
```
18+
can't reassign to a consumed identifier in a try expression if there is a
19+
partial call involved
20+
```
21+
22+
The workaround was to use a `match` expression instead. This has been fixed. The `as` form now compiles correctly, including when chaining method calls before the field assignment (e.g., `(a as Wumpus).some_method().hunger = 1`).

src/libponyc/pass/refer.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,10 @@ static bool ast_get_child(pass_opt_t *opt, ast_t* ast, const char* name)
10931093

10941094
while(child != NULL)
10951095
{
1096-
if(ast_get_child(opt, child, name))
1096+
// Don't recurse into children that introduce their own scope (e.g., a match
1097+
// from `as` desugaring). Variables defined in those scopes are not
1098+
// assignment targets of the outer expression.
1099+
if(!ast_has_scope(child) && ast_get_child(opt, child, name))
10971100
return true;
10981101

10991102
child = ast_sibling(child);

test/libponyc/verify.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,30 @@ TEST_F(VerifyTest, ConsumeVarReassignTrySameExpressionAs)
787787
" expression if there is a partial call involved");
788788
}
789789

790+
TEST_F(VerifyTest, AsFieldAssignInTry)
791+
{
792+
// Regression test for https://github.com/ponylang/ponyc/issues/3604
793+
// Assigning to a field on the result of `as` inside a try block should not
794+
// produce a spurious "can't reassign to a consumed identifier" error.
795+
const char* src =
796+
"class Wumpus\n"
797+
" var hunger: USize = 0\n"
798+
" fun ref self(): Wumpus => this\n"
799+
800+
"actor Main\n"
801+
"new create(env: Env) =>\n"
802+
"let a: (Wumpus | None) = Wumpus\n"
803+
"try\n"
804+
" (a as Wumpus).hunger = 1\n"
805+
"end\n"
806+
"let b: (Wumpus | None) = Wumpus\n"
807+
"try\n"
808+
" (b as Wumpus).self().hunger = 2\n"
809+
"end";
810+
811+
TEST_COMPILE(src);
812+
}
813+
790814
TEST_F(VerifyTest, ConsumeVarFieldReassignTrySameExpressionPartial)
791815
{
792816
const char* src =

0 commit comments

Comments
 (0)