Skip to content

Commit 2ee58bc

Browse files
committed
Fix infinite loop with my sub closures inside subroutines
The issue was that 'my sub' assignments were being executed at compile-time (in a BEGIN block) instead of at runtime. This caused lexical subroutines to capture closure variables from the first call rather than fresh values on each invocation of the enclosing subroutine. The fix differentiates: - state sub: Execute once at compile time (BEGIN block) - unchanged - my sub: Execute at runtime, creating fresh closure each scope entry Additionally, this fix properly handles 'my sub' with signatures by not consuming (...) as a prototype when signatures feature is enabled, letting parseSubroutineDefinition handle it as a proper signature. Fixes the infinite loop where a 'my sub inner' inside 'sub process' would incorrectly retain the $callback from the first call even when process was called again with undef.
1 parent 44ef95c commit 2ee58bc

File tree

2 files changed

+25
-9
lines changed

2 files changed

+25
-9
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ logs/
6363
test_*.log
6464
test_*.json
6565

66+
t.pl
67+
test_yaml_0yml

src/main/java/org/perlonjava/parser/StatementResolver.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,19 @@ public static Node parseStatement(Parser parser, String label) {
276276
}
277277

278278
// Then check for prototype if not already set by attribute
279-
if (prototype == null && peek(parser).text.equals("(")) {
279+
// IMPORTANT: Only parse as prototype if signatures feature is NOT enabled
280+
// When signatures are enabled, let parseSubroutineDefinition handle (...) as a signature
281+
if (prototype == null && peek(parser).text.equals("(")
282+
&& !parser.ctx.symbolTable.isFeatureCategoryEnabled("signatures")) {
280283
// Parse the prototype
281284
prototype = ((StringNode) StringParser.parseRawString(parser, "q")).value;
282285
}
283286

284287
// Now check if there's a body
285-
hasBody = peek(parser).text.equals("{");
288+
// When signatures are enabled, (...) followed by { also indicates a body
289+
String peekText = peek(parser).text;
290+
hasBody = peekText.equals("{") ||
291+
(peekText.equals("(") && parser.ctx.symbolTable.isFeatureCategoryEnabled("signatures"));
286292

287293
if (hasBody) {
288294
// Full definition: my sub name {...} or my sub name (...) {...}
@@ -315,13 +321,21 @@ public static Node parseStatement(Parser parser, String label) {
315321
}
316322
BinaryOperatorNode assignment = new BinaryOperatorNode("=", varRef, anonSub, parser.tokenIndex);
317323

318-
// Execute assignment immediately during parsing (like a BEGIN block)
319-
// This is crucial for cases like: state sub foo{...}; use overload => \&foo;
320-
BlockNode beginBlock = new BlockNode(new ArrayList<>(List.of(assignment)), parser.tokenIndex);
321-
SpecialBlockParser.runSpecialBlock(parser, "BEGIN", beginBlock);
322-
323-
// Return empty list since the assignment already executed
324-
yield new ListNode(parser.tokenIndex);
324+
if (declaration.equals("state")) {
325+
// For state sub: Execute assignment immediately during parsing (like a BEGIN block)
326+
// This is crucial for cases like: state sub foo{...}; use overload => \&foo;
327+
// The closure is created once and reused across all calls
328+
BlockNode beginBlock = new BlockNode(new ArrayList<>(List.of(assignment)), parser.tokenIndex);
329+
SpecialBlockParser.runSpecialBlock(parser, "BEGIN", beginBlock);
330+
331+
// Return empty list since the assignment already executed
332+
yield new ListNode(parser.tokenIndex);
333+
} else {
334+
// For my sub: Return the assignment to be executed at runtime
335+
// This creates a fresh closure each time the enclosing scope is entered,
336+
// correctly capturing the current values of closure variables
337+
yield assignment;
338+
}
325339
} else {
326340
// Forward declaration: my sub name; or my sub name ($);
327341
// For forward declarations, add &subName immediately since there's no body to be invisible in

0 commit comments

Comments
 (0)