diff --git a/src/main/java/org/perlonjava/parser/OperatorParser.java b/src/main/java/org/perlonjava/parser/OperatorParser.java index b30a0b3fa..756c372c9 100644 --- a/src/main/java/org/perlonjava/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/parser/OperatorParser.java @@ -951,6 +951,24 @@ static OperatorNode parseRequire(Parser parser) { } else if (token.text.matches("^v\\d+$")) { consume(parser); operand = StringParser.parseVstring(parser, token.text, parser.tokenIndex); + } else if (token.type == IDENTIFIER && !ParsePrimary.isIsQuoteLikeOperator(token.text)) { + // `require` bareword module name - parse directly without going through expression parser + // This avoids treating module names like "Encode" as subroutine calls when a sub + // with the same name exists in the current package (e.g., sub Encode in Image::ExifTool) + // But don't intercept quote-like operators like q(), qq(), etc. + String moduleName = IdentifierParser.parseSubroutineIdentifier(parser); + parser.ctx.logDebug("require module name `" + moduleName + "`"); + if (moduleName == null) { + throw new PerlCompilerException(parser.tokenIndex, "Syntax error", parser.ctx.errorUtil); + } + + // Check if module name starts with :: + if (moduleName.startsWith("::")) { + throw new PerlCompilerException(parser.tokenIndex, "Bareword in require must not start with a double-colon: \"" + moduleName + "\"", parser.ctx.errorUtil); + } + + String fileName = NameNormalizer.moduleToFilename(moduleName); + operand = ListNode.makeList(new StringNode(fileName, parser.tokenIndex)); } else { // Check for the specific pattern: :: followed by identifier (which is invalid for require) if (token.type == LexerTokenType.OPERATOR && token.text.equals("::")) { diff --git a/src/main/java/org/perlonjava/parser/ParsePrimary.java b/src/main/java/org/perlonjava/parser/ParsePrimary.java index 63f06e79f..8a8ff9f9e 100644 --- a/src/main/java/org/perlonjava/parser/ParsePrimary.java +++ b/src/main/java/org/perlonjava/parser/ParsePrimary.java @@ -122,7 +122,8 @@ private static Node parseIdentifier(Parser parser, int startIndex, LexerToken to // IMPORTANT: Check for lexical subs AFTER CORE::, but before checking for quote-like operators! // This allows "my sub y" to shadow the "y///" transliteration operator // But doesn't interfere with CORE:: prefix handling - if (!calledWithCore) { + // ALSO: Don't treat as lexical sub if :: follows - that's a qualified name like Encode::is_utf8 + if (!calledWithCore && !nextTokenText.equals("::")) { String lexicalKey = "&" + operator; SymbolTable.SymbolEntry lexicalEntry = parser.ctx.symbolTable.getSymbolEntry(lexicalKey); if (lexicalEntry != null && lexicalEntry.ast() instanceof OperatorNode) {