From e272ab1e96d52ddce3ecda6815fe800c4292f20e Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 12 Aug 2026 22:08:06 +0200 Subject: [PATCH] fix(parser): complete core threads syntax compatibility Keep unknown print-filehandle candidates in expression context when followed by a known indirect-object package. This lets postfix calls bind to completed indirect method expressions without a threads-specific parser exception. Add a system-Perl-validated generic regression test and record Phase 24 as complete after op/threads.t reaches 30/30 on both execution backends. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex <223018245+openai-codex[bot]@users.noreply.github.com> --- dev/design/concurrency.md | 20 +++++++----- .../frontend/parser/FileHandle.java | 31 +++++++++++++++++++ .../unit/print_indirect_method_postfix.t | 18 +++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/unit/print_indirect_method_postfix.t diff --git a/dev/design/concurrency.md b/dev/design/concurrency.md index 7a223e09d..fad2eb755 100644 --- a/dev/design/concurrency.md +++ b/dev/design/concurrency.md @@ -467,11 +467,17 @@ Acceptance: no semantic change or native/FFM surprise in supported workloads. Promotion beyond experimental additionally requires a measured benefit over platform threads; runtime-clone cost must not be mistaken for scheduler cost. -### Phase 24 — Final core syntax compatibility +### Phase 24 — Final core syntax compatibility (completed 2026-08-12) Finish the remaining `op/threads.t` postfix create/join expression without a thread-specific parser shortcut that changes ordinary anonymous-sub precedence. +Implemented by keeping an unknown print-filehandle candidate as an expression +when it is followed by a known indirect-object package. This fixes the general +`print method Package LIST` ambiguity rather than recognizing `threads` or the +specific postfix chain. A system-Perl-validated regression test covers the +generic form. + Acceptance: `op/threads.t` reaches 30/30 on JVM and interpreter backends while `class/threads.t` remains 4/4 and `threads-dirh.t` continues to exit cleanly. @@ -590,7 +596,7 @@ has a measured benefit over a fresh snapshot. ## 7. Progress Tracking -### Current Status: Phase 23 implemented; compatibility hardening begins next +### Current Status: Phase 24 complete; Phase 25 is next Hints, warnings, filters, and source maps are runtime-owned while compiler-only scratch remains protected by the global compile lock. The Phase 11 inventory is @@ -735,11 +741,11 @@ request history. ### Next Steps -1. After PR 939 merges, implement Phases 24–28 as independent, always-green PRs: - final core syntax, snapshot graph integrity, scalar operator parity, regex - concurrency/debug state, and then general regex parity. For every `_thr.t` - result, record the direct companion suite in the same run and require zero - thread-induced delta rather than comparing aggregate TAP counts alone. +1. Implement Phases 25–28 as independent, always-green PRs: snapshot graph + integrity, scalar operator parity, regex concurrency/debug state, and then + general regex parity. For every `_thr.t` result, record the direct companion + suite in the same run and require zero thread-induced delta rather than + comparing aggregate TAP counts alone. 2. Implement Phases 29–32 independently: truthful API behavior, resource inheritance and Test2 stress, native callback/handle ownership, and only then additional shared value categories. Preserve the green anchors after every diff --git a/src/main/java/org/perlonjava/frontend/parser/FileHandle.java b/src/main/java/org/perlonjava/frontend/parser/FileHandle.java index 4437f6941..81f7557cf 100644 --- a/src/main/java/org/perlonjava/frontend/parser/FileHandle.java +++ b/src/main/java/org/perlonjava/frontend/parser/FileHandle.java @@ -363,6 +363,33 @@ private static boolean isFollowedByMethodDereference(Parser parser) { return idx < parser.tokens.size() && "->".equals(parser.tokens.get(idx).text); } + /** + * An unknown bareword followed by a known package can be Perl's indirect + * object syntax ({@code method Package LIST}). In print's ambiguous + * filehandle position it must remain an expression; autovivifying the + * method name as an IO glob makes the package and arguments look like a + * malformed print list. + */ + private static boolean isFollowedByIndirectObjectPackage(Parser parser) { + int idx = parser.tokenIndex; + while (idx < parser.tokens.size() + && parser.tokens.get(idx).type == LexerTokenType.WHITESPACE) { + idx++; + } + if (idx >= parser.tokens.size() + || parser.tokens.get(idx).type != LexerTokenType.IDENTIFIER) { + return false; + } + + String packageName = parser.tokens.get(idx).text; + Boolean packageExists = GlobalVariable.packageExistsCache.get(packageName); + if (packageExists == null && !packageName.contains("::")) { + packageExists = GlobalVariable.packageExistsCache.get( + parser.ctx.symbolTable.getCurrentPackage() + "::" + packageName); + } + return Boolean.TRUE.equals(packageExists); + } + private static boolean shouldAutovivifyBarewordHandle(Parser parser, String name, boolean autovivifyUnknownBareword) { // Do not treat compile-time magic like __PACKAGE__ as print filehandles: // they match ^[A-Z_][A-Z0-9_]*$ but must fall through to the expression list @@ -385,6 +412,10 @@ private static boolean shouldAutovivifyBarewordHandle(Parser parser, String name return false; } + if (isFollowedByIndirectObjectPackage(parser)) { + return false; + } + if (ParserTables.CORE_PROTOTYPES.containsKey(name)) { return false; } diff --git a/src/test/resources/unit/print_indirect_method_postfix.t b/src/test/resources/unit/print_indirect_method_postfix.t new file mode 100644 index 000000000..d6626ee2f --- /dev/null +++ b/src/test/resources/unit/print_indirect_method_postfix.t @@ -0,0 +1,18 @@ +use strict; +use warnings; + +{ + package PrintIndirectFactory; + + sub create { + my ($class, $code) = @_; + return bless { code => $code }, $class; + } + + sub join { return $_[0]->{code}->() } +} + +print "1..1\n"; +print create PrintIndirectFactory sub { + return sub { return sub { return "ok 1 - print preserves an indirect postfix call chain\n" } }; +}=>->join->()();