Typecheck Mapping/Listing members lazily (#628)

This changes how the language performs typechecks for mappings and
listings.

Currently, Pkl will shallow-force any Mapping and Listing to check it
the type parameter (e.g. Listing<Person> means each element is checked
to be an instance of Person).

This changes the language to check each member's type when the member
is accessed.

This also adjust test runner to handle thrown errors from within tests.

With the change to make mapping/listing typechecks lazy, we can now
correctly handle thrown errors from within a single test case.

This adjusts the test runner to consider any thrown errors as a failure
for that specific test case.
This commit is contained in:
Daniel Chao
2024-09-06 15:05:23 -07:00
committed by GitHub
parent 7001a42149
commit 7868d9d9c8
86 changed files with 3342 additions and 385 deletions

View File

@@ -22,7 +22,6 @@ import java.util.function.Function;
import org.pkl.core.ast.member.DefaultPropertyBodyNode;
import org.pkl.core.runtime.VmExceptionBuilder;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.Nullable;
public abstract class MemberNode extends PklRootNode {
@@ -57,19 +56,6 @@ public abstract class MemberNode extends PklRootNode {
return new VmExceptionBuilder().withSourceSection(getHeaderSection());
}
/**
* If true, the property value computed by this node is not the final value exposed to user code
* but will still be amended.
*
* <p>Used to disable type check for to-be-amended properties. See {@link
* org.pkl.core.runtime.VmUtils#SKIP_TYPECHECK_MARKER}. IDEA: might be more appropriate to only
* skip constraints check
*/
protected final boolean shouldRunTypeCheck(VirtualFrame frame) {
return frame.getArguments().length != 4
|| frame.getArguments()[3] != VmUtils.SKIP_TYPECHECK_MARKER;
}
public boolean isUndefined() {
return bodyNode instanceof DefaultPropertyBodyNode propBodyNode && propBodyNode.isUndefined();
}

View File

@@ -442,13 +442,21 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
: doVisitNewExprWithInferredParent(ctx);
}
// `new Listing<Person> {}` is sugar for: `new Listing<Person> {} as Listing<Person>`
private Object doVisitNewExprWithExplicitParent(NewExprContext ctx, TypeContext typeCtx) {
return doVisitObjectBody(
ctx.objectBody(),
new GetParentForTypeNode(
createSourceSection(ctx),
visitType(typeCtx),
symbolTable.getCurrentScope().getQualifiedName()));
var parentType = visitType(typeCtx);
var expr =
doVisitObjectBody(
ctx.objectBody(),
new GetParentForTypeNode(
createSourceSection(ctx),
parentType,
symbolTable.getCurrentScope().getQualifiedName()));
if (typeCtx instanceof DeclaredTypeContext declaredTypeContext
&& declaredTypeContext.typeArgumentList() != null) {
return new TypeCastNode(parentType.getSourceSection(), expr, parentType);
}
return expr;
}
private Object doVisitNewExprWithInferredParent(NewExprContext ctx) {

View File

@@ -103,7 +103,7 @@ public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode {
var callTarget = member.getCallTarget();
value = callTarget.call(parent, owner, key);
}
owner.setCachedValue(key, value);
owner.setCachedValue(key, value, member);
}
frame.setAuxiliarySlot(customThisSlot, value);

View File

@@ -150,8 +150,8 @@ public abstract class EntriesLiteralNode extends SpecializedObjectLiteralNode {
@Fallback
@TruffleBoundary
protected void fallback(Object parent) {
elementsEntriesFallback(parent, values[0], false);
protected Object fallback(Object parent) {
return elementsEntriesFallback(parent, values[0], false);
}
@ExplodeLoop

View File

@@ -285,7 +285,7 @@ public abstract class SpecializedObjectLiteralNode extends ObjectLiteralNode {
}
@TruffleBoundary
protected void elementsEntriesFallback(
protected Object elementsEntriesFallback(
Object parent, @Nullable ObjectMember firstElemOrEntry, boolean isElementsOnly) {
var parentIsClass = parent instanceof VmClass;
var parentClass = parentIsClass ? (VmClass) parent : VmUtils.getClass(parent);

View File

@@ -71,7 +71,7 @@ public final class ReadLocalPropertyNode extends ExpressionNode {
if (result == null) {
result = callNode.call(objReceiver, owner, property.getName());
objReceiver.setCachedValue(property, result);
objReceiver.setCachedValue(property, result, property);
}
return result;

View File

@@ -184,12 +184,12 @@ public final class ResolveVariableNode extends ExpressionNode {
if (member != null) {
var constantValue = member.getConstantValue();
if (constantValue != null) {
baseModule.setCachedValue(variableName, constantValue);
baseModule.setCachedValue(variableName, constantValue, member);
return new ConstantValueNode(sourceSection, constantValue);
}
var computedValue = member.getCallTarget().call(baseModule, baseModule);
baseModule.setCachedValue(variableName, computedValue);
baseModule.setCachedValue(variableName, computedValue, member);
return new ConstantValueNode(sourceSection, computedValue);
}
}

View File

@@ -118,7 +118,7 @@ public final class FunctionNode extends RegularMemberNode {
var result = bodyNode.executeGeneric(frame);
if (checkedReturnTypeNode != null) {
checkedReturnTypeNode.execute(frame, result);
return checkedReturnTypeNode.execute(frame, result);
}
return result;

View File

@@ -0,0 +1,64 @@
/**
* 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.CompilerDirectives;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.PklRootNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.VmTypeMismatchException;
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 {
@Child private TypeNode typeNode;
private final String qualifiedName;
public ListingOrMappingTypeCastNode(
VmLanguage language, FrameDescriptor descriptor, TypeNode typeNode, String qualifiedName) {
super(language, descriptor);
this.typeNode = typeNode;
this.qualifiedName = qualifiedName;
}
public TypeNode getTypeNode() {
return typeNode;
}
@Override
public SourceSection getSourceSection() {
return typeNode.getSourceSection();
}
@Override
public @Nullable String getName() {
return qualifiedName;
}
@Override
public Object execute(VirtualFrame frame) {
try {
return typeNode.execute(frame, frame.getArguments()[2]);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();
}
}
}

View File

@@ -66,8 +66,7 @@ public final class LocalTypedPropertyNode extends RegularMemberNode {
unresolvedTypeNode = null;
}
var result = bodyNode.executeGeneric(frame);
typeNode.execute(frame, result);
return result;
return typeNode.execute(frame, result);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();

View File

@@ -134,7 +134,8 @@ public final class ObjectMember extends Member {
var skip = 0;
var text = candidate.getCharacters();
var ch = text.charAt(skip);
while (ch == '=' || Character.isWhitespace(ch)) {
// body section of entries needs to chomp the ending delimiter too.
while ((ch == ']' && isEntry()) || ch == '=' || Character.isWhitespace(ch)) {
ch = text.charAt(++skip);
}
return source.createSection(candidate.getCharIndex() + skip, candidate.getCharLength() - skip);

View File

@@ -62,10 +62,9 @@ public final class PropertyTypeNode extends PklRootNode {
}
@Override
public @Nullable Object execute(VirtualFrame frame) {
public Object execute(VirtualFrame frame) {
try {
typeNode.execute(frame, frame.getArguments()[2]);
return null;
return typeNode.execute(frame, frame.getArguments()[2]);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();

View File

@@ -54,8 +54,8 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
var result = executeBody(frame);
// TODO: propagate SUPER_CALL_MARKER to disable constraint (but not type) check
if (callNode != null && shouldRunTypeCheck(frame)) {
callNode.call(VmUtils.getReceiverOrNull(frame), property.getOwner(), result);
if (callNode != null && VmUtils.shouldRunTypeCheck(frame)) {
return callNode.call(VmUtils.getReceiverOrNull(frame), property.getOwner(), result);
}
return result;
@@ -67,11 +67,11 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
var result = executeBody(frame);
if (shouldRunTypeCheck(frame)) {
if (VmUtils.shouldRunTypeCheck(frame)) {
var property = getProperty(owner.getVmClass());
var typeAnnNode = property.getTypeNode();
if (typeAnnNode != null) {
callNode.call(
return callNode.call(
typeAnnNode.getCallTarget(),
VmUtils.getReceiverOrNull(frame),
property.getOwner(),

View File

@@ -45,8 +45,9 @@ public final class TypedPropertyNode extends RegularMemberNode {
@Override
public Object execute(VirtualFrame frame) {
var propertyValue = executeBody(frame);
if (shouldRunTypeCheck(frame)) {
typeCheckCallNode.call(VmUtils.getReceiver(frame), VmUtils.getOwner(frame), propertyValue);
if (VmUtils.shouldRunTypeCheck(frame)) {
return typeCheckCallNode.call(
VmUtils.getReceiver(frame), VmUtils.getOwner(frame), propertyValue);
}
return propertyValue;
}

View File

@@ -66,7 +66,7 @@ public final class IdentityMixinNode extends PklRootNode {
try {
var argument = arguments[2];
if (argumentTypeNode != null) {
argumentTypeNode.execute(frame, argument);
return argumentTypeNode.execute(frame, argument);
}
return argument;
} catch (VmTypeMismatchException e) {

View File

@@ -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);
module.setCachedValue(importName, result, member);
}
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);
module.setCachedValue(typeName, result, member);
}
return result;
}

View File

@@ -17,10 +17,12 @@ package org.pkl.core.ast.type;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.util.LateInit;
@NodeInfo(shortName = "as")
public final class TypeCastNode extends ExpressionNode {
@Child private ExpressionNode valueNode;
@Child private UnresolvedTypeNode unresolvedTypeNode;
@@ -47,8 +49,7 @@ public final class TypeCastNode extends ExpressionNode {
var value = valueNode.executeGeneric(frame);
try {
typeNode.execute(frame, value);
return value;
return typeNode.execute(frame, value);
} catch (VmTypeMismatchException e) {
CompilerDirectives.transferToInterpreter();
throw e.toVmException();

File diff suppressed because it is too large Load Diff

View File

@@ -17,10 +17,12 @@ package org.pkl.core.ast.type;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.util.Nullable;
@NodeInfo(shortName = "is")
public final class TypeTestNode extends ExpressionNode {
@Child private ExpressionNode valueNode;
@Child private UnresolvedTypeNode unresolvedTypeNode;
@@ -50,9 +52,11 @@ public final class TypeTestNode extends ExpressionNode {
unresolvedTypeNode = null;
}
// TODO: throw if typeNode is FunctionTypeNode (it's impossible to check)
// https://github.com/apple/pkl/issues/639
Object value = valueNode.executeGeneric(frame);
try {
typeNode.execute(frame, value);
typeNode.executeEagerly(frame, value);
return true;
} catch (VmTypeMismatchException e) {
return false;

View File

@@ -219,11 +219,11 @@ public abstract class UnresolvedTypeNode extends PklNode {
checkNumberOfTypeArguments(clazz);
if (clazz.isCollectionClass()) {
return CollectionTypeNodeGen.create(sourceSection, typeArgumentNodes[0].execute(frame));
return new CollectionTypeNode(sourceSection, typeArgumentNodes[0].execute(frame));
}
if (clazz.isListClass()) {
return ListTypeNodeGen.create(sourceSection, typeArgumentNodes[0].execute(frame));
return new ListTypeNode(sourceSection, typeArgumentNodes[0].execute(frame));
}
if (clazz.isSetClass()) {
@@ -231,25 +231,25 @@ public abstract class UnresolvedTypeNode extends PklNode {
}
if (clazz.isMapClass()) {
return MapTypeNodeGen.create(
return new MapTypeNode(
sourceSection,
typeArgumentNodes[0].execute(frame),
typeArgumentNodes[1].execute(frame));
}
if (clazz.isListingClass()) {
return ListingTypeNodeGen.create(sourceSection, typeArgumentNodes[0].execute(frame));
return new ListingTypeNode(sourceSection, typeArgumentNodes[0].execute(frame));
}
if (clazz.isMappingClass()) {
return MappingTypeNodeGen.create(
return new MappingTypeNode(
sourceSection,
typeArgumentNodes[0].execute(frame),
typeArgumentNodes[1].execute(frame));
}
if (clazz.isPairClass()) {
return PairTypeNodeGen.create(
return new PairTypeNode(
sourceSection,
typeArgumentNodes[0].execute(frame),
typeArgumentNodes[1].execute(frame));
@@ -324,7 +324,7 @@ public abstract class UnresolvedTypeNode extends PklNode {
public TypeNode execute(VirtualFrame frame) {
CompilerDirectives.transferToInterpreter();
return NullableTypeNodeGen.create(sourceSection, elementTypeNode.execute(frame));
return new NullableTypeNode(sourceSection, elementTypeNode.execute(frame));
}
}

View File

@@ -15,16 +15,19 @@
*/
package org.pkl.core.ast.type;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.ControlFlowException;
import com.oracle.truffle.api.source.SourceSection;
import java.util.*;
import java.util.stream.Collectors;
import org.pkl.core.StackFrame;
import org.pkl.core.ValueFormatter;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
import org.pkl.core.runtime.*;
import org.pkl.core.runtime.VmException.ProgramValue;
import org.pkl.core.util.ErrorMessages;
import org.pkl.core.util.Nullable;
/**
* Indicates that a type check failed. [TypeNode]s use this exception instead of [VmException] to
@@ -35,18 +38,35 @@ import org.pkl.core.util.ErrorMessages;
public abstract class VmTypeMismatchException extends ControlFlowException {
protected final SourceSection sourceSection;
protected final Object actualValue;
protected @Nullable Map<CallTarget, StackFrame> insertedStackFrames = null;
protected VmTypeMismatchException(SourceSection sourceSection, Object actualValue) {
this.sourceSection = sourceSection;
this.actualValue = actualValue;
}
@TruffleBoundary
public void putInsertedStackFrame(CallTarget callTarget, StackFrame stackFrame) {
if (this.insertedStackFrames == null) {
this.insertedStackFrames = new HashMap<>();
}
this.insertedStackFrames.put(callTarget, stackFrame);
}
@TruffleBoundary
public abstract void describe(StringBuilder builder, String indent);
@TruffleBoundary
public abstract VmException toVmException();
protected VmExceptionBuilder exceptionBuilder() {
var builder = new VmExceptionBuilder();
if (insertedStackFrames != null) {
builder.withInsertedStackFrames(insertedStackFrames);
}
return builder;
}
public static final class Simple extends VmTypeMismatchException {
private final Object expectedType;
@@ -128,11 +148,12 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return exceptionBuilder().build();
}
private VmExceptionBuilder exceptionBuilder() {
@Override
protected VmExceptionBuilder exceptionBuilder() {
var builder = new StringBuilder();
describe(builder, "");
return new VmExceptionBuilder()
return super.exceptionBuilder()
.adhocEvalError(builder.toString())
.withSourceSection(sourceSection);
}
@@ -162,11 +183,12 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return exceptionBuilder().build();
}
private VmExceptionBuilder exceptionBuilder() {
@Override
protected VmExceptionBuilder exceptionBuilder() {
var builder = new StringBuilder();
describe(builder, "");
return new VmExceptionBuilder()
return super.exceptionBuilder()
.adhocEvalError(builder.toString())
.withSourceSection(sourceSection);
}
@@ -199,14 +221,15 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return exceptionBuilder().build();
}
private VmExceptionBuilder exceptionBuilder() {
@Override
protected VmExceptionBuilder exceptionBuilder() {
var summary = new StringBuilder();
describeSummary(summary, "");
var details = new StringBuilder();
describeDetails(details, "");
return new VmExceptionBuilder()
return super.exceptionBuilder()
.adhocEvalError(summary.toString())
.withSourceSection(sourceSection)
.withHint(details.toString());
@@ -304,11 +327,12 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
return exceptionBuilder().build();
}
private VmExceptionBuilder exceptionBuilder() {
@Override
protected VmExceptionBuilder exceptionBuilder() {
var builder = new StringBuilder();
describe(builder, "");
return new VmExceptionBuilder()
return super.exceptionBuilder()
.adhocEvalError(builder.toString())
.withSourceSection(sourceSection);
}

View File

@@ -79,19 +79,25 @@ public final class TestRunner {
var factsMapping = (VmMapping) facts;
factsMapping.forceAndIterateMemberValues(
(groupKey, groupMember, groupValue) -> {
var listing = (VmListing) groupValue;
var result = results.newResult(String.valueOf(groupKey));
var groupListing = (VmListing) groupValue;
groupListing.forceAndIterateMemberValues(
((factIndex, factMember, factValue) -> {
assert factValue instanceof Boolean;
if (factValue == Boolean.FALSE) {
result.addFailure(
Failure.buildFactFailure(
factMember.getSourceSection(), getDisplayUri(factMember)));
return listing.iterateMembers(
(idx, member) -> {
if (member.isLocalOrExternalOrHidden()) {
return true;
}
try {
var factValue = VmUtils.readMember(listing, idx);
if (factValue == Boolean.FALSE) {
result.addFailure(
Failure.buildFactFailure(member.getSourceSection(), getDisplayUri(member)));
}
} catch (VmException err) {
result.addError(
new Error(err.getMessage(), err.toPklException(stackFrameTransformer)));
}
return true;
}));
return true;
});
});
}
@@ -142,12 +148,14 @@ public final class TestRunner {
var expectedExampleOutputs = loadExampleOutputs(expectedOutputFile);
var actualExampleOutputs = new MutableReference<VmDynamic>(null);
var allGroupsSucceeded = new MutableBoolean(true);
var errored = new MutableBoolean(false);
examples.forceAndIterateMemberValues(
(groupKey, groupMember, groupValue) -> {
var testName = String.valueOf(groupKey);
var group = (VmListing) groupValue;
var expectedGroup =
(VmDynamic) VmUtils.readMemberOrNull(expectedExampleOutputs, groupKey);
var result = results.newResult(testName);
if (expectedGroup == null) {
results.newResult(
@@ -158,8 +166,7 @@ public final class TestRunner {
}
if (group.getLength() != expectedGroup.getLength()) {
results.newResult(
testName,
result.addFailure(
Failure.buildExampleLengthMismatchFailure(
getDisplayUri(groupMember),
String.valueOf(groupKey),
@@ -169,8 +176,21 @@ public final class TestRunner {
}
var groupSucceeded = new MutableBoolean(true);
group.forceAndIterateMemberValues(
((exampleIndex, exampleMember, exampleValue) -> {
group.iterateMembers(
((exampleIndex, exampleMember) -> {
if (exampleMember.isLocalOrExternalOrHidden()) {
return true;
}
Object exampleValue;
try {
exampleValue = VmUtils.readMember(group, exampleIndex);
} catch (VmException err) {
errored.set(true);
result.addError(
new Error(err.getMessage(), err.toPklException(stackFrameTransformer)));
groupSucceeded.set(false);
return true;
}
var expectedValue = VmUtils.readMember(expectedGroup, exampleIndex);
var exampleValuePcf = renderAsPcf(exampleValue);
@@ -202,8 +222,7 @@ public final class TestRunner {
.build();
}
results.newResult(
testName,
result.addFailure(
Failure.buildExampleFailure(
getDisplayUri(exampleMember),
getDisplayUri(expectedMember),
@@ -215,9 +234,7 @@ public final class TestRunner {
return true;
}));
if (groupSucceeded.get()) {
results.newResult(testName);
} else {
if (!groupSucceeded.get()) {
allGroupsSucceeded.set(false);
}
@@ -231,26 +248,52 @@ public final class TestRunner {
}
if (examples.getCachedValue(groupKey) == null) {
allGroupsSucceeded.set(false);
results.newResult(
String.valueOf(groupKey),
Failure.buildExamplePropertyMismatchFailure(
getDisplayUri(groupMember), String.valueOf(groupKey), false));
results
.newResult(String.valueOf(groupKey))
.addFailure(
Failure.buildExamplePropertyMismatchFailure(
getDisplayUri(groupMember), String.valueOf(groupKey), false));
}
return true;
});
if (!allGroupsSucceeded.get() && actualExampleOutputs.isNull()) {
if (!allGroupsSucceeded.get() && actualExampleOutputs.isNull() && !errored.get()) {
writeExampleOutputs(actualOutputFile, examples);
}
}
private void doRunAndWriteExamples(VmMapping examples, Path outputFile, TestResults results) {
examples.forceAndIterateMemberValues(
(groupKey, groupMember, groupValue) -> {
results.newResult(String.valueOf(groupKey)).setExampleWritten(true);
return true;
});
writeExampleOutputs(outputFile, examples);
var allSucceeded =
examples.forceAndIterateMemberValues(
(groupKey, groupMember, groupValue) -> {
var listing = (VmListing) groupValue;
var success =
listing.iterateMembers(
(idx, member) -> {
if (member.isLocalOrExternalOrHidden()) {
return true;
}
try {
VmUtils.readMember(listing, idx);
return true;
} catch (VmException err) {
results
.newResult(String.valueOf(groupKey))
.addError(
new Error(
err.getMessage(), err.toPklException(stackFrameTransformer)));
return false;
}
});
if (!success) {
return false;
}
results.newResult(String.valueOf(groupKey)).setExampleWritten(true);
return true;
});
if (allSucceeded) {
writeExampleOutputs(outputFile, examples);
}
}
private void writeExampleOutputs(Path outputFile, VmMapping examples) {

View File

@@ -15,10 +15,12 @@
*/
package org.pkl.core.runtime;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.List;
import java.util.Map;
import org.pkl.core.*;
import org.pkl.core.util.Nullable;
@@ -32,7 +34,8 @@ public final class VmBugException extends VmException {
@Nullable Node location,
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable String hint) {
@Nullable String hint,
Map<CallTarget, StackFrame> insertedStackFrames) {
super(
message,
@@ -43,7 +46,8 @@ public final class VmBugException extends VmException {
location,
sourceSection,
memberName,
hint);
hint,
insertedStackFrames);
}
@Override

View File

@@ -15,9 +15,12 @@
*/
package org.pkl.core.runtime;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.List;
import java.util.Map;
import org.pkl.core.StackFrame;
import org.pkl.core.util.Nullable;
public class VmEvalException extends VmException {
@@ -30,7 +33,8 @@ public class VmEvalException extends VmException {
@Nullable Node location,
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable String hint) {
@Nullable String hint,
Map<CallTarget, StackFrame> insertedStackFrames) {
super(
message,
@@ -41,6 +45,7 @@ public class VmEvalException extends VmException {
location,
sourceSection,
memberName,
hint);
hint,
insertedStackFrames);
}
}

View File

@@ -20,7 +20,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.pkl.core.*;
@@ -33,8 +32,7 @@ public abstract class VmException extends AbstractTruffleException {
private final @Nullable SourceSection sourceSection;
private final @Nullable String memberName;
protected @Nullable String hint;
private final Map<CallTarget, StackFrame> insertedStackFrames = new HashMap<>();
private final Map<CallTarget, StackFrame> insertedStackFrames;
public VmException(
String message,
@@ -45,7 +43,8 @@ public abstract class VmException extends AbstractTruffleException {
@Nullable Node location,
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable String hint) {
@Nullable String hint,
Map<CallTarget, StackFrame> insertedStackFrames) {
super(message, cause, UNLIMITED_STACK_TRACE, location);
this.isExternalMessage = isExternalMessage;
this.messageArguments = messageArguments;
@@ -53,6 +52,7 @@ public abstract class VmException extends AbstractTruffleException {
this.sourceSection = sourceSection;
this.memberName = memberName;
this.hint = hint;
this.insertedStackFrames = insertedStackFrames;
}
public final boolean isExternalMessage() {

View File

@@ -15,11 +15,13 @@
*/
package org.pkl.core.runtime;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.pkl.core.StackFrame;
import org.pkl.core.runtime.MemberLookupSuggestions.Candidate.Kind;
import org.pkl.core.runtime.VmException.ProgramValue;
import org.pkl.core.util.Nullable;
@@ -50,6 +52,7 @@ import org.pkl.core.util.Nullable;
public final class VmExceptionBuilder {
private @Nullable Object receiver;
private @Nullable Map<CallTarget, StackFrame> insertedStackFrames;
public static class MultilineValue {
private final Iterable<?> lines;
@@ -329,11 +332,19 @@ public final class VmExceptionBuilder {
return this;
}
public VmExceptionBuilder withInsertedStackFrames(
Map<CallTarget, StackFrame> insertedStackFrames) {
this.insertedStackFrames = insertedStackFrames;
return this;
}
public VmException build() {
if (message == null) {
throw new IllegalStateException("No message set.");
}
var effectiveInsertedStackFrames =
insertedStackFrames == null ? new HashMap<CallTarget, StackFrame>() : insertedStackFrames;
return switch (kind) {
case EVAL_ERROR ->
new VmEvalException(
@@ -345,7 +356,8 @@ public final class VmExceptionBuilder {
location,
sourceSection,
memberName,
hint);
hint,
effectiveInsertedStackFrames);
case UNDEFINED_VALUE ->
new VmUndefinedValueException(
message,
@@ -357,7 +369,8 @@ public final class VmExceptionBuilder {
sourceSection,
memberName,
hint,
receiver);
receiver,
effectiveInsertedStackFrames);
case BUG ->
new VmBugException(
message,
@@ -368,7 +381,8 @@ public final class VmExceptionBuilder {
location,
sourceSection,
memberName,
hint);
hint,
effectiveInsertedStackFrames);
};
}

View File

@@ -113,7 +113,7 @@ public final class VmFunction extends VmObjectLike {
@Override
@TruffleBoundary
public void setCachedValue(Object key, Object value) {
public void setCachedValue(Object key, Object value, ObjectMember objectMember) {
throw new VmExceptionBuilder()
.bug("Class `VmFunction` does not support method `setCachedValue()`.")
.build();

View File

@@ -21,13 +21,12 @@ 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;
// TODO: make sure that "default" isn't forced
// when a listing is rendered ("default" should be allowed to be partial)
public final class VmListing extends VmObject {
public final class VmListing extends VmListingOrMapping<VmListing> {
private static final class EmptyHolder {
private static final VmListing EMPTY =
new VmListing(
@@ -48,7 +47,25 @@ public final class VmListing extends VmObject {
VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members,
int length) {
super(enclosingFrame, Objects.requireNonNull(parent), members);
super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null);
this.length = length;
}
public VmListing(
MaterializedFrame enclosingFrame,
VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members,
int length,
@Nullable VmListing delegate,
ListingOrMappingTypeCastNode typeCheckNode,
MaterializedFrame typeNodeFrame) {
super(
enclosingFrame,
Objects.requireNonNull(parent),
members,
delegate,
typeCheckNode,
typeNodeFrame);
this.length = length;
}
@@ -100,6 +117,20 @@ public final class VmListing extends VmObject {
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) {

View File

@@ -0,0 +1,169 @@
/**
* 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.runtime;
import com.oracle.truffle.api.CompilerDirectives;
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<SELF extends VmListingOrMapping<SELF>> extends VmObject {
/**
* A Listing or Mapping typecast creates a new object that contains a new typecheck node, and
* delegates member lookups to this delegate.
*/
private final @Nullable SELF delegate;
private final @Nullable ListingOrMappingTypeCastNode typeCastNode;
private final MaterializedFrame typeNodeFrame;
private final EconomicMap<Object, ObjectMember> cachedMembers = EconomicMaps.create();
private final EconomicSet<Object> checkedMembers = EconomicSets.create();
public VmListingOrMapping(
MaterializedFrame enclosingFrame,
@Nullable VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members,
@Nullable SELF delegate,
@Nullable ListingOrMappingTypeCastNode typeCastNode,
@Nullable MaterializedFrame typeNodeFrame) {
super(enclosingFrame, parent, members);
this.delegate = delegate;
this.typeCastNode = typeCastNode;
this.typeNodeFrame = typeNodeFrame;
}
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 @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);
}
var callTarget = typeCastNode.getCallTarget();
try {
return callNode.call(
callTarget, VmUtils.getReceiver(typeNodeFrame), VmUtils.getOwner(typeNodeFrame), ret);
} catch (VmException vmException) {
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 (sourceSection.isAvailable()) {
vmException
.getInsertedStackFrames()
.put(callTarget, VmUtils.createStackFrame(sourceSection, member.getQualifiedName()));
}
throw vmException;
}
}
public abstract SELF withCheckedMembers(
ListingOrMappingTypeCastNode typeCastNode, MaterializedFrame typeNodeFrame);
/** Tells if this mapping/listing runs the same typechecks as {@code typeNode}. */
public boolean hasSameChecksAs(TypeNode typeNode) {
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<Any>` delegates to `Listing<UInt>`, it has the same checks as a `UInt`
// typenode.
if (delegate != null) {
return delegate.hasSameChecksAs(typeNode);
}
return false;
}
}

View File

@@ -21,14 +21,14 @@ 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;
import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.util.CollectionUtils;
import org.pkl.core.util.EconomicMaps;
import org.pkl.core.util.LateInit;
// TODO: make sure that "default" isn't forced
// when a mapping is rendered ("default" should be allowed to be partial)
public final class VmMapping extends VmObject {
public final class VmMapping extends VmListingOrMapping<VmMapping> {
private int cachedEntryCount = -1;
@GuardedBy("this")
@@ -51,7 +51,23 @@ public final class VmMapping extends VmObject {
VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members) {
super(enclosingFrame, Objects.requireNonNull(parent), members);
super(enclosingFrame, Objects.requireNonNull(parent), members, null, null, null);
}
public VmMapping(
MaterializedFrame enclosingFrame,
VmObject parent,
UnmodifiableEconomicMap<Object, ObjectMember> members,
VmMapping delegate,
ListingOrMappingTypeCastNode typeCheckNode,
MaterializedFrame typeNodeFrame) {
super(
enclosingFrame,
Objects.requireNonNull(parent),
members,
delegate,
typeCheckNode,
typeNodeFrame);
}
public static boolean isDefaultProperty(Object propertyKey) {
@@ -181,4 +197,17 @@ public final class VmMapping extends VmObject {
cachedEntryCount = result;
return result;
}
@Override
@TruffleBoundary
public VmMapping withCheckedMembers(
ListingOrMappingTypeCastNode typeCheckNode, MaterializedFrame typeNodeFrame) {
return new VmMapping(
getEnclosingFrame(),
Objects.requireNonNull(getParent()),
getMembers(),
this,
typeCheckNode,
typeNodeFrame);
}
}

View File

@@ -82,12 +82,12 @@ public abstract class VmObject extends VmObjectLike {
}
@Override
public final @Nullable Object getCachedValue(Object key) {
public @Nullable Object getCachedValue(Object key) {
return EconomicMaps.get(cachedValues, key);
}
@Override
public final void setCachedValue(Object key, Object value) {
public void setCachedValue(Object key, Object value, ObjectMember objectMember) {
EconomicMaps.put(cachedValues, key, value);
}

View File

@@ -52,7 +52,7 @@ public abstract class VmObjectLike extends VmValue {
return extraStorage != null;
}
public final Object getExtraStorage() {
public Object getExtraStorage() {
assert extraStorage != null;
return extraStorage;
}
@@ -96,7 +96,7 @@ public abstract class VmObjectLike extends VmValue {
* receiver.
*/
@TruffleBoundary
public abstract void setCachedValue(Object key, Object value);
public abstract void setCachedValue(Object key, Object value, ObjectMember objectMember);
/**
* Prefer this method over {@link #getCachedValue} if the value is not required. (There is no

View File

@@ -15,11 +15,22 @@
*/
package org.pkl.core.runtime;
import java.util.HashMap;
import java.util.List;
public final class VmStackOverflowException extends VmException {
public VmStackOverflowException(StackOverflowError e) {
super("stackOverflow", e, true, new Object[0], List.of(), null, null, null, null);
super(
"stackOverflow",
e,
true,
new Object[0],
List.of(),
null,
null,
null,
null,
new HashMap<>());
}
}

View File

@@ -15,10 +15,13 @@
*/
package org.pkl.core.runtime;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import org.pkl.core.StackFrame;
import org.pkl.core.parser.Lexer;
import org.pkl.core.util.Nullable;
@@ -35,7 +38,8 @@ public final class VmUndefinedValueException extends VmEvalException {
@Nullable SourceSection sourceSection,
@Nullable String memberName,
@Nullable String hint,
@Nullable Object receiver) {
@Nullable Object receiver,
Map<CallTarget, StackFrame> insertedStackFrames) {
super(
message,
@@ -46,7 +50,8 @@ public final class VmUndefinedValueException extends VmEvalException {
location,
sourceSection,
memberName,
hint);
hint,
insertedStackFrames);
this.receiver = receiver;
}

View File

@@ -263,6 +263,7 @@ public final class VmUtils {
final var constantValue = member.getConstantValue();
if (constantValue != null) {
var ret = constantValue;
// for a property, do a type check
if (member.isProp()) {
var property = receiver.getVmClass().getProperty(member.getName());
@@ -270,10 +271,15 @@ public final class VmUtils {
var callTarget = property.getTypeNode().getCallTarget();
try {
if (checkType) {
callNode.call(callTarget, receiver, property.getOwner(), constantValue);
ret = callNode.call(callTarget, receiver, property.getOwner(), constantValue);
} else {
callNode.call(
callTarget, receiver, property.getOwner(), constantValue, SKIP_TYPECHECK_MARKER);
ret =
callNode.call(
callTarget,
receiver,
property.getOwner(),
constantValue,
VmUtils.SKIP_TYPECHECK_MARKER);
}
} catch (VmException e) {
CompilerDirectives.transferToInterpreter();
@@ -293,21 +299,25 @@ public final class VmUtils {
throw e;
}
}
} else if (receiver instanceof VmListingOrMapping<?> vmListingOrMapping) {
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode);
}
receiver.setCachedValue(memberKey, constantValue);
return constantValue;
receiver.setCachedValue(memberKey, ret, member);
return ret;
}
var callTarget = member.getCallTarget();
Object computedValue;
Object ret;
if (checkType) {
computedValue = callNode.call(callTarget, receiver, owner, memberKey);
ret = callNode.call(callTarget, receiver, owner, memberKey);
} else {
computedValue = callNode.call(callTarget, receiver, owner, memberKey, SKIP_TYPECHECK_MARKER);
ret = callNode.call(callTarget, receiver, owner, memberKey, VmUtils.SKIP_TYPECHECK_MARKER);
}
receiver.setCachedValue(memberKey, computedValue);
return computedValue;
if (receiver instanceof VmListingOrMapping<?> vmListingOrMapping) {
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode);
}
receiver.setCachedValue(memberKey, ret, member);
return ret;
}
public static @Nullable ObjectMember findMember(VmObjectLike receiver, Object memberKey) {
@@ -849,4 +859,17 @@ public final class VmUtils {
public static <K, V> V getMapValue(Map<K, V> map, K key) {
return map.get(key);
}
/**
* If true, the value computed by this node is not the final value exposed to user code but will
* still be amended.
*
* <p>Used to disable type check for to-be-amended properties. See {@link
* org.pkl.core.runtime.VmUtils#SKIP_TYPECHECK_MARKER}. IDEA: might be more appropriate to only
* skip constraints check
*/
public static boolean shouldRunTypeCheck(VirtualFrame frame) {
return frame.getArguments().length != 4
|| frame.getArguments()[3] != VmUtils.SKIP_TYPECHECK_MARKER;
}
}

View File

@@ -18,6 +18,7 @@ package org.pkl.core.stdlib.base;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import java.util.HashSet;
import org.pkl.core.ast.lambda.ApplyVmFunction3Node;
import org.pkl.core.ast.lambda.ApplyVmFunction3NodeGen;
import org.pkl.core.runtime.*;
@@ -50,9 +51,14 @@ public final class MappingNodes {
@Specialization
@TruffleBoundary
protected long eval(VmMapping self) {
MutableLong count = new MutableLong(0);
self.iterateMemberValues(
(key, member, value) -> {
var count = new MutableLong(0);
var visited = new HashSet<>();
self.iterateMembers(
(key, member) -> {
var alreadyVisited = !visited.add(key);
// important to record hidden member as visited before skipping it
// because any overriding member won't carry a `hidden` identifier
if (alreadyVisited || member.isLocalOrExternalOrHidden()) return true;
count.getAndIncrement();
return true;
});

View File

@@ -42,8 +42,6 @@ import org.pkl.core.ast.type.TypeNode.StringTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeAliasTypeNode;
import org.pkl.core.ast.type.TypeNode.UnionOfStringLiteralsTypeNode;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
import org.pkl.core.ast.type.TypeNodeFactory.ListingTypeNodeGen;
import org.pkl.core.ast.type.TypeNodeFactory.MappingTypeNodeGen;
import org.pkl.core.ast.type.VmTypeMismatchException;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmClass;
@@ -575,7 +573,7 @@ public final class RendererNodes {
type =
requiresWrapper()
? null
: ListingTypeNodeGen.create(VmUtils.unavailableSourceSection(), valueType);
: new ListingTypeNode(VmUtils.unavailableSourceSection(), valueType);
return type;
} else if (type instanceof MappingTypeNode mappingType) {
var keyType = resolveType(mappingType.getKeyTypeNode());
@@ -589,8 +587,7 @@ public final class RendererNodes {
}
var valueType = resolveType(mappingType.getValueTypeNode());
assert valueType != null : "Incomplete or malformed Mapping type";
mappingType =
MappingTypeNodeGen.create(VmUtils.unavailableSourceSection(), keyType, valueType);
mappingType = new MappingTypeNode(VmUtils.unavailableSourceSection(), keyType, valueType);
type = requiresWrapper() ? null : mappingType;
return type;

View File

@@ -30,4 +30,9 @@ public final class EconomicSets {
public static <E> boolean add(EconomicSet<E> self, E element) {
return self.add(element);
}
@TruffleBoundary
public static <E> boolean contains(EconomicSet<E> self, E element) {
return self.contains(element);
}
}

View File

@@ -20,9 +20,9 @@ examples {
["listing"] {
new Listing { 1; 2; 3 } as Listing<Int>
module.catch(() -> new Listing { 1; 2; 3 } as Listing<String>)
module.catch(() -> (new Listing { 1; 2; 3 } as Listing<String>)[0])
}
["mapping"] {
module.catch(() -> new Listing { 1; 2; 3 } as Mapping<Int, String>)
new Mapping { ["Pigeon"] = 42; ["Barn Owl"] = 21 } as Mapping<String, Int>
@@ -49,9 +49,9 @@ examples {
["function type"] {
(((x) -> x) as (Int) -> Int).apply(42)
(((x) -> x) as (String) -> String).apply(42)
module.catch(() -> ((x, y) -> x) as (Int) -> Int)
module.catch(() -> ((x, _) -> x) as (Int) -> Int)
}
["string literal type"] {
"Pigeon" as "Pigeon"|"Barn Owl"
module.catch(() -> "Pigeon" as "Piggy"|"Barn Owl")

View File

@@ -0,0 +1,34 @@
amends "../snippetTest.pkl"
examples {
["set of listing"] {
local s1 = Set(
new Listing { 1; 2; 3 }
) as Set<Listing<Int>>
local s2 = Set(
new Listing { "one"; "two"; "three" }
) as Set<Listing<Int>>
s1.first[0]
module.catchOrNull(() -> s2.first) == null
module.catch(() -> s2.first[0])
}
["listing"] {
local l = new Listing { 1; 2; 3 } as Listing<String>
module.catchOrNull(() -> l) == null
module.catch(() -> l[0])
}
["mapping"] {
local m1 = new Mapping {
["hi"] = 1
["bye"] = 2
} as Mapping<String, String>
module.catchOrNull(() -> m1) == null
module.catch(() -> m1["hi"])
module.catch(() -> m1["bye"])
local m2 = new Mapping {
["hi"] = 1
["bye"] = 2
} as Mapping<Int, Int>
module.catch(() -> m2)
}
}

View File

@@ -0,0 +1,3 @@
res = new Listing<String> {
1
}

View File

@@ -0,0 +1,5 @@
one = 1
res = new Listing<String> {
one
}

View File

@@ -0,0 +1,3 @@
res: Listing<String(!isEmpty)> = new Listing<String> {
""
}

View File

@@ -0,0 +1,3 @@
res: Listing<String(!isEmpty)> = new Listing<String(endsWith("ga"))> {
"hola"
}

View File

@@ -0,0 +1,4 @@
local l = new Listing { 1; 2; 3 } as Listing<String>
res = l[0]

View File

@@ -0,0 +1,3 @@
res = new Mapping<String, String> {
["foo"] = 1
}

View File

@@ -0,0 +1,3 @@
res: Mapping<String, String> = new {
["foo"] = 1
}

View File

@@ -0,0 +1,4 @@
// ConstantEntriesLiteralNode
res: Mapping<String, String> = new {
[1] = "foo"
}

View File

@@ -0,0 +1,6 @@
num = 1
// EntriesLiteralNode
res: Mapping<String, String> = new {
[num] = "foo"
}

View File

@@ -0,0 +1,7 @@
// GeneratorObjectLiteralNode
res: Mapping<String, String> = new {
when (false) {
["foo"] = "foo"
}
[1] = "foo"
}

View File

@@ -0,0 +1,11 @@
a = new Listing { "hi" }
b = (a) {
"hihih"
}
bar = new Mapping {
[b] = "foo"
}
res: Mapping<Listing<String(length == 5)>, String> = bar

View File

@@ -0,0 +1,3 @@
res = new Mapping<Listing<String(length == 5)>, String> {
[new Listing { "hi" }] = "hi"
}

View File

@@ -0,0 +1,3 @@
res: Mapping<String, String> = new {
...Map("foo", 1)
}

View File

@@ -0,0 +1,32 @@
amends "../snippetTest.pkl"
examples {
["listings are lazy"] {
// backed by ConstantEntriesLiteralNode
local listing = new Listing<String> {
"foo"
throw("uh oh")
}
listing[0]
module.catch(() -> listing[1])
}
["listings are lazy with generator entries"] {
local listing = new Listing<String> {
when (false) {
"uh oh"
}
"foo"
throw("uh oh")
}
listing[0]
}
["nested listings are also lazy"] {
local listing = new Listing<Listing<String>> {
new {
"bar"
throw("uh oh")
}
}
listing[0][0]
}
}

View File

@@ -0,0 +1,168 @@
amends "../snippetTest.pkl"
facts {
["equals"] {
local l1 = new Listing<String(length.isOdd)> {}
local l2: Listing<String(this == capitalize())> = l1
l1 == l2
}
}
examples {
["type check: new with explicit parent"] {
local l = new Listing<String> {
1
}
module.catch(() -> l[0])
}
["type check: local new with inferred parent"] {
local l: Listing<String> = new {
1
}
module.catch(() -> l[0])
}
["type check: local paramaterized property type, unparamaterized new with explicit parent"] {
local m: Listing<String> = new Listing {
1
}
module.catch(() -> m[0])
}
["type check: local unparameterized property type, paramaterized new with explicit parent"] {
local m: Listing = new Listing<String> {
1
}
module.catch(() -> m[0])
}
["amending listings does not require type checks on amending object members"] {
local m: Listing<String> = new {
"hi"
}
// ElementsLiteralNode
(m) {
1
}
// ElementsEntriesLiteralNode
(m) {
[0] = 1
2
}
// GeneratorObjectLiteralNode
(m) {
when (false) {
"hi"
}
1
}
}
["type check: constraints on both property type node and explicit parent type node are checked"] {
local l: Listing<String(length.isOdd)> = new Listing<String(this == capitalize())> {
"Ba"
"bar"
}
module.catch(() -> l[0])
module.catch(() -> l[1])
}
["type check: nested listings: constraints on both parent type node and child type node are checked"] {
local res12: Listing<Listing<String(length.isOdd)>> =
new Listing<Listing<String(this == capitalize())>> {
new {
"Ba"
"bar"
}
}
module.catch(() -> res12[0][0])
module.catch(() -> res12[0][1])
}
["type check: propagate from List"] {
local l: List<Listing<String(length.isOdd)>> = List(
new Listing<String(this == capitalize())> {
"Ba"
"bar"
}
)
module.catch(() -> l[0][0])
module.catch(() -> l[0][1])
}
["type check: propagate function types"] {
local l = new Listing<String(this == capitalize())> {
"Ba" // fails `length.isOdd`
"bar" // fails `this == capitalize()`
}
local l2 = new Listing {
"Ba" // fails `length.isOdd`
"bar" // fails `this == capitalize()`
}
// type check String(length.isOdd) should be propagated to the listing via a paramater type
// annotation
local function func1(listing: Listing<String(length.isOdd)>) = listing
// type check String(length.isOdd) should be propagated to the listing via a return type
// annotation
local function func2(listing): Listing<String(length.isOdd)> = listing
// type check String(length.isOdd) and String(this == capitalize()) should be propagated to the
// listing via both parameter type and return type annotations
local function func3(listing: Listing<String(length.isOdd)>): Listing<String(this == capitalize())> = listing
module.catch(() -> func1(l)[0])
module.catch(() -> func1(l)[1])
module.catch(() -> func2(l)[0])
module.catch(() -> func2(l)[1])
module.catch(() -> func3(l2)[0])
module.catch(() -> func3(l2)[1])
}
["type check: union type"] {
local l: Listing<String(length.isOdd)>|Listing<String(length == 4)> =
new Listing<String(this == capitalize())> {
"Ba" // fails length.isOdd and length == 4
"bar" // fails this == capitalize()
"Bazz" // passes this == capitalize() and length == 4
"Qux" // passes this == capitalize() and length.isOdd
}
module.catch(() -> l)
}
["type check: nullable type"] {
local l: Listing<String(length.isOdd)>? =
new Listing<String(this == capitalize())> {
"Ba" // fails length.isOdd
"bar" // fails this == capitalize()
}
module.catch(() -> l!![0])
module.catch(() -> l!![1])
}
["type check: propagate lambda type"] {
local func1 = (it: Listing<String(length.isOdd)>) -> it
local l = new Listing<String(this == capitalize())> {
"Ba" // fails `length.isOdd`
"bar" // fails `this == capitalize()`
}
module.catch(() -> func1.apply(l)[0])
module.catch(() -> func1.apply(l)[1])
}
["intermediary objects are not checked"] {
local l = new Listing<String> {
// okay, because this node never gets evaluated
50
}
(l) {
[0] = "Hello"
}
}
}

View File

@@ -0,0 +1,46 @@
amends "../snippetTest.pkl"
local class TheClass {
local isLongString = (it) -> it.length > 10
prop: Listing<String(isLongString)>
}
local class TheClass2 {
hidden requiredLength: Int
prop: Listing<String(length > requiredLength)>
}
examples {
["name resolution in type constraint"] {
// should be able to resolve `isLongString` when checking this member
module.catch(() -> new TheClass {
prop {
"too short"
}
}.prop[0])
new TheClass {
prop {
"this is long enough"
}
}
}
["resolves the receiver"] {
local base: TheClass2 = new {
requiredLength = 5
}
(base) {
prop {
"long enough"
}
}
module.catch(() -> (base) {
requiredLength = 10
prop {
"too short"
}
}.prop[0])
}
}

View File

@@ -0,0 +1,10 @@
// ensure that these members are only evaluated once (trace should only be emitted once)
listing = new Listing { trace(1) }
listing2: Listing<Int> = listing
listing3 = new Listing {
new Listing { trace(2) }
}
listing4: Listing<Listing<Int>> = listing3

View File

@@ -1,41 +1,367 @@
import "pkl:test"
hidden x1: Listing<String> = new {
local x1: Listing<String> = new {
"pigeon"
42
"barn owl"
}
hidden x2: Listing<String(length > 3)> = new {
local x2: Listing<String(length > 3)> = new {
"pigeon"
"bob"
}
hidden x3: Listing<String>(!isEmpty)
res1 = x1[0]
res2 = test.catchOrNull(() -> x1[1])
res3 = x2[0]
res4 = test.catchOrNull(() -> x2[1])
res1 = test.catch(() -> x1)
res2 = test.catch(() -> x2)
res3 = test.catch(() -> x3)
hidden x4: Listing = new {
local x4: Listing = new {
throw("element unnecessarily evaluated")
}
hidden x5: Listing<Any> = new {
local x5: Listing<Any> = new {
throw("element unnecessarily evaluated")
}
hidden x6: Listing<unknown> = new {
local x6: Listing<unknown> = new {
throw("element unnecessarily evaluated")
}
hidden x7 = new Listing {
local x7 = new Listing {
throw("element unnecessarily evaluated")
42
throw("element unnecessarily evaluated")
}
res4 = x4.length == 1
res5 = x5.length == 1
res6 = x6.length == 1
res7 = x7[1] == 42
local x8 = new Listing<String> {
throw("element unneccessarily evaluated")
}
res5 = x4.length == 1
res6 = x5.length == 1
res7 = x6.length == 1
res8 = x7[1] == 42
res9 = x8.length == 1
local x9 = new Listing {
"foo"
1
}
local x10 = x9 as Listing<String>
res10 = x9 is Listing<String>
res11 = x10[0]
res12 = test.catch(() -> x10[1])
local x11: Listing<String(!isEmpty)> = new Listing<String> {
""
}
res13 = test.catch(() -> x11[0])
local x12: Listing<String> = new Listing<String(!isEmpty)> {
""
}
res14 = test.catch(() -> x12[0])
local l = new Listing { "foo"; 1 }
local x13: (Listing<String>|Listing<Int>) = l
local x14: Listing<String>|Listing<Int>? = l
local x15: Listing<String>|(Listing<Int>|Int) = l
local x16: Listing<String>|Int = l
res15 = test.catch(() -> x13)
res16 = test.catch(() -> x14)
res17 = test.catch(() -> x15)
// just accessing x16 doesn't throw because only one Listing in the union type
res18 = x16.length
// noinspection TypeMismatch
res19 = test.catch(() -> x16[1])
local x17: Listing<Listing<String>> = new {
new {
5
}
}
res20 = x17.length
res21 = x17[0].length
res22 = test.catch(() -> x17[0][0])
local x18 = new Listing { 1; 2; 3 } as Listing<String>
res23 = x18.length
res24 = test.catch(() -> x18[0])
local x19 = new Listing<String> {
when (true) {
15
}
}
res25 = x19.length
res26 = test.catch(() -> x19[0])
local x20 = new Listing<String> {
...List(1, 2, 3)
}
res27 = x20.length
res28 = test.catch(() -> x20[0])
local x21 = new Listing<String> {
for (elem in List(1, 2, 3)) {
elem
}
}
res29 = x21.length
res30 = test.catch(() -> x21[0])
local x22: Listing<String> = new {
"hi"
}
// typechecks not required when amending
// ElementsLiteralNode
res31 = (x22) {
"hi"
}
// ElementsEntriesLiteralNode
res32 = (x22) {
[0] = 1
2
}
// GeneratorObjectLiteralNode
res33 = (x22) {
when (false) {
"hi"
}
1
}
// GeneratorSpreadNode
res34 = (x22) {
...List(1, 2, 3)
}
local x23: Listing<Listing<String(length.isOdd)>> =
new Listing<Listing<String(this == capitalize())>> {
new {
"Ba"
"bar"
}
}
res35 = test.catch(() -> x23[0][0])
res36 = test.catch(() -> x23[0][1])
// check listings from inside a list
local x24: List<Listing<String(length.isOdd)>> = List(
new Listing<String(this == capitalize())> {
"Ba"
"bar"
}
)
res37 = test.catch(() -> x24[0][0])
res38 = test.catch(() -> x24[0][1])
local x25: List<String|Listing<Int>> = List(
"hello",
new Listing {
"foo"
},
"goodbye"
)
res39 = x25[0]
// retain lazy typecheck of listing.
res40 = x25[1].length
res41 = test.catch(() -> x25[1][0])
res42 = x25[2]
// check listings from inside a set
local x26: Set<Listing<String(length.isOdd)>> = Set(
new Listing<String(this == capitalize())> {
"Ba"
"bar"
}
)
res43 = test.catch(() -> x26[0][0])
local x27: Set<String|Listing<Int>> = Set(
"hello",
new Listing {
"foo"
},
"goodbye"
)
// sets are eagerly checked (need to compute hash code, therefore need to deep force)
res45 = test.catch(() -> x27)
local x28: List<Listing<Int>>|List<Listing<String>> = List(
new Listing { "hello" }
)
res46 = x28[0][0]
local x29: List<Listing<Int>>|List<Listing<String>> = List(
new Listing { 1; "hello" }
)
res47 = test.catch(() -> x29)
// check listings from inside a map
local x30: Map<String, Listing<String(length.isOdd)>> = Map(
"hello",
new Listing<String(this == capitalize())> {
"Ba"
"bar"
}
)
res48 = x30["hello"].length
res49 = test.catch(() -> x30["hello"][0])
res50 = test.catch(() -> x30["hello"][1])
local x31: Map<String, Int|Listing<String>> = Map(
"hello", 1,
"thelisting", new Listing {
1
2
},
"goodbye", 2
)
res51 = x31.length
res52 = x31["hello"]
res53 = x31["goodbye"]
res54 = x31["thelisting"].length
res55 = test.catch(() -> x31["thelisting"][0])
res56 = test.catch(() -> x31["thelisting"][1])
local x32: Map<Listing<String>, Int> = Map(
new Listing { 1; 2 },
1
)
res57 = test.catch(() -> x32)
local x33: Map<String, Listing<Int>|Int> = Map(
"first", 1,
"second", new Listing { "hi" }
)
res58 = x33.length
res59 = x33["first"]
res60 = x33["second"].length
res61 = test.catch(() -> x33["second"][0])
local x34: Pair<Listing<String>, Listing<String>> = Pair(
new Listing { 1 },
new Listing { 2 }
)
res62 = x34.first.length
res63 = x34.second.length
res64 = test.catch(() -> x34.first[0])
res65 = test.catch(() -> x34.second[0])
local x35: Pair<Int, Listing<String>> = Pair(
5,
new Listing { 1 }
)
res66 = x35.first
res67 = x35.second.length
res68 = test.catch(() -> x35.second[0])
local x36: Collection<Int|Listing<String>> = List(
1,
new Listing { "hello"; 1 }
)
res69 = x36.length
res70 = x36.first
res71 = x36[1].length
res73 = x36[1][0]
res74 = test.catch(() -> x36[1][1])
local x37: Collection<Int|Listing<String>> = Set(
1,
new Listing {
"hello"
1
}
)
res75 = test.catch(() -> x37)
local x38: Collection<Listing<String>>|Collection<Listing<Int>> =
List(new Listing {
1
"hi"
})
res76 = test.catch(() -> x38)
local class Person {
prop1 = 1
prop2 = 2
prop3 = "hi"
}
local x39: Listing<Int> = new Person {}.toMap().values.toListing()
res77 = x39.length
res78 = x39[0]
res79 = x39[1]
res80 = test.catch(() -> x39[2])
local x40: Listing<Int> = new {
...List(1, 2, "hello")
}
res81 = x40.length
res82 = x40[0]
res83 = x40[1]
res84 = test.catch(() -> x40[2])
// returns a new listing
function myFunction(elem: Listing<Int>) = elem
local x41 = myFunction(new Listing { "hello" })
res85 = x41.length
res86 = test.catch(() -> x41[0])
function myFunction2(elem): Listing<Int> = elem
local x42 = myFunction(new Listing { "hello" })
res87 = x42.length
res88 = test.catch(() -> x42[0])
local x43 = (it: Listing<Int>) -> it
local x44 = x43.apply(new Listing { "hello" })
res89 = x44.length
res90 = test.catch(() -> x44[0])
function myFunction3(elem: Listing<Int>): Listing<Int> = elem
local x45 = myFunction3(new Listing { "hello" })
res91 = x45.length
res92 = test.catch(() -> x45[0])

View File

@@ -15,9 +15,9 @@ hidden x2: Listing<String(length > 3)> = new {
hidden x3: Listing<String>(!isEmpty)
res1 = test.catch(() -> x1)
res2 = test.catch(() -> x2)
res3 = test.catch(() -> x3)
res1 = test.catch(() -> x1.toList())
res2 = test.catch(() -> x2.toList())
res3 = test.catch(() -> x3.toList())
hidden x4: Listing = new {
when (true) {

View File

@@ -27,12 +27,16 @@ local x6: Mapping<String, String(!isEmpty)> = new {
["pigeon"] = ""
}
res1 = test.catch(() -> x1)
res2 = test.catch(() -> x2)
res3 = test.catch(() -> x3)
res4 = test.catch(() -> x4)
res5 = test.catch(() -> x5)
res6 = test.catch(() -> x6)
res1 = x1.length
res2 = test.catch(() -> x1["barn owl"])
res3 = x2.length
res4 = x2["fred"]
res5 = test.catch(() -> x2["barney"])
res6 = test.catch(() -> x3)
res7 = test.catch(() -> x4)
res8 = test.catch(() -> x5)
res9 = x6.length
res10 = test.catch(() -> x6["pigeon"])
hidden x7: Mapping = new {
["first"] = throw("value unnecessarily evaluated")
@@ -52,7 +56,312 @@ hidden x10 = new Mapping {
["third"] = throw("value unnecessarily evaluated")
}
res7 = !x7.isEmpty
res8 = !x8.isEmpty
res9 = !x9.isEmpty
res10 = x10["second"] == 42
res11 = x7.length
res12 = x8.length
res13 = x9.length
res14 = x10.length
res15 = x10["second"]
local x11: Mapping<String, String(!isEmpty)> = new Mapping<String, String> {
["foo"] = ""
}
res16 = x11.length
res17 = test.catch(() -> x11["foo"])
local x12: Mapping<String, String> = new Mapping<String, String(!isEmpty)> {
["foo"] = ""
}
res18 = x12.length
res19 = test.catch(() -> x12["foo"])
local m = new Mapping {
["one"] = 1
["two"] = "two"
}
local x13: Mapping<String, String>|Mapping<String, Int> = m
local x14: Mapping<String, String>|Mapping<String, Int>? = m
local x15: Mapping<String, String>|(Mapping<String, Int>|Int) = m
local x16: Mapping<String, String>|Int = m
res20 = test.catch(() -> x13)
res21 = test.catch(() -> x14)
res22 = test.catch(() -> x15)
res23 = x16.length
res24 = test.catch(() -> x16["one"])
local x17: Mapping<String, Mapping<String, String>> = new {
["foo"] {
["bar"] = 1
}
}
res25 = x17.length
res26 = x17["foo"].length
res27 = test.catch(() -> x17["foo"]["bar"])
local x18 = new Mapping { ["foo"] = 1 } as Mapping<String, String>
res28 = x18.length
res29 = test.catch(() -> x18["foo"])
local x19 = new Mapping<String, String> {
when (true) {
["foo"] = 1
}
}
res30 = x19.length
res31 = test.catch(() -> x19["foo"])
local x20 = new Mapping<String, String> {
...Map("foo", 1)
}
res32 = x20.length
res33 = test.catch(() -> x20["foo"])
local x21 = new Mapping<String, String> {
for (k, v in Map("foo", 1)) {
[k] = v
}
}
res34 = x21.length
res35 = test.catch(() -> x21["foo"])
local x22: Mapping<String, String> = new {
["hi"] = "hi"
}
// typechecks not required when amending
// ElementsEntriesLiteralNode
res36 = (x22) {
["hi2"] = 1
}
// GeneratorObjectLiteralNode
res37 = (x22) {
when (true) {
["hi2"] = 1
}
}
// GeneratorSpreadNode
res38 = (x22) {
...Map("hi2", 1)
}
local x23: Mapping<String, Mapping<String, String(length.isOdd)>> = new Mapping<String, Mapping<String, String(this == capitalize())>> {
["foo"] {
["first"] = "Ba"
["second"] = "bar"
}
}
res39 = test.catch(() -> x23["foo"]["first"])
res40 = test.catch(() -> x23[""])
// check mappings from inside a list
local x24: List<Mapping<String, String(length.isOdd)>> = List(
new Mapping<String, String(this == capitalize())> {
["first"] = "Ba"
["second"] = "bar"
}
)
res41 = test.catch(() -> x24[0]["first"])
res42 = test.catch(() -> x24[0]["second"])
local x25: List<String|Mapping<String, Int>> = List(
"hello",
new Mapping {
["foo"] = "foo"
},
"goodbye"
)
res43 = x25[0]
// retain lazy typecheck of mapping.
res44 = x25[1].length
res45 = test.catch(() -> x25[1]["foo"])
res46 = x25[2]
// check mappings from inside a set
local x26: Set<Mapping<String, String(length.isOdd)>> = Set(
new Mapping<String, String(this == capitalize())> {
["first"] = "Ba"
["second"] = "bar"
}
)
// sets are eagerly checked (need to compute hash code, therefore need to deep force)
res47 = test.catch(() -> x26)
local x28: List<Mapping<String, Int>>|List<Mapping<String, String>> = List(
new Mapping { ["foo"] = 1 }
)
res48 = x28[0]["foo"]
local x29: List<Mapping<String, Int>>|List<Mapping<String, String>> = List(
new Mapping {
["foo"] = 1
["bar"] = "hi"
}
)
res49 = test.catch(() -> x29)
// check mappings from inside a map
local x30: Map<String, Mapping<String, String(length.isOdd)>> = Map(
"hello",
new Mapping<String, String(this == capitalize())> {
["first"] = "Ba"
["second"] = "bar"
}
)
res50 = x30["hello"].length
res51 = test.catch(() -> x30["hello"]["first"])
res52 = test.catch(() -> x30["hello"]["second"])
local x31: Map<String, Int|Mapping<String, String>> = Map(
"hello", 1,
"themapping", new Mapping {
["one"] = 1
["two"] = 2
},
"goodbye", 2
)
res53 = x31.length
res54 = x31["hello"]
res55 = x31["goodbye"]
res56 = x31["themapping"].length
res57 = test.catch(() -> x31["themapping"]["one"])
res58 = test.catch(() -> x31["themapping"]["two"])
local x32: Map<Mapping<String, String>, Int> = Map(
new Mapping { ["one"] = 1 },
1
)
res59 = test.catch(() -> x32)
local x33: Map<String, Mapping<String, Int>|Int> = Map(
"first", 1,
"second", new Mapping { ["hi"] = "hi" }
)
res60 = x33.length
res61 = x33["first"]
res62 = x33["second"].length
res63 = test.catch(() -> x33["second"]["hi"])
local x34: Pair<Mapping<String, String>, Mapping<String, String>> = Pair(
new Mapping { ["one"] = 1 },
new Mapping { ["one"] = 1 }
)
res64 = x34.first.length
res65 = x34.second.length
res66 = test.catch(() -> x34.first["one"])
res67 = test.catch(() -> x34.second["one"])
local x35: Pair<Int, Mapping<String, String>> = Pair(
5,
new Mapping { ["one"] = 1 }
)
res68 = x35.first
res69 = x35.second.length
res70 = test.catch(() -> x35.second["one"])
local x36: Collection<Int|Mapping<String, String>> = List(
1,
new Mapping {
["one"] = "hello"
["two"] = 1
}
)
res71 = x36.length
res72 = x36.first
res73 = x36[1].length
res74 = x36[1]["one"]
res75 = test.catch(() -> x36[1]["two"])
local x37: Collection<Int|Mapping<String, String>> = Set(
1,
new Mapping {
["one"] = "hello"
["two"] = 1
}
)
res77 = test.catch(() -> x37)
local x38: Collection<Mapping<String, String>>|Collection<Mapping<String, Int>> =
List(new Mapping {
["one"] = "hello"
["two"] = 1
})
res78 = test.catch(() -> x38)
local class Person {
prop1 = 1
prop2 = 2
prop3 = "hi"
}
local x39: Mapping<String, Int> = new Person {}
.toMap()
.toMapping()
res79 = x39.length
res80 = x39["prop1"]
res81 = x39["prop2"]
res82 = test.catch(() -> x39["prop3"])
local x40: Mapping<String, Int> = new {
...Map("one", 1, "two", 2, "three", "hello")
}
res83 = x40.length
res84 = x40["one"]
res85 = x40["two"]
res86 = test.catch(() -> x40["three"])
// returns a new mapping
function myFunction(elem: Mapping<String, Int>) = elem
local x41 = myFunction(new Mapping { ["hello"] = "hello" })
res87 = x41.length
res88 = test.catch(() -> x41["hello"])
function myFunction2(elem): Mapping<String, Int> = elem
local x42 = myFunction(new Mapping { ["hello"] = "hello" })
res89 = x42.length
res90 = test.catch(() -> x42["hello"])
local x43 = (it: Mapping<String, Int>) -> it
local x44 = x43.apply(new Mapping { ["hello"] = "hello" })
res91 = x44.length
res92 = test.catch(() -> x44["hello"])
function myFunction3(elem: Mapping<String, Int>): Mapping<String, Int> = elem
local x45 = myFunction3(new Mapping { ["hello"] = "hello" })
res93 = x45.length
res94 = test.catch(() -> x45["hello"])

View File

@@ -33,12 +33,12 @@ local x6: Mapping<String, String(!isEmpty)> = new {
["pigeon"] = ""
}
res1 = test.catch(() -> x1)
res2 = test.catch(() -> x2)
res1 = test.catch(() -> x1["barn owl"])
res2 = test.catch(() -> x2["barney"])
res3 = test.catch(() -> x3)
res4 = test.catch(() -> x4)
res5 = test.catch(() -> x5)
res6 = test.catch(() -> x6)
res6 = test.catch(() -> x6["pigeon"])
hidden x7: Mapping = new {
when (true) {

View File

@@ -0,0 +1,17 @@
examples {
["set of listing"] {
1
false
"Expected value of type `Int`, but got type `String`. Value: \"one\""
}
["listing"] {
true
"Expected value of type `String`, but got type `Int`. Value: 1"
}
["mapping"] {
true
"Expected value of type `String`, but got type `Int`. Value: 1"
"Expected value of type `String`, but got type `Int`. Value: 2"
"Expected value of type `Int`, but got type `String`. Value: \"hi\""
}
}

View File

@@ -5,10 +5,6 @@ x | ["\(idx)_2"] = o // at this point, `o` should be out of scope
^
at forGeneratorWrongVariableName#res[#2] (file:///$snippetsDir/input/errors/forGeneratorWrongVariableName.pkl)
x | res: Mapping<String, Int> = new {
^^^^^
at forGeneratorWrongVariableName#res (file:///$snippetsDir/input/errors/forGeneratorWrongVariableName.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res = new Listing<String> {
^^^^^^
at listingTypeCheckError1#res (file:///$snippetsDir/input/errors/listingTypeCheckError1.pkl)
x | 1
^
at listingTypeCheckError1#res[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res = new Listing<String> {
^^^^^^
at listingTypeCheckError2#res (file:///$snippetsDir/input/errors/listingTypeCheckError2.pkl)
x | one
^^^
at listingTypeCheckError2#res[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Type constraint `!isEmpty` violated.
Value: ""
x | res: Listing<String(!isEmpty)> = new Listing<String> {
^^^^^^^^
at listingTypeCheckError3#res (file:///$snippetsDir/input/errors/listingTypeCheckError3.pkl)
x | ""
^^
at listingTypeCheckError3#res[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError3.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Type constraint `endsWith("ga")` violated.
Value: "hola"
x | res: Listing<String(!isEmpty)> = new Listing<String(endsWith("ga"))> {
^^^^^^^^^^^^^^
at listingTypeCheckError4#res (file:///$snippetsDir/input/errors/listingTypeCheckError4.pkl)
x | "hola"
^^^^^^
at listingTypeCheckError4#res[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError4.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,19 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | local l = new Listing { 1; 2; 3 } as Listing<String>
^^^^^^
at listingTypeCheckError5#l (file:///$snippetsDir/input/errors/listingTypeCheckError5.pkl)
x | local l = new Listing { 1; 2; 3 } as Listing<String>
^
at listingTypeCheckError5#l[#1] (file:///$snippetsDir/input/errors/listingTypeCheckError5.pkl)
x | res = l[0]
^^^^
at listingTypeCheckError5#res (file:///$snippetsDir/input/errors/listingTypeCheckError5.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res = new Mapping<String, String> {
^^^^^^
at mappingTypeCheckError1#res (file:///$snippetsDir/input/errors/mappingTypeCheckError1.pkl)
x | ["foo"] = 1
^
at mappingTypeCheckError1#res["foo"] (file:///$snippetsDir/input/errors/mappingTypeCheckError1.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res: Mapping<String, String> = new {
^^^^^^
at mappingTypeCheckError2#res (file:///$snippetsDir/input/errors/mappingTypeCheckError2.pkl)
x | ["foo"] = 1
^
at mappingTypeCheckError2#res["foo"] (file:///$snippetsDir/input/errors/mappingTypeCheckError2.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,19 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res: Mapping<String, String> = new {
^^^^^^
at mappingTypeCheckError3#res (file:///$snippetsDir/input/errors/mappingTypeCheckError3.pkl)
x | [1] = "foo"
^
at mappingTypeCheckError3#res[1] (file:///$snippetsDir/input/errors/mappingTypeCheckError3.pkl)
x | res: Mapping<String, String> = new {
^^^^^
at mappingTypeCheckError3#res (file:///$snippetsDir/input/errors/mappingTypeCheckError3.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,19 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res: Mapping<String, String> = new {
^^^^^^
at mappingTypeCheckError4#res (file:///$snippetsDir/input/errors/mappingTypeCheckError4.pkl)
x | [num] = "foo"
^^^
at mappingTypeCheckError4#res[#1] (file:///$snippetsDir/input/errors/mappingTypeCheckError4.pkl)
x | res: Mapping<String, String> = new {
^^^^^
at mappingTypeCheckError4#res (file:///$snippetsDir/input/errors/mappingTypeCheckError4.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,19 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res: Mapping<String, String> = new {
^^^^^^
at mappingTypeCheckError5#res (file:///$snippetsDir/input/errors/mappingTypeCheckError5.pkl)
x | [1] = "foo"
^
at mappingTypeCheckError5#res[1] (file:///$snippetsDir/input/errors/mappingTypeCheckError5.pkl)
x | res: Mapping<String, String> = new {
^^^^^
at mappingTypeCheckError5#res (file:///$snippetsDir/input/errors/mappingTypeCheckError5.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,19 @@
Pkl Error
Type constraint `length == 5` violated.
Value: "hi"
xx | res: Mapping<Listing<String(length == 5)>, String> = bar
^^^^^^^^^^^
at mappingTypeCheckError6#res (file:///$snippetsDir/input/errors/mappingTypeCheckError6.pkl)
x | [b] = "foo"
^
at mappingTypeCheckError6#bar[#1] (file:///$snippetsDir/input/errors/mappingTypeCheckError6.pkl)
xx | res: Mapping<Listing<String(length == 5)>, String> = bar
^^^
at mappingTypeCheckError6#res (file:///$snippetsDir/input/errors/mappingTypeCheckError6.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,15 @@
Pkl Error
Type constraint `length == 5` violated.
Value: "hi"
x | res = new Mapping<Listing<String(length == 5)>, String> {
^^^^^^^^^^^
at mappingTypeCheckError7#res (file:///$snippetsDir/input/errors/mappingTypeCheckError7.pkl)
x | [new Listing { "hi" }] = "hi"
^^^^^^^^^^^^^^^^^^^^
at mappingTypeCheckError7#res[#1] (file:///$snippetsDir/input/errors/mappingTypeCheckError7.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,11 @@
Pkl Error
Expected value of type `String`, but got type `Int`.
Value: 1
x | res: Mapping<String, String> = new {
^^^^^^
at mappingTypeCheckError8#res (file:///$snippetsDir/input/errors/mappingTypeCheckError8.pkl)
xxx | text = renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)

View File

@@ -0,0 +1,12 @@
examples {
["listings are lazy"] {
"foo"
"uh oh"
}
["listings are lazy with generator entries"] {
"foo"
}
["nested listings are also lazy"] {
"bar"
}
}

View File

@@ -0,0 +1,69 @@
facts {
["equals"] {
true
}
}
examples {
["type check: new with explicit parent"] {
"Expected value of type `String`, but got type `Int`. Value: 1"
}
["type check: local new with inferred parent"] {
"Expected value of type `String`, but got type `Int`. Value: 1"
}
["type check: local paramaterized property type, unparamaterized new with explicit parent"] {
"Expected value of type `String`, but got type `Int`. Value: 1"
}
["type check: local unparameterized property type, paramaterized new with explicit parent"] {
"Expected value of type `String`, but got type `Int`. Value: 1"
}
["amending listings does not require type checks on amending object members"] {
new {
"hi"
1
}
new {
1
2
}
new {
"hi"
1
}
}
["type check: constraints on both property type node and explicit parent type node are checked"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["type check: nested listings: constraints on both parent type node and child type node are checked"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["type check: propagate from List"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["type check: propagate function types"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["type check: union type"] {
"Expected value of type `Listing<String(length.isOdd)>|Listing<String(length == 4)>`, but got a different `Listing`. Value: new Listing { \"Ba\"; ?; ?; ? }"
}
["type check: nullable type"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["type check: propagate lambda type"] {
"Type constraint `length.isOdd` violated. Value: \"Ba\""
"Type constraint `this == capitalize()` violated. Value: \"bar\""
}
["intermediary objects are not checked"] {
new {
"Hello"
}
}
}

View File

@@ -0,0 +1,18 @@
examples {
["name resolution in type constraint"] {
"Type constraint `isLongString` violated. Value: \"too short\""
new {
prop {
"this is long enough"
}
}
}
["resolves the receiver"] {
new {
prop {
"long enough"
}
}
"Type constraint `length > requiredLength` violated. Value: \"too short\""
}
}

View File

@@ -0,0 +1,18 @@
listing {
1
}
listing2 {
1
}
listing3 {
new {
2
}
}
listing4 {
new {
2
}
}
pkl: TRACE: 1 = 1 (file:///$snippetsDir/input/listings/listing7.pkl)
pkl: TRACE: 2 = 2 (file:///$snippetsDir/input/listings/listing7.pkl)

View File

@@ -1,7 +1,104 @@
res1 = "Expected value of type `String`, but got type `Int`. Value: 42"
res2 = "Type constraint `length > 3` violated. Value: \"bob\""
res3 = "Type constraint `!isEmpty` violated. Value: new Listing {}"
res4 = true
res1 = "pigeon"
res2 = "Expected value of type `String`, but got type `Int`. Value: 42"
res3 = "pigeon"
res4 = "Type constraint `length > 3` violated. Value: \"bob\""
res5 = true
res6 = true
res7 = true
res8 = true
res9 = true
res10 = false
res11 = "foo"
res12 = "Expected value of type `String`, but got type `Int`. Value: 1"
res13 = "Type constraint `!isEmpty` violated. Value: \"\""
res14 = "Type constraint `!isEmpty` violated. Value: \"\""
res15 = "Expected value of type `Listing<String>|Listing<Int>`, but got a different `Listing`. Value: new Listing { \"foo\"; 1 }"
res16 = "Expected value of type `Listing<String>|Listing<Int>?`, but got a different `Listing`. Value: new Listing { \"foo\"; 1 }"
res17 = "Expected value of type `Listing<String>|(Listing<Int>|Int)`, but got a different `Listing`. Value: new Listing { \"foo\"; 1 }"
res18 = 2
res19 = "Expected value of type `String`, but got type `Int`. Value: 1"
res20 = 1
res21 = 1
res22 = "Expected value of type `String`, but got type `Int`. Value: 5"
res23 = 3
res24 = "Expected value of type `String`, but got type `Int`. Value: 1"
res25 = 1
res26 = "Expected value of type `String`, but got type `Int`. Value: 15"
res27 = 3
res28 = "Expected value of type `String`, but got type `Int`. Value: 1"
res29 = 3
res30 = "Expected value of type `String`, but got type `Int`. Value: 1"
res31 {
"hi"
"hi"
}
res32 {
1
2
}
res33 {
"hi"
1
}
res34 {
"hi"
1
2
3
}
res35 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res36 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res37 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res38 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res39 = "hello"
res40 = 1
res41 = "Expected value of type `Int`, but got type `String`. Value: \"foo\""
res42 = "goodbye"
res43 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res45 = "Expected value of type `String|Listing<Int>`, but got a different `Listing`. Value: new Listing { \"foo\" }"
res46 = "hello"
res47 = "Expected value of type `List<Listing<Int>>|List<Listing<String>>`, but got a different `List`. Value: List(new Listing { 1; \"hello\" })"
res48 = 2
res49 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res50 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res51 = 3
res52 = 1
res53 = 2
res54 = 2
res55 = "Expected value of type `String`, but got type `Int`. Value: 1"
res56 = "Expected value of type `String`, but got type `Int`. Value: 2"
res57 = "Expected value of type `String`, but got type `Int`. Value: 1"
res58 = 2
res59 = 1
res60 = 1
res61 = "Expected value of type `Int`, but got type `String`. Value: \"hi\""
res62 = 1
res63 = 1
res64 = "Expected value of type `String`, but got type `Int`. Value: 1"
res65 = "Expected value of type `String`, but got type `Int`. Value: 2"
res66 = 5
res67 = 1
res68 = "Expected value of type `String`, but got type `Int`. Value: 1"
res69 = 2
res70 = 1
res71 = 2
res73 = "hello"
res74 = "Expected value of type `String`, but got type `Int`. Value: 1"
res75 = "Expected value of type `Int|Listing<String>`, but got a different `Listing`. Value: new Listing { \"hello\"; 1 }"
res76 = "Expected value of type `Collection<Listing<String>>|Collection<Listing<Int>>`, but got a different `List`. Value: List(new Listing { 1; \"hi\" })"
res77 = 3
res78 = 1
res79 = 2
res80 = "Expected value of type `Int`, but got type `String`. Value: \"hi\""
res81 = 3
res82 = 1
res83 = 2
res84 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res85 = 1
res86 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res87 = 1
res88 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res89 = 1
res90 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res91 = 1
res92 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""

View File

@@ -1,10 +1,102 @@
res1 = "Expected value of type `List`, but got type `Int`. Value: 42"
res1 = 2
res2 = "Expected value of type `List`, but got type `Int`. Value: 42"
res3 = "Expected value of type `List`, but got type `Int`. Value: 42"
res4 = "Expected value of type `List`, but got type `Int`. Value: 42"
res5 = "Type constraint `!isEmpty` violated. Value: \"\""
res6 = "Type constraint `!isEmpty` violated. Value: \"\""
res7 = true
res8 = true
res9 = true
res10 = true
res3 = 2
res4 = List(1, 2, 3)
res5 = "Expected value of type `List`, but got type `Int`. Value: 42"
res6 = "Expected value of type `List`, but got type `Int`. Value: 42"
res7 = "Expected value of type `List`, but got type `Int`. Value: 42"
res8 = "Type constraint `!isEmpty` violated. Value: \"\""
res9 = 1
res10 = "Type constraint `!isEmpty` violated. Value: \"\""
res11 = 1
res12 = 1
res13 = 1
res14 = 3
res15 = 42
res16 = 1
res17 = "Type constraint `!isEmpty` violated. Value: \"\""
res18 = 1
res19 = "Type constraint `!isEmpty` violated. Value: \"\""
res20 = "Expected value of type `Mapping<String, String>|Mapping<String, Int>`, but got a different `Mapping`. Value: new Mapping { [\"one\"] = 1; [\"two\"] = \"two\" }"
res21 = "Expected value of type `Mapping<String, String>|Mapping<String, Int>?`, but got a different `Mapping`. Value: new Mapping { [\"one\"] = 1; [\"two\"] = \"two\" }"
res22 = "Expected value of type `Mapping<String, String>|(Mapping<String, Int>|Int)`, but got a different `Mapping`. Value: new Mapping { [\"one\"] = 1; [\"two\"] = \"two\" }"
res23 = 2
res24 = "Expected value of type `String`, but got type `Int`. Value: 1"
res25 = 1
res26 = 1
res27 = "Expected value of type `String`, but got type `Int`. Value: 1"
res28 = 1
res29 = "Expected value of type `String`, but got type `Int`. Value: 1"
res30 = 1
res31 = "Expected value of type `String`, but got type `Int`. Value: 1"
res32 = 1
res33 = "Expected value of type `String`, but got type `Int`. Value: 1"
res34 = 1
res35 = "Expected value of type `String`, but got type `Int`. Value: 1"
res36 {
["hi"] = "hi"
["hi2"] = 1
}
res37 {
["hi"] = "hi"
["hi2"] = 1
}
res38 {
["hi"] = "hi"
["hi2"] = 1
}
res39 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res40 = "Cannot find key `\"\"`."
res41 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res42 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res43 = "hello"
res44 = 1
res45 = "Expected value of type `Int`, but got type `String`. Value: \"foo\""
res46 = "goodbye"
res47 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res48 = 1
res49 = "Expected value of type `List<Mapping<String, Int>>|List<Mapping<String, String>>`, but got a different `List`. Value: List(new Mapping { [\"foo\"] = 1; [\"bar\"] = \"hi\" })"
res50 = 2
res51 = "Type constraint `length.isOdd` violated. Value: \"Ba\""
res52 = "Type constraint `this == capitalize()` violated. Value: \"bar\""
res53 = 3
res54 = 1
res55 = 2
res56 = 2
res57 = "Expected value of type `String`, but got type `Int`. Value: 1"
res58 = "Expected value of type `String`, but got type `Int`. Value: 2"
res59 = "Expected value of type `String`, but got type `Int`. Value: 1"
res60 = 2
res61 = 1
res62 = 1
res63 = "Expected value of type `Int`, but got type `String`. Value: \"hi\""
res64 = 1
res65 = 1
res66 = "Expected value of type `String`, but got type `Int`. Value: 1"
res67 = "Expected value of type `String`, but got type `Int`. Value: 1"
res68 = 5
res69 = 1
res70 = "Expected value of type `String`, but got type `Int`. Value: 1"
res71 = 2
res72 = 1
res73 = 2
res74 = "hello"
res75 = "Expected value of type `String`, but got type `Int`. Value: 1"
res77 = "Expected value of type `Int|Mapping<String, String>`, but got a different `Mapping`. Value: new Mapping { [\"one\"] = \"hello\"; [\"two\"] = 1 }"
res78 = "Expected value of type `Collection<Mapping<String, String>>|Collection<Mapping<String, Int>>`, but got a different `List`. Value: List(new Mapping { [\"one\"] = \"hello\"; [\"two\"] = 1 })"
res79 = 3
res80 = 1
res81 = 2
res82 = "Expected value of type `Int`, but got type `String`. Value: \"hi\""
res83 = 3
res84 = 1
res85 = 2
res86 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res87 = 1
res88 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res89 = 1
res90 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res91 = 1
res92 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""
res93 = 1
res94 = "Expected value of type `Int`, but got type `String`. Value: \"hello\""

View File

@@ -10,6 +10,6 @@ at pkl.Project#dependencies (pkl:Project)
Type constraint `isValidLoadDependency` violated.
Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetti...
x | dependencies {
^^^^^^^^^^^^^^
at PklProject#dependencies (file:///$snippetsDir/input/projects/badProjectDeps4/PklProject)
x | ["badLocalProject"] = import("../badLocalProject/PklProject")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at PklProject#dependencies["badLocalProject"] (file:///$snippetsDir/input/projects/badProjectDeps4/PklProject)

View File

@@ -114,13 +114,13 @@ class EvaluateTestsTest {
)
assertThat(results.totalTests()).isEqualTo(1)
assertThat(results.totalFailures()).isEqualTo(0)
assertThat(results.totalFailures()).isEqualTo(1)
assertThat(results.failed()).isTrue
val res = results.results[0]
assertThat(res.name).isEqualTo("text")
assertThat(res.failures).isEmpty()
assertThat(res.errors.size).isEqualTo(1)
assertThat(res.name).isEqualTo("should fail")
assertThat(res.failures).hasSize(1)
assertThat(res.errors).hasSize(1)
val error = res.errors[0]
assertThat(error.message).isEqualTo("got an error")
@@ -134,10 +134,6 @@ class EvaluateTestsTest {
^^^^^^^^^^^^^^^^^^^^^
at text#facts["should fail"][#2] (repl:text)
3 | facts {
^^^^^^^
at text#facts (repl:text)
"""
.trimIndent()
)