diff --git a/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java b/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
index 0b3c20637..980b22161 100644
--- a/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
+++ b/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java
@@ -15,6 +15,7 @@
*/
package org.pkl.core;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -195,7 +196,9 @@ public final class EvaluatorImpl implements Evaluator {
(module) -> {
var output = VmUtils.readModuleOutput(module);
return VmUtils.readFilesProperty(
- output, (fileOutput) -> new FileOutputImpl(this, fileOutput));
+ output,
+ (fileOutput) -> new FileOutputImpl(this, fileOutput),
+ IndirectCallNode.getUncached());
});
}
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/EqualNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/EqualNode.java
index c2c7987d8..99ef287c0 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/EqualNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/EqualNode.java
@@ -19,6 +19,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
@@ -34,6 +35,9 @@ public abstract class EqualNode extends ExpressionNode {
super(sourceSection);
}
+ /** When using this method, pass {@code null} in for {@code leftNode} and {@code rightNode}. */
+ public abstract boolean executeWith(VirtualFrame frame, Object left, Object right);
+
@Specialization
protected boolean eval(String left, String right) {
return left.equals(right);
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java
index 07ea357c9..9a685be61 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java
@@ -23,22 +23,31 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.profiles.LoopConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
+import org.pkl.core.ast.internal.ReadCursorValueNode;
+import org.pkl.core.ast.internal.ReadCursorValueNodeGen;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
+import org.pkl.core.runtime.Iterators.TruffleIterator;
import org.pkl.core.util.ArrayUtils;
public abstract class GeneratorForNode extends GeneratorMemberNode {
private final FrameDescriptor generatorDescriptor;
+ private final LoopConditionProfile loopConditionProfile = LoopConditionProfile.create();
@Child private ExpressionNode iterableNode;
@Child private @Nullable UnresolvedTypeNode unresolvedKeyTypeNode;
@Child private @Nullable UnresolvedTypeNode unresolvedValueTypeNode;
@Children private final GeneratorMemberNode[] childNodes;
@Child private @Nullable TypeNode keyTypeNode;
@Child private @Nullable TypeNode valueTypeNode;
+
+ @Child
+ private ReadCursorValueNode readCursorValueNode = ReadCursorValueNodeGen.create(sourceSection);
+
private final int keySlot;
private final int valueSlot;
private final int[] slotsToCopy;
@@ -101,23 +110,29 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmList iterable) {
- long idx = 0;
- for (var element : iterable) {
- executeIteration(frame, parent, data, idx++, element);
+ loopConditionProfile.profileCounted(iterable.getLength());
+ for (var idx = 0L; loopConditionProfile.inject(idx < iterable.getLength()); idx++) {
+ executeIteration(frame, parent, data, idx, iterable.get(idx));
}
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmMap iterable) {
- for (var entry : iterable) {
+ loopConditionProfile.profileCounted(iterable.getLength());
+ var iterator = new TruffleIterator<>(iterable);
+ while (loopConditionProfile.inject(iterator.hasNext())) {
+ var entry = iterator.next();
executeIteration(frame, parent, data, VmUtils.getKey(entry), VmUtils.getValue(entry));
}
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmSet iterable) {
- long idx = 0;
- for (var element : iterable) {
+ var idx = 0L;
+ loopConditionProfile.profileCounted(iterable.getLength());
+ var iterator = new TruffleIterator<>(iterable);
+ while (loopConditionProfile.inject(iterator.hasNext())) {
+ var element = iterator.next();
executeIteration(frame, parent, data, idx++, element);
}
}
@@ -125,16 +140,20 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmIntSeq iterable) {
var length = iterable.getLength();
- for (long key = 0, value = iterable.start; key < length; key++, value += iterable.step) {
+ loopConditionProfile.profileCounted(iterable.getLength());
+ for (long key = 0, value = iterable.start;
+ loopConditionProfile.inject(key < length);
+ key++, value += iterable.step) {
executeIteration(frame, parent, data, key, value);
}
}
@Specialization
protected void eval(VirtualFrame frame, Object parent, ObjectData data, VmBytes iterable) {
- long idx = 0;
- for (var byt : iterable.getBytes()) {
- executeIteration(frame, parent, data, idx++, (long) byt);
+ var bytes = iterable.getBytes();
+ loopConditionProfile.profileCounted(bytes.length);
+ for (var idx = 0; loopConditionProfile.inject(idx < bytes.length); idx++) {
+ executeIteration(frame, parent, data, (long) idx, (long) bytes[idx]);
}
}
@@ -150,16 +169,32 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
}
private void doEvalObject(VirtualFrame frame, VmObject iterable, Object parent, ObjectData data) {
- var materializedFrame = frame.materialize();
- iterable.forceAndIterateMemberValues(
- (key, member, value) -> {
- var convertedKey = member.isProp() ? key.toString() : key;
- // TODO: Executing iteration behind a Truffle boundary is bad for performance.
- // This and similar cases will be fixed in an upcoming PR that replaces method
- // `(forceAnd)iterateMemberValues` with cursor-based external iterators.
- executeIteration(materializedFrame, parent, data, convertedKey, value);
- return true;
- });
+ // TODO: skip types for module objects?
+ var cursor = iterable.members();
+ if (cursor.getLength() == -1) {
+ doEvalObjectProfiled(frame, parent, data, cursor);
+ } else {
+ doEvalObjectCounted(frame, parent, data, cursor);
+ }
+ }
+
+ private void doEvalObjectCounted(
+ VirtualFrame frame, Object parent, ObjectData data, VmObjectCursor cursor) {
+ loopConditionProfile.profileCounted(cursor.getLength());
+ while (loopConditionProfile.inject(cursor.advance())) {
+ var key = cursor.isProperty() ? cursor.keyToString() : cursor.key();
+ var value = readCursorValueNode.execute(frame, cursor);
+ executeIteration(frame, parent, data, key, value);
+ }
+ }
+
+ private void doEvalObjectProfiled(
+ VirtualFrame frame, Object parent, ObjectData data, VmObjectCursor cursor) {
+ while (loopConditionProfile.profile(cursor.advance())) {
+ var key = cursor.isProperty() ? cursor.keyToString() : cursor.key();
+ var value = readCursorValueNode.execute(frame, cursor);
+ executeIteration(frame, parent, data, key, value);
+ }
}
@ExplodeLoop
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java
index d4b798110..2ad2799c5 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java
@@ -23,8 +23,11 @@ import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.profiles.LoopConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
+import org.pkl.core.ast.internal.ReadCursorValueNode;
+import org.pkl.core.ast.internal.ReadCursorValueNodeGen;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.Identifier;
@@ -41,12 +44,18 @@ import org.pkl.core.runtime.VmMap;
import org.pkl.core.runtime.VmMapping;
import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmObject;
+import org.pkl.core.runtime.VmObjectCursor;
import org.pkl.core.runtime.VmTyped;
import org.pkl.core.runtime.VmUtils;
@ImportStatic(BaseModule.class)
public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
+ private final LoopConditionProfile loopConditionProfile = LoopConditionProfile.create();
@Child private ExpressionNode iterableNode;
+
+ @Child
+ private ReadCursorValueNode readCursorValueNode = ReadCursorValueNodeGen.create(sourceSection);
+
private final boolean nullable;
public GeneratorSpreadNode(
@@ -189,53 +198,73 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
}
protected void doEvalDynamic(VirtualFrame frame, ObjectData data, VmObject iterable) {
- var materializedFrame = frame.materialize();
- iterable.forceAndIterateMemberValues(
- (key, member, value) -> {
- if (member.isElement()) {
- data.addElement(materializedFrame, createMember(member, value), this);
- } else {
- data.addMember(materializedFrame, key, createMember(member, value), this);
- }
- return true;
- });
+ var cursor = iterable.members();
+ var counted = isCounted(cursor);
+ while (advance(cursor, counted)) {
+ var member = cursor.member();
+ var value = readCursorValueNode.execute(frame, cursor);
+ if (member.isElement()) {
+ data.addElement(frame, createMember(member, value), this);
+ } else {
+ data.addMember(frame, cursor.key(), createMember(member, value), this);
+ }
+ }
}
private void doEvalMapping(VirtualFrame frame, ObjectData data, VmObject iterable) {
- var materializedFrame = frame.materialize();
- iterable.forceAndIterateMemberValues(
- (key, member, value) -> {
- if (member.isElement() || member.isProp()) {
- cannotHaveMember(BaseModule.getMappingClass(), member);
- }
- data.addMember(materializedFrame, key, createMember(member, value), this);
- return true;
- });
+ var cursor = iterable.members();
+ var counted = isCounted(cursor);
+ while (advance(cursor, counted)) {
+ var member = cursor.member();
+ if (member.isElement() || member.isProp()) {
+ cannotHaveMember(BaseModule.getMappingClass(), member);
+ }
+ data.addMember(
+ frame,
+ cursor.key(),
+ createMember(member, readCursorValueNode.execute(frame, cursor)),
+ this);
+ }
}
private void doEvalListing(VirtualFrame frame, ObjectData data, VmObject iterable) {
- var materializedFrame = frame.materialize();
- iterable.forceAndIterateMemberValues(
- (key, member, value) -> {
- if (member.isEntry() || member.isProp()) {
- cannotHaveMember(getListingClass(), member);
- }
- data.addElement(materializedFrame, createMember(member, value), this);
- return true;
- });
+ var cursor = iterable.members();
+ var counted = isCounted(cursor);
+ while (advance(cursor, counted)) {
+ var member = cursor.member();
+ if (member.isEntry() || member.isProp()) {
+ cannotHaveMember(getListingClass(), member);
+ }
+ data.addElement(
+ frame, createMember(member, readCursorValueNode.execute(frame, cursor)), this);
+ }
}
private void doEvalTyped(VirtualFrame frame, VmClass clazz, ObjectData data, VmObject iterable) {
- var materializedFrame = frame.materialize();
- iterable.forceAndIterateMemberValues(
- (key, member, value) -> {
- if (member.isElement() || member.isEntry()) {
- cannotHaveMember(clazz, member);
- }
- checkIsValidTypedProperty(clazz, member);
- data.addProperty(materializedFrame, createMember(member, value), this);
- return true;
- });
+ var cursor = iterable.members();
+ var counted = isCounted(cursor);
+ while (advance(cursor, counted)) {
+ var member = cursor.member();
+ if (member.isElement() || member.isEntry()) {
+ cannotHaveMember(clazz, member);
+ }
+ checkIsValidTypedProperty(clazz, member);
+ data.addProperty(
+ frame, createMember(member, readCursorValueNode.execute(frame, cursor)), this);
+ }
+ }
+
+ private boolean isCounted(VmObjectCursor cursor) {
+ var length = cursor.getLength();
+ if (length == -1) return false;
+ loopConditionProfile.profileCounted(length);
+ return true;
+ }
+
+ private boolean advance(VmObjectCursor cursor, boolean counted) {
+ return counted
+ ? loopConditionProfile.inject(cursor.advance())
+ : loopConditionProfile.profile(cursor.advance());
}
// handles both `List` and `Set`
@@ -250,7 +279,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
.withProgramValue("Value", iterable)
.build();
}
- spreadIterable(frame, data, iterable);
+ spreadIterable(frame, data, iterable, iterable.getLength());
}
private void doEvalMap(VirtualFrame frame, VmClass parent, ObjectData data, VmMap iterable) {
@@ -278,7 +307,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
.withProgramValue("Value", iterable)
.build();
}
- spreadIterable(frame, data, iterable);
+ spreadIterable(frame, data, iterable, iterable.getLength());
}
private void doEvalBytes(VirtualFrame frame, VmClass parent, ObjectData data, VmBytes iterable) {
@@ -290,7 +319,7 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
.withProgramValue("Value", iterable)
.build();
}
- spreadIterable(frame, data, iterable);
+ spreadIterable(frame, data, iterable, iterable.getLength());
}
private void cannotHaveMember(VmClass clazz, ObjectMember member) {
@@ -352,9 +381,11 @@ public abstract class GeneratorSpreadNode extends GeneratorMemberNode {
return result;
}
- private void spreadIterable(VirtualFrame frame, ObjectData data, Iterable> iterable) {
+ private void spreadIterable(
+ VirtualFrame frame, ObjectData data, Iterable> iterable, long length) {
+ loopConditionProfile.profileCounted(length);
var iterator = new TruffleIterator<>(iterable);
- while (iterator.hasNext()) {
+ while (loopConditionProfile.inject(iterator.hasNext())) {
var elem = iterator.next();
var member = VmUtils.createSyntheticObjectElement(String.valueOf(data.length()), elem);
data.addElement(frame, member, this);
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java
index f6441af73..bd5e92d2e 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java
@@ -148,7 +148,7 @@ public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLitera
"checkIsValidListingAmendment()",
"checkMaxListingMemberIndex(0)"
})
- protected void evalListingClass(@SuppressWarnings("unused") VmClass parent) {
+ protected Object evalListingClass(@SuppressWarnings("unused") VmClass parent) {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder().unreachableCode().build();
}
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java
index 32f33d4c1..829ece78e 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java
@@ -34,8 +34,9 @@ import org.pkl.core.runtime.*;
import org.pkl.core.util.EconomicMaps;
/**
- * Object literal that contains both elements and entries (and possibly properties). Example: `new
- * foo { "pigeon", [3] = "barn owl" }`
+ * Object literal that contains both elements and entries (and possibly properties).
+ *
+ *
Example: {@code new foo { "pigeon"; [3] = "barn owl" } }
*/
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLiteralNode {
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java
index c627bc2c6..9f27a97c1 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java
@@ -42,6 +42,7 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
protected final boolean isCustomThisScope;
protected final @Nullable FrameDescriptor parametersDescriptor;
@Children protected final UnresolvedTypeNode[] parameterTypes;
+ @Child protected @Nullable AmendFunctionNode amendFunctionNode;
public ObjectLiteralNode(
SourceSection sourceSection,
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinPropertyNode.java
index db630a056..4e852e424 100644
--- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinPropertyNode.java
+++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InferParentWithinPropertyNode.java
@@ -16,9 +16,11 @@
package org.pkl.core.ast.expression.member;
import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.*;
@@ -36,13 +38,14 @@ public abstract class InferParentWithinPropertyNode extends ExpressionNode {
}
@Specialization(guards = "!owner.isPrototype()")
- protected Object evalTypedObject(VirtualFrame frame, VmTyped owner) {
+ protected Object evalTypedObject(
+ VirtualFrame frame, VmTyped owner, @Cached("create()") IndirectCallNode callNode) {
if (isLocalProperty) {
return getLocalPropertyDefaultValue(frame, owner);
}
try {
- var result = VmUtils.readMemberOrNull(owner.getPrototype(), ownPropertyName, false);
+ var result = VmUtils.readMemberOrNull(owner.getPrototype(), ownPropertyName, false, callNode);
assert result != null : "every property has a default";
return result;
} catch (VmUndefinedValueException e) {
diff --git a/pkl-core/src/main/java/org/pkl/core/ast/internal/ReadCursorValueNode.java b/pkl-core/src/main/java/org/pkl/core/ast/internal/ReadCursorValueNode.java
new file mode 100644
index 000000000..7711e0afc
--- /dev/null
+++ b/pkl-core/src/main/java/org/pkl/core/ast/internal/ReadCursorValueNode.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.pkl.core.ast.internal;
+
+import com.oracle.truffle.api.dsl.Bind;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
+import com.oracle.truffle.api.source.SourceSection;
+import org.jspecify.annotations.Nullable;
+import org.pkl.core.ast.PklNode;
+import org.pkl.core.runtime.VmObjectCursor;
+
+public abstract class ReadCursorValueNode extends PklNode {
+ protected ReadCursorValueNode() {}
+
+ protected ReadCursorValueNode(SourceSection sourceSection) {
+ super(sourceSection);
+ }
+
+ public abstract Object execute(VirtualFrame frame, VmObjectCursor cursor);
+
+ @Specialization(guards = "cachedValue != null")
+ protected Object evalCached(
+ @SuppressWarnings("unused") VmObjectCursor cursor,
+ @Bind("cursor.cachedValueOrNull()") Object cachedValue) {
+ return cachedValue;
+ }
+
+ @Specialization(replaces = "evalCached")
+ protected Object eval(
+ VmObjectCursor cursor,
+ @Bind("cursor.cachedValueOrNull()") @SuppressWarnings("unused") @Nullable Object cachedValue,
+ @Cached("create()") IndirectCallNode callNode) {
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+ return cursor.value(callNode);
+ }
+}
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/BaseModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/BaseModule.java
index 3188ee753..a8951a53d 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/BaseModule.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/BaseModule.java
@@ -36,6 +36,10 @@ public final class BaseModule extends StdLibModule {
return AnyClass.instance;
}
+ public static VmClass getObjectClass() {
+ return ObjectClass.instance;
+ }
+
public static VmClass getTypedClass() {
return TypedClass.instance;
}
@@ -256,6 +260,10 @@ public final class BaseModule extends StdLibModule {
static final VmClass instance = loadClass("Any");
}
+ private static final class ObjectClass {
+ static final VmClass instance = loadClass("Object");
+ }
+
private static final class TypedClass {
static final VmClass instance = loadClass("Typed");
}
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java b/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java
index a1a4d1a1b..41cf95ce3 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java
@@ -19,6 +19,7 @@ import static org.pkl.core.runtime.VmUtils.REPL_TEXT;
import static org.pkl.core.runtime.VmUtils.REPL_TEXT_URI;
import com.oracle.truffle.api.frame.FrameDescriptor;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
@@ -67,6 +68,7 @@ import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.externalreader.ExternalReaderProcessException;
import org.pkl.core.module.ModuleKeys;
import org.pkl.core.module.ResolvedModuleKey;
+import org.pkl.core.runtime.VmObjectCursor.CursorOption;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.GlobResolver;
import org.pkl.core.util.GlobResolver.InvalidGlobPatternException;
@@ -174,30 +176,27 @@ public final class CommandSpecParser {
var subcommandNames = new HashMap();
var subcommandsProperty = (VmObject) VmUtils.readMember(commandInfo, Identifier.SUBCOMMANDS);
subcommandsProperty.force(false, false);
- subcommandsProperty.iterateAlreadyForcedMemberValues(
- (key, member, value) -> {
- // track the subcommand index at first
- // replaced by subcommand name in doParse if reading name succeeds
- path.push("subcommands[" + key + "]");
- var spec = doParse((VmTyped) value, path);
- path.pop();
- var existingNameIndex = subcommandNames.get(spec.name());
- if (existingNameIndex != null) {
- throw exceptionBuilder()
- .withSourceSection(member.getSourceSection())
- .evalError(
- "commandSubcommandConflict",
- VmUtils.readMember(commandInfo, Identifier.NAME),
- spec.name(),
- existingNameIndex,
- key)
- .build();
- }
- subcommandNames.put(spec.name(), key);
- subcommands.add(spec);
- return true;
- });
-
+ var callNode = IndirectCallNode.getUncached();
+ for (var cursor = subcommandsProperty.members(CursorOption.ALL_VALUES); cursor.advance(); ) {
+ var value = (VmTyped) cursor.value(callNode);
+ path.push("subcommands[" + cursor.keyToString() + "]");
+ var spec = doParse(value, path);
+ path.pop();
+ var existingNameIndex = subcommandNames.get(spec.name());
+ if (existingNameIndex != null) {
+ throw exceptionBuilder()
+ .withSourceSection(cursor.member().getSourceSection())
+ .evalError(
+ "commandSubcommandConflict",
+ VmUtils.readMember(commandInfo, Identifier.NAME),
+ spec.name(),
+ existingNameIndex,
+ cursor.keyToString())
+ .build();
+ }
+ subcommandNames.put(spec.name(), cursor.key());
+ subcommands.add(spec);
+ }
return subcommands;
}
@@ -1087,7 +1086,10 @@ public final class CommandSpecParser {
// otherwise value is Listing so will export to List
if (!(value instanceof VmListing vmListing)) throw PklBugException.unreachableCode();
var result = new HashSet(vmListing.getLength());
- vmListing.forceAndIterateMemberValues((key, member, val) -> result.add((String) val));
+ var callNode = IndirectCallNode.getUncached();
+ for (var cursor = vmListing.members(); cursor.advance(); ) {
+ result.add((String) cursor.value(callNode));
+ }
return new Fixed(result);
}
@@ -1259,7 +1261,7 @@ public final class CommandSpecParser {
var output = VmUtils.readModuleOutput(evaluated);
return new CommandSpec.Result(
VmUtils.readBytesProperty(output).export(),
- VmUtils.readFilesProperty(output, makeFileOutput));
+ VmUtils.readFilesProperty(output, makeFileOutput, IndirectCallNode.getUncached()));
}
// endregion
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/KeyLookupSuggestions.java b/pkl-core/src/main/java/org/pkl/core/runtime/KeyLookupSuggestions.java
index a0615ab25..28eb33bae 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/KeyLookupSuggestions.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/KeyLookupSuggestions.java
@@ -20,6 +20,7 @@ import java.util.Comparator;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ValueFormatter;
+import org.pkl.core.runtime.VmObjectCursor.CursorOption;
import org.pkl.core.util.StringSimilarity;
public final class KeyLookupSuggestions {
@@ -49,15 +50,13 @@ public final class KeyLookupSuggestions {
public static List forObject(VmObjectLike object, String key) {
var candidates = new ArrayList();
- object.iterateMemberValues(
- (memberKey, member, value) -> {
- if (!(memberKey instanceof String stringKey)) return true;
- var similarity = STRING_SIMILARITY.similarity(stringKey, key);
- if (similarity >= SIMILARITY_THRESHOLD) {
- candidates.add(new Candidate(stringKey, similarity));
- }
- return true;
- });
+ for (var cursor = object.entries(CursorOption.ANY_ORDER); cursor.advance(); ) {
+ if (!(cursor.key() instanceof String stringKey)) continue;
+ var similarity = STRING_SIMILARITY.similarity(stringKey, key);
+ if (similarity >= SIMILARITY_THRESHOLD) {
+ candidates.add(new Candidate(stringKey, similarity));
+ }
+ }
candidates.sort(Comparator.naturalOrder());
return candidates;
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java
index bee4b5532..68259ca84 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java
@@ -15,6 +15,7 @@
*/
package org.pkl.core.runtime;
+import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.io.IOException;
@@ -40,8 +41,6 @@ import org.pkl.core.stdlib.base.PcfRenderer;
import org.pkl.core.util.AnsiStringBuilder;
import org.pkl.core.util.AnsiTheme;
import org.pkl.core.util.EconomicMaps;
-import org.pkl.core.util.MutableBoolean;
-import org.pkl.core.util.MutableReference;
/** Runs test results examples and facts. */
public final class TestRunner {
@@ -84,54 +83,52 @@ public final class TestRunner {
}
private TestSectionResults runFacts(VmTyped testModule) {
- var facts = VmUtils.readMember(testModule, Identifier.FACTS);
+ var callNode = IndirectCallNode.getUncached();
+ var facts = VmUtils.readMember(testModule, Identifier.FACTS, callNode);
if (facts instanceof VmNull) return new TestSectionResults(TestSectionName.FACTS, List.of());
var testResults = new ArrayList();
var factsMapping = (VmMapping) facts;
- factsMapping.forceAndIterateMemberValues(
- (groupKey, groupMember, groupValue) -> {
- var listing = (VmListing) groupValue;
- var name = String.valueOf(groupKey);
- var resultBuilder = new TestResult.Builder(name);
- listing.iterateMembers(
- (idx, member) -> {
- if (member.isLocalOrExternalOrHidden()) {
- return true;
- }
- try {
- var factValue = VmUtils.readMember(listing, idx);
- if (factValue == Boolean.FALSE) {
- if (PowerAssertions.isEnabled() && member.getSourceSection().isAvailable()) {
- try (var valueTracker = valueTrackerFactory.create()) {
- listing.cachedValues.clear();
- VmUtils.readMember(listing, idx);
- var failure =
- factFailure(
- member.getSourceSection(),
- getDisplayUri(member),
- valueTracker.values());
- resultBuilder.addFailure(failure);
- }
- } else {
- var failure =
- factFailure(member.getSourceSection(), getDisplayUri(member), null);
- resultBuilder.addFailure(failure);
- }
- } else {
- resultBuilder.addSuccess();
- }
- } catch (VmException err) {
- var error =
- new TestResults.Error(
- err.getMessage(), err.toPklException(stackFrameTransformer, useColor));
- resultBuilder.addError(error);
- }
- return true;
- });
- testResults.add(resultBuilder.build());
- return true;
- });
+ for (var cursor = factsMapping.entries(); cursor.advance(); ) {
+ var groupKey = cursor.key();
+ var listing = (VmListing) cursor.value(callNode);
+ var name = String.valueOf(groupKey);
+ var resultBuilder = new TestResult.Builder(name);
+ for (var lCursor = listing.elements(); lCursor.advance(); ) {
+ var idx = lCursor.key();
+ var member = lCursor.member();
+ if (member.isLocalOrExternalOrHidden()) {
+ continue;
+ }
+ try {
+ var factValue = lCursor.value(callNode);
+ if (factValue == Boolean.FALSE) {
+ if (PowerAssertions.isEnabled() && member.getSourceSection().isAvailable()) {
+ try (var valueTracker = valueTrackerFactory.create()) {
+ listing.cachedValues.clear();
+ // Must use `readMember` here to force a re-eval of this fact.
+ VmUtils.readMember(listing, idx, callNode);
+ var failure =
+ factFailure(
+ member.getSourceSection(), getDisplayUri(member), valueTracker.values());
+ resultBuilder.addFailure(failure);
+ }
+ } else {
+ var failure = factFailure(member.getSourceSection(), getDisplayUri(member), null);
+ resultBuilder.addFailure(failure);
+ }
+ } else {
+ resultBuilder.addSuccess();
+ }
+ } catch (VmException err) {
+ var error =
+ new TestResults.Error(
+ err.getMessage(), err.toPklException(stackFrameTransformer, useColor));
+ resultBuilder.addError(error);
+ }
+ }
+ testResults.add(resultBuilder.build());
+ }
return new TestSectionResults(TestSectionName.FACTS, Collections.unmodifiableList(testResults));
}
@@ -181,120 +178,111 @@ public final class TestRunner {
private TestSectionResults doRunAndValidateExamples(
VmMapping examples, Path expectedOutputFile, Path actualOutputFile) {
var expectedExampleOutputs = loadExampleOutputs(expectedOutputFile);
- var actualExampleOutputs = new MutableReference(null);
- var allGroupsSucceeded = new MutableBoolean(true);
- var errored = new MutableBoolean(false);
+ VmDynamic actualExampleOutputs = null;
+ var allGroupsSucceeded = true;
+ var errored = false;
var testResults = new ArrayList();
- examples.forceAndIterateMemberValues(
- (groupKey, groupMember, groupValue) -> {
- var testName = String.valueOf(groupKey);
- var group = (VmListing) groupValue;
- var expectedGroup =
- (VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
- var testResultBuilder = new TestResult.Builder(testName);
+ var callNode = IndirectCallNode.getUncached();
- if (expectedGroup == null) {
- testResultBuilder.addFailure(
- examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, true));
- testResults.add(testResultBuilder.build());
- return true;
+ for (var groupCursor = examples.entries(); groupCursor.advance(); ) {
+ var groupKey = groupCursor.key();
+ var groupMember = groupCursor.member();
+ var testName = String.valueOf(groupKey);
+ var group = (VmListing) groupCursor.value(callNode);
+ var expectedGroup = (VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
+ var testResultBuilder = new TestResult.Builder(testName);
+
+ if (expectedGroup == null) {
+ testResultBuilder.addFailure(
+ examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, true));
+ testResults.add(testResultBuilder.build());
+ continue;
+ }
+
+ if (group.getLength() != expectedGroup.getLength()) {
+ testResultBuilder.addFailure(
+ exampleLengthMismatchFailure(
+ getDisplayUri(groupMember),
+ testName,
+ expectedGroup.getLength(),
+ group.getLength()));
+ testResults.add(testResultBuilder.build());
+ continue;
+ }
+
+ for (var exampleCursor = group.elements(); exampleCursor.advance(); ) {
+ var exampleIndex = exampleCursor.key();
+
+ Object exampleValue;
+ try {
+ exampleValue = exampleCursor.value(callNode);
+ } catch (VmException err) {
+ errored = true;
+ testResultBuilder.addError(
+ new TestResults.Error(
+ err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
+ continue;
+ }
+ var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex, callNode);
+
+ var exampleValuePcf = renderAsPcf(exampleValue);
+ var expectedValuePcf = renderAsPcf(expectedValue);
+
+ if (!(exampleValuePcf.equals(expectedValuePcf))) {
+ if (actualExampleOutputs == null) {
+ // immediately write and load `-actual.pcf`
+ // so that we can generate deep link with correct line number for each
+ // mismatch
+ writeExampleOutputs(actualOutputFile, examples);
+ actualExampleOutputs = loadExampleOutputs(actualOutputFile);
}
- if (group.getLength() != expectedGroup.getLength()) {
- testResultBuilder.addFailure(
- exampleLengthMismatchFailure(
- getDisplayUri(groupMember),
- testName,
- expectedGroup.getLength(),
- group.getLength()));
- testResults.add(testResultBuilder.build());
- return true;
+ var expectedMember = VmUtils.findMember(expectedGroup, exampleIndex);
+ assert expectedMember != null;
+
+ var actualGroup = (VmObjectLike) VmUtils.readMemberOrNull(actualExampleOutputs, groupKey);
+ var actualMember =
+ actualGroup == null ? null : VmUtils.findMember(actualGroup, exampleIndex);
+ if (actualMember == null) {
+ // file was written earlier in this method;
+ // must have been tampered with by another process
+ throw new VmExceptionBuilder()
+ .evalError("invalidOutputFileStructure", actualOutputFile)
+ .build();
}
- group.iterateMembers(
- ((exampleIndex, exampleMember) -> {
- if (exampleMember.isLocalOrExternalOrHidden()) {
- return true;
- }
+ testResultBuilder.addFailure(
+ exampleFailure(
+ getDisplayUri(exampleCursor.member()),
+ getDisplayUri(expectedMember),
+ expectedValuePcf,
+ getDisplayUri(actualMember),
+ exampleValuePcf,
+ testResultBuilder.getCount()));
+ } else {
+ testResultBuilder.addSuccess();
+ }
+ }
- Object exampleValue;
- try {
- exampleValue = VmUtils.readMember(group, exampleIndex);
- } catch (VmException err) {
- errored.set(true);
- testResultBuilder.addError(
- new TestResults.Error(
- err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
- return true;
- }
- var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex);
+ testResults.add(testResultBuilder.build());
+ }
- var exampleValuePcf = renderAsPcf(exampleValue);
- var expectedValuePcf = renderAsPcf(expectedValue);
+ for (var groupCursor = expectedExampleOutputs.entries(); groupCursor.advance(); ) {
+ var groupKey = groupCursor.key();
+ if (examples.getCachedValue(groupKey) == null) {
+ var testName = String.valueOf(groupKey);
+ allGroupsSucceeded = false;
+ var result =
+ new TestResult.Builder(testName)
+ .addFailure(
+ examplePropertyMismatchFailure(
+ getDisplayUri(groupCursor.member()), testName, false))
+ .build();
+ testResults.add(result);
+ }
+ }
- if (!(exampleValuePcf.equals(expectedValuePcf))) {
- if (actualExampleOutputs.isNull()) {
- // immediately write and load `-actual.pcf`
- // so that we can generate deep link with correct line number for each
- // mismatch
- writeExampleOutputs(actualOutputFile, examples);
- actualExampleOutputs.set(loadExampleOutputs(actualOutputFile));
- }
-
- var expectedMember = VmUtils.findMember(expectedGroup, exampleIndex);
- assert expectedMember != null;
-
- var actualGroup =
- (VmObjectLike) VmUtils.readMemberOrNull(actualExampleOutputs.get(), groupKey);
- var actualMember =
- actualGroup == null ? null : VmUtils.findMember(actualGroup, exampleIndex);
- if (actualMember == null) {
- // file was written earlier in this method;
- // must have been tampered with by another process
- throw new VmExceptionBuilder()
- .evalError("invalidOutputFileStructure", actualOutputFile)
- .build();
- }
-
- testResultBuilder.addFailure(
- exampleFailure(
- getDisplayUri(exampleMember),
- getDisplayUri(expectedMember),
- expectedValuePcf,
- getDisplayUri(actualMember),
- exampleValuePcf,
- testResultBuilder.getCount()));
- } else {
- testResultBuilder.addSuccess();
- }
-
- return true;
- }));
-
- testResults.add(testResultBuilder.build());
-
- return true;
- });
-
- expectedExampleOutputs.iterateMembers(
- (groupKey, groupMember) -> {
- if (groupMember.isLocalOrExternalOrHidden()) {
- return true;
- }
- if (examples.getCachedValue(groupKey) == null) {
- var testName = String.valueOf(groupKey);
- allGroupsSucceeded.set(false);
- var result =
- new TestResult.Builder(testName)
- .addFailure(
- examplePropertyMismatchFailure(getDisplayUri(groupMember), testName, false))
- .build();
- testResults.add(result);
- }
- return true;
- });
-
- if (!allGroupsSucceeded.get() && actualExampleOutputs.isNull() && !errored.get()) {
+ if (!allGroupsSucceeded && actualExampleOutputs == null && !errored) {
writeExampleOutputs(actualOutputFile, examples);
}
return new TestSectionResults(
@@ -303,40 +291,33 @@ public final class TestRunner {
private TestSectionResults doRunAndWriteExamples(VmMapping examples, Path outputFile) {
var testResults = new ArrayList();
- var allSucceeded = new MutableBoolean(true);
- examples.forceAndIterateMemberValues(
- (groupKey, groupMember, groupValue) -> {
- var testName = String.valueOf(groupKey);
- var listing = (VmListing) groupValue;
- var testResultBuilder = new TestResult.Builder(testName);
- var success = new MutableBoolean(true);
- listing.iterateMembers(
- (idx, member) -> {
- if (member.isLocalOrExternalOrHidden()) {
- return true;
- }
- try {
- VmUtils.readMember(listing, idx);
- return true;
- } catch (VmException err) {
- testResultBuilder.addError(
- new TestResults.Error(
- err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
- allSucceeded.set(false);
- success.set(false);
- return true;
- }
- });
- if (success.get()) {
- // treat writing an example as a message
- testResultBuilder.setExampleWritten(true);
- testResultBuilder.addFailure(
- writtenExampleOutputFailure(testName, getDisplayUri(groupMember)));
- }
- testResults.add(testResultBuilder.build());
- return true;
- });
- if (allSucceeded.get()) {
+ var allSucceeded = true;
+ var callNode = IndirectCallNode.getUncached();
+ for (var cursor = examples.entries(); cursor.advance(); ) {
+ var testName = cursor.keyToString();
+ var groupMember = cursor.member();
+ var listing = (VmListing) cursor.value(callNode);
+ var testResultBuilder = new TestResult.Builder(testName);
+ var success = true;
+ for (var listingCursor = listing.elements(); listingCursor.advance(); ) {
+ try {
+ listingCursor.value(callNode);
+ } catch (VmException err) {
+ testResultBuilder.addError(
+ new TestResults.Error(
+ err.getMessage(), err.toPklException(stackFrameTransformer, useColor)));
+ allSucceeded = false;
+ success = false;
+ }
+ }
+ if (success) {
+ testResultBuilder.setExampleWritten(true);
+ testResultBuilder.addFailure(
+ writtenExampleOutputFailure(testName, getDisplayUri(groupMember)));
+ }
+ testResults.add(testResultBuilder.build());
+ }
+ if (allSucceeded) {
writeExampleOutputs(outputFile, examples);
}
return new TestSectionResults(
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java
index 08ade02f0..6620a8eec 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java
@@ -864,21 +864,14 @@ public final class VmClass extends VmValue {
var size = superclass == null ? 0 : superclass.getAllRegularPropertyNames().size();
var result = EconomicSet.create(size);
+ if (superclass != null) {
+ result.addAll(superclass.getAllRegularPropertyNames());
+ }
for (var property : EconomicMaps.getValues(declaredProperties)) {
if (!(property.isLocal() || isHiddenProperty(property.getName()) || property.isExternal())) {
result.add(property.getName());
}
}
-
- if (superclass == null) {
- return result;
- }
-
- if (result.isEmpty()) {
- return superclass.getAllRegularPropertyNames();
- }
-
- result.addAll(superclass.getAllRegularPropertyNames());
return result;
}
diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmCollection.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmCollection.java
index 4d4bfbb59..0709e3870 100644
--- a/pkl-core/src/main/java/org/pkl/core/runtime/VmCollection.java
+++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmCollection.java
@@ -17,8 +17,11 @@ package org.pkl.core.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.frame.VirtualFrame;
import java.util.Iterator;
import org.organicdesign.fp.xform.Xform;
+import org.pkl.core.ast.internal.ReadCursorValueNode;
+import org.pkl.core.runtime.VmObjectCursor.CursorOption;
public abstract class VmCollection extends VmValue implements Iterable