diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 3b34d2c1..7f65c54b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -1221,7 +1221,7 @@ public final class AstBuilder extends AbstractAstBuilder { member.initConstantValue(constantNode); } else { member.initMemberNode( - new UntypedObjectMemberNode( + ElementOrEntryNodeGen.create( language, scope.buildFrameDescriptor(), member, elementNode)); } @@ -1278,7 +1278,7 @@ public final class AstBuilder extends AbstractAstBuilder { member.initConstantValue(constantNode); } else { member.initMemberNode( - new UntypedObjectMemberNode( + ElementOrEntryNodeGen.create( language, scope.buildFrameDescriptor(), member, valueNode)); } } else { // ["key"] { ... } @@ -1287,7 +1287,7 @@ public final class AstBuilder extends AbstractAstBuilder { objectBodyCtxs, new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); member.initMemberNode( - new UntypedObjectMemberNode( + ElementOrEntryNodeGen.create( language, scope.buildFrameDescriptor(), member, objectBody)); } @@ -2446,6 +2446,7 @@ public final class AstBuilder extends AbstractAstBuilder { return new UnresolvedTypeNode.Parameterized( createSourceSection(ctx), + language, doVisitTypeName(idCtx), argCtx.ts.stream().map(this::visitType).toArray(UnresolvedTypeNode[]::new)); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java index a6446a53..95f1013a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java @@ -103,7 +103,7 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode { var callTarget = member.getCallTarget(); value = callTarget.call(parent, owner, key); } - owner.setCachedValue(key, value, member); + owner.setCachedValue(key, value); } frame.setAuxiliarySlot(customThisSlot, value); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadLocalPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadLocalPropertyNode.java index 7cf039dd..e7c18a8b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadLocalPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadLocalPropertyNode.java @@ -71,7 +71,7 @@ public final class ReadLocalPropertyNode extends ExpressionNode { if (result == null) { result = callNode.call(objReceiver, owner, property.getName()); - objReceiver.setCachedValue(property, result, property); + objReceiver.setCachedValue(property, result); } return result; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ResolveVariableNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ResolveVariableNode.java index bebfd723..932dfee0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ResolveVariableNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ResolveVariableNode.java @@ -184,12 +184,12 @@ public final class ResolveVariableNode extends ExpressionNode { if (member != null) { var constantValue = member.getConstantValue(); if (constantValue != null) { - baseModule.setCachedValue(variableName, constantValue, member); + baseModule.setCachedValue(variableName, constantValue); return new ConstantValueNode(sourceSection, constantValue); } var computedValue = member.getCallTarget().call(baseModule, baseModule); - baseModule.setCachedValue(variableName, computedValue, member); + baseModule.setCachedValue(variableName, computedValue); return new ConstantValueNode(sourceSection, computedValue); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ElementOrEntryNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ElementOrEntryNode.java new file mode 100644 index 00000000..876fd3fc --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ElementOrEntryNode.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.ast.member; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.Executed; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.FrameDescriptor; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.IndirectCallNode; +import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.ast.expression.primary.GetReceiverNode; +import org.pkl.core.runtime.VmDynamic; +import org.pkl.core.runtime.VmLanguage; +import org.pkl.core.runtime.VmListing; +import org.pkl.core.runtime.VmMapping; +import org.pkl.core.runtime.VmUtils; +import org.pkl.core.util.Nullable; + +/** Equivalent of {@link TypedPropertyNode} for elements/entries. */ +public abstract class ElementOrEntryNode extends RegularMemberNode { + @Child @Executed protected ExpressionNode receiverNode = new GetReceiverNode(); + + protected ElementOrEntryNode( + @Nullable VmLanguage language, + FrameDescriptor descriptor, + ObjectMember member, + ExpressionNode bodyNode) { + + super(language, descriptor, member, bodyNode); + } + + @Specialization + protected Object evalListing( + VirtualFrame frame, + VmListing receiver, + @Cached("create()") @Shared("callNode") IndirectCallNode callNode) { + var result = executeBody(frame); + return VmUtils.shouldRunTypeCheck(frame) + ? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null) + : result; + } + + @Specialization + protected Object evalMapping( + VirtualFrame frame, + VmMapping receiver, + @Cached("create()") @Shared("callNode") IndirectCallNode callNode) { + var result = executeBody(frame); + return VmUtils.shouldRunTypeCheck(frame) + ? receiver.executeTypeCasts(result, VmUtils.getOwner(frame), callNode, null, null) + : result; + } + + @Specialization + protected Object evalDynamic(VirtualFrame frame, VmDynamic ignored) { + return executeBody(frame); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java index b8cc60ea..16a327d7 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ListingOrMappingTypeCastNode.java @@ -26,7 +26,7 @@ import org.pkl.core.runtime.VmLanguage; import org.pkl.core.util.Nullable; /** Performs a typecast on a Mapping entry value, or a Listing element. */ -public class ListingOrMappingTypeCastNode extends PklRootNode { +public final class ListingOrMappingTypeCastNode extends PklRootNode { @Child private TypeNode typeNode; private final String qualifiedName; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java index 846e5325..cdf281b7 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/ResolveDeclaredTypeNode.java @@ -73,7 +73,7 @@ public abstract class ResolveDeclaredTypeNode extends ExpressionNode { var result = module.getCachedValue(importName); if (result == null) { result = callNode.call(member.getCallTarget(), module, module, importName); - module.setCachedValue(importName, result, member); + module.setCachedValue(importName, result); } return (VmTyped) result; } @@ -94,7 +94,7 @@ public abstract class ResolveDeclaredTypeNode extends ExpressionNode { var result = module.getCachedValue(typeName); if (result == null) { result = callNode.call(member.getCallTarget(), module, module, typeName); - module.setCachedValue(typeName, result, member); + module.setCachedValue(typeName, result); } return result; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 7b42b7ce..3c8e6b90 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -21,6 +21,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.Frame; import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.VirtualFrame; @@ -1418,8 +1419,9 @@ public abstract class TypeNode extends PklNode { } public static final class ListingTypeNode extends ListingOrMappingTypeNode { - public ListingTypeNode(SourceSection sourceSection, TypeNode valueTypeNode) { - super(sourceSection, null, valueTypeNode); + public ListingTypeNode( + SourceSection sourceSection, VmLanguage language, TypeNode valueTypeNode) { + super(sourceSection, language, null, valueTypeNode); } @Override @@ -1427,7 +1429,17 @@ public abstract class TypeNode extends PklNode { if (!(value instanceof VmListing vmListing)) { throw typeMismatch(value, BaseModule.getListingClass()); } - return doTypeCast(frame, vmListing); + if (vmListing.isValueTypeKnownSubtypeOf(valueTypeNode)) { + return vmListing; + } + return new VmListing( + vmListing.getEnclosingFrame(), + vmListing, + EconomicMaps.emptyMap(), + vmListing.getLength(), + getValueTypeCastNode(), + VmUtils.getReceiver(frame), + VmUtils.getOwner(frame)); } @Override @@ -1470,9 +1482,12 @@ public abstract class TypeNode extends PklNode { public static final class MappingTypeNode extends ListingOrMappingTypeNode { public MappingTypeNode( - SourceSection sourceSection, TypeNode keyTypeNode, TypeNode valueTypeNode) { + SourceSection sourceSection, + VmLanguage language, + TypeNode keyTypeNode, + TypeNode valueTypeNode) { - super(sourceSection, keyTypeNode, valueTypeNode); + super(sourceSection, language, keyTypeNode, valueTypeNode); } @Override @@ -1482,7 +1497,16 @@ public abstract class TypeNode extends PklNode { } // execute type checks on mapping keys doEagerCheck(frame, vmMapping, false, true); - return doTypeCast(frame, vmMapping); + if (vmMapping.isValueTypeKnownSubtypeOf(valueTypeNode)) { + return vmMapping; + } + return new VmMapping( + vmMapping.getEnclosingFrame(), + vmMapping, + EconomicMaps.emptyMap(), + getValueTypeCastNode(), + VmUtils.getReceiver(frame), + VmUtils.getOwner(frame)); } @Override @@ -1530,17 +1554,22 @@ public abstract class TypeNode extends PklNode { } public abstract static class ListingOrMappingTypeNode extends ObjectSlotTypeNode { + private final VmLanguage language; @Child protected @Nullable TypeNode keyTypeNode; @Child protected TypeNode valueTypeNode; - @Child @Nullable protected ListingOrMappingTypeCastNode listingOrMappingTypeCastNode; + @Child @Nullable protected ListingOrMappingTypeCastNode valueTypeCastNode; private final boolean skipKeyTypeChecks; private final boolean skipValueTypeChecks; protected ListingOrMappingTypeNode( - SourceSection sourceSection, @Nullable TypeNode keyTypeNode, TypeNode valueTypeNode) { + SourceSection sourceSection, + VmLanguage language, + @Nullable TypeNode keyTypeNode, + TypeNode valueTypeNode) { super(sourceSection); + this.language = language; this.keyTypeNode = keyTypeNode; this.valueTypeNode = valueTypeNode; @@ -1560,17 +1589,14 @@ public abstract class TypeNode extends PklNode { return valueTypeNode; } - protected ListingOrMappingTypeCastNode getListingOrMappingTypeCastNode() { - if (listingOrMappingTypeCastNode == null) { + protected ListingOrMappingTypeCastNode getValueTypeCastNode() { + if (valueTypeCastNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - listingOrMappingTypeCastNode = + valueTypeCastNode = new ListingOrMappingTypeCastNode( - VmLanguage.get(this), - getRootNode().getFrameDescriptor(), - valueTypeNode, - getRootNode().getName()); + language, new FrameDescriptor(), valueTypeNode, getRootNode().getName()); } - return listingOrMappingTypeCastNode; + return valueTypeCastNode; } // either (if defaultMemberValue != null): @@ -1651,15 +1677,6 @@ public abstract class TypeNode extends PklNode { EconomicMaps.of(Identifier.DEFAULT, defaultMember)); } - protected > T doTypeCast(VirtualFrame frame, T original) { - // optimization: don't create new object if the original already has the same typecheck, or if - // this typecheck is a no-op. - if (isNoopTypeCheck() || original.hasSameChecksAs(valueTypeNode)) { - return original; - } - return original.withCheckedMembers(getListingOrMappingTypeCastNode(), frame.materialize()); - } - protected void doEagerCheck(VirtualFrame frame, VmObject object) { doEagerCheck(frame, object, skipKeyTypeChecks, skipValueTypeChecks); } @@ -1704,7 +1721,7 @@ public abstract class TypeNode extends PklNode { var callTarget = member.getCallTarget(); memberValue = callTarget.call(object, owner, memberKey); } - object.setCachedValue(memberKey, memberValue, member); + object.setCachedValue(memberKey, memberValue); } valueTypeNode.executeEagerly(frame, memberValue); } @@ -2391,14 +2408,14 @@ public abstract class TypeNode extends PklNode { public Object execute(VirtualFrame frame, Object value) { var prevOwner = VmUtils.getOwner(frame); var prevReceiver = VmUtils.getReceiver(frame); - VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); - VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); + setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); + setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); try { return aliasedTypeNode.execute(frame, value); } finally { - VmUtils.setOwner(frame, prevOwner); - VmUtils.setReceiver(frame, prevReceiver); + setOwner(frame, prevOwner); + setReceiver(frame, prevReceiver); } } @@ -2407,14 +2424,14 @@ public abstract class TypeNode extends PklNode { public Object executeAndSet(VirtualFrame frame, Object value) { var prevOwner = VmUtils.getOwner(frame); var prevReceiver = VmUtils.getReceiver(frame); - VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); - VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); + setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); + setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); try { return aliasedTypeNode.executeAndSet(frame, value); } finally { - VmUtils.setOwner(frame, prevOwner); - VmUtils.setReceiver(frame, prevReceiver); + setOwner(frame, prevOwner); + setReceiver(frame, prevReceiver); } } @@ -2423,14 +2440,14 @@ public abstract class TypeNode extends PklNode { public Object executeEagerlyAndSet(VirtualFrame frame, Object value) { var prevOwner = VmUtils.getOwner(frame); var prevReceiver = VmUtils.getReceiver(frame); - VmUtils.setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); - VmUtils.setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); + setOwner(frame, VmUtils.getOwner(typeAlias.getEnclosingFrame())); + setReceiver(frame, VmUtils.getReceiver(typeAlias.getEnclosingFrame())); try { return aliasedTypeNode.executeEagerlyAndSet(frame, value); } finally { - VmUtils.setOwner(frame, prevOwner); - VmUtils.setReceiver(frame, prevReceiver); + setOwner(frame, prevOwner); + setReceiver(frame, prevReceiver); } } @@ -2502,6 +2519,22 @@ public abstract class TypeNode extends PklNode { protected boolean isParametric() { return typeArgumentNodes.length > 0; } + + // Note that mutating a frame's receiver and owner argument is very risky + // because any VmObject instantiated within the same root node execution + // holds a reference to (not immutable snapshot of) the frame + // via VmObjectLike.enclosingFrame. + // *Maybe* this works out for TypeAliasTypeNode because an object instantiated + // within a type constraint doesn't escape the constraint expression. + // If mutating receiver and owner can't be avoided, it would be safer + // to have VmObjectLike store them directly instead of storing enclosingFrame. + private static void setReceiver(Frame frame, Object receiver) { + frame.getArguments()[0] = receiver; + } + + private static void setOwner(Frame frame, VmObjectLike owner) { + frame.getArguments()[1] = owner; + } } public static final class ConstrainedTypeNode extends TypeNode { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java index 9b9b6c40..7a61a6e0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java @@ -197,14 +197,17 @@ public abstract class UnresolvedTypeNode extends PklNode { } public static final class Parameterized extends UnresolvedTypeNode { + private final VmLanguage language; @Child private ExpressionNode resolveTypeNode; @Children private final UnresolvedTypeNode[] typeArgumentNodes; public Parameterized( SourceSection sourceSection, + VmLanguage language, ExpressionNode resolveTypeNode, UnresolvedTypeNode[] typeArgumentNodes) { super(sourceSection); + this.language = language; this.resolveTypeNode = resolveTypeNode; this.typeArgumentNodes = typeArgumentNodes; } @@ -238,12 +241,13 @@ public abstract class UnresolvedTypeNode extends PklNode { } if (clazz.isListingClass()) { - return new ListingTypeNode(sourceSection, typeArgumentNodes[0].execute(frame)); + return new ListingTypeNode(sourceSection, language, typeArgumentNodes[0].execute(frame)); } if (clazz.isMappingClass()) { return new MappingTypeNode( sourceSection, + language, typeArgumentNodes[0].execute(frame), typeArgumentNodes[1].execute(frame)); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java index e9cb3f66..95dfbdb2 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java @@ -113,7 +113,7 @@ public final class VmFunction extends VmObjectLike { @Override @TruffleBoundary - public void setCachedValue(Object key, Object value, ObjectMember objectMember) { + public void setCachedValue(Object key, Object value) { throw new VmExceptionBuilder() .bug("Class `VmFunction` does not support method `setCachedValue()`.") .build(); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java index 608385ec..7eed3b66 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java @@ -19,14 +19,13 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; -public final class VmListing extends VmListingOrMapping { +public final class VmListing extends VmListingOrMapping { private static final class EmptyHolder { private static final VmListing EMPTY = new VmListing( @@ -47,7 +46,7 @@ public final class VmListing extends VmListingOrMapping { VmObject parent, UnmodifiableEconomicMap members, int length) { - super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null); + super(enclosingFrame, parent, members); this.length = length; } @@ -56,16 +55,10 @@ public final class VmListing extends VmListingOrMapping { VmObject parent, UnmodifiableEconomicMap members, int length, - @Nullable VmListing delegate, - ListingOrMappingTypeCastNode typeCheckNode, - MaterializedFrame typeNodeFrame) { - super( - enclosingFrame, - Objects.requireNonNull(parent), - members, - delegate, - typeCheckNode, - typeNodeFrame); + ListingOrMappingTypeCastNode typeCastNode, + Object typeCheckReceiver, + VmObjectLike typeCheckOwner) { + super(enclosingFrame, parent, members, typeCastNode, typeCheckReceiver, typeCheckOwner); this.length = length; } @@ -117,20 +110,6 @@ public final class VmListing extends VmListingOrMapping { return converter.convertListing(this, path); } - @Override - public VmListing withCheckedMembers( - ListingOrMappingTypeCastNode typeCheckNode, MaterializedFrame typeNodeFrame) { - - return new VmListing( - getEnclosingFrame(), - Objects.requireNonNull(parent), - members, - length, - this, - typeCheckNode, - typeNodeFrame); - } - @Override @TruffleBoundary public boolean equals(@Nullable Object obj) { @@ -142,10 +121,14 @@ public final class VmListing extends VmListingOrMapping { force(false); other.force(false); - for (var i = 0L; i < length; i++) { - var value = getCachedValue(i); + var cursor = cachedValues.getEntries(); + while (cursor.advance()) { + var key = cursor.getKey(); + if (key instanceof Identifier) continue; + + var value = cursor.getValue(); assert value != null; - var otherValue = other.getCachedValue(i); + var otherValue = other.getCachedValue(key); if (!value.equals(otherValue)) return false; } @@ -156,14 +139,16 @@ public final class VmListing extends VmListingOrMapping { @TruffleBoundary public int hashCode() { if (cachedHash != 0) return cachedHash; - // It's possible that the delegate has already computed its hash code. - // If so, we can go ahead and use it. - if (delegate != null && delegate.cachedHash != 0) return delegate.cachedHash; force(false); var result = 0; - for (var i = 0L; i < length; i++) { - var value = getCachedValue(i); + var cursor = cachedValues.getEntries(); + + while (cursor.advance()) { + var key = cursor.getKey(); + if (key instanceof Identifier) continue; + + var value = cursor.getValue(); assert value != null; result = 31 * result + value.hashCode(); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java index b4d9b2f5..bb3b48c4 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java @@ -16,159 +16,122 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.nodes.IndirectCallNode; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; import org.graalvm.collections.UnmodifiableEconomicMap; -import org.pkl.core.PklBugException; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.util.EconomicMaps; -import org.pkl.core.util.EconomicSets; import org.pkl.core.util.Nullable; -public abstract class VmListingOrMapping> extends VmObject { - - /** - * A Listing or Mapping typecast creates a new object that contains a new typecheck node, and - * delegates member lookups to this delegate. - */ - protected final @Nullable SELF delegate; - +public abstract class VmListingOrMapping extends VmObject { + // reified type of listing elements and mapping values private final @Nullable ListingOrMappingTypeCastNode typeCastNode; - private final MaterializedFrame typeNodeFrame; - private final EconomicMap cachedMembers = EconomicMaps.create(); - private final EconomicSet checkedMembers = EconomicSets.create(); + private final @Nullable Object typeCheckReceiver; + private final @Nullable VmObjectLike typeCheckOwner; + + public VmListingOrMapping( + MaterializedFrame enclosingFrame, + @Nullable VmObject parent, + UnmodifiableEconomicMap members) { + super(enclosingFrame, parent, members); + typeCastNode = null; + typeCheckReceiver = null; + typeCheckOwner = null; + } public VmListingOrMapping( MaterializedFrame enclosingFrame, @Nullable VmObject parent, UnmodifiableEconomicMap members, - @Nullable SELF delegate, - @Nullable ListingOrMappingTypeCastNode typeCastNode, - @Nullable MaterializedFrame typeNodeFrame) { + ListingOrMappingTypeCastNode typeCastNode, + Object typeCheckReceiver, + VmObjectLike typeCheckOwner) { super(enclosingFrame, parent, members); - this.delegate = delegate; this.typeCastNode = typeCastNode; - this.typeNodeFrame = typeNodeFrame; + this.typeCheckReceiver = typeCheckReceiver; + this.typeCheckOwner = typeCheckOwner; } - ObjectMember findMember(Object key) { - var member = EconomicMaps.get(cachedMembers, key); - if (member != null) { - return member; - } - if (delegate != null) { - return delegate.findMember(key); - } - // member is guaranteed to exist; this is only called if `getCachedValue()` returns non-null - // and `setCachedValue` will record the object member in `cachedMembers`. - throw PklBugException.unreachableCode(); - } - - public @Nullable ListingOrMappingTypeCastNode getTypeCastNode() { - return typeCastNode; - } - - @Override - public void setCachedValue(Object key, Object value, ObjectMember objectMember) { - super.setCachedValue(key, value, objectMember); - EconomicMaps.put(cachedMembers, key, objectMember); - } - - @Override - public boolean hasCachedValue(Object key) { - return super.hasCachedValue(key) || delegate != null && delegate.hasCachedValue(key); - } - - @Override - public @Nullable Object getCachedValue(Object key) { - var myCachedValue = super.getCachedValue(key); - if (myCachedValue != null || delegate == null) { - return myCachedValue; - } - var memberValue = delegate.getCachedValue(key); - // if this object member appears inside `checkedMembers`, we have already checked its type - // and can safely return it. - if (EconomicSets.contains(checkedMembers, key)) { - return memberValue; - } - if (memberValue == null) { - return null; - } - // If a cached value already exists on the delegate, run a typecast on it. - // optimization: don't use `VmUtils.findMember` to avoid iterating over all members - var objectMember = findMember(key); - var ret = typecastObjectMember(objectMember, memberValue, IndirectCallNode.getUncached()); - if (ret != memberValue) { - EconomicMaps.put(cachedValues, key, ret); - } else { - // optimization: don't add to own cached values if typecast results in the same value - EconomicSets.add(checkedMembers, key); - } - return ret; - } - - @Override - public Object getExtraStorage() { - if (delegate != null) { - return delegate.getExtraStorage(); - } - assert extraStorage != null; - return extraStorage; - } - - /** Perform a typecast on this member, */ - public Object typecastObjectMember( - ObjectMember member, Object memberValue, IndirectCallNode callNode) { - if (!(member.isEntry() || member.isElement()) || typeCastNode == null) { - return memberValue; - } - assert typeNodeFrame != null; - var ret = memberValue; - if (delegate != null) { - ret = delegate.typecastObjectMember(member, ret, callNode); - } + // Recursively executes type casts between `owner` and `this` and returns the resulting value. + public final Object executeTypeCasts( + Object value, + VmObjectLike owner, + IndirectCallNode callNode, + // if non-null, a stack frame for this member is inserted if a type cast fails + @Nullable ObjectMember member, + // Next type cast to be performed by the caller. + // Avoids repeating the same type cast in some cases. + @Nullable ListingOrMappingTypeCastNode nextTypeCastNode) { + var newNextTypeCastNode = typeCastNode != null ? typeCastNode : nextTypeCastNode; + @SuppressWarnings("DataFlowIssue") + var result = + this == owner + ? value + : ((VmListingOrMapping) parent) + .executeTypeCasts(value, owner, callNode, member, newNextTypeCastNode); + if (typeCastNode == null || typeCastNode == nextTypeCastNode) return result; var callTarget = typeCastNode.getCallTarget(); try { - return callNode.call( - callTarget, VmUtils.getReceiver(typeNodeFrame), VmUtils.getOwner(typeNodeFrame), ret); - } catch (VmException vmException) { + return callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result); + } catch (VmException e) { CompilerDirectives.transferToInterpreter(); - // treat typecheck as part of the call stack to read the original member if there is a - // source section for it. - var sourceSection = member.getBodySection(); - if (!sourceSection.isAvailable()) { - sourceSection = member.getSourceSection(); + if (member != null) { + VmUtils.insertStackFrame(member, callTarget, e); } - if (sourceSection.isAvailable()) { - vmException - .getInsertedStackFrames() - .put(callTarget, VmUtils.createStackFrame(sourceSection, member.getQualifiedName())); - } - throw vmException; + throw e; } } - public abstract SELF withCheckedMembers( - ListingOrMappingTypeCastNode typeCastNode, MaterializedFrame typeNodeFrame); + @Override + @TruffleBoundary + public final @Nullable Object getCachedValue(Object key) { + var result = EconomicMaps.get(cachedValues, key); + // if this object has members, `this[key]` may differ from `parent[key]`, so stop the search + if (result != null || !members.isEmpty()) return result; - /** Tells if this mapping/listing runs the same typechecks as {@code typeNode}. */ - public boolean hasSameChecksAs(TypeNode typeNode) { + // Optimization: Recursively steal value from parent cache to avoid computing it multiple times. + // The current implementation has the following limitations and drawbacks: + // * It only works if a parent has, coincidentally, already cached `key`. + // * It turns getCachedValue() into an operation that isn't guaranteed to be fast and fail-safe. + // * It requires making VmObject.getCachedValue() non-final, + // which is unfavorable for Truffle partial evaluation and JVM inlining. + // * It may not be worth its cost for constant members and members that are cheap to compute. + + assert parent != null; // VmListingOrMapping always has a parent + result = parent.getCachedValue(key); + if (result == null) return null; + + if (typeCastNode != null && !(key instanceof Identifier)) { + var callNode = IndirectCallNode.getUncached(); + var callTarget = typeCastNode.getCallTarget(); + try { + result = callNode.call(callTarget, typeCheckReceiver, typeCheckOwner, result); + } catch (VmException e) { + var member = VmUtils.findMember(parent, key); + assert member != null; // already found the member's cached value + VmUtils.insertStackFrame(member, callTarget, e); + throw e; + } + } + setCachedValue(key, result); + return result; + } + + /** + * Tells whether the value type of this listing/mapping is known to be a subtype of {@code + * typeNode}. If {@code true}, type checks of individual values can be elided because + * listings/mappings are covariant in their value type. + */ + public final boolean isValueTypeKnownSubtypeOf(TypeNode typeNode) { + if (typeNode.isNoopTypeCheck()) { + return true; + } if (typeCastNode == null) { return false; } - if (typeCastNode.getTypeNode().isEquivalentTo(typeNode)) { - return true; - } - // we can say the check is the same if the delegate has this check. - // when `Listing` delegates to `Listing`, it has the same checks as a `UInt` - // typenode. - if (delegate != null) { - return delegate.hasSameChecksAs(typeNode); - } - return false; + return typeCastNode.getTypeNode().isEquivalentTo(typeNode); } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java index 10f050f4..76622f93 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java @@ -18,7 +18,6 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.Map; -import java.util.Objects; import javax.annotation.concurrent.GuardedBy; import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; @@ -27,7 +26,7 @@ import org.pkl.core.util.CollectionUtils; import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.LateInit; -public final class VmMapping extends VmListingOrMapping { +public final class VmMapping extends VmListingOrMapping { private int cachedEntryCount = -1; @@ -50,24 +49,17 @@ public final class VmMapping extends VmListingOrMapping { MaterializedFrame enclosingFrame, VmObject parent, UnmodifiableEconomicMap members) { - - super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null); + super(enclosingFrame, parent, members); } public VmMapping( MaterializedFrame enclosingFrame, VmObject parent, UnmodifiableEconomicMap members, - VmMapping delegate, - ListingOrMappingTypeCastNode typeCheckNode, - MaterializedFrame typeNodeFrame) { - super( - enclosingFrame, - Objects.requireNonNull(parent), - members, - delegate, - typeCheckNode, - typeNodeFrame); + ListingOrMappingTypeCastNode typeCastNode, + Object typeCheckReceiver, + VmObjectLike typeCheckOwner) { + super(enclosingFrame, parent, members, typeCastNode, typeCheckReceiver, typeCheckOwner); } public static boolean isDefaultProperty(Object propertyKey) { @@ -81,16 +73,12 @@ public final class VmMapping extends VmListingOrMapping { @TruffleBoundary public VmSet getAllKeys() { - if (delegate != null) { - return delegate.getAllKeys(); - } synchronized (this) { if (__allKeys == null) { // building upon parent's `getAllKeys()` should improve at least worst case efficiency - var parentKeys = - getParent() instanceof VmMapping mapping ? mapping.getAllKeys() : VmSet.EMPTY; + var parentKeys = parent instanceof VmMapping mapping ? mapping.getAllKeys() : VmSet.EMPTY; var builder = VmSet.builder(parentKeys); - for (var cursor = getMembers().getEntries(); cursor.advance(); ) { + for (var cursor = members.getEntries(); cursor.advance(); ) { var member = cursor.getValue(); if (!member.isEntry()) continue; builder.add(cursor.getKey()); @@ -133,12 +121,17 @@ public final class VmMapping extends VmListingOrMapping { if (this == obj) return true; if (!(obj instanceof VmMapping other)) return false; - if (getEntryCount() != other.getEntryCount()) return false; // could use shallow force, but deep force is cached force(false); other.force(false); - for (var key : getAllKeys()) { - var value = getCachedValue(key); + if (getEntryCount() != other.getEntryCount()) return false; + + var cursor = cachedValues.getEntries(); + while (cursor.advance()) { + Object key = cursor.getKey(); + if (key instanceof Identifier) continue; + + var value = cursor.getValue(); assert value != null; var otherValue = other.getCachedValue(key); if (!value.equals(otherValue)) return false; @@ -151,38 +144,34 @@ public final class VmMapping extends VmListingOrMapping { @TruffleBoundary public int hashCode() { if (cachedHash != 0) return cachedHash; - // It's possible that the delegate has already computed its hash code. - // If so, we can go ahead and use it. - if (delegate != null && delegate.cachedHash != 0) return delegate.cachedHash; force(false); var result = 0; - for (var key : getAllKeys()) { + var cursor = cachedValues.getEntries(); + + while (cursor.advance()) { + var key = cursor.getKey(); if (key instanceof Identifier) continue; - var value = getCachedValue(key); + + var value = cursor.getValue(); assert value != null; result += key.hashCode() ^ value.hashCode(); } + cachedHash = result; return result; } + // assumes mapping has been forced public int getEntryCount() { if (cachedEntryCount != -1) return cachedEntryCount; - cachedEntryCount = getAllKeys().getLength(); - return cachedEntryCount; - } - @Override - @TruffleBoundary - public VmMapping withCheckedMembers( - ListingOrMappingTypeCastNode typeCheckNode, MaterializedFrame typeNodeFrame) { - return new VmMapping( - getEnclosingFrame(), - Objects.requireNonNull(getParent()), - getMembers(), - this, - typeCheckNode, - typeNodeFrame); + var result = 0; + for (var key : cachedValues.getKeys()) { + if (key instanceof Identifier) continue; + result += 1; + } + cachedEntryCount = result; + return result; } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java index c7d86a68..3987d9ac 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java @@ -87,12 +87,12 @@ public abstract class VmObject extends VmObjectLike { } @Override - public void setCachedValue(Object key, Object value, ObjectMember objectMember) { + public final void setCachedValue(Object key, Object value) { EconomicMaps.put(cachedValues, key, value); } @Override - public boolean hasCachedValue(Object key) { + public final boolean hasCachedValue(Object key) { return EconomicMaps.containsKey(cachedValues, key); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java index d665162b..0330f04f 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java @@ -96,7 +96,7 @@ public abstract class VmObjectLike extends VmValue { * receiver. */ @TruffleBoundary - public abstract void setCachedValue(Object key, Object value, ObjectMember objectMember); + public abstract void setCachedValue(Object key, Object value); /** * Prefer this method over {@link #getCachedValue} if the value is not required. (There is no 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 46909071..079aca07 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 @@ -15,6 +15,7 @@ */ package org.pkl.core.runtime; +import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; @@ -134,10 +135,6 @@ public final class VmUtils { return result; } - public static void setReceiver(Frame frame, Object receiver) { - frame.getArguments()[0] = receiver; - } - public static VmObjectLike getObjectReceiver(Frame frame) { return (VmObjectLike) getReceiver(frame); } @@ -158,10 +155,6 @@ public final class VmUtils { return result; } - public static void setOwner(Frame frame, VmObjectLike owner) { - frame.getArguments()[1] = owner; - } - /** Returns a `ObjectMember`'s key while executing the corresponding `MemberNode`. */ public static Object getMemberKey(Frame frame) { return frame.getArguments()[2]; @@ -261,17 +254,17 @@ public final class VmUtils { final var constantValue = member.getConstantValue(); if (constantValue != null) { - var ret = constantValue; - // for a property, do a type check + var result = constantValue; + // for a property, Listing element, or Mapping value, do a type check if (member.isProp()) { var property = receiver.getVmClass().getProperty(member.getName()); if (property != null && property.getTypeNode() != null) { var callTarget = property.getTypeNode().getCallTarget(); try { if (checkType) { - ret = callNode.call(callTarget, receiver, property.getOwner(), constantValue); + result = callNode.call(callTarget, receiver, property.getOwner(), constantValue); } else { - ret = + result = callNode.call( callTarget, receiver, @@ -281,44 +274,52 @@ public final class VmUtils { } } catch (VmException e) { CompilerDirectives.transferToInterpreter(); - // A failed property type check looks as follows in the stack trace: - // x: Int(isPositive) - // at ... - // x = -10 - // at ... - // However, if the value of `x` is a parse-time constant (as in `x = -10`), - // there's no root node for it and hence no stack trace element. - // In this case, insert a stack trace element to make the stack trace look the same - // as in the non-constant case. (Parse-time constants are an internal optimization.) - e.getInsertedStackFrames() - .put( - callTarget, - createStackFrame(member.getBodySection(), member.getQualifiedName())); + insertStackFrame(member, callTarget, e); throw e; } } - } else if (receiver instanceof VmListingOrMapping vmListingOrMapping) { - if (owner != receiver && owner instanceof VmListingOrMapping vmListingOrMappingOwner) { - ret = vmListingOrMappingOwner.typecastObjectMember(member, ret, callNode); - } - ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); + } else if (receiver instanceof VmListingOrMapping listingOrMapping + && owner instanceof VmListingOrMapping) { + // `owner instanceof VmListingOrMapping` guards against + // PropertiesRenderer amending VmDynamic with VmListing (hack?) + result = listingOrMapping.executeTypeCasts(constantValue, owner, callNode, member, null); } - receiver.setCachedValue(memberKey, ret, member); - return ret; + + receiver.setCachedValue(memberKey, result); + return result; } var callTarget = member.getCallTarget(); - Object ret; + Object result; if (checkType) { - ret = callNode.call(callTarget, receiver, owner, memberKey); + result = callNode.call(callTarget, receiver, owner, memberKey); } else { - ret = callNode.call(callTarget, receiver, owner, memberKey, VmUtils.SKIP_TYPECHECK_MARKER); + result = callNode.call(callTarget, receiver, owner, memberKey, VmUtils.SKIP_TYPECHECK_MARKER); } - if (receiver instanceof VmListingOrMapping vmListingOrMapping) { - ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); + receiver.setCachedValue(memberKey, result); + return result; + } + + // A failed property type check looks as follows in the stack trace: + // x: Int(isPositive) + // at ... + // x = -10 + // at ... + // However, if the value of `x` is a parse-time constant (as in `x = -10`), + // there's no root node for it and hence no stack trace element. + // In this case, insert a stack trace element to make the stack trace look the same + // as in the non-constant case. (Parse-time constants are an internal optimization.) + public static void insertStackFrame( + ObjectMember member, CallTarget location, VmException exception) { + var sourceSection = member.getBodySection(); + if (!sourceSection.isAvailable()) { + sourceSection = member.getSourceSection(); + } + if (sourceSection.isAvailable()) { + exception + .getInsertedStackFrames() + .put(location, createStackFrame(sourceSection, member.getQualifiedName())); } - receiver.setCachedValue(memberKey, ret, member); - return ret; } public static @Nullable ObjectMember findMember(VmObjectLike receiver, Object memberKey) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java index 8b829250..ec24c60a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java @@ -50,6 +50,7 @@ import org.pkl.core.runtime.VmDuration; import org.pkl.core.runtime.VmDynamic; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.runtime.VmIntSeq; +import org.pkl.core.runtime.VmLanguage; import org.pkl.core.runtime.VmList; import org.pkl.core.runtime.VmListing; import org.pkl.core.runtime.VmMap; @@ -573,7 +574,8 @@ public final class RendererNodes { type = requiresWrapper() ? null - : new ListingTypeNode(VmUtils.unavailableSourceSection(), valueType); + : new ListingTypeNode( + VmUtils.unavailableSourceSection(), VmLanguage.get(null), valueType); return type; } else if (type instanceof MappingTypeNode mappingType) { var keyType = resolveType(mappingType.getKeyTypeNode()); @@ -587,7 +589,9 @@ public final class RendererNodes { } var valueType = resolveType(mappingType.getValueTypeNode()); assert valueType != null : "Incomplete or malformed Mapping type"; - mappingType = new MappingTypeNode(VmUtils.unavailableSourceSection(), keyType, valueType); + mappingType = + new MappingTypeNode( + VmUtils.unavailableSourceSection(), VmLanguage.get(null), keyType, valueType); type = requiresWrapper() ? null : mappingType; return type; diff --git a/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java b/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java index ab462c2e..2bfe2e72 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java +++ b/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java @@ -30,6 +30,10 @@ import org.graalvm.collections.UnmodifiableMapCursor; public final class EconomicMaps { private EconomicMaps() {} + public static UnmodifiableEconomicMap emptyMap() { + return EconomicMap.emptyMap(); + } + @TruffleBoundary public static EconomicMap create() { return EconomicMap.create(); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/listings/cacheStealingTypeCheck.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/listings/cacheStealingTypeCheck.pkl new file mode 100644 index 00000000..8720ec59 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/listings/cacheStealingTypeCheck.pkl @@ -0,0 +1,3 @@ +function isValid(str): Boolean = str.startsWith("a") + +foo: Listing(isDistinct) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError8.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError8.pkl new file mode 100644 index 00000000..8cf75b36 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError8.pkl @@ -0,0 +1,6 @@ +local a = new Listing { new Listing { 0 } } +local b = a as Listing> +local c = (b) { new Listing { 1 } } +local d = c as Listing> + +result = d diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError9.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError9.pkl new file mode 100644 index 00000000..f98994e4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/listingTypeCheckError9.pkl @@ -0,0 +1,10 @@ +local a = new Listing { new Foo {} } +local b = (a) { new Bar {} } +local c = b as Listing +local d = (c) { new Foo {} } +local e = d as Listing + +result = e + +open class Foo +class Bar extends Foo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/mappingTypeCheckError11.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/mappingTypeCheckError11.pkl new file mode 100644 index 00000000..6c212ae1 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/mappingTypeCheckError11.pkl @@ -0,0 +1,10 @@ +local a = new Mapping { [0] = new Foo {} } +local b = (a) { [1] = new Bar {} } +local c = b as Mapping +local d = (c) { [2] = new Foo {} } +local e = d as Mapping + +result = e + +open class Foo +class Bar extends Foo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealing.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealing.pkl new file mode 100644 index 00000000..e083e10a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealing.pkl @@ -0,0 +1,7 @@ +foo1: Listing = new { "hello" } +foo2: Listing = foo1 + +res1 = foo1.isDistinct +// steals isDistinct from foo1's VmListing.cachedValues but must not +// perform a String|Int type check because isDistinct is not an element +res2 = foo2.isDistinct diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealingTypeCheck.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealingTypeCheck.pkl new file mode 100644 index 00000000..bad4db38 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/listings/cacheStealingTypeCheck.pkl @@ -0,0 +1,8 @@ +amends "../../input-helper/listings/cacheStealingTypeCheck.pkl" + +// this test covers a regression where the wrong receiver +// and owner was used to typecheck a stolen value +foo { + "abcdx" + "ax" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/helpers/originalTypealias.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/helpers/originalTypealias.pkl new file mode 100644 index 00000000..93e5759c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/helpers/originalTypealias.pkl @@ -0,0 +1,5 @@ +const local lastName = "Birdo" + +typealias Birds = Listing + +typealias Birds2 = Pair, Int> diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAlias1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAlias1.pkl index 3f5ddcd0..0c556adb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAlias1.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAlias1.pkl @@ -1,5 +1,7 @@ import "pkl:test" +import "helpers/originalTypealias.pkl" + typealias Simple = String const function simple(arg: Simple): Simple = arg @@ -105,3 +107,8 @@ res19: LocalTypeAlias = "abc" typealias VeryComposite = Pair res20: VeryComposite = Pair(Map("abc", List("def")), Map("abc", List("def"))) + +// typealiases should be executed in their original context +res21: originalTypealias.Birds = new { "John Birdo" } + +res22: originalTypealias.Birds2 = Pair(new Listing { "John Birdo" }, 0) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasContext.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasContext.pkl new file mode 100644 index 00000000..ff392fdb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasContext.pkl @@ -0,0 +1,3 @@ +import "helpers/originalTypealias.pkl" + +res: originalTypealias.Birds = new { "Jimmy Bird" } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError8.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError8.err new file mode 100644 index 00000000..6d638586 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError8.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Expected value of type `String`, but got type `Int`. +Value: 0 + +x | local b = a as Listing> + ^^^^^^ +at listingTypeCheckError8#b (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl) + +x | local a = new Listing { new Listing { 0 } } + ^ +at listingTypeCheckError8#a[#1][#1] (file:///$snippetsDir/input/errors/listingTypeCheckError8.pkl) + +xxx | text = renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError9.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError9.err new file mode 100644 index 00000000..cf4661cb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError9.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Expected value of type `listingTypeCheckError9#Bar`, but got type `listingTypeCheckError9#Foo`. +Value: new Foo {} + +x | local c = b as Listing + ^^^ +at listingTypeCheckError9#c (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl) + +x | local a = new Listing { new Foo {} } + ^^^^^^^^^^ +at listingTypeCheckError9#a[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError9.pkl) + +xxx | text = renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError11.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError11.err new file mode 100644 index 00000000..2c4c8df7 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError11.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Expected value of type `mappingTypeCheckError11#Bar`, but got type `mappingTypeCheckError11#Foo`. +Value: new Foo {} + +x | local c = b as Mapping + ^^^ +at mappingTypeCheckError11#c (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl) + +x | local a = new Mapping { [0] = new Foo {} } + ^^^^^^^^^^ +at mappingTypeCheckError11#a[0] (file:///$snippetsDir/input/errors/mappingTypeCheckError11.pkl) + +xxx | text = renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealing.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealing.pcf new file mode 100644 index 00000000..589fe471 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealing.pcf @@ -0,0 +1,8 @@ +foo1 { + "hello" +} +foo2 { + "hello" +} +res1 = true +res2 = true diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealingTypeCheck.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealingTypeCheck.pcf new file mode 100644 index 00000000..141c3923 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/listings/cacheStealingTypeCheck.pcf @@ -0,0 +1,4 @@ +foo { + "abcdx" + "ax" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAlias1.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAlias1.pcf index 40979571..fb673be3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAlias1.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAlias1.pcf @@ -38,3 +38,9 @@ res18 = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb res18b = "Expected value of type `Duration`, but got type `DataSize`. Value: 5.mb" res19 = "abc" res20 = Pair(Map("abc", List("def")), Map("abc", List("def"))) +res21 { + "John Birdo" +} +res22 = Pair(new { + "John Birdo" +}, 0) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err new file mode 100644 index 00000000..e1e58112 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Type constraint `endsWith(lastName)` violated. +Value: "Jimmy Bird" + +x | typealias Birds = Listing + ^^^^^^^^^^^^^^^^^^ +at typeAliasContext#res (file:///$snippetsDir/input/types/helpers/originalTypealias.pkl) + +x | res: originalTypealias.Birds = new { "Jimmy Bird" } + ^^^^^^^^^^^^ +at typeAliasContext#res[#1] (file:///$snippetsDir/input/types/typeAliasContext.pkl) + +xxx | text = renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base)