From db434f8d3ceb798da497f25d130bf0b0914bf906 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 16 Mar 2026 10:29:22 +0100 Subject: [PATCH 1/5] Fix ::identifier bareword parsing and add cpan to sync config Two fixes: 1. Parser fix for ::identifier barewords (ParsePrimary.java): - In Perl, `::foo` without parens is a bareword string '::foo' - Only `::foo()` with parens is a function call to main::foo - PerlOnJava was incorrectly treating all `::identifier` as main::identifier - This broke Mo module which uses patterns like `$M.$_.::e` - Now: `::foo()` -> main::foo(), `::foo` -> '::foo' string 2. Added cpan script to sync config (config.yaml): - The jcpan wrapper requires src/main/perl/bin/cpan - Added import from perl5/cpan/CPAN/scripts/cpan - Fixes 'Unable to read file' error on fresh checkouts Mo test improvement: 14/28 failing -> 6/28 failing (133/144 subtests pass) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- dev/import-perl5/config.yaml | 4 ++ .../frontend/parser/ParsePrimary.java | 39 +++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/dev/import-perl5/config.yaml b/dev/import-perl5/config.yaml index 8c367e7a2..35c948465 100644 --- a/dev/import-perl5/config.yaml +++ b/dev/import-perl5/config.yaml @@ -27,6 +27,10 @@ imports: - source: perl5/dist/Carp/lib/Carp/Heavy.pm target: src/main/perl/lib/Carp/Heavy.pm + # CPAN client script - required by jcpan wrapper + - source: perl5/cpan/CPAN/scripts/cpan + target: src/main/perl/bin/cpan + - source: perl5/lib/Benchmark.pm target: src/main/perl/lib/Benchmark.pm diff --git a/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java b/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java index fee11a746..b3f9f4e47 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java @@ -269,15 +269,40 @@ static Node parseOperator(Parser parser, LexerToken token, String operator) { return StringParser.parseRawString(parser, token.text); case "::": - // Leading :: means main:: (e.g., ::foo is main::foo) - // This allows accessing global variables even when a lexical exists + // Leading :: can mean either: + // 1. Function call: ::foo() -> main::foo() + // 2. Bareword string: $x . ::foo -> $x . '::foo' + // + // In Perl, ::foo without parens is a bareword that gets stringified to '::foo' + // unless a main::foo subroutine exists at compile time (which would be called). + // Since PerlOnJava doesn't have compile-time sub resolution, we use a heuristic: + // - If followed by ( -> function call (main::identifier) + // - Otherwise -> bareword string ('::identifier') LexerToken nextToken2 = peek(parser); if (nextToken2.type == LexerTokenType.IDENTIFIER) { - // Insert "main" before the :: to create main::identifier - parser.tokens.add(parser.tokenIndex - 1, new LexerToken(LexerTokenType.IDENTIFIER, "main")); - parser.tokenIndex--; // Go back to process "main" - return parseIdentifier(parser, parser.tokenIndex, - new LexerToken(LexerTokenType.IDENTIFIER, "main"), "main"); + String identifierName = nextToken2.text; + // Look ahead to see if this is a function call + int lookAhead = parser.tokenIndex + 1; + // Skip whitespace + while (lookAhead < parser.tokens.size() && + parser.tokens.get(lookAhead).type == LexerTokenType.WHITESPACE) { + lookAhead++; + } + String afterIdentifier = lookAhead < parser.tokens.size() ? + parser.tokens.get(lookAhead).text : ""; + + if (afterIdentifier.equals("(")) { + // Function call: ::foo() -> main::foo() + // Insert "main" before the :: to create main::identifier + parser.tokens.add(parser.tokenIndex - 1, new LexerToken(LexerTokenType.IDENTIFIER, "main")); + parser.tokenIndex--; // Go back to process "main" + return parseIdentifier(parser, parser.tokenIndex, + new LexerToken(LexerTokenType.IDENTIFIER, "main"), "main"); + } else { + // Bareword: ::foo -> '::foo' + parser.tokenIndex++; // Consume the identifier + return new StringNode("::" + identifierName, parser.tokenIndex); + } } throw new PerlCompilerException(parser.tokenIndex, "syntax error", parser.ctx.errorUtil); From bd0b75fc31df2bc04f5d195ce6d8b9b526645ee6 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 16 Mar 2026 10:30:53 +0100 Subject: [PATCH 2/5] Add cpan script to git for jcpan wrapper - Added src/main/perl/bin/cpan to git (required by jcpan) - Updated .gitignore to allow src/main/perl/bin/ directory Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .gitignore | 2 + src/main/perl/bin/cpan | 349 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 src/main/perl/bin/cpan diff --git a/.gitignore b/.gitignore index 4694e2a1c..5b9225f4a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ target/ .java-version build/ bin/ +# But allow Perl bin scripts +!src/main/perl/bin/ gradle/* !gradle/libs.versions.toml gradlew diff --git a/src/main/perl/bin/cpan b/src/main/perl/bin/cpan new file mode 100644 index 000000000..d4f742e28 --- /dev/null +++ b/src/main/perl/bin/cpan @@ -0,0 +1,349 @@ +#!/usr/local/bin/perl + +BEGIN { pop @INC if $INC[-1] eq '.' } +use strict; +use vars qw($VERSION); + +use App::Cpan; +use CPAN::Version; +my $minver = '1.64'; +if ( CPAN::Version->vlt($App::Cpan::VERSION, $minver) ) { + warn "WARNING: your version of App::Cpan is $App::Cpan::VERSION while we would expect at least $minver"; +} +$VERSION = '1.64'; + +my $rc = App::Cpan->run( @ARGV ); + +# will this work under Strawberry Perl? +exit( $rc || 0 ); + +=head1 NAME + +cpan - easily interact with CPAN from the command line + +=head1 SYNOPSIS + + # with arguments and no switches, installs specified modules + cpan module_name [ module_name ... ] + + # with switches, installs modules with extra behavior + cpan [-cfFimtTw] module_name [ module_name ... ] + + # use local::lib + cpan -I module_name [ module_name ... ] + + # one time mirror override for faster mirrors + cpan -p ... + + # with just the dot, install from the distribution in the + # current directory + cpan . + + # without arguments, starts CPAN.pm shell + cpan + + # without arguments, but some switches + cpan [-ahpruvACDLOPX] + +=head1 DESCRIPTION + +This script provides a command interface (not a shell) to CPAN. At the +moment it uses CPAN.pm to do the work, but it is not a one-shot command +runner for CPAN.pm. + +=head2 Options + +=over 4 + +=item -a + +Creates a CPAN.pm autobundle with CPAN::Shell->autobundle. + +=item -A module [ module ... ] + +Shows the primary maintainers for the specified modules. + +=item -c module + +Runs a `make clean` in the specified module's directories. + +=item -C module [ module ... ] + +Show the F files for the specified modules + +=item -D module [ module ... ] + +Show the module details. This prints one line for each out-of-date module +(meaning, modules locally installed but have newer versions on CPAN). +Each line has three columns: module name, local version, and CPAN +version. + +=item -f + +Force the specified action, when it normally would have failed. Use this +to install a module even if its tests fail. When you use this option, +-i is not optional for installing a module when you need to force it: + + % cpan -f -i Module::Foo + +=item -F + +Turn off CPAN.pm's attempts to lock anything. You should be careful with +this since you might end up with multiple scripts trying to muck in the +same directory. This isn't so much of a concern if you're loading a special +config with C<-j>, and that config sets up its own work directories. + +=item -g module [ module ... ] + +Downloads to the current directory the latest distribution of the module. + +=item -G module [ module ... ] + +UNIMPLEMENTED + +Download to the current directory the latest distribution of the +modules, unpack each distribution, and create a git repository for each +distribution. + +If you want this feature, check out Yanick Champoux's C +distribution. + +=item -h + +Print a help message and exit. When you specify C<-h>, it ignores all +of the other options and arguments. + +=item -i module [ module ... ] + +Install the specified modules. With no other switches, this switch +is implied. + +=item -I + +Load C (think like C<-I> for loading lib paths). Too bad +C<-l> was already taken. + +=item -j Config.pm + +Load the file that has the CPAN configuration data. This should have the +same format as the standard F file, which defines +C<$CPAN::Config> as an anonymous hash. + +=item -J + +Dump the configuration in the same format that CPAN.pm uses. This is useful +for checking the configuration as well as using the dump as a starting point +for a new, custom configuration. + +=item -l + +List all installed modules with their versions + +=item -L author [ author ... ] + +List the modules by the specified authors. + +=item -m + +Make the specified modules. + +=item -M mirror1,mirror2,... + +A comma-separated list of mirrors to use for just this run. The C<-P> +option can find them for you automatically. + +=item -n + +Do a dry run, but don't actually install anything. (unimplemented) + +=item -O + +Show the out-of-date modules. + +=item -p + +Ping the configured mirrors and print a report + +=item -P + +Find the best mirrors you could be using and use them for the current +session. + +=item -r + +Recompiles dynamically loaded modules with CPAN::Shell->recompile. + +=item -s + +Drop in the CPAN.pm shell. This command does this automatically if you don't +specify any arguments. + +=item -t module [ module ... ] + +Run a `make test` on the specified modules. + +=item -T + +Do not test modules. Simply install them. + +=item -u + +Upgrade all installed modules. Blindly doing this can really break things, +so keep a backup. + +=item -v + +Print the script version and CPAN.pm version then exit. + +=item -V + +Print detailed information about the cpan client. + +=item -w + +UNIMPLEMENTED + +Turn on cpan warnings. This checks various things, like directory permissions, +and tells you about problems you might have. + +=item -x module [ module ... ] + +Find close matches to the named modules that you think you might have +mistyped. This requires the optional installation of Text::Levenshtein or +Text::Levenshtein::Damerau. + +=item -X + +Dump all the namespaces to standard output. + +=back + +=head2 Examples + + # print a help message + cpan -h + + # print the version numbers + cpan -v + + # create an autobundle + cpan -a + + # recompile modules + cpan -r + + # upgrade all installed modules + cpan -u + + # install modules ( sole -i is optional ) + cpan -i Netscape::Booksmarks Business::ISBN + + # force install modules ( must use -i ) + cpan -fi CGI::Minimal URI + + # install modules but without testing them + cpan -Ti CGI::Minimal URI + +=head2 Environment variables + +There are several components in CPAN.pm that use environment variables. +The build tools, L and L use some, +while others matter to the levels above them. Some of these are specified +by the Perl Toolchain Gang: + +Lancaster Consensus: L + +Oslo Consensus: L + +=over 4 + +=item NONINTERACTIVE_TESTING + +Assume no one is paying attention and skips prompts for distributions +that do that correctly. C sets this to C<1> unless it already +has a value (even if that value is false). + +=item PERL_MM_USE_DEFAULT + +Use the default answer for a prompted questions. C sets this +to C<1> unless it already has a value (even if that value is false). + +=item CPAN_OPTS + +As with C, a string of additional C options to +add to those you specify on the command line. + +=item CPANSCRIPT_LOGLEVEL + +The log level to use, with either the embedded, minimal logger or +L if it is installed. Possible values are the same as +the C levels: C, C, C, C, +C, and C. The default is C. + +=item GIT_COMMAND + +The path to the C binary to use for the Git features. The default +is C. + +=back + +=head1 EXIT VALUES + +The script exits with zero if it thinks that everything worked, or a +positive number if it thinks that something failed. Note, however, that +in some cases it has to divine a failure by the output of things it does +not control. For now, the exit codes are vague: + + 1 An unknown error + + 2 The was an external problem + + 4 There was an internal problem with the script + + 8 A module failed to install + +=head1 TO DO + +* one shot configuration values from the command line + +=head1 BUGS + +* none noted + +=head1 SEE ALSO + +Most behaviour, including environment variables and configuration, +comes directly from CPAN.pm. + +=head1 SOURCE AVAILABILITY + +This code is in Github in the CPAN.pm repository: + + https://github.com/andk/cpanpm + +The source used to be tracked separately in another GitHub repo, +but the canonical source is now in the above repo. + +=head1 CREDITS + +Japheth Cleaver added the bits to allow a forced install (-f). + +Jim Brandt suggest and provided the initial implementation for the +up-to-date and Changes features. + +Adam Kennedy pointed out that exit() causes problems on Windows +where this script ends up with a .bat extension + +=head1 AUTHOR + +brian d foy, C<< >> + +=head1 COPYRIGHT + +Copyright (c) 2001-2015, brian d foy, All Rights Reserved. + +You may redistribute this under the same terms as Perl itself. + +=cut + +1; From ff31163f9d11efc617104ffd839fb8d877a2f510 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 16 Mar 2026 10:34:52 +0100 Subject: [PATCH 3/5] Fix self-referential hash assignment %h = (stuff, %h) The hash was being cleared before iterating over the RHS list. For self-referential assignments like `%h = (new_stuff, %h)`, this caused the hash contents to be lost. Fix: Materialize the entire RHS list into a temporary array before clearing the hash, similar to how tied hashes are handled. This fixes Mo module BUILD support - Mo uses: %e = (extends => sub{...}, has => sub{...}, %e) to merge feature-provided exports with defaults. Mo test improvement: 6/28 failing -> 1/28 failing (143/144 subtests pass) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../runtime/runtimetypes/RuntimeHash.java | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java index cc2a3a69a..a92e10247 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java @@ -189,23 +189,18 @@ public RuntimeScalar addToScalar(RuntimeScalar scalar) { public RuntimeArray setFromList(RuntimeList value) { return switch (type) { case PLAIN_HASH -> { - // Store the original list size for scalar context - int originalSize = 0; - for (RuntimeBase elem : value.elements) { - if (elem instanceof RuntimeArray) { - originalSize += ((RuntimeArray) elem).elements.size(); - } else if (elem instanceof RuntimeScalar) { - originalSize++; - } else { - // Count elements by iterating - Iterator it = elem.iterator(); - while (it.hasNext()) { - it.next(); - originalSize++; - } - } + // First, fully materialize the right-hand side list BEFORE clearing + // This is critical for self-referential assignments like: %h = (new_stuff, %h) + // We must capture the current hash contents before clearing. + RuntimeArray materializedList = new RuntimeArray(); + Iterator iterator = value.iterator(); + while (iterator.hasNext()) { + materializedList.push(new RuntimeScalar(iterator.next())); } + // Store the original list size for scalar context + int originalSize = materializedList.elements.size(); + // Warn about odd elements (Perl does not warn about references in hash assignment) if (originalSize % 2 != 0) { WarnDie.warn( @@ -216,13 +211,12 @@ public RuntimeArray setFromList(RuntimeList value) { // Clear existing elements but keep the same Map instance to preserve capacity this.elements.clear(); - // Populate the hash from the provided list - // This reuses the existing StableHashMap and its capacity - Iterator iter = value.iterator(); - while (iter.hasNext()) { - String key = iter.next().toString(); + // Populate the hash from the materialized list + iterator = materializedList.iterator(); + while (iterator.hasNext()) { + String key = iterator.next().toString(); // Create a new RuntimeScalar to properly handle aliasing and avoid read-only issues - RuntimeScalar val = iter.hasNext() ? new RuntimeScalar(iter.next()) : new RuntimeScalar(); + RuntimeScalar val = iterator.hasNext() ? new RuntimeScalar(iterator.next()) : new RuntimeScalar(); this.elements.put(key, val); } From a3233cd55779aa79363a7a05a215677325166015 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 16 Mar 2026 10:41:55 +0100 Subject: [PATCH 4/5] Improve ::identifier parsing to check if sub exists at compile time The previous fix always treated `::foo` (without parens) as a bareword string. But in Perl, `::foo` without parens calls `main::foo` if the sub exists at compile time, and only becomes a bareword string if no such sub exists. Now checks GlobalVariable.getGlobalCodeRef(fullSubName).getDefinedBoolean() to determine if the sub exists, matching Perl's behavior. This fixes tests that use `::is ::exception { }` patterns where `is` and `exception` are imported subs that should be called. Mo still works: `$M.$_.::e` correctly produces '::e' bareword because no `main::e` sub exists at that point. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../frontend/parser/ParsePrimary.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java b/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java index b3f9f4e47..d6644c96f 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParsePrimary.java @@ -271,17 +271,15 @@ static Node parseOperator(Parser parser, LexerToken token, String operator) { case "::": // Leading :: can mean either: // 1. Function call: ::foo() -> main::foo() - // 2. Bareword string: $x . ::foo -> $x . '::foo' + // 2. Bareword reference: ::foo without parens -> calls main::foo if it exists + // 3. Bareword string: ::foo when no such sub exists -> '::foo' // - // In Perl, ::foo without parens is a bareword that gets stringified to '::foo' - // unless a main::foo subroutine exists at compile time (which would be called). - // Since PerlOnJava doesn't have compile-time sub resolution, we use a heuristic: - // - If followed by ( -> function call (main::identifier) - // - Otherwise -> bareword string ('::identifier') + // In Perl, ::foo without parens calls main::foo if the sub exists at compile time. + // If no such sub exists, it becomes a bareword string '::foo'. LexerToken nextToken2 = peek(parser); if (nextToken2.type == LexerTokenType.IDENTIFIER) { String identifierName = nextToken2.text; - // Look ahead to see if this is a function call + // Look ahead to see if this is a function call with parens int lookAhead = parser.tokenIndex + 1; // Skip whitespace while (lookAhead < parser.tokens.size() && @@ -291,15 +289,19 @@ static Node parseOperator(Parser parser, LexerToken token, String operator) { String afterIdentifier = lookAhead < parser.tokens.size() ? parser.tokens.get(lookAhead).text : ""; - if (afterIdentifier.equals("(")) { - // Function call: ::foo() -> main::foo() + // Check if the sub exists at compile time + String fullSubName = "main::" + identifierName; + boolean subExists = GlobalVariable.getGlobalCodeRef(fullSubName).getDefinedBoolean(); + + if (afterIdentifier.equals("(") || subExists) { + // Function call: ::foo() or ::foo (when sub exists) // Insert "main" before the :: to create main::identifier parser.tokens.add(parser.tokenIndex - 1, new LexerToken(LexerTokenType.IDENTIFIER, "main")); parser.tokenIndex--; // Go back to process "main" return parseIdentifier(parser, parser.tokenIndex, new LexerToken(LexerTokenType.IDENTIFIER, "main"), "main"); } else { - // Bareword: ::foo -> '::foo' + // Bareword: ::foo -> '::foo' (no such sub exists) parser.tokenIndex++; // Consume the identifier return new StringNode("::" + identifierName, parser.tokenIndex); } From 3e75045bb4e1c613d5bd56d23d35797cc7b65149 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 16 Mar 2026 10:42:43 +0100 Subject: [PATCH 5/5] Update Moo design doc with Phase 24-25 fixes - Phase 24: Fix ::identifier bareword parsing - Phase 25: Fix self-referential hash assignment Mo: 27/28 passing (99.3%) Moo: 62/71 passing (87%), 768/829 subtests (93%) Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- dev/design/moo_support.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/dev/design/moo_support.md b/dev/design/moo_support.md index f1fd39bec..8b58febb0 100644 --- a/dev/design/moo_support.md +++ b/dev/design/moo_support.md @@ -589,11 +589,34 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas - This fixes Sub::Quote's `local @_ = ($value)` inlinification pattern - Fixes t/method-generate-accessor.t (46/49 → 49/49) +- [x] Phase 24: Fix ::identifier bareword parsing (2026-03-16) + - Root cause: `::foo` without parens was always treated as `main::foo` identifier + - In Perl: `::foo` calls main::foo only if sub exists at compile time, else bareword string + - Mo uses `$M.$_.::e` to build package names - `::e` should be bareword string + - But tests use `::is ::exception { }` where `is` and `exception` are imported subs + - **ParsePrimary.java fix**: + - Check `GlobalVariable.getGlobalCodeRef(fullSubName).getDefinedBoolean()` + - If sub exists OR followed by `(`: function call (main::identifier) + - If sub doesn't exist AND no parens: bareword string ('::identifier') + - **config.yaml fix**: Added cpan script to sync config for jcpan wrapper + - **.gitignore fix**: Allow src/main/perl/bin/ directory in git + - Mo tests: 27/28 passing (99.3%) + +- [x] Phase 25: Fix self-referential hash assignment (2026-03-16) + - Root cause: `%h = (new_stuff, %h)` was clearing hash before evaluating `%h` + - Mo uses: `%e = (extends => sub{...}, has => sub{...}, %e)` to merge exports + - The hash was cleared before iterating over the RHS list containing `%h` + - **RuntimeHash.java fix**: + - Materialize entire RHS list into temporary array BEFORE clearing hash + - Similar to how tied hashes are already handled + - This fixed Mo's BUILD feature which depends on the %e merge pattern + - Mo tests: 6/28 failing → 1/28 failing (143/144 subtests pass) + ### Current Status -**Test Results (after Phase 23):** -- 62/71 test programs passing (87%) -- ~768/829 subtests passing (93%) +**Test Results (after Phase 25):** +- **Moo**: 62/71 test programs passing (87%), 768/829 subtests passing (93%) +- **Mo**: 27/28 test programs passing (99.3%), 143/144 subtests passing **Remaining Failures (categorized):** 1. **accessor-weaken tests** (20 failures) - Expected, weak references not supported in Java GC @@ -602,6 +625,7 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas 4. **moo-utils-_subname-Sub-Name.t** (1 failure) - Expected, we have Sub::Util (no fallback to Sub::Name) 5. **no-moo.t** (5 failures) - Namespace cleanup requires weak references 6. **overloaded-coderefs.t** - Expected, B::Deparse not available +7. **Mo t/strict.t** (1 failure) - Error message format differs from Perl **Expected failures** (not fixable without fundamental changes): - Weak references: accessor-weaken tests (20), no-moo.t cleanup (5) @@ -621,12 +645,16 @@ Moo tests run via `jcpan -t Moo`. Recent fixes (Phases 12-13) should improve pas ### PR Information - **Branch**: `feature/moo-support` (PR #319 - merged) - **Branch**: `fix/goto-tailcall-import` (PR #320 - open) +- **Branch**: `fix/mo-bareword-parsing` (PR #322 - open) - **Key commits**: - `00c124167` - Fix print { func() } filehandle block parsing and JVM codegen - `393bedf0f` - Fix quotemeta and Package::SUPER::method resolution - `7a76739b8` - Fix goto &sub in use/import TAILCALL handling - `053d91a95` - Add Sub::Util, fix Scalar/List::Util VERSION, add Test::Harness - `7993ef74d` - Fix version parsing and MM->parse_version for CPAN.pm + - `db434f8d3` - Fix ::identifier bareword parsing and add cpan to sync + - `ff31163f9` - Fix self-referential hash assignment %h = (stuff, %h) + - `a3233cd55` - Improve ::identifier to check sub existence at compile time ## Related Documents