@@ -209,7 +209,7 @@ static ListNode consumeArgsWithPrototype(Parser parser, String prototype, boolea
209209// element.setAnnotation("context", "LIST");
210210// }
211211 } else {
212- parsePrototypeArguments (parser , args , prototype );
212+ parsePrototypeArguments (parser , args , prototype , hasParentheses );
213213
214214 // Check for too many arguments without parentheses only if prototype expects 2+ args
215215 if (!hasParentheses && countPrototypeArgs (prototype ) >= 2 ) {
@@ -328,8 +328,9 @@ private static boolean handleOpeningParenthesis(Parser parser) {
328328 * @param parser The parser instance
329329 * @param args The argument list to populate
330330 * @param prototype The prototype string to parse
331+ * @param hasParentheses Whether the function was called with explicit parentheses
331332 */
332- private static void parsePrototypeArguments (Parser parser , ListNode args , String prototype ) {
333+ private static void parsePrototypeArguments (Parser parser , ListNode args , String prototype , boolean hasParentheses ) {
333334 boolean isOptional = false ;
334335 boolean needComma = false ;
335336 int skipCount = 0 ; // Number of prototype characters to skip (for flattened my/our/state)
@@ -382,7 +383,7 @@ private static void parsePrototypeArguments(Parser parser, ListNode args, String
382383 needComma = true ;
383384 }
384385 case '\\' -> {
385- i = handleBackslashArgument (parser , args , prototype , i + 1 , isOptional , needComma );
386+ i = handleBackslashArgument (parser , args , prototype , i + 1 , isOptional , needComma , hasParentheses );
386387 needComma = true ;
387388 }
388389 case ',' -> {
@@ -747,7 +748,7 @@ private static Node unwrapUnaryPlus(Node arg, char refType) {
747748 }
748749
749750 private static int handleBackslashArgument (Parser parser , ListNode args , String prototype , int prototypeIndex ,
750- boolean isOptional , boolean needComma ) {
751+ boolean isOptional , boolean needComma , boolean hasParentheses ) {
751752 if (prototypeIndex >= prototype .length ()) {
752753 parser .throwError ("syntax error, incomplete backslash reference in prototype" );
753754 }
@@ -762,7 +763,22 @@ private static int handleBackslashArgument(Parser parser, ListNode args, String
762763 parser .parsingTakeReference = true ;
763764 }
764765
765- Node referenceArg = parseArgumentWithComma (parser , isOptional , needComma , expectedType );
766+ // Parse the backslash-prototype argument.
767+ // With parentheses: always parse at comma precedence (level 5).
768+ // Without parentheses:
769+ // - Single-arg prototypes (e.g. \[$@%*], \$): parse at named-unary precedence
770+ // so operators like && and == are NOT consumed. Example:
771+ // tied *STDOUT && $cond → (tied *STDOUT) && $cond
772+ // - Multi-arg prototypes (e.g. \$$, \$;$): parse at comma precedence
773+ // so assignment and other operators ARE consumed. Example:
774+ // sreftest my $a = 'val', $i++ → sreftest(\(my $a = 'val'), $i++)
775+ Node referenceArg ;
776+ boolean useNamedUnary = !hasParentheses && countPrototypeArgs (prototype ) <= 1 ;
777+ if (useNamedUnary ) {
778+ referenceArg = parseBackslashArgWithComma (parser , isOptional , needComma , expectedType );
779+ } else {
780+ referenceArg = parseArgumentWithComma (parser , isOptional , needComma , expectedType );
781+ }
766782
767783 // Restore flag
768784 parser .parsingTakeReference = oldParsingTakeReference ;
@@ -867,6 +883,50 @@ private static Node parseRequiredArgument(Parser parser, boolean isOptional) {
867883 return expr ;
868884 }
869885
886+ /**
887+ * Parses a backslash-prototype argument at named-unary precedence.
888+ *
889+ * <p>Backslash prototypes like {@code \[$@%*]} expect a single variable term.
890+ * In Perl, these parse at named-unary precedence (between "isa" and shift operators),
891+ * so operators like {@code &&}, {@code ||}, {@code ==}, {@code <} are NOT consumed,
892+ * but arithmetic operators like {@code +}, {@code *}, {@code >>} ARE consumed.</p>
893+ *
894+ * @param parser The parser instance
895+ * @param isOptional Whether the argument is optional
896+ * @param needComma Whether a comma is required before the argument
897+ * @param expectedType Description of the expected argument type for error messages
898+ * @return The parsed argument node, or null if parsing failed and the argument was optional
899+ */
900+ private static Node parseBackslashArgWithComma (Parser parser , boolean isOptional , boolean needComma , String expectedType ) {
901+ if (isArgumentTerminator (parser )) {
902+ if (!isOptional ) {
903+ throwNotEnoughArgumentsError (parser );
904+ }
905+ return null ;
906+ }
907+
908+ if (needComma && !consumeCommaIfPresent (parser , isOptional )) {
909+ return null ;
910+ }
911+
912+ if (isArgumentTerminator (parser )) {
913+ if (isOptional ) {
914+ return null ;
915+ }
916+ throwNotEnoughArgumentsError (parser );
917+ }
918+
919+ // Parse at named-unary precedence (level 15, same as "isa")
920+ // This ensures that comparison and logical operators are NOT consumed as part of the argument
921+ Node expr = parser .parseExpression (parser .getPrecedence ("isa" ));
922+ if (expr == null ) {
923+ if (!isOptional ) {
924+ throwNotEnoughArgumentsError (parser );
925+ }
926+ }
927+ return expr ;
928+ }
929+
870930 /**
871931 * Checks if there are consecutive commas (like ", ,") which should be a syntax error.
872932 *
0 commit comments