From 8e271682009dec905588d1fd809073458b44bd99 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 13 Mar 2026 16:23:43 +0100 Subject: [PATCH 1/3] Add experimental warnings for defer, class, field, method - Added "defer is experimental" warning when using defer blocks - Added "class is experimental" warning when using class keyword - Added "field is experimental" warning when using field keyword - Added "method is experimental" warning when using method keyword - Added warningLocation() method to ErrorMessageUtil for Perl-style location format - Updated declared_refs warning to use the same format - All warnings can be disabled with no warnings 'experimental::...' Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../frontend/parser/FieldParser.java | 16 +++++++++++ .../frontend/parser/OperatorParser.java | 16 +++++------ .../frontend/parser/StatementParser.java | 28 +++++++++++++++++++ .../frontend/parser/StatementResolver.java | 15 ++++++++++ .../runtimetypes/ErrorMessageUtil.java | 12 ++++++++ 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/perlonjava/frontend/parser/FieldParser.java b/src/main/java/org/perlonjava/frontend/parser/FieldParser.java index 11a998bba..441b1f830 100644 --- a/src/main/java/org/perlonjava/frontend/parser/FieldParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/FieldParser.java @@ -5,7 +5,9 @@ import org.perlonjava.frontend.astnode.OperatorNode; import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; /** * FieldParser handles parsing of field declarations in Perl classes. @@ -29,9 +31,23 @@ public class FieldParser { * @return A comment node placeholder for the field */ public static Node parseFieldDeclaration(Parser parser) { + int index = parser.tokenIndex; // Consume 'field' keyword TokenUtils.consume(parser, LexerTokenType.IDENTIFIER, "field"); + // Emit experimental warning for 'field' if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("field is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(index)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("field is experimental" + parser.ctx.errorUtil.warningLocation(index) + "."); + } + } + // Parse the field variable (sigil + name) LexerToken token = TokenUtils.peek(parser); diff --git a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java index ce48e6ec6..a859b4a76 100644 --- a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java @@ -284,11 +284,11 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); } } @@ -328,11 +328,11 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex) + "."); } } @@ -862,11 +862,11 @@ static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(currentIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(currentIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); } } @@ -905,11 +905,11 @@ static OperatorNode parseLocal(Parser parser, LexerToken token, int currentIndex try { WarnDie.warn( new RuntimeScalar("Declaring references is experimental"), - new RuntimeScalar(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "")) + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex)) ); } catch (Exception e) { // If warning system isn't initialized yet, fall back to System.err - System.err.println(parser.ctx.errorUtil.errorMessage(operandNode.tokenIndex, "Declaring references is experimental")); + System.err.println("Declaring references is experimental" + parser.ctx.errorUtil.warningLocation(operandNode.tokenIndex) + "."); } } diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java index 3fab0526e..deed326f3 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java @@ -10,6 +10,7 @@ import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.runtime.mro.InheritanceResolver; import org.perlonjava.runtime.operators.ModuleOperators; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.operators.VersionHelper; import org.perlonjava.runtime.perlmodule.Universal; import org.perlonjava.runtime.runtimetypes.*; @@ -351,6 +352,19 @@ public static Node parseDeferStatement(Parser parser) { int index = parser.tokenIndex; TokenUtils.consume(parser, LexerTokenType.IDENTIFIER); // "defer" + // Emit experimental warning if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::defer")) { + try { + WarnDie.warn( + new RuntimeScalar("defer is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(index)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("defer is experimental" + parser.ctx.errorUtil.warningLocation(index) + "."); + } + } + // Parse the defer block TokenUtils.consume(parser, LexerTokenType.OPERATOR, "{"); Node deferBlock = ParseBlock.parseBlock(parser); @@ -679,6 +693,20 @@ public static Node parsePackageDeclaration(Parser parser, LexerToken token) { packageExistsCache.put(packageName, true); boolean isClass = token.text.equals("class"); + + // Emit experimental warning for 'class' if warnings are enabled + if (isClass && parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("class is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(parser.tokenIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("class is experimental" + parser.ctx.errorUtil.warningLocation(parser.tokenIndex) + "."); + } + } + IdentifierNode nameNode = new IdentifierNode(packageName, parser.tokenIndex); OperatorNode packageNode = new OperatorNode(token.text, nameNode, parser.tokenIndex); packageNode.setAnnotation("isClass", isClass); diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java index b70b1488c..e9ccb39de 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementResolver.java @@ -8,8 +8,10 @@ import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.frontend.lexer.LexerTokenType; import org.perlonjava.frontend.semantic.SymbolTable; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; +import org.perlonjava.runtime.runtimetypes.RuntimeScalar; import java.util.ArrayList; import java.util.List; @@ -121,6 +123,19 @@ public static Node parseStatement(Parser parser, String label) { case "method" -> { if (parser.ctx.symbolTable.isFeatureCategoryEnabled("class")) { + // Emit experimental warning for 'method' if warnings are enabled + if (parser.ctx.symbolTable.isWarningCategoryEnabled("experimental::class")) { + try { + WarnDie.warn( + new RuntimeScalar("method is experimental"), + new RuntimeScalar(parser.ctx.errorUtil.warningLocation(currentIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("method is experimental" + parser.ctx.errorUtil.warningLocation(currentIndex) + "."); + } + } + // Parse method more directly parser.tokenIndex++; // consume "method" diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 77efd771d..aaed6e781 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -209,6 +209,18 @@ public String errorMessage(int index, String message) { return message + " at " + loc.fileName() + " line " + loc.lineNumber() + ", near " + errorMessageQuote(nearString) + "\n"; } + /** + * Gets a simple warning location string in the format " at filename line N". + * This matches system Perl's warning format without the ", near" suffix. + * + * @param index the index of the token where the warning occurred + * @return the location string (e.g., " at script.pl line 42") + */ + public String warningLocation(int index) { + SourceLocation loc = getSourceLocationAccurate(index); + return " at " + loc.fileName() + " line " + loc.lineNumber(); + } + private String buildNearString(int index) { int end = Math.min(tokens.size() - 1, index + 5); StringBuilder sb = new StringBuilder(); From feda3a84083ae098f00e487504fcb12e8d7388c8 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 13 Mar 2026 16:27:02 +0100 Subject: [PATCH 2/3] Add 'Aliasing via reference is experimental' warning for refaliasing - Emits warning when using \$x = \$y syntax with refaliasing feature - Warning can be disabled with no warnings 'experimental::refaliasing' - Or by using: use experimental 'refaliasing' Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../org/perlonjava/backend/jvm/EmitVariable.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index c484a205b..d07b1a18a 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -11,6 +11,7 @@ import org.perlonjava.frontend.semantic.SymbolTable; import org.perlonjava.runtime.perlmodule.Strict; import org.perlonjava.runtime.perlmodule.Warnings; +import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.runtimetypes.*; import java.util.ArrayList; @@ -767,6 +768,18 @@ static void handleAssignOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo if (!ctx.symbolTable.isFeatureCategoryEnabled("refaliasing")) { throw new PerlCompilerException(node.tokenIndex, "Experimental aliasing via reference not enabled", ctx.errorUtil); } + // Emit experimental warning if warnings are enabled + if (ctx.symbolTable.isWarningCategoryEnabled("experimental::refaliasing")) { + try { + WarnDie.warn( + new RuntimeScalar("Aliasing via reference is experimental"), + new RuntimeScalar(ctx.errorUtil.warningLocation(node.tokenIndex)) + ); + } catch (Exception e) { + // If warning system isn't initialized yet, fall back to System.err + System.err.println("Aliasing via reference is experimental" + ctx.errorUtil.warningLocation(node.tokenIndex) + "."); + } + } // TODO: Implement proper reference aliasing // For now, we just assign the reference value without creating an alias // This is not fully correct but allows tests to progress From 589c49b42d968900f5f82eed897d6f3832a5c229 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 13 Mar 2026 16:13:35 +0100 Subject: [PATCH 3/3] Fix variable redeclaration warnings to match system Perl - `our` variables now show "redeclared" instead of "masks earlier declaration" - `my` variables now show "masks earlier declaration in same scope" - Removed "Warning:" prefix to match system Perl format - Fixed typo "ctx.symbolTable" to "scope" in warning message Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin --- .../java/org/perlonjava/backend/jvm/EmitVariable.java | 7 ++++--- .../org/perlonjava/frontend/parser/OperatorParser.java | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index d07b1a18a..feca84082 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -1109,11 +1109,12 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("MY " + operator + " " + sigil + name); if (emitterVisitor.ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { if (Warnings.warningManager.isWarningEnabled("redefine")) { + String message = operator.equals("our") + ? "\"" + operator + "\" variable " + var + " redeclared" + : "\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"; System.err.println( emitterVisitor.ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); + message)); } } int varIndex = emitterVisitor.ctx.symbolTable.addVariable(var, operator, sigilNode); diff --git a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java index a859b4a76..d3d34c7d9 100644 --- a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java @@ -234,11 +234,11 @@ private static void addVariableToScope(EmitterContext ctx, String operator, Oper String name = ((IdentifierNode) identifierNode).name; String var = sigil + name; if (ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { + String message = operator.equals("our") + ? "\"" + operator + "\" variable " + var + " redeclared" + : "\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"; System.err.println( - ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); + ctx.errorUtil.errorMessage(node.getIndex(), message)); } int varIndex = ctx.symbolTable.addVariable(var, operator, node); // Note: the isDeclaredReference flag is stored in node.annotations