diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java index 03a9b1a5e..18767d30b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java @@ -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(); } 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 ec8c1b159..f6441af73 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 @@ -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); } } 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 56886a59f..9e9893429 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 @@ -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 diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java index b68f1ade8..ba7a43b41 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java @@ -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 diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java index fd8a111ad..4ff4fb9bf 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java @@ -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 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 c8a8d8ea8..7e56ff737 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 @@ -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; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java index b35db9438..d58bef4f3 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java @@ -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(); } 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 e43586702..3188ee753 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 @@ -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; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 17891b1e3..9dcf29610 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -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(); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/lambdas/amendLambdaNullable.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/lambdas/amendLambdaNullable.pkl new file mode 100644 index 000000000..73ad5f9ce --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/lambdas/amendLambdaNullable.pkl @@ -0,0 +1,9 @@ +class Person { name: String } + +hidden x: Mixin? + +local y = (x) { + name = "Fred" +} + +res = y.apply(new Person {}) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing1.pkl index 0d4d4f1ee..6f322aac4 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing1.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing1.pkl @@ -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 }) +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing2.pkl index afe04222e..058d758de 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing2.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/listing2.pkl @@ -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" }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/mapping1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/mapping1.pkl index dbe97271b..e93551162 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/mapping1.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/mappings/mapping1.pkl @@ -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" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/constantEntries.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/constantEntries.pkl new file mode 100644 index 000000000..93f72054b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/constantEntries.pkl @@ -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 }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elements.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elements.pkl new file mode 100644 index 000000000..16b89b58a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elements.pkl @@ -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" }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elementsEntries.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elementsEntries.pkl new file mode 100644 index 000000000..dd39eaf47 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/elementsEntries.pkl @@ -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" }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/entries.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/entries.pkl new file mode 100644 index 000000000..248e1e3fc --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/entries.pkl @@ -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 }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/generator.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/generator.pkl new file mode 100644 index 000000000..7074a2cf3 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/generator.pkl @@ -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? +res10 = test.catch(() -> ((x) { when (true) { foo = 1 } })) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/properties.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/properties.pkl new file mode 100644 index 000000000..377d8ecef --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/nullParents/properties.pkl @@ -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 +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/lambdas/amendLambdaNullable.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/lambdas/amendLambdaNullable.pcf new file mode 100644 index 000000000..dbb0e933c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/lambdas/amendLambdaNullable.pcf @@ -0,0 +1,3 @@ +res { + name = "Fred" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing1.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing1.pcf index 2161d6d9f..8008f555c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing1.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing1.pcf @@ -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 + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing2.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing2.pcf index be9103ae4..4b7c1cc85 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing2.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/listing2.pcf @@ -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`)." diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/mapping1.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/mapping1.pcf index a34aeccef..2ba1a11e8 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/mapping1.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/mappings/mapping1.pcf @@ -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" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/constantEntries.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/constantEntries.pcf new file mode 100644 index 000000000..bc94a80d6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/constantEntries.pcf @@ -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`." diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elements.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elements.pcf new file mode 100644 index 000000000..fed5edc03 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elements.pcf @@ -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`, but got type `Int`. Value: 42" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elementsEntries.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elementsEntries.pcf new file mode 100644 index 000000000..3c988ce32 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/elementsEntries.pcf @@ -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`, but got type `Int`. Value: 42" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/entries.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/entries.pcf new file mode 100644 index 000000000..7d471fd1a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/entries.pcf @@ -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`." diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/generator.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/generator.pcf new file mode 100644 index 000000000..d6c4908b6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/generator.pcf @@ -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`." diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/properties.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/properties.pcf new file mode 100644 index 000000000..a27ab438d --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/nullParents/properties.pcf @@ -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 +}