This document explains how PerlOnJava implements lexical pragmas like use warnings, no warnings, use strict, and no strict.
Perl's pragmas are lexically scoped—they affect only the code in the current block and nested blocks, not code in called subroutines:
{
use strict;
use warnings;
# strict and warnings enabled here
foo(); # foo() has its own pragma state
}
# strict and warnings NOT enabled hereThis is fundamentally different from dynamic scoping (local)—pragmas affect compilation, not runtime.
Pragmas have two aspects:
-
Compile-Time: Affects how code is parsed and compiled
use strict 'vars'- undeclared variable is a compile erroruse warnings 'syntax'- emit warning during compilation
-
Runtime: Affects behavior of running code
use warnings 'uninitialized'- warn when using undefno warnings 'DateTime'- suppress warnif() calls
PerlOnJava tracks both using different mechanisms.
ScopedSymbolTable maintains stacks for each pragma type:
public class ScopedSymbolTable {
// Warning flags - one BitSet per scope level
public final Stack<BitSet> warningFlagsStack = new Stack<>();
// Feature flags - integer bitmask per scope
public final Stack<Integer> featureFlagsStack = new Stack<>();
// Strict options - integer bitmask per scope
public final Stack<Integer> strictOptionsStack = new Stack<>();
// Warning disabled flags - tracks "no warnings 'category'" per scope
public final Stack<BitSet> warningDisabledStack = new Stack<>();
// Warning fatal flags - tracks "use warnings FATAL => 'category'" per scope
public final Stack<BitSet> warningFatalStack = new Stack<>();
}When entering a new scope (block, subroutine, file):
int enterScope() {
// Clone current flags for new scope
warningFlagsStack.push((BitSet) warningFlagsStack.peek().clone());
featureFlagsStack.push(featureFlagsStack.peek());
strictOptionsStack.push(strictOptionsStack.peek());
warningDisabledStack.push((BitSet) warningDisabledStack.peek().clone());
warningFatalStack.push((BitSet) warningFatalStack.peek().clone());
// returns current scope depth
}WarningFlags defines the warning category hierarchy:
public class WarningFlags {
// Hierarchy: "all" contains all categories
private static final Map<String, String[]> warningHierarchy = new HashMap<>();
static {
warningHierarchy.put("all", new String[]{
"closure", "deprecated", "experimental", "io",
"numeric", "once", "redefine", "substr", "syntax",
"uninitialized", "void", ...
});
warningHierarchy.put("io", new String[]{
"io::closed", "io::exec", "io::layer", ...
});
// ... more subcategories
}
// Each category maps to a bit position
// Enabling/disabling warnings delegates to ScopedSymbolTable:
// ScopedSymbolTable.enableWarningCategory(category)
// which calls setWarningState() to update the current scope's BitSet.
// Subcategories are recursively expanded via the hierarchy map.
}Strict has three options encoded as bit constants in Strict.java:
// In Strict.java (not a separate StrictOptions class)
public static final int HINT_STRICT_REFS = 0x00000002;
public static final int HINT_STRICT_SUBS = 0x00000200;
public static final int HINT_STRICT_VARS = 0x00000400;When pragmas change, the parser creates a CompilerFlagNode:
public class CompilerFlagNode extends AbstractNode {
private final BitSet warningFlags;
private final int featureFlags;
private final int strictOptions;
private final int warningScopeId; // For runtime propagation
private final java.util.BitSet warningFatalFlags;
private final java.util.BitSet warningDisabledFlags;
private final int hintHashSnapshotId;
}This node is inserted into the AST to update compiler state during code generation.
Compile-time pragma tracking works for code compiled together. But what about:
# user_code.pl
{
no warnings 'DateTime';
DateTime->new(...); # DateTime.pm calls warnif('DateTime', $msg)
}DateTime.pm is compiled separately—it doesn't know about user_code.pl's no warnings.
We use a runtime mechanism with dynamic scoping:
-
Scope Registration: Each
no warnings 'category'block gets a unique scope ID:public static int registerScopeWarnings(Set<String> categories) { int scopeId = scopeIdCounter.incrementAndGet(); scopeDisabledWarnings.put(scopeId, expandCategory(categories)); return scopeId; }
-
Local Assignment:
CompilerFlagNodeemits:// local ${^WARNING_SCOPE} = scopeId GlobalRuntimeScalar.makeLocal("${^WARNING_SCOPE}"); scopeVar.set(scopeId);
-
Runtime Check:
warnif()checks the scope:public static RuntimeList warnIf(RuntimeArray args, int ctx) { int scopeId = GlobalVariable.getGlobalVariable(WARNING_SCOPE).getInt(); if (scopeId > 0 && isWarningDisabledInScope(scopeId, category)) { return new RuntimeScalar().getList(); // Suppressed } // ... emit warning }
Note: The actual implementation in
Warnings.javais significantly more complex—it usescaller()[9]warning bits as the primary mechanism for checking whether a warning category is enabled at the call site, with${^WARNING_SCOPE}as an additional suppression check. The pseudocode above is a simplified illustration. -
Automatic Restore: When scope exits,
DynamicVariableManagerrestores${^WARNING_SCOPE}to its previous value.
Runtime warning checks are needed in:
| Location | Warning Category |
|---|---|
RuntimeIO.java |
syscalls (nul in pathname) |
Warnings.java |
Custom categories via warnif() |
CompareOperators.java |
uninitialized |
Operator.java |
substr, numeric |
Example check:
if (Warnings.warningManager.isWarningEnabled("syscalls")
&& !WarningFlags.isWarningSuppressedAtRuntime("syscalls")) {
WarnDie.warn(message, location);
}Modules can register custom categories via warnings::register:
package DateTime;
use warnings::register; # Registers "DateTime" categoryImplementation:
public static void registerCategory(String category) {
customCategories.add(category);
// Allocate a bit for this category
symbolTable.registerCustomWarningCategory(category);
}Then warnif() uses the custom category:
warnings::warnif('DateTime', $message);use warnings 'all';
│
▼
StatementParser.parseUseDeclaration()
│
▼
Warnings.useWarnings() ──► warningManager.enableWarning("all")
│
▼
CompilerFlagNode created with current flags
│
▼
EmitCompilerFlag.emitCompilerFlag()
│
▼
Symbol table stacks updated
no warnings 'DateTime';
│
▼
Warnings.noWarnings()
│
├──► warningManager.disableWarning("DateTime") [compile-time]
│
└──► WarningFlags.registerScopeWarnings({"DateTime"}) [runtime]
│
▼
lastScopeId = N
│
▼
CompilerFlagNode(warningScopeId=N)
│
▼
EmitCompilerFlag.emitWarningScopeLocal()
│
▼
Bytecode: local ${^WARNING_SCOPE} = N
DateTime->new(year => 5001, ...)
│
▼
DateTime::_warn() calls warnif('DateTime', $msg)
│
▼
Warnings.warnIf()
│
├──► Check ${^WARNING_SCOPE}
│ scopeId = N > 0?
│ isWarningDisabledInScope(N, 'DateTime')?
│ YES → return (suppressed)
│
└──► Check compile-time flags
warningManager.isWarningEnabled('DateTime')?
YES → WarnDie.warn()
| File | Purpose |
|---|---|
WarningFlags.java |
Warning category management, scope tracking |
Warnings.java |
use warnings, no warnings, warnif() |
Strict.java |
use strict, no strict |
Feature.java |
use feature |
ScopedSymbolTable.java |
Compile-time flag stacks |
CompilerFlagNode.java |
AST node for pragma changes |
EmitCompilerFlag.java |
Bytecode emission for pragmas |
StatementParser.java |
Parses use/no statements |
GlobalContext.java |
Initializes ${^WARNING_SCOPE} |
- dynamic-scope.md - How
localmakes runtime scoping work - ../design/lexical-warnings.md - Detailed design for warning scope propagation