Skip to content

Commit 0426a49

Browse files
authored
Merge pull request #42 from fglock/fix-proto-validation
Fix prototype validation for (&) and (\&) prototypes
2 parents 2444dec + 709d281 commit 0426a49

File tree

1 file changed

+74
-46
lines changed

1 file changed

+74
-46
lines changed

src/main/java/org/perlonjava/parser/PrototypeArgs.java

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,6 @@ private static boolean isFilehandleOperator(String operatorName) {
376376
operatorName.equals("binmode") ||
377377
operatorName.equals("fileno") ||
378378
operatorName.equals("getc") ||
379-
operatorName.equals("open") ||
380379
operatorName.equals("read") ||
381380
operatorName.equals("sysread") ||
382381
operatorName.equals("syswrite") ||
@@ -452,22 +451,6 @@ private static void handleListOrHashArgument(Parser parser, ListNode args, boole
452451
// for (Node element : argList.elements) {
453452
// element.setAnnotation("context", "LIST");
454453
// }
455-
456-
// Check if this is the first argument and might be a bareword filehandle
457-
// For operators like truncate, seek, tell, eof, binmode, etc.
458-
if (!argList.elements.isEmpty() && argList.elements.getFirst() instanceof IdentifierNode idNode) {
459-
String operatorName = parser.ctx.symbolTable.getCurrentSubroutine();
460-
if (isFilehandleOperator(operatorName)) {
461-
// Try to parse as bareword filehandle
462-
Node filehandleNode = FileHandle.parseBarewordHandle(parser, idNode.name);
463-
if (filehandleNode != null) {
464-
// It's a known filehandle, use the typeglob reference
465-
// filehandleNode.setAnnotation("context", "SCALAR");
466-
argList.elements.set(0, filehandleNode);
467-
}
468-
}
469-
}
470-
471454
args.elements.addAll(argList.elements);
472455
}
473456

@@ -488,37 +471,55 @@ private static boolean handleCodeReferenceArgument(Parser parser, ListNode args,
488471

489472
Node codeRef = parseRequiredArgument(parser, isOptional);
490473
if (codeRef != null) {
491-
// Unwrap reference to code reference: \(&code) should be treated as &code
492-
// This matches Perl's behavior where prototype (&) unwraps REF to CODE
493-
if (codeRef instanceof OperatorNode opNode && opNode.operator.equals("\\")) {
494-
// Check for direct OperatorNode operand (e.g., \@array, \%hash, \$scalar)
495-
if (opNode.operand instanceof OperatorNode innerOp) {
496-
if (innerOp.operator.equals("&")) {
497-
// This is actually a direct \&code, not wrapped in parens - leave as is
498-
} else if (innerOp.operator.equals("@") || innerOp.operator.equals("%") || innerOp.operator.equals("$")) {
499-
// Reject non-code references like \@array, \%hash, \$scalar
500-
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
501-
if (subName != null && !subName.isEmpty()) {
502-
parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not single ref constructor)");
503-
} else {
504-
parser.throwError("Type of arg 1 must be block or sub {} (not single ref constructor)");
505-
}
474+
// Check if a bare array, hash, or scalar sigil was passed (e.g., @array, %hash, $scalar)
475+
// These should be rejected for (&) prototype
476+
if (codeRef instanceof OperatorNode opNode) {
477+
if (opNode.operator.equals("@") || opNode.operator.equals("%") || opNode.operator.equals("$")) {
478+
// Reject bare arrays, hashes, and scalars
479+
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
480+
if (subName != null && !subName.isEmpty()) {
481+
parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not " +
482+
(opNode.operator.equals("@") ? "array" :
483+
opNode.operator.equals("%") ? "hash" : "scalar variable") + ")");
484+
} else {
485+
parser.throwError("Type of arg 1 must be block or sub {} (not " +
486+
(opNode.operator.equals("@") ? "array" :
487+
opNode.operator.equals("%") ? "hash" : "scalar variable") + ")");
506488
}
507489
}
508-
// Check if it's a ListNode containing operators (e.g., \(&code))
509-
else if (opNode.operand instanceof ListNode listNode && !listNode.elements.isEmpty()) {
510-
Node firstElement = listNode.elements.get(0);
511-
if (firstElement instanceof OperatorNode innerOp && innerOp.operator.equals("&")) {
512-
// Unwrap: use the inner &code node instead of \&code
513-
codeRef = innerOp;
514-
} else if (firstElement instanceof OperatorNode innerOp &&
515-
(innerOp.operator.equals("@") || innerOp.operator.equals("%") || innerOp.operator.equals("$"))) {
516-
// Reject non-code references
517-
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
518-
if (subName != null && !subName.isEmpty()) {
519-
parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not single ref constructor)");
520-
} else {
521-
parser.throwError("Type of arg 1 must be block or sub {} (not single ref constructor)");
490+
491+
// Unwrap reference to code reference: \(&code) should be treated as &code
492+
// This matches Perl's behavior where prototype (&) unwraps REF to CODE
493+
if (opNode.operator.equals("\\")) {
494+
// Check for direct OperatorNode operand (e.g., \@array, \%hash, \$scalar)
495+
if (opNode.operand instanceof OperatorNode innerOp) {
496+
if (innerOp.operator.equals("&")) {
497+
// This is actually a direct \&code, not wrapped in parens - leave as is
498+
} else if (innerOp.operator.equals("@") || innerOp.operator.equals("%") || innerOp.operator.equals("$")) {
499+
// Reject non-code references like \@array, \%hash, \$scalar
500+
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
501+
if (subName != null && !subName.isEmpty()) {
502+
parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not single ref constructor)");
503+
} else {
504+
parser.throwError("Type of arg 1 must be block or sub {} (not single ref constructor)");
505+
}
506+
}
507+
}
508+
// Check if it's a ListNode containing operators (e.g., \(&code))
509+
else if (opNode.operand instanceof ListNode listNode && !listNode.elements.isEmpty()) {
510+
Node firstElement = listNode.elements.get(0);
511+
if (firstElement instanceof OperatorNode innerOp && innerOp.operator.equals("&")) {
512+
// Unwrap: use the inner &code node instead of \&code
513+
codeRef = innerOp;
514+
} else if (firstElement instanceof OperatorNode innerOp &&
515+
(innerOp.operator.equals("@") || innerOp.operator.equals("%") || innerOp.operator.equals("$"))) {
516+
// Reject non-code references
517+
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
518+
if (subName != null && !subName.isEmpty()) {
519+
parser.throwError("Type of arg 1 to " + subName + " must be block or sub {} (not single ref constructor)");
520+
} else {
521+
parser.throwError("Type of arg 1 must be block or sub {} (not single ref constructor)");
522+
}
522523
}
523524
}
524525
}
@@ -559,8 +560,35 @@ private static int handleBackslashArgument(Parser parser, ListNode args, String
559560
char refType = isGroup ? ' ' : prototype.charAt(prototypeIndex);
560561
String expectedType = isGroup ? "reference" : "reference to " + refType;
561562

563+
// Set flag for \& to prevent &sub from being called
564+
boolean oldParsingTakeReference = parser.parsingTakeReference;
565+
if (refType == '&') {
566+
parser.parsingTakeReference = true;
567+
}
568+
562569
Node referenceArg = parseArgumentWithComma(parser, isOptional, needComma, expectedType);
570+
571+
// Restore flag
572+
parser.parsingTakeReference = oldParsingTakeReference;
563573
if (referenceArg != null) {
574+
// For \& prototype, check for invalid forms like &foo(), foo(), or bareword foo
575+
if (refType == '&') {
576+
String subName = parser.ctx.symbolTable.getCurrentSubroutine();
577+
String subNamePart = (subName == null || subName.isEmpty()) ? "" : " to " + subName;
578+
579+
// Check for function calls: &foo() or foo()
580+
if (referenceArg instanceof BinaryOperatorNode binOp && binOp.operator.equals("(")) {
581+
parser.throwError("Type of arg " + (args.elements.size() + 1) + subNamePart +
582+
" must be subroutine (not subroutine entry)");
583+
}
584+
585+
// Check for bareword (identifier without &)
586+
if (referenceArg instanceof IdentifierNode) {
587+
parser.throwError("Type of arg " + (args.elements.size() + 1) + subNamePart +
588+
" must be subroutine (not subroutine entry)");
589+
}
590+
}
591+
564592
// Check if user passed an explicit reference when prototype expects auto-reference
565593
if (refType == '$' && referenceArg instanceof OperatorNode opNode &&
566594
opNode.operator.equals("\\")) {

0 commit comments

Comments
 (0)