Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/main/java/org/perlonjava/frontend/parser/FileHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ public static Node parseFileHandle(Parser parser, boolean autovivifyUnknownBarew
// Handle bareword file handles (most common case)
// Examples: STDOUT, STDERR, STDIN, or user-defined handles like LOG, FILE, etc.
else if (token.type == LexerTokenType.IDENTIFIER) {
// `print foo()` is always a call whose result is printed. A prior
// typeglob assignment can create an IO placeholder while the CODE
// slot is still installed only at runtime, so checking the current
// global slots first would incorrectly make foo the filehandle.
if (!hasBracket && isBarewordCallAtCurrentPosition(parser)) {
return null;
}
// Check if this is a function call or method chain
// In that case, we need to parse it as an expression, not a bareword
LexerToken nextToken = parser.tokens.get(parser.tokenIndex + 1);
Expand Down Expand Up @@ -354,6 +361,19 @@ private static boolean isImmediatelyFollowedByOpenParen(Parser parser) {
&& "(".equals(parser.tokens.get(parser.tokenIndex).text);
}

/**
* Checks a bareword before it has been consumed as a prospective
* filehandle. Unlike {@link #isImmediatelyFollowedByOpenParen}, the
* parser is still positioned on the identifier itself.
*/
private static boolean isBarewordCallAtCurrentPosition(Parser parser) {
int index = parser.tokenIndex + 1;
while (index + 1 < parser.tokens.size() && "::".equals(parser.tokens.get(index).text)) {
index += 2;
}
return index < parser.tokens.size() && "(".equals(parser.tokens.get(index).text);
}

private static boolean isFollowedByMethodDereference(Parser parser) {
int idx = parser.tokenIndex;
while (idx < parser.tokens.size()
Expand Down
37 changes: 36 additions & 1 deletion src/main/java/org/perlonjava/frontend/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,14 @@ static BinaryOperatorNode parsePrint(Parser parser, LexerToken token, int curren
parser.debugHeredocState("PRINT_START");

try {
operand = ListParser.parseZeroOrMoreList(parser, 0, false, true, true, false, true);
// A parenthesized bareword call is part of print's argument list,
// not a parenthesized filehandle form. In particular, a CODE slot
// may be installed through a typeglob only when this statement
// runs, so filehandle probing cannot use the current glob slots to
// disambiguate it.
boolean parenthesizedBarewordCall = isParenthesizedBarewordCall(parser);
operand = ListParser.parseZeroOrMoreList(
parser, 0, false, true, !parenthesizedBarewordCall, false, true);
parser.debugHeredocState("PRINT_PARSE_SUCCESS");
} catch (PerlCompilerException e) {
parser.debugHeredocState("PRINT_BEFORE_BACKTRACK");
Expand Down Expand Up @@ -310,6 +317,34 @@ static BinaryOperatorNode parsePrint(Parser parser, LexerToken token, int curren
return new BinaryOperatorNode(token.text, handle, operand, currentIndex);
}

/** True for {@code print(foo(...), ...)}, but not {@code print(FH (...))}. */
private static boolean isParenthesizedBarewordCall(Parser parser) {
if (!peek(parser).text.equals("(")) {
return false;
}

int index = parser.tokenIndex + 1;
while (parser.tokens.get(index).type == WHITESPACE) {
index++;
}
if (parser.tokens.get(index).type != IDENTIFIER) {
return false;
}
index++;

// Qualified calls such as print(Package::foo(), ...) follow the same
// rule. Do not skip whitespace before the call parenthesis: that is
// the meaningful distinction from print(FH (...)).
while (parser.tokens.get(index).text.equals("::")) {
index++;
if (parser.tokens.get(index).type != IDENTIFIER) {
return false;
}
index++;
}
return parser.tokens.get(index).text.equals("(");
}

/**
* Check if a variable name refers to a forced-global variable that cannot
* be lexicalized with 'my' or 'state'.
Expand Down
25 changes: 25 additions & 0 deletions src/test/resources/unit/typeglob_print_dynamic_sub.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use strict;
use warnings;
use Test::More tests => 2;

# The call sites are parsed before this assignment runs, so they must retain
# dynamic named-sub lookup instead of being interpreted as filehandles.
*issue_1163_bar = sub { 'glob-installed' };

my $output = '';
open my $fh, '>', \$output or die "open scalar handle: $!";
{
local *STDOUT = $fh;
print issue_1163_bar(), "\n";
}
is $output, "glob-installed\n",
'unparenthesized print invokes a sub installed through a typeglob';

$output = '';
open $fh, '>', \$output or die "open scalar handle: $!";
{
local *STDOUT = $fh;
print(issue_1163_bar(), "\n");
}
is $output, "glob-installed\n",
'parenthesized print invokes a sub installed through a typeglob';
Loading