Improve partial-evaluation when amending null parents (#1836)

GraalVM's partial evaulator can't handle recursive calls into the same
node.

The current implementation around evaluating `VmNull` as a parent causes
the partial evaluator to bail out, leaving Pkl stuck in interpreter
mode.

This rewrites the various object literals to add individual
specializations for each type of default value we can see from a VmNull
parent.

Also:
* Fix `isTypeObjectClass` impl
* Fix bug when amending with generator object literal node with object params
This commit is contained in:
Daniel Chao
2026-09-02 18:18:55 +00:00
committed by GitHub
parent 2c2f50113d
commit 0035199176
29 changed files with 1038 additions and 41 deletions
@@ -17,6 +17,7 @@ package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Idempotent;
@@ -34,7 +35,7 @@ import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*;
/** An object literal node that contains at least one for- or when-expression. */
@ImportStatic(BaseModule.class)
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
@Children private final GeneratorMemberNode[] memberNodes;
@@ -80,6 +81,20 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
return data.storeGeneratorFrames(result);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isDynamicClass()", "checkObjectCannotHaveParameters()"})
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
var parentDynamic = (VmDynamic) defaultValue;
var data = executeChildren(frame, parentDynamic, parentDynamic.getLength());
if (data.hasNoMembers()) {
return parentDynamic;
}
var result = new VmDynamic(frame.materialize(), parentDynamic, data.members(), data.length());
return data.storeGeneratorFrames(result);
}
@Specialization(guards = "checkObjectCannotHaveParameters()")
protected VmTyped evalTyped(VirtualFrame frame, VmTyped parent) {
VmUtils.checkIsInstantiable(parent.getVmClass(), getParentNode());
@@ -91,6 +106,14 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
return new VmTyped(frame.materialize(), parent, parent.getVmClass(), data.members());
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isTypedObjectClass(getClass(defaultValue))", "checkObjectCannotHaveParameters()"})
protected Object evalNullWithTypedDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalTyped(frame, (VmTyped) defaultValue);
}
@Specialization(guards = "checkListingCannotHaveParameters()")
protected VmListing evalListing(VirtualFrame frame, VmListing parent) {
var data = executeChildren(frame, parent, parent.getLength());
@@ -101,6 +124,14 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
return data.storeGeneratorFrames(result);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkListingCannotHaveParameters()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalListing(frame, (VmListing) defaultValue);
}
@Specialization(guards = "checkMappingCannotHaveParameters()")
protected VmMapping evalMapping(VirtualFrame frame, VmMapping parent) {
var data = executeChildren(frame, parent, 0);
@@ -111,10 +142,12 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
return data.storeGeneratorFrames(result);
}
@Specialization(guards = "checkObjectCannotHaveParameters()")
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isMappingClass()", "checkMappingCannotHaveParameters()"})
protected Object evalNullWithMappingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalMapping(frame, (VmMapping) defaultValue);
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
@@ -127,6 +160,18 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(guards = {"parent == getDynamicClass()", "checkObjectCannotHaveParameters()"})
protected VmDynamic evalDynamicClass(VirtualFrame frame, VmClass parent) {
var data = executeChildren(frame, parent, 0);
@@ -173,8 +218,13 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
@Fallback
@TruffleBoundary
protected void fallback(Object parent) {
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
VmUtils.checkIsInstantiable(
parent instanceof VmClass vmClass ? vmClass : VmUtils.getClass(parent), getParentNode());
value instanceof VmClass vmClass ? vmClass : VmUtils.getClass(value), getParentNode());
throw exceptionBuilder().unreachableCode().build();
}
@@ -186,7 +236,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("objectAmendmentCannotHaveParameters")
.withLocation(parameterTypes[0])
.withLocation(getParentNode())
.build();
}
@@ -197,7 +247,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("listingAmendmentCannotHaveParameters")
.withLocation(parameterTypes[0])
.withLocation(getParentNode())
.build();
}
@@ -208,7 +258,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode {
CompilerDirectives.transferToInterpreter();
throw exceptionBuilder()
.evalError("mappingAmendmentCannotHaveParameters")
.withLocation(parameterTypes[0])
.withLocation(getParentNode())
.build();
}
@@ -33,7 +33,7 @@ import org.pkl.core.runtime.*;
* all entry keys are constants. Example: `new foo { ["one"] = 1 }`
*/
// IDEA: don't materialize frames if all members have constant values
@ImportStatic(BaseModule.class)
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLiteralNode {
public ConstantEntriesLiteralNode(
SourceSection sourceSection,
@@ -73,21 +73,38 @@ public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLitera
return new VmMapping(frame.materialize(), parent, members);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isMappingClass()", "checkIsValidMappingAmendment()"})
protected Object evalNullWithMappingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalMapping(frame, (VmMapping) defaultValue);
}
@Specialization
protected VmDynamic evalDynamic(VirtualFrame frame, VmDynamic parent) {
return new VmDynamic(frame.materialize(), parent, members, parent.getLength());
}
@SuppressWarnings("unused")
@Specialization(guards = "getClass(defaultValue).isDynamicClass()")
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalDynamic(frame, (VmDynamic) defaultValue);
}
@Specialization(guards = "checkIsValidListingAmendment()")
protected VmListing evalListing(VirtualFrame frame, VmListing parent) {
checkMaxListingMemberIndex(parent.getLength());
return new VmListing(frame.materialize(), parent, members, parent.getLength());
}
@Specialization
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkIsValidListingAmendment()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalListing(frame, (VmListing) defaultValue);
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
@@ -100,6 +117,18 @@ public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLitera
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(guards = {"parent == getMappingClass()", "checkIsValidMappingAmendment()"})
protected VmMapping evalMappingClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
@@ -127,6 +156,11 @@ public abstract class ConstantEntriesLiteralNode extends SpecializedObjectLitera
@Fallback
@TruffleBoundary
protected void fallback(Object parent) {
elementsEntriesFallback(parent, findFirstNonProperty(members), false);
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
elementsEntriesFallback(value, findFirstNonProperty(members), false);
}
}
@@ -16,6 +16,7 @@
package org.pkl.core.ast.expression.literal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.ImportStatic;
@@ -36,7 +37,7 @@ import org.pkl.core.util.EconomicMaps;
* Object literal that contains both elements and entries (and possibly properties). Example: `new
* foo { "pigeon", [3] = "barn owl" }`
*/
@ImportStatic(BaseModule.class)
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLiteralNode {
private final ObjectMember[] elements;
@Children private final ExpressionNode[] keyNodes;
@@ -95,6 +96,14 @@ public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLitera
parent.getLength() + elements.length);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkIsValidListingAmendment()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalListing(frame, (VmListing) defaultValue);
}
@Specialization
protected VmDynamic evalDynamic(VirtualFrame frame, VmDynamic parent) {
return new VmDynamic(
@@ -104,10 +113,11 @@ public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLitera
parent.getLength() + elements.length);
}
@Specialization
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(guards = "getClass(defaultValue).isDynamicClass()")
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalDynamic(frame, (VmDynamic) defaultValue);
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
@@ -120,6 +130,18 @@ public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLitera
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(guards = {"parent == getListingClass()", "checkIsValidListingAmendment()"})
protected VmListing evalListingClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
@@ -145,7 +167,12 @@ public abstract class ElementsEntriesLiteralNode extends SpecializedObjectLitera
@Fallback
@TruffleBoundary
protected void fallback(Object parent) {
elementsEntriesFallback(parent, elements[0], true);
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
elementsEntriesFallback(value, elements[0], true);
}
@ExplodeLoop
@@ -32,7 +32,7 @@ import org.pkl.core.util.EconomicMaps;
* Object literal that contains elements (and possibly properties) but not entries. Example: `new
* foo { "pigeon" }`
*/
@ImportStatic(BaseModule.class)
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class ElementsLiteralNode extends SpecializedObjectLiteralNode {
private final ObjectMember[] elements;
@@ -94,10 +94,11 @@ public abstract class ElementsLiteralNode extends SpecializedObjectLiteralNode {
parent.getLength() + elements.length);
}
@Specialization
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(guards = "getClass(defaultValue).isDynamicClass()")
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalDynamicUncached(frame, (VmDynamic) defaultValue);
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
@@ -110,6 +111,18 @@ public abstract class ElementsLiteralNode extends SpecializedObjectLiteralNode {
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(
guards = {
"parent == getListingClass()",
@@ -162,10 +175,23 @@ public abstract class ElementsLiteralNode extends SpecializedObjectLiteralNode {
parent.getLength() + elements.length);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkIsValidListingAmendment()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalListingUncached(frame, (VmListing) defaultValue);
}
@Fallback
@TruffleBoundary
protected void fallback(Object parent) {
elementsEntriesFallback(parent, elements[0], true);
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
elementsEntriesFallback(value, elements[0], true);
}
// offset element keys according to parentLength
@@ -38,7 +38,7 @@ import org.pkl.core.util.EconomicMaps;
* used.) Example: `foo { ["on" + "e"] = 1 }`
*/
// IDEA: don't materialize frames if all members have constant values
@ImportStatic(BaseModule.class)
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
@Children private final ExpressionNode[] keyNodes;
private final ObjectMember[] values;
@@ -92,11 +92,26 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
return new VmMapping(frame.materialize(), parent, createMapMembers(frame));
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isMappingClass()", "checkIsValidMappingAmendment()"})
protected Object evalNullWithMappingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalMapping(frame, (VmMapping) defaultValue);
}
@Specialization
protected VmDynamic evalDynamic(VirtualFrame frame, VmDynamic parent) {
return new VmDynamic(frame.materialize(), parent, createMapMembers(frame), parent.getLength());
}
@SuppressWarnings("unused")
@Specialization(guards = {"getClass(defaultValue).isDynamicClass()"})
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalDynamic(frame, (VmDynamic) defaultValue);
}
@Specialization(guards = "checkIsValidListingAmendment()")
protected VmListing evalListing(VirtualFrame frame, VmListing parent) {
return new VmListing(
@@ -107,10 +122,12 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
parent.getLength());
}
@Specialization
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkIsValidListingAmendment()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalListing(frame, (VmListing) defaultValue);
}
@Specialization(guards = "checkIsValidFunctionAmendment(parent)")
@@ -123,6 +140,18 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(guards = {"parent == getMappingClass()", "checkIsValidMappingAmendment()"})
protected VmMapping evalMappingClass(
VirtualFrame frame, @SuppressWarnings("unused") VmClass parent) {
@@ -152,7 +181,12 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
@Fallback
@TruffleBoundary
protected Object fallback(Object parent) {
return elementsEntriesFallback(parent, values[0], false);
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
return elementsEntriesFallback(value, values[0], false);
}
@ExplodeLoop
@@ -20,14 +20,17 @@ import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.source.SourceSection;
import org.jspecify.annotations.Nullable;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.VmClass;
import org.pkl.core.runtime.VmFunction;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmNull;
import org.pkl.core.runtime.VmUtils;
// IDEA: don't materialize frames when all members are constants
@@ -57,8 +60,6 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
protected abstract ExpressionNode getParentNode();
protected abstract Object executeWithParent(VirtualFrame frame, Object parent);
protected abstract ObjectLiteralNode copy(ExpressionNode newParentNode);
protected final AmendFunctionNode createAmendFunctionNode(VirtualFrame frame) {
@@ -71,7 +72,14 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
@Idempotent
protected static boolean isTypedObjectClass(VmClass clazz) {
return !(clazz.isListingClass() || clazz.isMappingClass() || clazz.isDynamicClass());
if (clazz.isListingClass()
|| clazz.isMappingClass()
|| clazz.isDynamicClass()
|| clazz.isFunctionClass()
|| clazz.isFunctionNClass()) {
return false;
}
return BaseModule.getTypedClass().isSuperclassOf(clazz);
}
protected final boolean checkIsValidFunctionAmendment(VmFunction parent) {
@@ -85,4 +93,25 @@ public abstract class ObjectLiteralNode extends ExpressionNode {
}
return true;
}
@Idempotent
protected final boolean checkIsValidFunctionAmendment(Object parent) {
return checkIsValidFunctionAmendment((VmFunction) parent);
}
@Idempotent
protected final boolean isFunction(Object value) {
return value instanceof VmFunction;
}
protected final Object getNullDefaultValue(VmNull parent) {
var value = parent.getDefaultValue();
var count = 0;
while (value instanceof VmNull n) {
value = n.getDefaultValue();
count++;
}
LoopNode.reportLoopCount(this, count);
return value;
}
}
@@ -16,7 +16,9 @@
package org.pkl.core.ast.expression.literal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -31,6 +33,7 @@ import org.pkl.core.runtime.*;
/** Object literal that contains properties but not elements or entries. */
// IDEA: don't materialize frame when all members are constants
@ImportStatic({BaseModule.class, VmUtils.class})
public abstract class PropertiesLiteralNode extends SpecializedObjectLiteralNode {
public PropertiesLiteralNode(
SourceSection sourceSection,
@@ -82,26 +85,77 @@ public abstract class PropertiesLiteralNode extends SpecializedObjectLiteralNode
return new VmTyped(frame.materialize(), parent, parent.getVmClass(), members);
}
@SuppressWarnings("unused")
@Specialization(
guards = {
"parentClass == getClass(defaultValue)",
"isTypedObjectClass(parentClass)",
"checkIsValidTypedAmendment(parentClass)"
})
protected Object evalNullWithTypedObjectDefaultCached(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached("getClass(defaultValue)") VmClass parentClass) {
var parentTyped = (VmTyped) defaultValue;
return new VmTyped(frame.materialize(), parentTyped, parentTyped.getVmClass(), members);
}
@SuppressWarnings("unused")
@Specialization(
guards = {
"isTypedObjectClass(getClass(defaultValue))",
"checkIsValidTypedAmendment(defaultValue)"
})
protected Object evalNullWithTypedObjectDefaultUncached(
VirtualFrame frame,
VmNull parent,
@Bind(value = "getNullDefaultValue(parent)") Object defaultValue) {
var parentTyped = (VmTyped) defaultValue;
return new VmTyped(frame.materialize(), parentTyped, parentTyped.getVmClass(), members);
}
@Specialization
protected Object evalDynamic(VirtualFrame frame, VmDynamic parent) {
return new VmDynamic(frame.materialize(), parent, members, parent.getLength());
}
@SuppressWarnings("unused")
@Specialization(guards = "getClass(defaultValue).isDynamicClass()")
protected Object evalNullWithDynamicDefault(
VirtualFrame frame, VmNull parent, @Bind("getNullDefaultValue(parent)") Object defaultValue) {
return evalDynamic(frame, (VmDynamic) defaultValue);
}
@Specialization(guards = "checkIsValidListingAmendment()")
protected Object evalListing(VirtualFrame frame, VmListing parent) {
return new VmListing(frame.materialize(), parent, members, parent.getLength());
}
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isListingClass()", "checkIsValidListingAmendment()"})
protected Object evalNullWithListingDefault(
VirtualFrame frame,
VmNull parent,
@Bind(value = "getNullDefaultValue(parent)") Object defaultValue) {
return evalListing(frame, (VmListing) defaultValue);
}
@ExplodeLoop
@Specialization(guards = "checkIsValidMappingAmendment()")
protected Object evalMapping(VirtualFrame frame, VmMapping parent) {
return new VmMapping(frame.materialize(), parent, members);
}
@Specialization
protected Object evalNull(VirtualFrame frame, VmNull parent) {
// assumes that Graal PE can handle recursive call to same node
return executeWithParent(frame, parent.getDefaultValue());
@SuppressWarnings("unused")
@Specialization(
guards = {"getClass(defaultValue).isMappingClass()", "checkIsValidMappingAmendment()"})
protected Object evalNullWithMappingDefault(
VirtualFrame frame,
VmNull parent,
@Bind(value = "getNullDefaultValue(parent)") Object defaultValue) {
return evalMapping(frame, (VmMapping) defaultValue);
}
// Ultimately, this lambda or a lambda returned from it will call one of the other
@@ -121,6 +175,18 @@ public abstract class PropertiesLiteralNode extends SpecializedObjectLiteralNode
return amendFunctionNode.execute(frame, parent);
}
@SuppressWarnings("unused")
@Specialization(
guards = {"isFunction(defaultValue)", "checkIsValidFunctionAmendment(defaultValue)"})
protected Object evalNullWithFunctionDefault(
VirtualFrame frame,
VmNull parent,
@Bind("getNullDefaultValue(parent)") Object defaultValue,
@Cached(value = "createAmendFunctionNode(frame)", neverDefault = true)
AmendFunctionNode amendFunctionNode) {
return evalFunction(frame, (VmFunction) defaultValue, amendFunctionNode);
}
@Specialization(
guards = {
"parent == cachedParent",
@@ -199,8 +265,13 @@ public abstract class PropertiesLiteralNode extends SpecializedObjectLiteralNode
@Specialization
@TruffleBoundary
protected void fallback(Object parent) {
var value = parent;
// blame the non-null type (e.g. blame `Duration` instead of `Null` in the case of `Duration?`)
if (value instanceof VmNull vmNull) {
value = getNullDefaultValue(vmNull);
}
// should always throw
checkIsValidTypedAmendment(parent);
checkIsValidTypedAmendment(value);
throw exceptionBuilder().unreachableCode().build();
}
@@ -19,6 +19,7 @@ import static org.pkl.core.PClassInfo.pklBaseUri;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Idempotent;
public final class BaseModule extends StdLibModule {
static final VmTyped instance = VmUtils.createEmptyModule();
@@ -91,6 +92,7 @@ public final class BaseModule extends StdLibModule {
return SetClass.instance;
}
@Idempotent
public static VmClass getListingClass() {
return ListingClass.instance;
}
@@ -99,10 +101,12 @@ public final class BaseModule extends StdLibModule {
return MapClass.instance;
}
@Idempotent
public static VmClass getMappingClass() {
return MappingClass.instance;
}
@Idempotent
public static VmClass getDynamicClass() {
return DynamicClass.instance;
}
@@ -135,6 +139,7 @@ public final class BaseModule extends StdLibModule {
return RegexMatchClass.instance;
}
@Idempotent
public static VmClass getFunctionClass() {
return FunctionClass.instance;
}
@@ -21,6 +21,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.dsl.Idempotent;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.nodes.*;
import com.oracle.truffle.api.source.Source;
@@ -528,6 +529,7 @@ public final class VmUtils {
}
// implements same behavior as AnyNodes#getClass
@Idempotent
public static VmClass getClass(Object value) {
if (value instanceof VmValue vmValue) {
return vmValue.getVmClass();
@@ -0,0 +1,9 @@
class Person { name: String }
hidden x: Mixin<Person>?
local y = (x) {
name = "Fred"
}
res = y.apply(new Person {})
@@ -26,6 +26,25 @@ res2 = new Listing {
new Mapping { ["one"] = 1; ["two"] = 2 }
}
res2n = (Null(new Listing {})) {
null
true
42
1.23
"foo"
Regex("bar")
5.gb
3.min
Pair(1, 2)
List(1, 2, 3)
Set(1, 2, 3)
Map("one", 1, "two", 2)
new Dynamic { name = "Pigeon"; age = 42 }
new Person { name = "Pigeon"; age = 42 }
new Listing { 1; 2; 3 }
new Mapping { ["one"] = 1; ["two"] = 2 }
}
res3 = new Listing {
id(null)
id(true)
@@ -44,3 +63,22 @@ res3 = new Listing {
id(new Listing { 1; 2; 3 })
id(new Mapping { ["one"] = 1; ["two"] = 2 })
}
res3n = (Null(new Listing {})) {
id(null)
id(true)
id(42)
id(1.23)
id("foo")
id(Regex("bar"))
id(5.gb)
id(3.min)
id(Pair(1, 2))
id(List(1, 2, 3))
id(Set(1, 2, 3))
id(Map("one", 1, "two", 2))
id(new Dynamic { name = "Pigeon"; age = 42 })
id(new Person { name = "Pigeon"; age = 42 })
id(new Listing { 1; 2; 3 })
id(new Mapping { ["one"] = 1; ["two"] = 2 })
}
@@ -73,11 +73,20 @@ res8 = test.catch(() -> (x) { ["wrong type"] = "value" })
res8a = test.catch(() -> (x) { [id("wrong type")] = id("value") })
res9 = test.catch(() -> new Listing { foo = "bar" })
res9n = test.catch(() -> (Null(new Listing {})) { foo = "bar" })
res10 = test.catch(() -> (x) { foo = "foo" })
res10n = test.catch(() -> (Null(x)) { foo = "foo" })
res11 = test.catch(() -> new Listing { "one"; foo = "foo" })
res11n = test.catch(() -> (Null(new Listing { "one" })) { foo = "foo" })
res12 = test.catch(() -> (x) { "four"; foo = "foo" })
res12n = test.catch(() -> (Null(x)) { "four"; foo = "foo" })
res12a = test.catch(() -> (x) { id("four"); foo = "foo" })
res12an = test.catch(() -> (Null(x)) { id("four"); foo = "foo" })
res13 = test.catch(() -> (x) { [1] = "updated two"; foo = "foo" })
res13n = test.catch(() -> (Null(x)) { [1] = "updated two"; foo = "foo" })
res13a = test.catch(() -> (x) { [id(1)] = id("updated two"); foo = "foo" })
res13an = test.catch(() -> (Null(x)) { [id(1)] = id("updated two"); foo = "foo" })
res14 = test.catch(() -> (x) { "four"; [1] = "updated two"; foo = "foo" })
res14n = test.catch(() -> (Null(x)) { "four"; [1] = "updated two"; foo = "foo" })
res14a = test.catch(() -> (x) { id("four"); [id(1)] = id("updated two"); foo = "foo" })
res14an = test.catch(() -> (Null(x)) { id("four"); [id(1)] = id("updated two"); foo = "foo" })
@@ -26,6 +26,25 @@ res2 = new Mapping {
[new Mapping { ["one"] = 1; ["two"] = 2 }] = new Mapping { ["one"] = 1; ["two"] = 2 }
}
res2n = (Null(new Mapping {})) {
[null] = null
[true] = true
[42] = 42
[1.23] = 1.23
["foo"] = "foo"
[Regex("bar")] = Regex("bar")
[5.gb] = 5.gb
[3.min] = 3.min
[Pair(1, 2)] = Pair(1, 2)
[List(1, 2, 3)] = List(1, 2, 3)
[Set(1, 2, 3)] = Set(1, 2, 3)
[Map("one", 1, "two", 2)] = Map("one", 1, "two", 2)
[new Dynamic { name = "Pigeon"; age = 42 }] = new Dynamic { name = "Pigeon"; age = 42 }
[new Person { name = "Pigeon"; age = 42 }] = new Person { name = "Pigeon"; age = 42 }
[new Listing { 1; 2; 3 }] = new Listing { 1; 2; 3 }
[new Mapping { ["one"] = 1; ["two"] = 2 }] = new Mapping { ["one"] = 1; ["two"] = 2 }
}
res3 = new Mapping {
[id(null)] = id(null)
[id(true)] = id(true)
@@ -44,3 +63,34 @@ res3 = new Mapping {
[id(new Listing { 1; 2; 3 })] = id(new Listing { 1; 2; 3 })
[id(new Mapping { ["one"] = 1; ["two"] = 2 })] = id(new Mapping { ["one"] = 1; ["two"] = 2 })
}
res3n = (Null(new Mapping {})) {
[id(null)] = id(null)
[id(true)] = id(true)
[id(42)] = id(42)
[id(1.23)] = id(1.23)
[id("foo")] = id("foo")
[id(Regex("bar"))] = id(Regex("bar"))
[id(5.gb)] = id(5.gb)
[id(3.min)] = id(3.min)
[id(Pair(1, 2))] = id(Pair(1, 2))
[id(List(1, 2, 3))] = id(List(1, 2, 3))
[id(Set(1, 2, 3))] = id(Set(1, 2, 3))
[id(Map("one", 1, "two", 2))] = id(Map("one", 1, "two", 2))
[id(new Dynamic { name = "Pigeon"; age = 42 })] = id(new Dynamic { name = "Pigeon"; age = 42 })
[id(new Person { name = "Pigeon"; age = 42 })] = id(new Person { name = "Pigeon"; age = 42 })
[id(new Listing { 1; 2; 3 })] = id(new Listing { 1; 2; 3 })
[id(new Mapping { ["one"] = 1; ["two"] = 2 })] = id(new Mapping { ["one"] = 1; ["two"] = 2 })
}
// ConstantEntriesLiteralNode
res4 = new Mapping {
["foo"] = "1"
["bar"] = "2"
}
// ConstantEntriesLiteralNode
res4n = (Null(new Mapping {})) {
["foo"] = "1"
["bar"] = "2"
}
@@ -0,0 +1,47 @@
// Covers ConstantEntriesLiteralNode's specializations for amending a `VmNull` whose default value
// is each of the underlying parent types it supports. All entry keys below are constants, so
// these bodies compile to ConstantEntriesLiteralNode rather than EntriesLiteralNode.
import "pkl:test"
// evalNullWithMappingDefault
local mapping = Null(new Mapping { ["a"] = 1 })
res1 = (mapping) {
["b"] = 2
}
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { a = 1 })
res2 = (dynamic) {
["b"] = 2
}
// evalNullWithListingDefault
local listing = Null(new Listing { "a"; "b" })
res3 = (listing) {
[0] = "updated a"
}
// evalNullWithFunctionDefault
local fun = Null(() -> new Mapping { ["a"] = 1 })
local amendedFun = (fun) {
["b"] = 2
}
res4 = amendedFun.apply()
// error: listing entry index out of range
res5 = test.catch(() -> (Null(new Listing { "a" })) { [5] = "value" })
// error: listing cannot have a property (other than `default`)
res6 = test.catch(() -> (Null(new Listing { "a" })) { [0] = "updated a"; foo = "bar" })
// error: mapping cannot have a property (other than `default`)
res7 = test.catch(() -> (Null(new Mapping { ["a"] = 1 })) { ["b"] = 2; foo = "bar" })
// error: function amendment with wrong parameter count
local twoArgFun = (x, y) -> new Mapping {}
res8 = test.catch(() -> (Null(twoArgFun)) { x -> ["a"] = 1 })
hidden foo: "foo"?
// error: default value's type has no matching specialization; falls back to the generic error
res9 = test.catch(() -> (foo) { ["a"] = 1 })
@@ -0,0 +1,37 @@
// Covers ElementsLiteralNode's specializations for amending a `VmNull` whose default value is
// each of the underlying parent types it supports.
import "pkl:test"
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { a = 1 })
res1 = (dynamic) {
"b"
"c"
}
// evalNullWithListingDefault
local listing = Null(new Listing { "a" })
res2 = (listing) {
"b"
"c"
}
// evalNullWithFunctionDefault
local fun = Null(() -> new Listing { "a" })
local amendedFun = (fun) {
"b"
}
res3 = amendedFun.apply()
// error: mapping cannot have an element
res4 = test.catch(() -> (Null(new Mapping { ["a"] = 1 })) { "b" })
// error: listing cannot have a property (other than `default`)
res5 = test.catch(() -> (Null(new Listing { "a" })) { "b"; foo = "bar" })
// error: function amendment with wrong parameter count
local twoArgFun = (x, y) -> new Listing {}
res6 = test.catch(() -> (Null(twoArgFun)) { x -> "b" })
// error: default value's type has no matching specialization; falls back to the generic error
res7 = test.catch(() -> (Null(42)) { "b" })
@@ -0,0 +1,45 @@
// Covers ElementsEntriesLiteralNode's specializations for amending a `VmNull` whose default value
// is each of the underlying parent types it supports. Element and entry keys are mixed, and at
// least one key is non-constant (via `id()`), matching this node's shape.
import "pkl:test"
function id(x) = x
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { "a" })
res1 = (dynamic) {
[id(0)] = "updated a"
"b"
}
// evalNullWithListingDefault
local listing = Null(new Listing { "a"; "b" })
res2 = (listing) {
"c"
[id(0)] = "updated a"
}
// evalNullWithFunctionDefault
local fun = Null(() -> new Listing { "a" })
local amendedFun = (fun) {
"b"
[id(0)] = "updated a"
}
res3 = amendedFun.apply()
// error: mapping cannot have an element
res4 = test.catch(() -> (Null(new Mapping { ["a"] = 1 })) { "b"; [id("c")] = 2 })
// error: listing entry index out of range
res5 = test.catch(() -> (Null(new Listing { "a" })) { "b"; [id(5)] = "value" })
// error: listing cannot have a property (other than `default`)
res6 = test.catch(() -> (Null(new Listing { "a" })) { "b"; [id(0)] = "updated a"; foo = "bar" })
// error: function amendment with wrong parameter count
local twoArgFun = (x, y) -> new Listing {}
res7 = test.catch(() -> (Null(twoArgFun)) { x -> "b"; [id(0)] = "updated a" })
// error: default value's type has no matching specialization; falls back to the generic error
res8 = test.catch(() -> (Null(42)) { "b"; [id(0)] = "updated a" })
@@ -0,0 +1,53 @@
// Covers EntriesLiteralNode's specializations for amending a `VmNull` whose default value is
// each of the underlying parent types it supports. At least one entry key below is non-constant
// (via `id()`) so that these bodies compile to EntriesLiteralNode rather than
// ConstantEntriesLiteralNode.
import "pkl:test"
function id(x) = x
// evalNullableMapping
local mapping = Null(new Mapping { ["a"] = 1 })
res1 = (mapping) {
[id("b")] = 2
}
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { a = 1 })
res2 = (dynamic) {
[id("b")] = 2
}
// evalNullWithListingDefault
local listing = Null(new Listing { "a"; "b" })
res3 = (listing) {
[id(0)] = "updated a"
}
// evalNullWithFunctionDefault
local fun = Null(() -> new Mapping { ["a"] = 1 })
local amendedFun = (fun) {
[id("b")] = 2
}
res4 = amendedFun.apply()
// error: listing entry with wrong key type
res5 = test.catch(() -> (Null(new Listing { "a" })) { [id("wrong type")] = "value" })
// error: listing entry index out of range
res6 = test.catch(() -> (Null(new Listing { "a" })) { [id(5)] = "value" })
// error: mapping cannot have a property (other than `default`)
res7 = test.catch(() -> (Null(new Mapping { ["a"] = 1 })) { foo = "bar"; [id("b")] = 2 })
// error: duplicate key
res8 = test.catch(() -> (Null(new Mapping {})) { [id("a")] = 1; [id("a")] = 2 })
// error: function amendment with wrong parameter count
local twoArgFun = (x, y) -> new Mapping {}
res9 = test.catch(() -> (Null(twoArgFun)) { x -> [id("a")] = 1 })
hidden foo: "foo"?
// error: default value's type has no matching specialization; falls back to the generic error
res10 = test.catch(() -> (foo) { [id("a")] = 1 })
@@ -0,0 +1,68 @@
// Covers GeneratorObjectLiteralNode's specializations for amending a `VmNull` whose default value
// is each of the underlying parent types it supports. Each body contains a `for`- or
// when-generator, which is what forces this node type instead of ElementsLiteralNode/
// EntriesLiteralNode/PropertiesLiteralNode.
import "pkl:test"
local class Person {
name: String
age: Int = 0
}
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { a = 1 })
res1 = (dynamic) {
for (i in List(2, 3)) {
i
}
}
// evalNullWithTypedDefault
local person = Null(new Person { name = "Pigeon" })
res2 = (person) {
when (true) {
age = 10
}
}
// evalNullWithListingDefault
local listing = Null(new Listing { "a" })
res3 = (listing) {
for (i in List(2, 3)) {
i
}
}
// evalNullableMapping
local mapping = Null(new Mapping { ["a"] = 1 })
res4 = (mapping) {
for (i in List(2, 3)) {
[i] = i
}
}
// evalNullWithFunctionDefault
local fun = Null(() -> new Dynamic { a = 1 })
local amendedFun = (fun) {
for (i in List(2, 3)) {
i
}
}
res5 = amendedFun.apply()
// error: listing amendment cannot have parameters
res6 = test.catch(() -> (Null(new Listing { "a" })) { x -> for (i in List(2)) { i } })
// error: mapping amendment cannot have parameters
res7 = test.catch(() -> (Null(new Mapping { ["a"] = 1 })) { x -> for (i in List(2)) { [i] = i } })
// error: object amendment cannot have parameters
res8 = test.catch(() -> (Null(new Dynamic { a = 1 })) { x -> for (i in List(2)) { i } })
hidden foo: "foo"?
// error: default value's type has no matching specialization; falls back to the generic error
res9 = test.catch(() -> (foo) { for (i in List(2)) { i } })
hidden x: List<Int>?
res10 = test.catch(() -> ((x) { when (true) { foo = 1 } }))
@@ -0,0 +1,63 @@
// Covers PropertiesLiteralNode's specializations for amending a `VmNull` whose default value is
// each of the underlying parent types it supports.
import "pkl:test"
local class Person {
name: String
age: Int = 0
}
// evalNullWithTypedDefault
local person = Null(new Person { name = "Pigeon" })
res1 = (person) {
age = 3
}
// evalNullWithDynamicDefault
local dynamic = Null(new Dynamic { name = "Pigeon" })
res2 = (dynamic) {
age = 3
}
// evalNullWithListingDefault
local listing = Null(new Listing { "one"; "two" })
res3 = (listing) {
default = (_) -> "three"
}.getOrDefault(5)
// evalNullableMapping
local mapping = Null(new Mapping { ["one"] = 1 })
res4 = (mapping) {
default = (_) -> 0
}.getOrDefault("missing")
// evalNullWithFunctionDefault
local fun = Null(() -> new Dynamic { zero = 0 })
local amendedFun = (fun) {
amended = "amended"
}
res5 = amendedFun.apply()
// error: typed object amendment with an unknown property
res6 = test.catch(() -> (Null(new Person { name = "Pigeon" })) { nickname = "Pidge" })
// error: listing cannot have a property (other than `default`)
res7 = test.catch(() -> (Null(new Listing { "one" })) { foo = "bar" })
// error: mapping cannot have a property (other than `default`)
res8 = test.catch(() -> (Null(new Mapping { ["one"] = 1 })) { foo = "bar" })
// error: function amendment with wrong parameter count
local twoArgFun = (x, y) -> new Dynamic {}
res9 = test.catch(() -> (Null(twoArgFun)) { x -> prop = x })
hidden foo: "foo"?
// error: default value's type has no matching specialization; falls back to the generic error
res10 = test.catch(() -> (foo) { foo = "bar" })
hidden clazz: Class?
res11 = (clazz) {
x = 1
}
@@ -0,0 +1,3 @@
res {
name = "Fred"
}
@@ -30,6 +30,37 @@ res2 {
["two"] = 2
}
}
res2n {
null
true
42
1.23
"foo"
Regex("bar")
5.gb
3.min
Pair(1, 2)
List(1, 2, 3)
Set(1, 2, 3)
Map("one", 1, "two", 2)
new {
name = "Pigeon"
age = 42
}
new {
name = "Pigeon"
age = 42
}
new {
1
2
3
}
new {
["one"] = 1
["two"] = 2
}
}
res3 {
null
true
@@ -61,3 +92,34 @@ res3 {
["two"] = 2
}
}
res3n {
null
true
42
1.23
"foo"
Regex("bar")
5.gb
3.min
Pair(1, 2)
List(1, 2, 3)
Set(1, 2, 3)
Map("one", 1, "two", 2)
new {
name = "Pigeon"
age = 42
}
new {
name = "Pigeon"
age = 42
}
new {
1
2
3
}
new {
["one"] = 1
["two"] = 2
}
}
@@ -55,11 +55,20 @@ res7a = "Element index `-1` is out of range `0`..`2`."
res8 = "Expected key of type `Int`, but got type `String`."
res8a = "Expected key of type `Int`, but got type `String`."
res9 = "Object of type `Listing` cannot have a property (other than `default`)."
res9n = "Object of type `Listing` cannot have a property (other than `default`)."
res10 = "Object of type `Listing` cannot have a property (other than `default`)."
res10n = "Object of type `Listing` cannot have a property (other than `default`)."
res11 = "Object of type `Listing` cannot have a property (other than `default`)."
res11n = "Object of type `Listing` cannot have a property (other than `default`)."
res12 = "Object of type `Listing` cannot have a property (other than `default`)."
res12n = "Object of type `Listing` cannot have a property (other than `default`)."
res12a = "Object of type `Listing` cannot have a property (other than `default`)."
res12an = "Object of type `Listing` cannot have a property (other than `default`)."
res13 = "Object of type `Listing` cannot have a property (other than `default`)."
res13n = "Object of type `Listing` cannot have a property (other than `default`)."
res13a = "Object of type `Listing` cannot have a property (other than `default`)."
res13an = "Object of type `Listing` cannot have a property (other than `default`)."
res14 = "Object of type `Listing` cannot have a property (other than `default`)."
res14n = "Object of type `Listing` cannot have a property (other than `default`)."
res14a = "Object of type `Listing` cannot have a property (other than `default`)."
res14an = "Object of type `Listing` cannot have a property (other than `default`)."
@@ -43,6 +43,50 @@ res2 {
["two"] = 2
}
}
res2n {
[null] = null
[true] = true
[42] = 42
[1.23] = 1.23
["foo"] = "foo"
[Regex("bar")] = Regex("bar")
[5.gb] = 5.gb
[3.min] = 3.min
[Pair(1, 2)] = Pair(1, 2)
[List(1, 2, 3)] = List(1, 2, 3)
[Set(1, 2, 3)] = Set(1, 2, 3)
[Map("one", 1, "two", 2)] = Map("one", 1, "two", 2)
[new {
name = "Pigeon"
age = 42
}] {
name = "Pigeon"
age = 42
}
[new {
name = "Pigeon"
age = 42
}] {
name = "Pigeon"
age = 42
}
[new {
1
2
3
}] {
1
2
3
}
[new {
["one"] = 1
["two"] = 2
}] {
["one"] = 1
["two"] = 2
}
}
res3 {
[null] = null
[true] = true
@@ -87,3 +131,55 @@ res3 {
["two"] = 2
}
}
res3n {
[null] = null
[true] = true
[42] = 42
[1.23] = 1.23
["foo"] = "foo"
[Regex("bar")] = Regex("bar")
[5.gb] = 5.gb
[3.min] = 3.min
[Pair(1, 2)] = Pair(1, 2)
[List(1, 2, 3)] = List(1, 2, 3)
[Set(1, 2, 3)] = Set(1, 2, 3)
[Map("one", 1, "two", 2)] = Map("one", 1, "two", 2)
[new {
name = "Pigeon"
age = 42
}] {
name = "Pigeon"
age = 42
}
[new {
name = "Pigeon"
age = 42
}] {
name = "Pigeon"
age = 42
}
[new {
1
2
3
}] {
1
2
3
}
[new {
["one"] = 1
["two"] = 2
}] {
["one"] = 1
["two"] = 2
}
}
res4 {
["foo"] = "1"
["bar"] = "2"
}
res4n {
["foo"] = "1"
["bar"] = "2"
}
@@ -0,0 +1,21 @@
res1 {
["a"] = 1
["b"] = 2
}
res2 {
a = 1
["b"] = 2
}
res3 {
"updated a"
"b"
}
res4 {
["a"] = 1
["b"] = 2
}
res5 = "Element index `5` is out of range `0`..`0`."
res6 = "Object of type `Listing` cannot have a property (other than `default`)."
res7 = "Object of type `Mapping` cannot have a property (other than `default`)."
res8 = "Expected a function with 1 parameters, but got 2."
res9 = "Cannot instantiate, or amend an instance of, external class `String`."
@@ -0,0 +1,18 @@
res1 {
a = 1
"b"
"c"
}
res2 {
"a"
"b"
"c"
}
res3 {
"a"
"b"
}
res4 = "Object of type `Mapping` cannot have an element."
res5 = "Object of type `Listing` cannot have a property (other than `default`)."
res6 = "Expected a function with 1 parameters, but got 2."
res7 = "Expected value of type `Object | Function<Object>`, but got type `Int`. Value: 42"
@@ -0,0 +1,18 @@
res1 {
"updated a"
"b"
}
res2 {
"updated a"
"b"
"c"
}
res3 {
"updated a"
"b"
}
res4 = "Object of type `Mapping` cannot have an element."
res5 = "Element index `5` is out of range `0`..`0`."
res6 = "Object of type `Listing` cannot have a property (other than `default`)."
res7 = "Expected a function with 1 parameters, but got 2."
res8 = "Expected value of type `Object | Function<Object>`, but got type `Int`. Value: 42"
@@ -0,0 +1,22 @@
res1 {
["a"] = 1
["b"] = 2
}
res2 {
a = 1
["b"] = 2
}
res3 {
"updated a"
"b"
}
res4 {
["a"] = 1
["b"] = 2
}
res5 = "Expected key of type `Int`, but got type `String`."
res6 = "Element index `5` is out of range `0`..`0`."
res7 = "Object of type `Mapping` cannot have a property (other than `default`)."
res8 = "Duplicate definition of member `\"a\"`."
res9 = "Expected a function with 1 parameters, but got 2."
res10 = "Cannot instantiate, or amend an instance of, external class `String`."
@@ -0,0 +1,29 @@
res1 {
a = 1
2
3
}
res2 {
name = "Pigeon"
age = 10
}
res3 {
"a"
2
3
}
res4 {
["a"] = 1
[2] = 2
[3] = 3
}
res5 {
a = 1
2
3
}
res6 = "Expected a function that returns a `Listing`, but got a `Listing`."
res7 = "Expected a function that returns a `Mapping`, but got a `Mapping`."
res8 = "Expected a function that returns an object, but got an object."
res9 = "Cannot instantiate, or amend an instance of, external class `String`."
res10 = "Cannot instantiate, or amend an instance of, external class `List`."
@@ -0,0 +1,22 @@
res1 {
name = "Pigeon"
age = 3
}
res2 {
name = "Pigeon"
age = 3
}
res3 = "three"
res4 = 0
res5 {
zero = 0
amended = "amended"
}
res6 = "Cannot find property `nickname` in object of type `properties#Person`."
res7 = "Object of type `Listing` cannot have a property (other than `default`)."
res8 = "Object of type `Mapping` cannot have a property (other than `default`)."
res9 = "Expected a function with 1 parameters, but got 2."
res10 = "Cannot instantiate, or amend an instance of, external class `String`."
res11 {
x = 1
}