diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index d8b6e0dfd..8fc6664d6 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "cb3dcd790"; + public static final String gitCommitId = "36ce11560"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 29 2026 11:15:25"; + public static final String buildTimestamp = "Apr 29 2026 11:48:26"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java b/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java index f36d8aeed..e23776ba2 100644 --- a/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java @@ -213,7 +213,11 @@ public static String parseComplexIdentifierInner(Parser parser, boolean insideBr return null; // Force fallback to expression parsing for unary plus + hash constructor } // Check if this is a leading single quote followed by an identifier ($'foo means $main::foo) - if (firstChar == '\'' && (nextToken.type == LexerTokenType.IDENTIFIER || nextToken.type == LexerTokenType.NUMBER)) { + // BUT: inside ${...}, a leading ' starts a string literal expression (e.g. ${'Foo::'}) + // and must not be treated as the legacy package separator. Returning null here forces + // parseBracedVariable to fall back to parseBlock, which evaluates the string literal. + if (firstChar == '\'' && !insideBraces + && (nextToken.type == LexerTokenType.IDENTIFIER || nextToken.type == LexerTokenType.NUMBER)) { // This is $'foo which means $main::foo // We convert it to ::foo internally (leading :: means main::) variableName.append("::"); @@ -221,6 +225,10 @@ public static String parseComplexIdentifierInner(Parser parser, boolean insideBr token = parser.tokens.get(parser.tokenIndex); nextToken = parser.tokens.get(parser.tokenIndex + 1); // Continue to parse the rest of the identifier - fall through to main loop + } else if (firstChar == '\'' && insideBraces) { + // Inside ${...}: the ' starts a string literal — fail identifier parsing so the + // caller falls through to parseBlock and evaluates 'Foo::' as a normal expression. + return null; } else { // Either it's a special variable like $' (postmatch), $| (autoflush), etc. // Consume the character from the token (which might be "|=" or just "|") diff --git a/src/main/perl/lib/namespace/autoclean.pm b/src/main/perl/lib/namespace/autoclean.pm index 72435220e..f89f481f4 100644 --- a/src/main/perl/lib/namespace/autoclean.pm +++ b/src/main/perl/lib/namespace/autoclean.pm @@ -118,7 +118,12 @@ sub _method_check { # For Moose/Moo classes, use the metaclass if available if (defined &Class::MOP::class_of) { my $meta = Class::MOP::class_of($package); - if ($meta) { + # Only metaclasses that mix in HasMethods (Class::MOP::Class and friends) + # implement get_method_list. A bare Class::MOP::Package instance does not, + # so guard with can() to avoid "Can't locate object method" errors during + # on_scope_end callbacks (seen with MooseX::Types loading namespace::autoclean + # in non-class packages). + if ($meta && $meta->can('get_method_list')) { my %methods = map +($_ => 1), $meta->get_method_list; $methods{meta} = 1 if $meta->isa('Moose::Meta::Role')