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
5 changes: 5 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ priorities and future plans.
- Apply source filters installed through `Test::More::use_ok` to the caller's remaining source and preserve `print FILEHANDLE (LIST) x N` output (#1375, #1441).
- Cover `Test::Needs` prerequisite scanning of quoted module names and Perl version literals (#1396).
- Preserve reference-valued FETCH results from tied-hash `values()` and retain scalar-reference envelopes in Storable round trips (#1554, #1468).
- Release temporary files once an exited closure capture retaining a Catalyst request context no longer has a live Perl root.
- Ignore commented-out code when checking lexical constant closures for later mutations, preserving BEGIN-time generated constants (#1785).
- Pack the method-dispatch inline-cache guard key into one primitive value while preserving class and method invalidation checks.
- Keep watcher maps and live `substr`-lvalue observers in lazy scalar extension state, reducing the common `RuntimeScalar` footprint.
Expand Down Expand Up @@ -1018,6 +1019,10 @@ priorities and future plans.
- Preserve strict-subs imports for `Compress::Raw::Zlib` constants.
- Evaluate `ref` operands in scalar context on the interpreter backend.
- Fix RFC 2047 header decoding for Email::MIME.
- Load the MIME-Header codec when `Encode::decode` first encounters it.
- Parse quoted display names and address groups in the Email::Address shim.
- Recognize inherited `fields` entries in typed hash access.
- Match Storable's default and optional export lists.
- Fix XML::Parser streaming from native filehandles before `IO::Handle` has
been explicitly loaded.
- Bundle the complete CPAN `File::Path` 2.18 implementation, including modern
Expand Down
27 changes: 25 additions & 2 deletions src/main/java/org/perlonjava/frontend/parser/ParseInfix.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Set;

import static org.perlonjava.frontend.parser.OperatorParser.ensureOneOperand;
import static org.perlonjava.frontend.parser.PrototypeArgs.consumeArgsWithPrototype;
Expand Down Expand Up @@ -69,11 +71,10 @@ private static void validateTypedFields(
// key as an unknown class field.
if (!GlobalVariable.existsGlobalHash(fieldsName)
&& !GlobalVariable.isDeclaredGlobalHash(fieldsName)) return;
RuntimeHash fields = GlobalVariable.getGlobalHash(fieldsName);
for (Node key : keys.elements) {
String name = key instanceof StringNode string ? string.value
: key instanceof IdentifierNode id ? id.name : null;
if (name != null && !fields.containsKey(name)) {
if (name != null && !hasFieldInClassHierarchy(typeName, name, new HashSet<>())) {
throw PerlCompilerException.withSourceLocation(tokenIndex,
"No such class field \"" + name + "\" in variable $"
+ identifier.name + " of type " + typeName,
Expand All @@ -82,6 +83,28 @@ private static void validateTypedFields(
}
}

/**
* `fields` declarations are inherited through @ISA. A typed variable of
* a child class may therefore access fields declared by any ancestor,
* even though those names are absent from the child's own %FIELDS hash.
*/
private static boolean hasFieldInClassHierarchy(String className, String fieldName,
Set<String> visited) {
if (!visited.add(className)) return false;

String fieldsName = className + "::FIELDS";
if ((GlobalVariable.existsGlobalHash(fieldsName)
|| GlobalVariable.isDeclaredGlobalHash(fieldsName))
&& GlobalVariable.getGlobalHash(fieldsName).containsKey(fieldName)) {
return true;
}

for (RuntimeScalar parent : GlobalVariable.getGlobalArray(className + "::ISA").elements) {
if (hasFieldInClassHierarchy(parent.toString(), fieldName, visited)) return true;
}
return false;
}

// Non-chainable comparison operators (cannot be chained with any operator)
private static final List<String> NON_CHAINABLE_COMPARISON_OPS = Arrays.asList("<=>", "cmp", "~~");

Expand Down
26 changes: 24 additions & 2 deletions src/main/java/org/perlonjava/runtime/perlmodule/Encode.java
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ public static RuntimeList encode(RuntimeArray args, int ctx) {

// Check %Encode::Encoding for Perl-registered encodings
String encodingName = encodingArg.toString();
RuntimeScalar perlEncObj = lookupPerlEncoding(encodingName);
RuntimeScalar perlEncObj = lookupOperationEncoding(encodingName);
if (perlEncObj != null) {
return dispatchToEncodingObject(perlEncObj, "encode", args, 1, 2, ctx);
}
Expand Down Expand Up @@ -680,7 +680,7 @@ public static RuntimeList decode(RuntimeArray args, int ctx) {

// Check %Encode::Encoding for Perl-registered encodings
String encodingName = encodingArg.toString();
RuntimeScalar perlEncObj = lookupPerlEncoding(encodingName);
RuntimeScalar perlEncObj = lookupOperationEncoding(encodingName);
if (perlEncObj != null) {
return dispatchToEncodingObject(perlEncObj, "decode", args, 1, 2, ctx);
}
Expand Down Expand Up @@ -1251,6 +1251,28 @@ private static RuntimeScalar lookupPerlEncoding(String encodingName) {
return null;
}

/**
* Resolve codecs used by encode/decode through Perl's public lookup path
* when they are lazily loaded. In particular, Encode::MIME::Header
* registers itself from the Perl find_encoding wrapper; bypassing that
* wrapper here makes decode("MIME-Header", ...) fail on first use.
*/
private static RuntimeScalar lookupOperationEncoding(String encodingName) {
RuntimeScalar encoding = lookupPerlEncoding(encodingName);
if (encoding != null || !encodingName.toLowerCase(Locale.ROOT).startsWith("mime-")) {
return encoding;
}

String finderName = "Encode::find_encoding";
if (!GlobalVariable.existsGlobalCodeRef(finderName)) return null;
RuntimeArray finderArgs = new RuntimeArray();
finderArgs.push(new RuntimeScalar(encodingName));
RuntimeList result = RuntimeCode.apply(
GlobalVariable.getGlobalCodeRef(finderName), finderArgs, RuntimeContextType.SCALAR);
RuntimeScalar found = result.scalar();
return found != null && RuntimeScalarType.isReference(found) ? found : null;
}

/**
* Dispatches an encode or decode call to a Perl encoding object's method.
* Calls $encObj->encode($string, $check) or $encObj->decode($octets, $check).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public static void initialize() {
storable.registerMethod("dclone", "$");
storable.registerMethod("last_op_in_netorder", null);

storable.defineExport("EXPORT", "store", "retrieve", "nstore", "freeze", "thaw", "nfreeze", "dclone");
storable.defineExport("EXPORT", "store", "retrieve");
storable.defineExport("EXPORT_OK", "nstore", "freeze", "thaw", "nfreeze", "dclone");

} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing Storable method: " + e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ public static void registerIfDestroyable(RuntimeBase referent, int blessId) {
if (referent == null || blessId == 0) return;
String className = NameNormalizer.getBlessStr(blessId);
boolean hasDestroy = className != null && classHasDestroy(blessId, className);
boolean statementBoundaryCleanup = hasDestroy
&& isStatementBoundaryCleanupClassName(className);
if (hasDestroy && (className.endsWith("::Cursor") || statementBoundaryCleanup)) {
boolean statementBoundaryCleanup = isStatementBoundaryCleanupClassName(className);
if ((hasDestroy && className != null && className.endsWith("::Cursor"))
|| statementBoundaryCleanup) {
state().destroyableObjects.add(referent);
} else {
state().destroyableObjects.remove(referent);
Expand Down Expand Up @@ -189,7 +189,8 @@ public static boolean classHasDestroy(int blessId, String className) {
}

private static boolean isStatementBoundaryCleanupClassName(String className) {
return "File::Temp".equals(className) || "File::Temp::Dir".equals(className);
return "File::Temp".equals(className)
|| "File::Temp::Dir".equals(className);
}

/**
Expand Down
Loading
Loading