diff --git a/docs/modules/release-notes/pages/0.31.adoc b/docs/modules/release-notes/pages/0.31.adoc index cb62260d..e40a361c 100644 --- a/docs/modules/release-notes/pages/0.31.adoc +++ b/docs/modules/release-notes/pages/0.31.adoc @@ -20,7 +20,89 @@ To get started, follow xref:pkl-cli:index.adoc#installation[Installation].# News you don't want to miss. -=== XXX +=== Power Assertions + +Pkl's test output and error output has been improved with power assertions (https://github.com/apple/pkl/pull/1384[#1384])! + +Pkl has two places that are effectively assertions. +These are: + +* Type constraint expressions +* Test facts + +Currently, when these assertions fail, the error message prints the assertion's source section. +However, this information can sometimes be lacking. +It tells you what the mechanics of the assertion is, but doesn't tell you _why_ the assertion is failing. + +For example, here is the current error output of a failing typecheck. + +[source,text] +---- +–– Pkl Error –– +Type constraint `name.endsWith(lastName)` violated. +Value: new Person { name = "Bub Johnson" } + +7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" } +---- + +Just from this error message, we don't know what the last name is supposed to be. +What is `name` supposed to end with? +We just know it's some property called `lastName` but, we don't know what it _is_. + +In Pkl 0.31, the error output becomes: + +[source,text] +---- +–– Pkl Error –– +Type constraint `name.endsWith(lastName)` violated. +Value: new Person { name = "Bub Johnson" } + + name.endsWith(lastName) + │ │ │ + │ false "Smith" + "Bub Johnson" + +7 | passenger: Person(name.endsWith(lastName)) = new { name = "Bub Johnson" } +---- + +Now, we know what the expecation actually is. + +This type of diagram is also added to test facts. +When tests fail, Pkl emits a diagram of the expression, and the values produced. + +For example, given the following test: + +.math.pkl +[source,pkl] +---- +amends "pkl:test" + +facts { + local function add(a: Int, b: Int) = a * b + local function divide(a: Int, b: Int) = a % b + ["math"] { + add(3, 4) == 7 + divide(8, 2) == 4 + } +} +---- + +The error output now includes a power assertion diagram, which helps explain why the test failed. + +[source,text] +---- +module math + facts + ✘ math + add(3, 4) == 7 (math.pkl:9) + │ │ + 12 false + divide(8, 2) == 4 (math.pkl:10) + │ │ + 0 false + +0.0% tests pass [1/1 failed], 0.0% asserts pass [2/2 failed] +---- == Noteworthy [small]#🎶# diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt index 71998ae2..9287baa1 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ class CliTestRunnerTest { facts { ["fail"] { 4 == 9 - "foo" != "bar" + 1 == 5 } } """ @@ -101,8 +101,13 @@ class CliTestRunnerTest { facts ✘ fail 4 == 9 (/tempDir/test.pkl, line xx) + │ + false + 1 == 5 (/tempDir/test.pkl, line xx) + │ + false - 0.0% tests pass [1/1 failed], 50.0% asserts pass [1/2 failed] + 0.0% tests pass [1/1 failed], 0.0% asserts pass [2/2 failed] """ .trimIndent() @@ -283,12 +288,14 @@ class CliTestRunnerTest { - 5 == 9 (/tempDir/test.pkl, line xx) + 5 == 9 (/tempDir/test.pkl, line xx) + │ + false - + """ .trimIndent() ) @@ -481,26 +488,30 @@ class CliTestRunnerTest { assertThat(junitReport) .isEqualTo( """ - - - - - - 5 == 9 (/tempDir/test1.pkl, line xx) - - - - - false (/tempDir/test2.pkl, line xx) - - - false (/tempDir/test2.pkl, line xx) - - - - - - """ + + + + + + 5 == 9 (/tempDir/test1.pkl, line xx) + │ + false + + + + + false (/tempDir/test2.pkl, line xx) + + + + false (/tempDir/test2.pkl, line xx) + + + + + + + """ .trimIndent() ) } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java index 3df66c54..e9ff3dc4 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,18 @@ package org.pkl.core.ast; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.GenerateWrapper; +import com.oracle.truffle.api.instrumentation.InstrumentableNode; +import com.oracle.truffle.api.instrumentation.ProbeNode; +import com.oracle.truffle.api.instrumentation.Tag; import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.source.SourceSection; +import org.pkl.core.runtime.PklTags; import org.pkl.core.runtime.VmTypesGen; import org.pkl.core.runtime.VmUtils; -public abstract class ExpressionNode extends PklNode { +@GenerateWrapper +public abstract class ExpressionNode extends PklNode implements InstrumentableNode { protected ExpressionNode(SourceSection sourceSection) { super(sourceSection); } @@ -43,4 +49,19 @@ public abstract class ExpressionNode extends PklNode { public boolean executeBoolean(VirtualFrame frame) throws UnexpectedResultException { return VmTypesGen.expectBoolean(executeGeneric(frame)); } + + @Override + public boolean hasTag(Class tag) { + return tag == PklTags.Expression.class; + } + + @Override + public boolean isInstrumentable() { + return true; + } + + @Override + public WrapperNode createWrapper(ProbeNode probe) { + return new ExpressionNodeWrapper(this, probe); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/PklNode.java b/pkl-core/src/main/java/org/pkl/core/ast/PklNode.java index 1eead635..0c96cf50 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/PklNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/PklNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.pkl.core.ast; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.TypeSystemReference; +import com.oracle.truffle.api.instrumentation.InstrumentableNode.WrapperNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.source.SourceSection; @@ -38,7 +39,10 @@ public abstract class PklNode extends Node { } @Override - public SourceSection getSourceSection() { + public final SourceSection getSourceSection() { + if (this instanceof WrapperNode wrapperNode) { + return wrapperNode.getDelegateNode().getSourceSection(); + } return sourceSection; } 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 0afce5b9..244f2870 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 @@ -2813,7 +2813,7 @@ public class AstBuilder extends AbstractAstBuilder { throw exceptionBuilder() .evalError(e.getMessage(), e.getMessageArguments()) .withCause(e.getCause()) - .withHint(e.getHint()) + .withHintBuilder(e.getHintBuilder()) .withSourceSection(createSourceSection(ctx)) .build(); } catch (ExternalReaderProcessException e) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/ComparatorNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/ComparatorNode.java index d288c601..ec376018 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/ComparatorNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/ComparatorNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,13 @@ */ package org.pkl.core.ast.expression.binary; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; public abstract class ComparatorNode extends BinaryExpressionNode { - public ComparatorNode(SourceSection sourceSection) { + protected ComparatorNode(SourceSection sourceSection) { super(sourceSection); } - public abstract boolean executeWith(Object left, Object right); + public abstract boolean executeWith(VirtualFrame frame, Object left, Object right); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/GreaterThanNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/GreaterThanNode.java index 6846511d..1f0ad8fa 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/GreaterThanNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/GreaterThanNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,15 @@ package org.pkl.core.ast.expression.binary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.instrumentation.GenerateWrapper; +import com.oracle.truffle.api.instrumentation.ProbeNode; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.runtime.VmDataSize; import org.pkl.core.runtime.VmDuration; @NodeInfo(shortName = ">") +@GenerateWrapper public abstract class GreaterThanNode extends ComparatorNode { protected GreaterThanNode(SourceSection sourceSection) { super(sourceSection); @@ -63,4 +66,9 @@ public abstract class GreaterThanNode extends ComparatorNode { protected boolean eval(VmDataSize left, VmDataSize right) { return left.compareTo(right) > 0; } + + @Override + public WrapperNode createWrapper(ProbeNode probe) { + return new GreaterThanNodeWrapper(sourceSection, this, probe); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LessThanNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LessThanNode.java index 2d6517d5..6690ebe9 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LessThanNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LessThanNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,15 @@ package org.pkl.core.ast.expression.binary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.instrumentation.GenerateWrapper; +import com.oracle.truffle.api.instrumentation.ProbeNode; import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.runtime.VmDataSize; import org.pkl.core.runtime.VmDuration; @NodeInfo(shortName = "<") +@GenerateWrapper public abstract class LessThanNode extends ComparatorNode { protected LessThanNode(SourceSection sourceSection) { super(sourceSection); @@ -63,4 +66,9 @@ public abstract class LessThanNode extends ComparatorNode { protected boolean eval(VmDataSize left, VmDataSize right) { return left.compareTo(right) < 0; } + + @Override + public WrapperNode createWrapper(ProbeNode probe) { + return new LessThanNodeWrapper(sourceSection, this, probe); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java index 79e1cdd6..9b7de94c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/InvokeMethodVirtualNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.GenerateWrapper; +import com.oracle.truffle.api.instrumentation.ProbeNode; import com.oracle.truffle.api.nodes.DirectCallNode; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.IndirectCallNode; @@ -40,6 +42,7 @@ import org.pkl.core.runtime.VmFunction; @ImportStatic(Identifier.class) @NodeChild(value = "receiverNode", type = ExpressionNode.class) @NodeChild(value = "receiverClassNode", type = GetClassNode.class, executeWith = "receiverNode") +@GenerateWrapper public abstract class InvokeMethodVirtualNode extends ExpressionNode { protected final Identifier methodName; @Children private final ExpressionNode[] argumentNodes; @@ -173,6 +176,12 @@ public abstract class InvokeMethodVirtualNode extends ExpressionNode { .build(); } + @Override + public WrapperNode createWrapper(ProbeNode probe) { + return new InvokeMethodVirtualNodeWrapper( + sourceSection, methodName, argumentNodes, lookupMode, needsConst, this, probe); + } + private void checkConst(ClassMethod method) { if (needsConst && !method.isConst()) { CompilerDirectives.transferToInterpreter(); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/frame/WriteFrameSlotNode.java b/pkl-core/src/main/java/org/pkl/core/ast/frame/WriteFrameSlotNode.java index 9beaf0e7..5ccfb697 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/frame/WriteFrameSlotNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/frame/WriteFrameSlotNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,4 +78,9 @@ public abstract class WriteFrameSlotNode extends ExpressionNode { var kind = frame.getFrameDescriptor().getSlotKind(slot); return kind == FrameSlotKind.Boolean || kind == FrameSlotKind.Illegal; } + + @Override + public boolean isInstrumentable() { + return false; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/internal/BlackholeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/internal/BlackholeNode.java index ffdd9f1e..f4d17edc 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/internal/BlackholeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/internal/BlackholeNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,4 +47,9 @@ public abstract class BlackholeNode extends ExpressionNode { CompilerDirectives.blackhole(value); return null; } + + @Override + public boolean isInstrumentable() { + return false; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/internal/GetClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/internal/GetClassNode.java index d7a1e34a..9d50409e 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/internal/GetClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/internal/GetClassNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,13 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.GenerateWrapper; +import com.oracle.truffle.api.instrumentation.ProbeNode; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.runtime.*; +@GenerateWrapper // NOTE: needs to be kept in sync with VmUtils.getClass() @NodeChild(value = "valueNode", type = ExpressionNode.class) public abstract class GetClassNode extends ExpressionNode { @@ -76,4 +79,9 @@ public abstract class GetClassNode extends ExpressionNode { // - has a guard that triggers per-class respecialization return ((VmValue) value).getClass(); } + + @Override + public WrapperNode createWrapper(ProbeNode probe) { + return new GetClassNodeWrapper(this, probe); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/internal/SyntheticNode.java b/pkl-core/src/main/java/org/pkl/core/ast/internal/SyntheticNode.java new file mode 100644 index 00000000..eaa51f8c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/internal/SyntheticNode.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.ast.internal; + +import com.oracle.truffle.api.source.SourceSection; +import org.pkl.core.ast.PklNode; + +/** + * A node that stands in for source sections that are intrinsified (e.g. `isBetween` in `typealias + * Int8 = Int(isBetween(..))`. + */ +public class SyntheticNode extends PklNode { + public SyntheticNode(SourceSection sourceSection) { + super(sourceSection); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/lambda/ApplyVmFunction1Node.java b/pkl-core/src/main/java/org/pkl/core/ast/lambda/ApplyVmFunction1Node.java index b30ccb12..9f74a64c 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/lambda/ApplyVmFunction1Node.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/lambda/ApplyVmFunction1Node.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,4 +86,9 @@ public abstract class ApplyVmFunction1Node extends ExpressionNode { return callNode.call(function.getCallTarget(), function.getThisValue(), function, arg1); } + + @Override + public boolean isInstrumentable() { + return false; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeConstraintNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeConstraintNode.java index 1692ee68..2f6a7b9b 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeConstraintNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeConstraintNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,13 @@ import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.PklNode; import org.pkl.core.ast.lambda.ApplyVmFunction1Node; import org.pkl.core.runtime.BaseModule; +import org.pkl.core.runtime.VmContext; import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmUtils; @NodeChild(value = "bodyNode", type = ExpressionNode.class) public abstract class TypeConstraintNode extends PklNode { + @CompilationFinal private int customThisSlot = -1; protected TypeConstraintNode(SourceSection sourceSection) { @@ -40,6 +42,8 @@ public abstract class TypeConstraintNode extends PklNode { public abstract void execute(VirtualFrame frame); + protected abstract ExpressionNode getBodyNode(); + public String export() { return getSourceSection().getCharacters().toString(); } @@ -49,8 +53,15 @@ public abstract class TypeConstraintNode extends PklNode { initConstraintSlot(frame); if (!result) { - throw new VmTypeMismatchException.Constraint( - sourceSection, frame.getAuxiliarySlot(customThisSlot)); + CompilerDirectives.transferToInterpreterAndInvalidate(); + try (var valueTracker = VmContext.get(this).getValueTrackerFactory().create()) { + getBodyNode().executeGeneric(frame); + throw new VmTypeMismatchException.Constraint( + sourceSection, + frame.getAuxiliarySlot(customThisSlot), + sourceSection, + valueTracker.values()); + } } } @@ -64,7 +75,12 @@ public abstract class TypeConstraintNode extends PklNode { var value = frame.getAuxiliarySlot(customThisSlot); var result = applyNode.executeBoolean(function, value); if (!result) { - throw new VmTypeMismatchException.Constraint(sourceSection, value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + try (var valueTracker = VmContext.get(this).getValueTrackerFactory().create()) { + applyNode.executeBoolean(function, value); + throw new VmTypeMismatchException.Constraint( + sourceSection, value, function.getRootNode().getSourceSection(), valueTracker.values()); + } } } 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 74866b46..8bf98611 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 @@ -29,6 +29,8 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.LoopNode; import com.oracle.truffle.api.source.SourceSection; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.pkl.core.PType; @@ -40,6 +42,7 @@ import org.pkl.core.ast.builder.SymbolTable.CustomThisScope; import org.pkl.core.ast.expression.primary.GetModuleNode; import org.pkl.core.ast.frame.WriteFrameSlotNode; import org.pkl.core.ast.frame.WriteFrameSlotNodeGen; +import org.pkl.core.ast.internal.SyntheticNode; import org.pkl.core.ast.member.DefaultPropertyBodyNode; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; @@ -143,6 +146,14 @@ public abstract class TypeNode extends PklNode { /** Visit child type nodes of this type. */ protected abstract boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer); + protected VmTypeMismatchException constraintException(Object value, SourceSection sourceSection) { + throw new VmTypeMismatchException.Constraint( + sourceSection, + value, + sourceSection, + Map.of(new SyntheticNode(sourceSection), List.of(false))); + } + public static TypeNode forClass(SourceSection sourceSection, VmClass clazz) { return clazz.isClosed() ? new FinalClassTypeNode(sourceSection, clazz) @@ -1719,9 +1730,9 @@ public abstract class TypeNode extends PklNode { //noinspection ConstantConditions defaultMember.initConstantValue( new VmFunction( - VmUtils.createEmptyMaterializedFrame(), // Assumption: don't need to set the correct `thisValue` // because it is guaranteed to be never accessed. + VmUtils.createEmptyMaterializedFrame(), null, 1, new SimpleRootNode( @@ -2232,8 +2243,8 @@ public abstract class TypeNode extends PklNode { @Override protected Object executeLazily(VirtualFrame frame, Object value) { if (value instanceof VmNull) { - throw new VmTypeMismatchException.Constraint( - BaseModule.getNonNullTypeAlias().getConstraintSection(), value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw constraintException(value, BaseModule.getNonNullTypeAlias().getConstraintSection()); } return value; } @@ -2274,7 +2285,9 @@ public abstract class TypeNode extends PklNode { if (value instanceof Long l) { if ((l & mask) == l) return value; - throw new VmTypeMismatchException.Constraint(typealias.getConstraintSection(), value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + var sourceSection = typealias.getConstraintSection(); + throw constraintException(value, sourceSection); } throw new VmTypeMismatchException.Simple( @@ -2337,8 +2350,9 @@ public abstract class TypeNode extends PklNode { if (value instanceof Long l) { if (l == l.byteValue()) return value; - throw new VmTypeMismatchException.Constraint( - BaseModule.getInt8TypeAlias().getConstraintSection(), value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + var sourceSection = BaseModule.getInt8TypeAlias().getConstraintSection(); + throw constraintException(value, sourceSection); } throw new VmTypeMismatchException.Simple( @@ -2381,8 +2395,9 @@ public abstract class TypeNode extends PklNode { if (value instanceof Long l) { if (l == l.shortValue()) return value; - throw new VmTypeMismatchException.Constraint( - BaseModule.getInt16TypeAlias().getConstraintSection(), value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + var sourceSection = BaseModule.getInt16TypeAlias().getConstraintSection(); + throw constraintException(value, sourceSection); } throw new VmTypeMismatchException.Simple( @@ -2425,10 +2440,11 @@ public abstract class TypeNode extends PklNode { if (value instanceof Long l) { if (l == l.intValue()) return value; - throw new VmTypeMismatchException.Constraint( - BaseModule.getInt32TypeAlias().getConstraintSection(), value); + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw constraintException(value, BaseModule.getInt32TypeAlias().getConstraintSection()); } + CompilerDirectives.transferToInterpreterAndInvalidate(); throw new VmTypeMismatchException.Simple( BaseModule.getInt32TypeAlias().getBaseTypeSection(), value, BaseModule.getIntClass()); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java b/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java index 7422dd38..b9182acd 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ 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.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.*; import java.util.stream.Collectors; @@ -26,6 +27,7 @@ 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.AnsiStringBuilder; import org.pkl.core.util.ErrorMessages; import org.pkl.core.util.Nullable; @@ -36,6 +38,7 @@ import org.pkl.core.util.Nullable; * there aren't any.) */ public abstract class VmTypeMismatchException extends ControlFlowException { + protected final SourceSection sourceSection; protected final Object actualValue; protected @Nullable Map insertedStackFrames = null; @@ -54,20 +57,36 @@ public abstract class VmTypeMismatchException extends ControlFlowException { } @TruffleBoundary - public abstract void describe(StringBuilder builder, String indent); + public abstract void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions); @TruffleBoundary - public abstract VmException toVmException(); + public void buildHint(AnsiStringBuilder builder, String indent, boolean withPowerAssertions) {} - protected VmExceptionBuilder exceptionBuilder() { - var builder = new VmExceptionBuilder(); + @TruffleBoundary + public final VmException toVmException() { + return exceptionBuilder().build(); + } + + protected final VmExceptionBuilder exceptionBuilder() { + var builder = + new VmExceptionBuilder() + .withSourceSection(sourceSection) + .withMessageBuilder( + (b, withPowerAssertions) -> buildMessage(b, "", withPowerAssertions)); + if (hasHint()) { + builder.withHintBuilder((b, withPowerAssertions) -> buildHint(b, "", withPowerAssertions)); + } if (insertedStackFrames != null) { builder.withInsertedStackFrames(insertedStackFrames); } return builder; } + protected abstract Boolean hasHint(); + public static final class Simple extends VmTypeMismatchException { + private final Object expectedType; public Simple(SourceSection sourceSection, Object actualValue, Object expectedType) { @@ -83,7 +102,8 @@ public abstract class VmTypeMismatchException extends ControlFlowException { @Override @TruffleBoundary - public void describe(StringBuilder builder, String indent) { + public void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { String renderedType; var valueFormatter = ValueFormatter.basic(); if (expectedType instanceof String string) { @@ -143,54 +163,48 @@ public abstract class VmTypeMismatchException extends ControlFlowException { } @Override - @TruffleBoundary - public VmException toVmException() { - return exceptionBuilder().build(); - } - - @Override - protected VmExceptionBuilder exceptionBuilder() { - var builder = new StringBuilder(); - describe(builder, ""); - - return super.exceptionBuilder() - .adhocEvalError(builder.toString()) - .withSourceSection(sourceSection); + protected Boolean hasHint() { + return false; } } public static final class Constraint extends VmTypeMismatchException { - public Constraint(SourceSection sourceSection, Object actualValue) { + + private final SourceSection constraintBodySourceSection; + private final Map> trackedValues; + + public Constraint( + SourceSection sourceSection, + Object actualValue, + SourceSection constraintBodySourceSection, + Map> trackedValues) { super(sourceSection, actualValue); + this.constraintBodySourceSection = constraintBodySourceSection; + this.trackedValues = trackedValues; } @Override @TruffleBoundary - public void describe(StringBuilder builder, String indent) { + public void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { + var expression = sourceSection.getCharacters().toString(); builder - .append( - ErrorMessages.createIndented( - "typeConstraintViolated", indent, sourceSection.getCharacters().toString())) + .append(ErrorMessages.createIndented("typeConstraintViolated", indent, expression)) .append("\n") .append(indent) .append("Value: ") .append(VmValueRenderer.singleLine(80 - indent.length()).render(actualValue)); + if (!withPowerAssertions) { + return; + } + builder.append("\n\n"); + PowerAssertions.render( + builder, indent + " ", constraintBodySourceSection, trackedValues, null); } @Override - @TruffleBoundary - public VmException toVmException() { - return exceptionBuilder().build(); - } - - @Override - protected VmExceptionBuilder exceptionBuilder() { - var builder = new StringBuilder(); - describe(builder, ""); - - return super.exceptionBuilder() - .adhocEvalError(builder.toString()) - .withSourceSection(sourceSection); + protected Boolean hasHint() { + return false; } } @@ -209,33 +223,15 @@ public abstract class VmTypeMismatchException extends ControlFlowException { } @Override - @TruffleBoundary - public void describe(StringBuilder builder, String indent) { - describeSummary(builder, indent); - describeDetails(builder, indent); + protected Boolean hasHint() { + var nonTrivialMatches = findNonTrivialMismatches(); + return !nonTrivialMatches.isEmpty(); } @Override @TruffleBoundary - public VmException toVmException() { - return exceptionBuilder().build(); - } - - @Override - protected VmExceptionBuilder exceptionBuilder() { - var summary = new StringBuilder(); - describeSummary(summary, ""); - - var details = new StringBuilder(); - describeDetails(details, ""); - - return super.exceptionBuilder() - .adhocEvalError(summary.toString()) - .withSourceSection(sourceSection) - .withHint(details.toString()); - } - - private void describeSummary(StringBuilder builder, String indent) { + public void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { var nonTrivialMismatches = findNonTrivialMismatches(); if (nonTrivialMismatches.isEmpty()) { @@ -267,12 +263,16 @@ public abstract class VmTypeMismatchException extends ControlFlowException { .append(VmValueRenderer.singleLine(80 - indent.length()).render(actualValue)); } - private void describeDetails(StringBuilder builder, String indent) { + @Override + @TruffleBoundary + public void buildHint(AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { var nonTrivialMismatches = findNonTrivialMismatches(); var isPeerError = false; for (var idx : nonTrivialMismatches) { - if (!indent.isEmpty() || isPeerError) builder.append("\n\n"); + if (isPeerError) { + builder.append("\n\n"); + } isPeerError = true; builder .append( @@ -285,7 +285,12 @@ public abstract class VmTypeMismatchException extends ControlFlowException { .getCharacters() .toString())) .append("\n"); - children[idx].describe(builder, indent + " "); + var child = children[idx]; + child.buildMessage(builder, indent + " ", withPowerAssertions); + if (child.hasHint()) { + builder.append("\n\n"); + child.buildHint(builder, indent + " ", withPowerAssertions); + } } } @@ -304,13 +309,15 @@ public abstract class VmTypeMismatchException extends ControlFlowException { } public static final class Nothing extends VmTypeMismatchException { + public Nothing(SourceSection sourceSection, Object actualValue) { super(sourceSection, actualValue); } @Override @TruffleBoundary - public void describe(StringBuilder builder, String indent) { + public void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { builder .append( ErrorMessages.createIndented( @@ -322,19 +329,8 @@ public abstract class VmTypeMismatchException extends ControlFlowException { } @Override - @TruffleBoundary - public VmException toVmException() { - return exceptionBuilder().build(); - } - - @Override - protected VmExceptionBuilder exceptionBuilder() { - var builder = new StringBuilder(); - describe(builder, ""); - - return super.exceptionBuilder() - .adhocEvalError(builder.toString()) - .withSourceSection(sourceSection); + protected Boolean hasHint() { + return false; } } } diff --git a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java index d62a9cea..8e000924 100644 --- a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java +++ b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java @@ -92,7 +92,8 @@ public class ReplServer implements AutoCloseable { this.workingDir = workingDir; this.securityManager = securityManager; this.moduleResolver = new ModuleResolver(moduleKeyFactories); - this.errorRenderer = new VmExceptionRenderer(new StackTraceRenderer(frameTransformer), color); + this.errorRenderer = + new VmExceptionRenderer(new StackTraceRenderer(frameTransformer), color, true); this.color = color; replState = new ReplState(createEmptyReplModule(BaseModule.getModuleClass().getPrototype())); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/PklTags.java b/pkl-core/src/main/java/org/pkl/core/runtime/PklTags.java new file mode 100644 index 00000000..4f33f1b5 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/PklTags.java @@ -0,0 +1,27 @@ +/* + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.runtime; + +import com.oracle.truffle.api.instrumentation.Tag; + +public final class PklTags { + private PklTags() {} + + @Tag.Identifier("EXPRESSION") + public static final class Expression extends Tag { + private Expression() {} + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/PowerAssertions.java b/pkl-core/src/main/java/org/pkl/core/runtime/PowerAssertions.java new file mode 100644 index 00000000..a6901f87 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/PowerAssertions.java @@ -0,0 +1,508 @@ +/* + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.runtime; + +import com.oracle.truffle.api.instrumentation.InstrumentableNode.WrapperNode; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.SourceSection; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.pkl.core.ast.ConstantValueNode; +import org.pkl.core.ast.expression.member.InferParentWithinMethodNode; +import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode; +import org.pkl.core.ast.expression.member.InferParentWithinPropertyNode; +import org.pkl.core.ast.expression.member.InvokeMethodDirectNode; +import org.pkl.core.ast.type.GetParentForTypeNode; +import org.pkl.core.util.AnsiStringBuilder; +import org.pkl.core.util.AnsiStringBuilder.AnsiCode; +import org.pkl.core.util.Nullable; +import org.pkl.core.util.SyntaxHighlighter; +import org.pkl.parser.Lexer; +import org.pkl.parser.Parser; +import org.pkl.parser.Token; +import org.pkl.parser.syntax.Expr; +import org.pkl.parser.syntax.Expr.BinaryOperatorExpr; +import org.pkl.parser.syntax.Expr.BoolLiteralExpr; +import org.pkl.parser.syntax.Expr.FloatLiteralExpr; +import org.pkl.parser.syntax.Expr.FunctionLiteralExpr; +import org.pkl.parser.syntax.Expr.IntLiteralExpr; +import org.pkl.parser.syntax.Expr.LetExpr; +import org.pkl.parser.syntax.Expr.NullLiteralExpr; +import org.pkl.parser.syntax.Expr.QualifiedAccessExpr; +import org.pkl.parser.syntax.Expr.StringLiteralExpr; +import org.pkl.parser.syntax.Expr.SubscriptExpr; +import org.pkl.parser.syntax.Expr.SuperAccessExpr; +import org.pkl.parser.syntax.Expr.SuperSubscriptExpr; +import org.pkl.parser.syntax.Expr.TypeCheckExpr; +import org.pkl.parser.syntax.Expr.UnqualifiedAccessExpr; +import org.pkl.parser.syntax.ObjectMember.ForGenerator; +import org.pkl.parser.syntax.ObjectMember.MemberPredicate; +import org.pkl.parser.syntax.StringPart.StringChars; +import org.pkl.parser.syntax.StringPart.StringInterpolation; + +public class PowerAssertions { + private PowerAssertions() {} + + private static final VmValueRenderer vmValueRenderer = VmValueRenderer.singleLine(100); + private static final Parser parser = new Parser(); + + public static void render( + AnsiStringBuilder out, + String indent, + SourceSection sourceSection, + Map> trackedValues, + @Nullable Consumer firstFrameSuffix) { + out.appendSandboxed( + () -> { + var lines = lines(sourceSection); + var layerEntries = getLayerEntries(trackedValues, sourceSection); + var indentation = + lines.size() == 1 + ? 0 + : Collections.min( + lines.stream() + .skip(1) + .map((it) -> leadingWhitespace(it.getCharacters())) + .toList()); + var sourceLines = lines(sourceSection); + var renderedMarkers = false; + for (var i = 0; i < sourceLines.size(); i++) { + if (renderedMarkers) { + out.append("\n\n"); + } + var line = sourceLines.get(i); + var offset = i == 0 ? line.getStartColumn() - 1 : indentation; + renderedMarkers = + renderLine( + out, indent, line, layerEntries, offset, i == 0 ? firstFrameSuffix : null); + } + }); + } + + private static boolean isInForGeneratorOrLambdaOrPredicate( + org.pkl.parser.syntax.Node myNode, Expr rootExpr) { + var parent = myNode.parent(); + while (parent != null) { + if (parent instanceof FunctionLiteralExpr) { + // okay to show power assert if this lambda is the root constraint expr (this was a lambda + // passed in as a a constraint) + return !parent.equals(rootExpr); + } + if (parent instanceof MemberPredicate) { + return true; + } + // okay if it's in expr section of for generator + if (parent instanceof ForGenerator forGenerator) { + return !forGenerator.getExpr().span().contains(myNode.span()); + } + parent = parent.parent(); + } + return false; + } + + private static boolean isLiteral(org.pkl.parser.syntax.Node parserNode) { + if (parserNode instanceof IntLiteralExpr + || parserNode instanceof FloatLiteralExpr + || parserNode instanceof BoolLiteralExpr + || parserNode instanceof NullLiteralExpr) { + return true; + } + if (parserNode instanceof StringLiteralExpr stringLiteralExpr) { + return !stringLiteralExpr.hasInterpolation(); + } + return false; + } + + // tells if this method is call to a literal value + // treats method calls like `List(1, 2, 3)` as literal values. + private static boolean isLiteral(Node truffleNode, org.pkl.parser.syntax.Node parserNode) { + if (isLiteral(parserNode)) { + return true; + } + if (truffleNode instanceof ConstantValueNode) { + if (parserNode instanceof UnqualifiedAccessExpr unqualifiedAccessExpr) { + // Assumption: if we have both ConstantValueNode, and the parser node is + // UnqualifiedAccessExpr with arguments, then this must be a `List()`, `Map()`, etc. + // + // Note: reading a local property whose value is a constant will also turn into a + // ConstantValueNode. + return unqualifiedAccessExpr.getArgumentList() != null; + } + return true; + } + // assumption: only calls to methods within the base module are direct method calls. + if (!(truffleNode instanceof InvokeMethodDirectNode)) { + return false; + } + var accessExpr = (UnqualifiedAccessExpr) parserNode; + // assumption: "literal" values are method calls that are uppercased. e.g. IntSeq(..), Pair(..) + if (!Character.isUpperCase(accessExpr.getIdentifier().getValue().charAt(0))) { + return false; + } + var argsList = accessExpr.getArgumentList(); + assert argsList != null; + return argsList.getArguments().stream().allMatch(PowerAssertions::isLiteral); + } + + private static boolean shouldHide( + Node truffleNode, org.pkl.parser.syntax.Node parserNode, Object value, Expr rootExpr) { + // literal values are self-evident in their source code + if (isLiteral(truffleNode, parserNode)) { + return true; + } + if ( + // let expressions will already show the resolved value in the expression body; showing the + // let as well simply adds noise. + parserNode instanceof LetExpr + // we'll already show the expression within string interpolation + || parserNode instanceof StringInterpolation + || parserNode instanceof StringChars) { + return true; + } + if (isInForGeneratorOrLambdaOrPredicate(parserNode, rootExpr)) { + return true; + } + if (value instanceof VmFunction) { + return true; + } + // implicit behavior around `new` + return truffleNode instanceof GetParentForTypeNode + || truffleNode instanceof InferParentWithinMethodNode + || truffleNode instanceof InferParentWithinObjectMethodNode + || truffleNode instanceof InferParentWithinPropertyNode; + } + + // tries to find the parser node for this node + private static @Nullable org.pkl.parser.syntax.Node findParserNode( + Node node, @Nullable org.pkl.parser.syntax.Node parserNode, int offset) { + if (!node.getSourceSection().isAvailable()) { + return null; + } + if (parserNode == null) { + return null; + } + var span = parserNode.span(); + var charIndex = span.charIndex() + offset; + var ss = node.getSourceSection(); + if (charIndex == ss.getCharIndex() && span.length() == ss.getCharLength()) { + return parserNode; + } + var children = parserNode.children(); + if (children == null) { + return null; + } + for (var child : children) { + var found = findParserNode(node, child, offset); + if (found != null) { + return found; + } + } + return null; + } + + private static Collection getLayerEntries( + Map> trackedValues, SourceSection sourceSection) { + var exprNode = parser.parseExpressionInput(sourceSection.getCharacters().toString()); + // it's possible that two nodes can turn into identical `SourceEntry`s; ensure these entries are + // distinct by using a set. + var ret = new LinkedHashSet(); + for (var entry : trackedValues.entrySet()) { + var truffleNode = entry.getKey(); + var values = entry.getValue(); + var parserNode = findParserNode(truffleNode, exprNode, sourceSection.getCharIndex()); + if (parserNode == null) { + continue; + } + // this node was executed multiple times; not sure how to best show this in a power assert + // graph + if (values.size() > 1) { + continue; + } + var value = values.get(0); + if (shouldHide(truffleNode, parserNode, value, exprNode)) { + continue; + } + ret.add(SourceEntry.create(truffleNode, parserNode, value)); + } + return ret; + } + + private static boolean canFit(Deque layer, LayerEntry elem) { + if (layer.isEmpty()) { + return true; + } + var nextEntry = layer.getFirst(); + return elem.startColumn() + elem.length() + 1 < nextEntry.startColumn(); + } + + private static boolean canFitMarker(Deque layer, LayerEntry elem) { + if (layer.isEmpty()) { + return true; + } + var nextEntry = layer.getFirst(); + return elem.startColumn() < nextEntry.startColumn(); + } + + private static List> buildLayers( + Collection layerEntries, SourceSection line) { + var sortedSections = + layerEntries.stream() + .filter((it) -> it.startLine() == line.getStartLine()) + .sorted(Comparator.comparingInt((it) -> -it.startColumn())) + .collect(Collectors.toCollection(LinkedList::new)); + if (sortedSections.isEmpty()) { + return Collections.emptyList(); + } + + var layers = new ArrayList>(); + + // first layer is all markers + layers.add(firstLayerMarkers(sortedSections)); + + while (!sortedSections.isEmpty()) { + var layer = new ArrayDeque(); + var iter = sortedSections.iterator(); + while (iter.hasNext()) { + var next = iter.next(); + if (canFit(layer, next)) { + layer.addFirst(next); + iter.remove(); + } else if (canFitMarker(layer, next)) { + layer.addFirst(next.toMarker()); + } + } + layers.add(layer); + } + return layers; + } + + private static Collection firstLayerMarkers(List entries) { + var layer = new ArrayDeque(); + LayerEntry prevEntry = null; + for (var entry : entries) { + if (prevEntry != null && prevEntry.startColumn() == entry.startColumn()) { + continue; + } + layer.addFirst(entry.toMarker()); + prevEntry = entry; + } + return layer; + } + + private static List lines(SourceSection sourceSection) { + var startLine = sourceSection.getStartLine(); + var endLine = sourceSection.getEndLine(); + if (!sourceSection.isAvailable() || startLine == endLine) { + return Collections.singletonList(sourceSection); + } + + var result = new ArrayList(); + var source = sourceSection.getSource(); + var charIndex = sourceSection.getCharIndex(); + var endCharIndex = charIndex + sourceSection.getCharLength(); + + for (var lineNumber = startLine; lineNumber <= endLine; lineNumber++) { + var lineSection = source.createSection(lineNumber); + var lineStartChar = lineSection.getCharIndex(); + var lineEndChar = lineStartChar + lineSection.getCharLength(); + + var sectionStartChar = Math.max(charIndex, lineStartChar); + var sectionEndChar = Math.min(endCharIndex, lineEndChar); + + if (sectionStartChar < sectionEndChar) { + result.add(source.createSection(sectionStartChar, sectionEndChar - sectionStartChar)); + } + } + + return result; + } + + public static String trimLeadingWhitespace(String str, int n) { + var i = 0; + + while (i < str.length() && i < n && Character.isWhitespace(str.charAt(i))) { + i++; + } + + return str.substring(i); + } + + private static boolean renderLine( + AnsiStringBuilder out, + String indent, + SourceSection line, + Collection layerEntries, + int trimStart, + @Nullable Consumer lineSuffix) { + var layers = buildLayers(layerEntries, line); + var content = trimLeadingWhitespace(line.getCharacters().toString(), trimStart); + out.append(indent).append(content); + if (lineSuffix != null) { + lineSuffix.accept(out); + } + out.append('\n'); + if (layers.isEmpty()) { + return false; + } + for (var i = 0; i < layers.size(); i++) { + var layer = layers.get(i); + out.append(indent); + var cursor = 0; + for (var entry : layer) { + var currentOffset = entry.startColumn() - 1 - trimStart - cursor; + out.append(" ".repeat(currentOffset)); + entry.appendTo(out); + cursor += currentOffset + entry.length(); + } + if (i < layers.size() - 1) { + out.append('\n'); + } + } + return true; + } + + private static int leadingWhitespace(CharSequence src) { + var result = 0; + for (var i = 0; i < src.length(); i++) { + var c = src.charAt(i); + if (c != ' ' && c != '\t') { + break; + } + result++; + } + return result; + } + + private sealed interface LayerEntry permits SourceEntry, MarkerEntry { + + /** 1-based (because {@link SourceSection}'s is too). */ + int startLine(); + + /** 1-based (because {@link SourceSection}'s is too). */ + int startColumn(); + + int length(); + + void appendTo(AnsiStringBuilder builder); + + default MarkerEntry toMarker() { + return new MarkerEntry(startLine(), startColumn()); + } + } + + private record SourceEntry(int startLine, int startColumn, String src) implements LayerEntry { + static SourceEntry create( + Node truffleNode, org.pkl.parser.syntax.Node parserNode, Object value) { + var effectiveSourceSection = + truffleNode + .getSourceSection() + .getSource() + .createSection(getCharIndex(truffleNode, parserNode), 1); + return new SourceEntry( + effectiveSourceSection.getStartLine(), + effectiveSourceSection.getStartColumn(), + vmValueRenderer.render(value)); + } + + // create a subsection that points to the operator + // + // 1 + 1 + // | + // + // or in the case of qualified access, the very next character after qualified access + // + // foo.bar + // | + private static int getCharIndex(Node truffleNode, org.pkl.parser.syntax.Node parserNode) { + if (truffleNode instanceof WrapperNode wrapperNode) { + truffleNode = wrapperNode.getDelegateNode(); + } + var originalSource = truffleNode.getSourceSection(); + if (!originalSource.isAvailable()) { + return originalSource.getCharIndex(); + } + var exprText = originalSource.getCharacters().toString(); + var skip = 0; + if (parserNode instanceof BinaryOperatorExpr binaryOperatorExpr) { + skip = + binaryOperatorExpr.getLeft().span().stopIndexExclusive() + - parserNode.span().charIndex(); + } else if (parserNode instanceof TypeCheckExpr typeCheckExpr) { + skip = typeCheckExpr.getExpr().span().stopIndexExclusive() - parserNode.span().charIndex(); + } else if (parserNode instanceof SubscriptExpr subscriptExpr) { + skip = subscriptExpr.getExpr().span().stopIndexExclusive() - parserNode.span().charIndex(); + } else if (parserNode instanceof SuperAccessExpr + || parserNode instanceof SuperSubscriptExpr) { + skip = "super".length(); + } else if (parserNode instanceof QualifiedAccessExpr qualifiedAccessExpr) { + skip = + qualifiedAccessExpr.getExpr().span().stopIndexExclusive() + - parserNode.span().charIndex(); + } + if (skip == 0) { + return originalSource.getCharIndex(); + } + // need to lex the source again because the parse tree doesn't carry the operator's position + var lexer = new Lexer(exprText.substring(skip)); + var nextToken = lexer.next(); + while (!nextToken.isOperator()) { + nextToken = lexer.next(); + } + var span = lexer.span(); + if (nextToken == Token.DOT || nextToken == Token.QDOT) { + return originalSource.getCharIndex() + skip + span.charIndex() + span.length(); + } + return originalSource.getCharIndex() + skip + span.charIndex(); + } + + @Override + public int length() { + return src.length(); + } + + @Override + public void appendTo(AnsiStringBuilder builder) { + SyntaxHighlighter.writeTo(builder, src); + } + } + + private record MarkerEntry(int startLine, int startColumn) implements LayerEntry { + @Override + public int length() { + return 1; + } + + @Override + public void appendTo(AnsiStringBuilder builder) { + builder.append(AnsiCode.FAINT, "│"); + } + + @Override + public MarkerEntry toMarker() { + return this; + } + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceGenerator.java b/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceGenerator.java index 27a3c732..9e6776c2 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceGenerator.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceRenderer.java b/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceRenderer.java index d328cf08..d88a1ad6 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/StackTraceRenderer.java @@ -17,6 +17,7 @@ package org.pkl.core.runtime; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Function; import org.pkl.core.StackFrame; import org.pkl.core.util.AnsiStringBuilder; @@ -31,15 +32,20 @@ public final class StackTraceRenderer { this.frameTransformer = frameTransformer; } - public void render(List frames, @Nullable String hint, AnsiStringBuilder out) { + public void render( + List frames, + @Nullable String hint, + @Nullable BiConsumer hintBuilder, + AnsiStringBuilder out) { var compressed = compressFrames(frames); - doRender(compressed, hint, out, "", true); + doRender(compressed, hint, hintBuilder, out, "", true); } // non-private for testing void doRender( List frames, @Nullable String hint, + @Nullable BiConsumer hintBuilder, AnsiStringBuilder out, String leftMargin, boolean isFirstElement) { @@ -47,7 +53,7 @@ public final class StackTraceRenderer { if (frame instanceof StackFrameLoop loop) { // ensure a cycle of length 1 doesn't get rendered as a loop if (loop.count == 1) { - doRender(loop.frames, null, out, leftMargin, isFirstElement); + doRender(loop.frames, null, null, out, leftMargin, isFirstElement); } else { if (!isFirstElement) { out.append(AnsiTheme.STACK_TRACE_MARGIN, leftMargin).append('\n'); @@ -57,9 +63,9 @@ public final class StackTraceRenderer { .append(AnsiTheme.STACK_TRACE_LOOP_COUNT, loop.count) .append(" repetitions of:\n"); var newLeftMargin = leftMargin + "│ "; - doRender(loop.frames, null, out, newLeftMargin, isFirstElement); + doRender(loop.frames, null, null, out, newLeftMargin, isFirstElement); if (isFirstElement) { - renderHint(hint, out, newLeftMargin); + renderHint(hint, hintBuilder, out, newLeftMargin); isFirstElement = false; } out.append(AnsiTheme.STACK_TRACE_MARGIN, leftMargin + "└─").append('\n'); @@ -72,7 +78,7 @@ public final class StackTraceRenderer { } if (isFirstElement) { - renderHint(hint, out, leftMargin); + renderHint(hint, hintBuilder, out, leftMargin); isFirstElement = false; } } @@ -84,13 +90,20 @@ public final class StackTraceRenderer { renderSourceLocation(transformed, out, leftMargin); } - private void renderHint(@Nullable String hint, AnsiStringBuilder out, String leftMargin) { - if (hint == null || hint.isEmpty()) return; + private void renderHint( + @Nullable String hint, + @Nullable BiConsumer hintBuilder, + AnsiStringBuilder out, + String leftMargin) { + if (hint == null && hintBuilder == null) return; - out.append('\n') - .append(AnsiTheme.STACK_TRACE_MARGIN, leftMargin) - .append(AnsiTheme.ERROR_MESSAGE_HINT, hint) - .append('\n'); + out.append('\n').append(AnsiTheme.STACK_TRACE_MARGIN, leftMargin); + if (hint != null) { + out.append(AnsiTheme.ERROR_MESSAGE_HINT, hint); + } else { + out.append(AnsiTheme.ERROR_MESSAGE_HINT, () -> hintBuilder.accept(out, true)); + } + out.append('\n'); } private void renderSourceLine(StackFrame frame, AnsiStringBuilder out, String leftMargin) { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java index 5364b77c..e416f088 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.pkl.core.runtime; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -23,6 +24,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.pkl.core.BufferedLogger; import org.pkl.core.StackFrameTransformer; import org.pkl.core.TestResults; @@ -47,6 +49,7 @@ public final class TestRunner { private final StackFrameTransformer stackFrameTransformer; private final boolean overwrite; private final boolean useColor; + private final VmValueTrackerFactory valueTrackerFactory; public TestRunner( BufferedLogger logger, @@ -57,6 +60,7 @@ public final class TestRunner { this.stackFrameTransformer = stackFrameTransformer; this.overwrite = overwrite; this.useColor = useColor; + this.valueTrackerFactory = VmContext.get(null).getValueTrackerFactory(); } public TestResults run(VmTyped testModule) { @@ -108,8 +112,16 @@ public final class TestRunner { try { var factValue = VmUtils.readMember(listing, idx); if (factValue == Boolean.FALSE) { - var failure = factFailure(member.getSourceSection(), getDisplayUri(member)); - resultBuilder.addFailure(failure); + try (var valueTracker = valueTrackerFactory.create()) { + listing.cachedValues.clear(); + VmUtils.readMember(listing, idx); + var failure = + factFailure( + member.getSourceSection(), + getDisplayUri(member), + valueTracker.values()); + resultBuilder.addFailure(failure); + } } else { resultBuilder.addSuccess(); } @@ -395,10 +407,18 @@ public final class TestRunner { moduleInfo.getModuleKey().getUri(), VmContext.get(null).getFrameTransformer()); } - private Failure factFailure(SourceSection sourceSection, String location) { + private Failure factFailure( + SourceSection sourceSection, String location, Map> trackedValues) { var sb = new AnsiStringBuilder(useColor); - sb.append(AnsiTheme.TEST_FACT_SOURCE, sourceSection.getCharacters().toString()).append(" "); - appendLocation(sb, location); + PowerAssertions.render( + sb, + "", + sourceSection, + trackedValues, + (it) -> { + it.append(" "); + appendLocation(it, location); + }); return new Failure("Fact Failure", sb.toString()); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmBugException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmBugException.java index 4c4f12d6..6e7d8a5f 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmBugException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmBugException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,20 +21,23 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.pkl.core.*; +import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.Nullable; public final class VmBugException extends VmException { public VmBugException( - String message, + @Nullable String message, @Nullable Throwable cause, boolean isExternalMessage, Object[] messageArguments, + @Nullable BiConsumer messageBuilder, List programValues, @Nullable Node location, @Nullable SourceSection sourceSection, @Nullable String memberName, - @Nullable String hint, + @Nullable BiConsumer hintBuilder, Map insertedStackFrames) { super( @@ -42,11 +45,12 @@ public final class VmBugException extends VmException { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, insertedStackFrames); } @@ -59,7 +63,7 @@ public final class VmBugException extends VmException { @Override @TruffleBoundary public PklException toPklException(StackFrameTransformer transformer, boolean color) { - var renderer = new VmExceptionRenderer(new StackTraceRenderer(transformer), color); + var renderer = new VmExceptionRenderer(new StackTraceRenderer(transformer), color, true); var rendered = renderer.render(this); return new PklBugException(rendered, this); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmContext.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmContext.java index 50be8fa4..4402cbe3 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmContext.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmContext.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.pkl.core.runtime; import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.TruffleLanguage.Env; +import com.oracle.truffle.api.instrumentation.Instrumenter; import com.oracle.truffle.api.nodes.Node; import java.nio.file.Path; import java.util.HashMap; @@ -33,6 +35,11 @@ import org.pkl.core.util.Nullable; public final class VmContext { private static final ContextReference REFERENCE = ContextReference.create(VmLanguage.class); + private final VmValueTrackerFactory valueTrackerFactory; + + public VmContext(VmLanguage vmLanguage, Env env) { + this.valueTrackerFactory = new VmValueTrackerFactory(env.lookup(Instrumenter.class)); + } @LateInit private Holder holder; @@ -151,4 +158,8 @@ public final class VmContext { public TraceMode getTraceMode() { return holder.traceMode; } + + public VmValueTrackerFactory getValueTrackerFactory() { + return valueTrackerFactory; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmEvalException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmEvalException.java index 1543ae1c..17bc1951 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmEvalException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmEvalException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,23 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.pkl.core.StackFrame; +import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.Nullable; public class VmEvalException extends VmException { public VmEvalException( - String message, + @Nullable String message, @Nullable Throwable cause, boolean isExternalMessage, Object[] messageArguments, + @Nullable BiConsumer messageBuilder, List programValues, @Nullable Node location, @Nullable SourceSection sourceSection, @Nullable String memberName, - @Nullable String hint, + @Nullable BiConsumer hintBuilder, Map insertedStackFrames) { super( @@ -41,11 +44,12 @@ public class VmEvalException extends VmException { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, insertedStackFrames); } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmException.java index bc27d340..f107273a 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.pkl.core.*; +import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.Nullable; public abstract class VmException extends AbstractTruffleException { @@ -31,28 +33,31 @@ public abstract class VmException extends AbstractTruffleException { private final List programValues; private final @Nullable SourceSection sourceSection; private final @Nullable String memberName; - protected @Nullable String hint; private final Map insertedStackFrames; + @Nullable private final BiConsumer messageBuilder; + @Nullable protected BiConsumer hintBuilder; public VmException( - String message, + @Nullable String message, @Nullable Throwable cause, boolean isExternalMessage, Object[] messageArguments, + @Nullable BiConsumer messageBuilder, List programValues, @Nullable Node location, @Nullable SourceSection sourceSection, @Nullable String memberName, - @Nullable String hint, + @Nullable BiConsumer hintBuilder, Map insertedStackFrames) { super(message, cause, UNLIMITED_STACK_TRACE, location); + this.messageBuilder = messageBuilder; this.isExternalMessage = isExternalMessage; this.messageArguments = messageArguments; this.programValues = programValues; this.sourceSection = sourceSection; this.memberName = memberName; - this.hint = hint; this.insertedStackFrames = insertedStackFrames; + this.hintBuilder = hintBuilder; } public final boolean isExternalMessage() { @@ -75,14 +80,6 @@ public abstract class VmException extends AbstractTruffleException { return memberName; } - public @Nullable String getHint() { - return hint; - } - - public void setHint(@Nullable String hint) { - this.hint = hint; - } - /** * Stack frames to insert into the stack trace presented to the user. For each entry `(target, * frame)`, `frame` will be inserted below the top-most frame associated with `target`. @@ -91,6 +88,18 @@ public abstract class VmException extends AbstractTruffleException { return insertedStackFrames; } + public @Nullable BiConsumer getMessageBuilder() { + return messageBuilder; + } + + public @Nullable BiConsumer getHintBuilder() { + return hintBuilder; + } + + public void setHint(String hint) { + this.hintBuilder = ((builder, aBoolean) -> builder.append(hint)); + } + public enum Kind { EVAL_ERROR, UNDEFINED_VALUE, @@ -110,7 +119,7 @@ public abstract class VmException extends AbstractTruffleException { @TruffleBoundary public PklException toPklException(StackFrameTransformer transformer, boolean color) { - var renderer = new VmExceptionRenderer(new StackTraceRenderer(transformer), color); + var renderer = new VmExceptionRenderer(new StackTraceRenderer(transformer), color, true); var rendered = renderer.render(this); return new PklException(rendered); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java index bb69ccaf..46321dde 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,13 @@ 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.function.BiConsumer; 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.AnsiStringBuilder; import org.pkl.core.util.Nullable; /** @@ -49,11 +51,13 @@ import org.pkl.core.util.Nullable; * preferable but isn't currently used. One problem with special formatting is that error * output doesn't always go to a terminal and hence may be rendered verbatim.) */ +@SuppressWarnings("UnusedReturnValue") public final class VmExceptionBuilder { private @Nullable Object receiver; private @Nullable Map insertedStackFrames; private VmException wrappedException; + private @Nullable BiConsumer hintBuilder; public static class MultilineValue { private final Iterable lines; @@ -86,6 +90,7 @@ public final class VmExceptionBuilder { } private @Nullable String message; + private @Nullable BiConsumer messageBuilder; private @Nullable Throwable cause; private VmException.Kind kind = VmException.Kind.EVAL_ERROR; private boolean isExternalMessage; @@ -94,7 +99,6 @@ public final class VmExceptionBuilder { private @Nullable Node location; private @Nullable SourceSection sourceSection; private @Nullable String memberName; - private @Nullable String hint; public VmExceptionBuilder typeMismatch(Object value, VmClass expectedType) { if (value instanceof VmNull) { @@ -293,6 +297,12 @@ public final class VmExceptionBuilder { return withMessage(message, args); } + public VmExceptionBuilder withMessageBuilder( + BiConsumer messageBuilder) { + this.messageBuilder = messageBuilder; + return this; + } + public VmExceptionBuilder withProgramValue(String name, Object value) { programValues.add(new ProgramValue(name, value)); return this; @@ -329,7 +339,15 @@ public final class VmExceptionBuilder { } public VmExceptionBuilder withHint(@Nullable String hint) { - this.hint = hint; + if (hint != null) { + this.hintBuilder = (builder, ignored) -> builder.append(hint); + } + return this; + } + + public VmExceptionBuilder withHintBuilder( + @Nullable BiConsumer hintBuilder) { + this.hintBuilder = hintBuilder; return this; } @@ -346,9 +364,12 @@ public final class VmExceptionBuilder { } public VmException build() { - if (message == null) { + if (message == null && messageBuilder == null) { throw new IllegalStateException("No message set."); } + if (message != null && messageBuilder != null) { + throw new IllegalStateException("Both message and messageBuilder are set"); + } var effectiveInsertedStackFrames = insertedStackFrames == null ? new HashMap() : insertedStackFrames; @@ -359,11 +380,12 @@ public final class VmExceptionBuilder { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, effectiveInsertedStackFrames); case UNDEFINED_VALUE -> new VmUndefinedValueException( @@ -371,11 +393,12 @@ public final class VmExceptionBuilder { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, receiver, effectiveInsertedStackFrames); case BUG -> @@ -384,11 +407,12 @@ public final class VmExceptionBuilder { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, effectiveInsertedStackFrames); case WRAPPED -> new VmWrappedEvalException( @@ -396,11 +420,12 @@ public final class VmExceptionBuilder { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, effectiveInsertedStackFrames, wrappedException); }; diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionRenderer.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionRenderer.java index c048051b..6358f783 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmExceptionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,14 +28,17 @@ import org.pkl.core.util.Nullable; public final class VmExceptionRenderer { private final @Nullable StackTraceRenderer stackTraceRenderer; private final boolean color; + private final boolean powerAssertions; /** * Constructs an error renderer with the given stack trace renderer. If stack trace renderer is * {@code null}, stack traces will not be included in error output. */ - public VmExceptionRenderer(@Nullable StackTraceRenderer stackTraceRenderer, boolean color) { + public VmExceptionRenderer( + @Nullable StackTraceRenderer stackTraceRenderer, boolean color, boolean powerAssertions) { this.stackTraceRenderer = stackTraceRenderer; this.color = color; + this.powerAssertions = powerAssertions; } @TruffleBoundary @@ -78,7 +81,8 @@ public final class VmExceptionRenderer { private void renderException(VmException exception, AnsiStringBuilder out, boolean withHeader) { String message; - var hint = exception.getHint(); + var hintBuilder = exception.getHintBuilder(); + @Nullable String hint = null; if (exception.isExternalMessage()) { var totalMessage = ErrorMessages.create(exception.getMessage(), exception.getMessageArguments()); @@ -99,7 +103,14 @@ public final class VmExceptionRenderer { if (withHeader) { out.append(AnsiTheme.ERROR_HEADER, "–– Pkl Error ––").append('\n'); } - out.append(AnsiTheme.ERROR_MESSAGE, message).append('\n'); + if (exception.getMessageBuilder() != null) { + out.append( + AnsiTheme.ERROR_MESSAGE, + () -> exception.getMessageBuilder().accept(out, powerAssertions)); + out.append('\n'); + } else { + out.append(AnsiTheme.ERROR_MESSAGE, message).append('\n'); + } // include cause's message unless it's the same as this exception's message if (exception.getCause() != null) { @@ -132,10 +143,13 @@ public final class VmExceptionRenderer { } if (!frames.isEmpty()) { - stackTraceRenderer.render(frames, hint, out.append('\n')); + stackTraceRenderer.render(frames, hint, hintBuilder, out.append('\n')); } else if (hint != null) { // render hint if there are no stack frames out.append('\n').append(AnsiTheme.ERROR_MESSAGE_HINT, hint); + } else if (hintBuilder != null) { + out.append('\n') + .append(AnsiTheme.ERROR_MESSAGE_HINT, () -> hintBuilder.accept(out, powerAssertions)); } } } 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 8ba81cef..2fe73ec6 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; public final class VmFunction extends VmObjectLike { + private final Object thisValue; private final int paramCount; private final PklRootNode rootNode; @@ -97,6 +98,10 @@ public final class VmFunction extends VmObjectLike { return EconomicMaps.create(); } + public PklRootNode getRootNode() { + return rootNode; + } + @Override public @Nullable Object getCachedValue(Object key) { return null; diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java index 1b57be15..2bbb4591 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmLanguage.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.ContextThreadLocal; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.ContextPolicy; +import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.Source; import org.pkl.core.ast.builder.AstBuilder; @@ -36,6 +37,7 @@ import org.pkl.parser.syntax.Module; version = "0.31.0-dev", characterMimeTypes = VmLanguage.MIME_TYPE, contextPolicy = ContextPolicy.SHARED) +@ProvidedTags(PklTags.Expression.class) public final class VmLanguage extends TruffleLanguage { public static final String MIME_TYPE = "application/x-pkl"; @@ -51,7 +53,7 @@ public final class VmLanguage extends TruffleLanguage { @Override protected VmContext createContext(Env env) { - return new VmContext(); + return new VmContext(this, env); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmStackOverflowException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmStackOverflowException.java index 018ee41d..29d8859f 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmStackOverflowException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmStackOverflowException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ public final class VmStackOverflowException extends VmException { e, true, new Object[0], + null, List.of(), null, null, diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java index 672f6187..0df25e43 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUndefinedValueException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,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.Collections; import java.util.Deque; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.pkl.core.StackFrame; +import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.Nullable; import org.pkl.parser.Lexer; @@ -29,38 +32,40 @@ public final class VmUndefinedValueException extends VmEvalException { private final @Nullable Object receiver; public VmUndefinedValueException( - String message, + @Nullable String message, @Nullable Throwable cause, boolean isExternalMessage, Object[] messageArguments, + @Nullable BiConsumer messageBuilder, List programValues, @Nullable Node location, @Nullable SourceSection sourceSection, @Nullable String memberName, - @Nullable String hint, + @Nullable BiConsumer hintBuilder, @Nullable Object receiver, - Map insertedStackFrames) { + @Nullable Map insertedStackFrames) { super( message, cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, - insertedStackFrames); + hintBuilder, + insertedStackFrames == null ? Collections.emptyMap() : insertedStackFrames); this.receiver = receiver; } public VmUndefinedValueException fillInHint(Deque path, Object topLevelValue) { - if (hint != null) return this; + if (hintBuilder != null) return this; + var builder = new StringBuilder(); var memberKey = getMessageArguments()[0]; path.push(memberKey); - var builder = new StringBuilder(); builder.append("The above error occurred when rendering path `"); renderPath(builder, path); builder.append('`'); @@ -72,7 +77,7 @@ public final class VmUndefinedValueException extends VmEvalException { .append('`'); } builder.append('.'); - hint = builder.toString(); + this.hintBuilder = (b, ignored) -> b.append(builder.toString()); return this; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTracker.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTracker.java new file mode 100644 index 00000000..772d2954 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTracker.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.runtime; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.EventBinding; +import com.oracle.truffle.api.instrumentation.ExecutionEventNode; +import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; +import com.oracle.truffle.api.instrumentation.Instrumenter; +import com.oracle.truffle.api.instrumentation.SourceSectionFilter; +import com.oracle.truffle.api.nodes.Node; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import org.pkl.core.util.Nullable; + +public final class VmValueTracker implements AutoCloseable { + + private final EventBinding binding; + + private final Map> values = new IdentityHashMap<>(); + + public VmValueTracker(Instrumenter instrumenter) { + binding = + instrumenter.attachExecutionEventFactory( + SourceSectionFilter.newBuilder().tagIs(PklTags.Expression.class).build(), + context -> + new ExecutionEventNode() { + @Override + @TruffleBoundary + protected void onReturnValue(VirtualFrame frame, @Nullable Object result) { + if (result == null) { + return; + } + var node = context.getInstrumentedNode(); + var myValues = values.getOrDefault(node, new ArrayList<>()); + myValues.add(result); + values.put(node, myValues); + } + }); + } + + public Map> values() { + return values; + } + + @Override + public void close() { + binding.dispose(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTrackerFactory.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTrackerFactory.java new file mode 100644 index 00000000..e1114194 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmValueTrackerFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.runtime; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.instrumentation.Instrumenter; + +public final class VmValueTrackerFactory { + + private final Instrumenter instrumenter; + + public VmValueTrackerFactory(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + @TruffleBoundary + public VmValueTracker create() { + return new VmValueTracker(instrumenter); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmWrappedEvalException.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmWrappedEvalException.java index 59880849..014dd21b 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmWrappedEvalException.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmWrappedEvalException.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,9 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.pkl.core.StackFrame; +import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.Nullable; public class VmWrappedEvalException extends VmEvalException { @@ -28,15 +30,16 @@ public class VmWrappedEvalException extends VmEvalException { private final VmException wrappedException; public VmWrappedEvalException( - String message, + @Nullable String message, @Nullable Throwable cause, boolean isExternalMessage, Object[] messageArguments, + @Nullable BiConsumer messageBuilder, List programValues, @Nullable Node location, @Nullable SourceSection sourceSection, @Nullable String memberName, - @Nullable String hint, + @Nullable BiConsumer hintBuilder, Map insertedStackFrames, VmException wrappedException) { super( @@ -44,11 +47,12 @@ public class VmWrappedEvalException extends VmEvalException { cause, isExternalMessage, messageArguments, + messageBuilder, programValues, location, sourceSection, memberName, - hint, + hintBuilder, insertedStackFrames); this.wrappedException = wrappedException; } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/ExternalPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/stdlib/ExternalPropertyNode.java index fde7f016..bfdd252d 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/ExternalPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/ExternalPropertyNode.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,4 +21,9 @@ public abstract class ExternalPropertyNode extends ExternalMemberNode { public interface Factory { ExternalPropertyNode create(ExpressionNode receiverNode); } + + @Override + public boolean isInstrumentable() { + return false; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/CollectionNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/CollectionNodes.java index 3da094c8..96a0e7ae 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/CollectionNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/CollectionNodes.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.pkl.core.stdlib.base; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.VirtualFrame; import org.pkl.core.ast.PklNode; import org.pkl.core.ast.expression.binary.LessThanNode; import org.pkl.core.ast.expression.binary.LessThanNodeGen; @@ -32,7 +33,8 @@ public final class CollectionNodes { private CollectionNodes() {} public abstract static class SortComparatorNode extends PklNode { - public abstract boolean executeWith(Object left, Object right, @Nullable VmFunction function); + public abstract boolean executeWith( + VirtualFrame frame, Object left, Object right, @Nullable VmFunction function); } public static final class CompareNode extends SortComparatorNode { @@ -42,9 +44,10 @@ public final class CollectionNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Override - public boolean executeWith(Object left, Object right, @Nullable VmFunction function) { + public boolean executeWith( + VirtualFrame frame, Object left, Object right, @Nullable VmFunction function) { assert function == null; - return lessThanNode.executeWith(left, right); + return lessThanNode.executeWith(frame, left, right); } } @@ -57,11 +60,12 @@ public final class CollectionNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Override - public boolean executeWith(Object left, Object right, @Nullable VmFunction selector) { + public boolean executeWith( + VirtualFrame frame, Object left, Object right, @Nullable VmFunction selector) { assert selector != null; var leftResult = applyLambdaNode.execute(selector, left); var rightResult = applyLambdaNode.execute(selector, right); - return lessThanNode.executeWith(leftResult, rightResult); + return lessThanNode.executeWith(frame, leftResult, rightResult); } } @@ -69,7 +73,8 @@ public final class CollectionNodes { @Child private ApplyVmFunction2Node applyLambdaNode = ApplyVmFunction2NodeGen.create(); @Override - public boolean executeWith(Object left, Object right, @Nullable VmFunction comparator) { + public boolean executeWith( + VirtualFrame frame, Object left, Object right, @Nullable VmFunction comparator) { assert comparator != null; var result = applyLambdaNode.execute(comparator, left, right); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java index a36f952d..af33801b 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java @@ -856,7 +856,7 @@ public final class ListNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self) { + protected Object eval(VirtualFrame frame, VmList self) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -864,7 +864,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (lessThanNode.executeWith(elem, result)) { + if (lessThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -881,7 +881,7 @@ public final class ListNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self) { + protected Object eval(VirtualFrame frame, VmList self) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -889,7 +889,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (lessThanNode.executeWith(elem, result)) { + if (lessThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -906,7 +906,7 @@ public final class ListNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self) { + protected Object eval(VirtualFrame frame, VmList self) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -914,7 +914,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (greaterThanNode.executeWith(elem, result)) { + if (greaterThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -931,7 +931,7 @@ public final class ListNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self) { + protected Object eval(VirtualFrame frame, VmList self) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -939,7 +939,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (greaterThanNode.executeWith(elem, result)) { + if (greaterThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -958,7 +958,7 @@ public final class ListNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmList self, VmFunction function) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -968,7 +968,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (lessThanNode.executeWith(elemValue, resultValue)) { + if (lessThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -988,7 +988,7 @@ public final class ListNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmList self, VmFunction function) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -998,7 +998,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (greaterThanNode.executeWith(elemValue, resultValue)) { + if (greaterThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -1018,7 +1018,7 @@ public final class ListNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmList self, VmFunction function) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -1028,7 +1028,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (lessThanNode.executeWith(elemValue, resultValue)) { + if (lessThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -1048,7 +1048,7 @@ public final class ListNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmList self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmList self, VmFunction function) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -1058,7 +1058,7 @@ public final class ListNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (greaterThanNode.executeWith(elemValue, resultValue)) { + if (greaterThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -1161,8 +1161,8 @@ public final class ListNodes { @Child private CompareNode compareNode = new CompareNode(); @Specialization - protected VmList eval(VmList self) { - return VmList.create(MergeSort.sort(self.toArray(), compareNode, null)); + protected VmList eval(VirtualFrame frame, VmList self) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareNode, null)); } } @@ -1170,8 +1170,8 @@ public final class ListNodes { @Child private CompareByNode compareByNode = new CompareByNode(); @Specialization - protected VmList eval(VmList self, VmFunction selector) { - return VmList.create(MergeSort.sort(self.toArray(), compareByNode, selector)); + protected VmList eval(VirtualFrame frame, VmList self, VmFunction selector) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareByNode, selector)); } } @@ -1179,8 +1179,8 @@ public final class ListNodes { @Child private CompareWithNode compareWithNode = new CompareWithNode(); @Specialization - protected VmList eval(VmList self, VmFunction function) { - return VmList.create(MergeSort.sort(self.toArray(), compareWithNode, function)); + protected VmList eval(VirtualFrame frame, VmList self, VmFunction function) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareWithNode, function)); } } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MergeSort.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MergeSort.java index 3b9cdc22..d7ad0e8e 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MergeSort.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MergeSort.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package org.pkl.core.stdlib.base; +import com.oracle.truffle.api.frame.VirtualFrame; import org.pkl.core.runtime.VmFunction; import org.pkl.core.stdlib.base.CollectionNodes.SortComparatorNode; import org.pkl.core.util.Nullable; @@ -26,7 +27,10 @@ final class MergeSort { private MergeSort() {} public static Object[] sort( - Object[] array, SortComparatorNode comparator, @Nullable VmFunction function) { + VirtualFrame frame, + Object[] array, + SortComparatorNode comparator, + @Nullable VmFunction function) { var length = array.length; var temp = new Object[length]; @@ -35,14 +39,14 @@ final class MergeSort { for (var start = 0; start < length; start += INITIAL_MERGE_SORT_STRIDE) { var end = Math.min(start + INITIAL_MERGE_SORT_STRIDE, length); - insertionSort(array, start, end, comparator, function); + insertionSort(frame, array, start, end, comparator, function); } for (var stride = INITIAL_MERGE_SORT_STRIDE; stride < length; stride *= 2) { for (var start = 0; start < length - stride; start += stride + stride) { var end = Math.min(start + stride + stride, length); var mid = start + stride; // start of second half - merge(array, temp, start, mid, end, comparator, function); + merge(frame, array, temp, start, mid, end, comparator, function); } } @@ -50,6 +54,7 @@ final class MergeSort { } private static void merge( + VirtualFrame frame, Object[] array, Object[] temp, int start, @@ -58,7 +63,7 @@ final class MergeSort { SortComparatorNode comparator, @Nullable VmFunction function) { - if (comparator.executeWith(array[mid - 1], array[mid], function)) { + if (comparator.executeWith(frame, array[mid - 1], array[mid], function)) { return; // already sorted } @@ -70,12 +75,13 @@ final class MergeSort { for (var k = start; k < end; k++) { if (i >= mid) array[k] = temp[j++]; else if (j >= end) array[k] = temp[i++]; - else if (comparator.executeWith(temp[j], temp[i], function)) array[k] = temp[j++]; + else if (comparator.executeWith(frame, temp[j], temp[i], function)) array[k] = temp[j++]; else array[k] = temp[i++]; } } private static void insertionSort( + VirtualFrame frame, Object[] array, int start, int end, @@ -83,7 +89,9 @@ final class MergeSort { @Nullable VmFunction function) { for (var i = start; i < end; i++) { - for (var j = i; j > start && comparator.executeWith(array[j], array[j - 1], function); j--) { + for (var j = i; + j > start && comparator.executeWith(frame, array[j], array[j - 1], function); + j--) { Object swap = array[j]; array[j] = array[j - 1]; array[j - 1] = swap; diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/SetNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/SetNodes.java index 76f250d2..f189fe3f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/SetNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/SetNodes.java @@ -18,6 +18,7 @@ package org.pkl.core.stdlib.base; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.LoopNode; import org.pkl.core.ast.expression.binary.GreaterThanNode; import org.pkl.core.ast.expression.binary.GreaterThanNodeGen; @@ -632,7 +633,7 @@ public final class SetNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self) { + protected Object eval(VirtualFrame frame, VmSet self) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -640,7 +641,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (lessThanNode.executeWith(elem, result)) { + if (lessThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -657,7 +658,7 @@ public final class SetNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self) { + protected Object eval(VirtualFrame frame, VmSet self) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -665,7 +666,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (lessThanNode.executeWith(elem, result)) { + if (lessThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -684,7 +685,7 @@ public final class SetNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmSet self, VmFunction function) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -694,7 +695,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (lessThanNode.executeWith(elemValue, resultValue)) { + if (lessThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -714,7 +715,7 @@ public final class SetNodes { LessThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmSet self, VmFunction function) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -724,7 +725,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (lessThanNode.executeWith(elemValue, resultValue)) { + if (lessThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -800,7 +801,7 @@ public final class SetNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self) { + protected Object eval(VirtualFrame frame, VmSet self) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -808,7 +809,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (greaterThanNode.executeWith(elem, result)) { + if (greaterThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -825,7 +826,7 @@ public final class SetNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self) { + protected Object eval(VirtualFrame frame, VmSet self) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -833,7 +834,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); - if (greaterThanNode.executeWith(elem, result)) { + if (greaterThanNode.executeWith(frame, elem, result)) { result = elem; } } @@ -852,7 +853,7 @@ public final class SetNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmSet self, VmFunction function) { self.checkNonEmpty(); var iterator = self.iterator(); @@ -862,7 +863,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (greaterThanNode.executeWith(elemValue, resultValue)) { + if (greaterThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -882,7 +883,7 @@ public final class SetNodes { GreaterThanNodeGen.create(VmUtils.unavailableSourceSection(), null, null); @Specialization - protected Object eval(VmSet self, VmFunction function) { + protected Object eval(VirtualFrame frame, VmSet self, VmFunction function) { if (self.isEmpty()) return VmNull.withoutDefault(); var iterator = self.iterator(); @@ -892,7 +893,7 @@ public final class SetNodes { while (iterator.hasNext()) { var elem = iterator.next(); var elemValue = applyLambdaNode.execute(function, elem); - if (greaterThanNode.executeWith(elemValue, resultValue)) { + if (greaterThanNode.executeWith(frame, elemValue, resultValue)) { result = elem; resultValue = elemValue; } @@ -965,8 +966,8 @@ public final class SetNodes { @Child private CompareNode compareNode = new CompareNode(); @Specialization - protected VmList eval(VmSet self) { - return VmList.create(MergeSort.sort(self.toArray(), compareNode, null)); + protected VmList eval(VirtualFrame frame, VmSet self) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareNode, null)); } } @@ -974,8 +975,8 @@ public final class SetNodes { @Child private CompareByNode compareByNode = new CompareByNode(); @Specialization - protected VmList eval(VmSet self, VmFunction selector) { - return VmList.create(MergeSort.sort(self.toArray(), compareByNode, selector)); + protected VmList eval(VirtualFrame frame, VmSet self, VmFunction selector) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareByNode, selector)); } } @@ -983,8 +984,8 @@ public final class SetNodes { @Child private CompareWithNode compareWithNode = new CompareWithNode(); @Specialization - protected VmList eval(VmSet self, VmFunction function) { - return VmList.create(MergeSort.sort(self.toArray(), compareWithNode, function)); + protected VmList eval(VirtualFrame frame, VmSet self, VmFunction function) { + return VmList.create(MergeSort.sort(frame, self.toArray(), compareWithNode, function)); } } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/test/TestNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/test/TestNodes.java index b40643d6..bebb40ed 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/test/TestNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/test/TestNodes.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,8 @@ import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.PklName; public final class TestNodes { - private static final VmExceptionRenderer noStackTraceExceptionRenderer = - new VmExceptionRenderer(null, false); + private static final VmExceptionRenderer testNodeRenderer = + new VmExceptionRenderer(null, false, false); private TestNodes() {} @@ -64,7 +64,7 @@ public final class TestNodes { @TruffleBoundary private static String render(VmException e) { - return noStackTraceExceptionRenderer + return testNodeRenderer .render(e) .lines() .skip(1) // remove meaningless header line diff --git a/pkl-core/src/main/java/org/pkl/core/util/AnsiStringBuilder.java b/pkl-core/src/main/java/org/pkl/core/util/AnsiStringBuilder.java index 2e4ffe97..13d1871a 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/AnsiStringBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/util/AnsiStringBuilder.java @@ -108,6 +108,19 @@ public final class AnsiStringBuilder { return this; } + public AnsiStringBuilder append(EnumSet codes, Runnable runnable) { + if (!usingColor) { + runnable.run(); + return this; + } + var prevDeclaredCodes = declaredCodes; + declaredCodes = EnumSet.copyOf(codes); + declaredCodes.addAll(prevDeclaredCodes); + runnable.run(); + declaredCodes = prevDeclaredCodes; + return this; + } + /** Provides a runnable where anything appended is not affected by the existing context. */ public AnsiStringBuilder appendSandboxed(Runnable runnable) { if (!usingColor) { diff --git a/pkl-core/src/main/java/org/pkl/core/util/AnsiTheme.java b/pkl-core/src/main/java/org/pkl/core/util/AnsiTheme.java index 3b54d050..49019a90 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/AnsiTheme.java +++ b/pkl-core/src/main/java/org/pkl/core/util/AnsiTheme.java @@ -16,7 +16,6 @@ package org.pkl.core.util; import java.util.EnumSet; -import java.util.Set; import org.pkl.core.util.AnsiStringBuilder.AnsiCode; public final class AnsiTheme { @@ -24,7 +23,7 @@ public final class AnsiTheme { public static final AnsiCode ERROR_MESSAGE_HINT = AnsiCode.YELLOW; public static final AnsiCode ERROR_HEADER = AnsiCode.RED; - public static final Set ERROR_MESSAGE = EnumSet.of(AnsiCode.RED, AnsiCode.BOLD); + public static final EnumSet ERROR_MESSAGE = EnumSet.of(AnsiCode.RED, AnsiCode.BOLD); public static final AnsiCode STACK_FRAME = AnsiCode.FAINT; public static final AnsiCode STACK_TRACE_MARGIN = AnsiCode.YELLOW; diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints1.pkl new file mode 100644 index 00000000..8e05ef33 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints1.pkl @@ -0,0 +1 @@ +foo: String(this == "foo") = "bar" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints10.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints10.pkl new file mode 100644 index 00000000..8377d0ed --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints10.pkl @@ -0,0 +1,3 @@ +local list = List(1, 2, 3) + +foo: Dynamic(this == new Dynamic { ...list }) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints11.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints11.pkl new file mode 100644 index 00000000..cbf6a132 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints11.pkl @@ -0,0 +1,4 @@ +local list = List(1) + +foo: Dynamic(this == new Dynamic { for (elem in list) { elem + 1 } }) + diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints12.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints12.pkl new file mode 100644 index 00000000..3ea336ce --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints12.pkl @@ -0,0 +1 @@ +foo: String(true, this.length > 5) = "bob" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints13.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints13.pkl new file mode 100644 index 00000000..73394616 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints13.pkl @@ -0,0 +1,3 @@ +name: String = "Bub" + +foo: String(this == "Hello, \(name)") = "Hello, Patty" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints14.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints14.pkl new file mode 100644 index 00000000..65df6323 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints14.pkl @@ -0,0 +1,9 @@ +open class Foo { + name: String = "Foo" +} + +class Bar extends Foo { + bub: String(startsWith(super.name)) = "Bubby" +} + +bar: Bar diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints15.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints15.pkl new file mode 100644 index 00000000..149935bb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints15.pkl @@ -0,0 +1 @@ +foo: String(this == "foo") | String(this == "qux") = "bar" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints16.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints16.pkl new file mode 100644 index 00000000..f81c47d7 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints16.pkl @@ -0,0 +1,10 @@ +class Bird { + name: String? +} + +// this will create `NullPropagatingOperationNode` and `InvokeVirtualMethodNode` for +// `name?.contains("Bob")`. +// this tests that we don't have two duplicate values in the diagram. +p: Bird(name?.contains("Bob") ?? false) = new { + name = "Pigeon" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints17.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints17.pkl new file mode 100644 index 00000000..055973eb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints17.pkl @@ -0,0 +1,4 @@ +local name = "Bob" +local function greet(name: String) = "Hello, \(name)" + +foo: Any(greet(name) == "Hello, \(name)!") = null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints2.pkl new file mode 100644 index 00000000..4e2cd490 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints2.pkl @@ -0,0 +1,7 @@ +foo: String(startsWith(bub.name)) = "Huzzah" + +bub: Person = new { name = "Bub" } + +class Person { + name: String +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints3.pkl new file mode 100644 index 00000000..ed70c48f --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints3.pkl @@ -0,0 +1,10 @@ +foo: Int(this + + two + / three + == four) = 4 + +local two = 2 + +local three = 3 + +local four = 4 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints4.pkl new file mode 100644 index 00000000..9c3caad4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints4.pkl @@ -0,0 +1,3 @@ +foo: List(fold(0, (a, b) -> a + b) == 5) = myList + +myList = List(1, 2, 3) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints5.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints5.pkl new file mode 100644 index 00000000..8de4f737 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints5.pkl @@ -0,0 +1,3 @@ +foo: String(isCapitalized) = "hello" + +local isCapitalized = (it: String) -> it == it.capitalize() diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints6.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints6.pkl new file mode 100644 index 00000000..cf36f038 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints6.pkl @@ -0,0 +1,5 @@ +foo: String(isCapitalized) = "hello" + +local isCapitalized = (it: String) -> + let (first = it.take(1).toUpperCase()) + it == "\(first)\(it.drop(1))" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints7.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints7.pkl new file mode 100644 index 00000000..911df0b9 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints7.pkl @@ -0,0 +1 @@ +foo: UInt8 = -5 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints8.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints8.pkl new file mode 100644 index 00000000..aabb4419 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints8.pkl @@ -0,0 +1 @@ +foo: NonNull = null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9.pkl new file mode 100644 index 00000000..8ea0c3fc --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9.pkl @@ -0,0 +1,10 @@ +// stdlib constructors are treated as literals +foo: Int( + IntSeq(1, 5).toList().contains(this) + || Map(1, 2, 3, 4).values.contains(this) + || List(1, 2, 3, 4).contains(this) + || Set(1, 2, 3, 4).contains(this) + || Pair(1, 2).second == this + || Bytes(1, 2, 3, 4).toList().contains(this) + || Regex("1234").toString().length == this + ) = 10 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9a.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9a.pkl new file mode 100644 index 00000000..5ce65b7b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/power/typeConstraints9a.pkl @@ -0,0 +1,12 @@ +local num = 1 + +// stdlib constructors that have a non-literal are not treated as literals +foo: Int( + IntSeq(num, 5).toList().contains(this) + || Map(num, 2, 3, 4).values.contains(this) + || List(num, 2, 3, 4).contains(this) + || Set(num, 2, 3, 4).contains(this) + || Pair(num, 2).second == this + || Bytes(num, 2, 3, 4).toList().contains(this) + || Regex("\(num)234").toString().length == this + ) = 10 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/projects/badProjectDeps4/PklProject b/pkl-core/src/test/files/LanguageSnippetTests/input/projects/badProjectDeps4/PklProject index 35200a12..b532f4c1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/projects/badProjectDeps4/PklProject +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/projects/badProjectDeps4/PklProject @@ -1,5 +1,10 @@ amends "pkl:Project" dependencies { - ["badLocalProject"] = import("../badLocalProject/PklProject") + ["badLocalProject"] = import("PklProject") } + +// hack: override the project file URI so that our tests render the same error message +// across different machines (it will truncate at 100 chars, and otherwise would be influenced by +// where the pkl project is placed within the file tree). +projectFileUri = "file:///$snippetsDir/projects/badProjectDeps4/PklProject" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/constraints5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/constraints5.err index a8c15af9..7216aa97 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/constraints5.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/constraints5.err @@ -2,6 +2,11 @@ Type constraint `this >= min` violated. Value: 3 + this >= min + │ │ │ + 3 │ 4 + false + x | max: Int(this >= min) ^^^^^^^^^^^ at constraints5#Gauge.max (file:///$snippetsDir/input/classes/constraints5.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/unionTypesErrorDifferent2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/unionTypesErrorDifferent2.err index 8ac2e5fe..be5f9e17 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/unionTypesErrorDifferent2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/unionTypesErrorDifferent2.err @@ -10,6 +10,10 @@ at unionTypesErrorDifferent2#X.a (file:///$snippetsDir/input/classes/unionTypesE Type constraint `length > 3` violated. Value: List(1, 2, 3) + length > 3 + │ │ + 3 false + x | a = List(1, 2, 3) ^^^^^^^^^^^^^ at unionTypesErrorDifferent2#res1.a (file:///$snippetsDir/input/classes/unionTypesErrorDifferent2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/wrongType6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/wrongType6.err index 5ec145dc..09349ba3 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/classes/wrongType6.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/classes/wrongType6.err @@ -2,6 +2,11 @@ Type constraint `!isEmpty` violated. Value: List() + !isEmpty + ││ + │true + false + x | names: List(!isEmpty) ^^^^^^^^ at wrongType6#Person.names (file:///$snippetsDir/input/classes/wrongType6.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails1.err index f2590461..8e029c38 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails1.err @@ -2,6 +2,13 @@ Type constraint `firstOneIsSandy` violated. Value: new Listing { new Bird { name = "Bob" }; new Bird { name = ? } } + (it: Listing) -> it[0].name == "Sandy" + │ │ │ │ + │ │ │ false + │ │ "Bob" + │ new Bird { name = "Bob" } + new Listing { new Bird { name = "Bob" }; new Bird { name = ? } } + x | birds: Listing(firstOneIsSandy) = new { ^^^^^^^^^^^^^^^ at constraintDetails1#birds (file:///$snippetsDir/input/errors/constraintDetails1.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails2.err index 7ca7df89..d2c6cba6 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails2.err @@ -3,6 +3,17 @@ Type constraint `let (myself: Listing = this) myself[0].name == "Sandy"` violated. Value: new Listing { new Bird { name = "Bob" }; new Bird { name = ? } } + let (myself: Listing = this) + │ + new Listing { new Bird { name = "Bob" }; new Bird { name = ? } } + + myself[0].name == "Sandy" + │ │ │ │ + │ │ │ false + │ │ "Bob" + │ new Bird { name = "Bob" } + new Listing { new Bird { name = "Bob" }; new Bird { name = ? } } + x | let (myself: Listing = this) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at constraintDetails2#birds (file:///$snippetsDir/input/errors/constraintDetails2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails3.err index 2239f172..f847d7de 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/constraintDetails3.err @@ -2,6 +2,11 @@ Type constraint `toList().every((it: Listing) -> it[0].name == "Bob")` violated. Value: new Listing { new Listing { new Bird { name = "Eagle" }; new Bird { name = ? ... + toList().every((it: Listing) -> it[0].name == "Bob") + │ │ + │ false + List(new Listing { new Bird { name = "Eagle" }; new Bird { name = ? } }) + x | foo: Listing(toList().every((it: Listing) -> it[0].name == "Bob")) = new { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at constraintDetails3#foo (file:///$snippetsDir/input/errors/constraintDetails3.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias2.err index 56f4b730..28455cdf 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias2.err @@ -2,6 +2,10 @@ Type constraint `isBetween(0, 65535)` violated. Value: -1 + isBetween(0, 65535) + │ + false + xxxx | typealias UInt16 = Int(isBetween(0, 65535)) ^^^^^^^^^^^^^^^^^^^ at intrinsifiedTypeAlias2#res1 (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias3.err index 773a8a8f..ff7c4f96 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/intrinsifiedTypeAlias3.err @@ -2,6 +2,10 @@ Type constraint `!(this is Null)` violated. Value: null + !(this is Null) + │ + false + xx | typealias NonNull = Any(!(this is Null)) ^^^^^^^^^^^^^^^ at intrinsifiedTypeAlias3#res1 (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes1.err index 9a7b70ac..c54d1f2b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes1.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes1.err @@ -2,6 +2,10 @@ Type constraint `isBetween(0, 255)` violated. Value: 65535 + isBetween(0, 255) + │ + false + xxxx | typealias UInt8 = Int(isBetween(0, 255)) ^^^^^^^^^^^^^^^^^ at invalidBytes1#foo (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes2.err index 8891ae6b..9ef6f1cb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes2.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes2.err @@ -2,6 +2,10 @@ Type constraint `isBetween(0, 255)` violated. Value: 4095 + isBetween(0, 255) + │ + false + xxxx | typealias UInt8 = Int(isBetween(0, 255)) ^^^^^^^^^^^^^^^^^ at invalidBytes2#res (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes3.err index 9945f4f4..e906a074 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes3.err @@ -2,6 +2,10 @@ Type constraint `isBetween(0, 255)` violated. Value: 65535 + isBetween(0, 255) + │ + false + xxxx | typealias UInt8 = Int(isBetween(0, 255)) ^^^^^^^^^^^^^^^^^ at invalidBytes3#bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes4.err index afc60559..ced9008a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidBytes4.err @@ -2,6 +2,10 @@ Type constraint `isBetween(0, 255)` violated. Value: 65535 + isBetween(0, 255) + │ + false + xxxx | typealias UInt8 = Int(isBetween(0, 255)) ^^^^^^^^^^^^^^^^^ at pkl.base#List.toBytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError3.err index 5362f45a..b29f5f70 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError3.err @@ -2,6 +2,11 @@ Type constraint `!isEmpty` violated. Value: "" + !isEmpty + ││ + │true + false + x | res: Listing = new Listing { ^^^^^^^^ at listingTypeCheckError3#res (file:///$snippetsDir/input/errors/listingTypeCheckError3.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError4.err index 5f2d3918..5bc79790 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError4.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/listingTypeCheckError4.err @@ -2,6 +2,10 @@ Type constraint `endsWith("ga")` violated. Value: "hola" + endsWith("ga") + │ + false + x | res: Listing = new Listing { ^^^^^^^^^^^^^^ at listingTypeCheckError4#res (file:///$snippetsDir/input/errors/listingTypeCheckError4.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError6.err index 07fd1783..1c303f75 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError6.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError6.err @@ -2,6 +2,10 @@ Type constraint `length == 5` violated. Value: "hi" + length == 5 + │ │ + 2 false + xx | res: Mapping, String> = bar ^^^^^^^^^^^ at mappingTypeCheckError6#res (file:///$snippetsDir/input/errors/mappingTypeCheckError6.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError7.err index c49d8561..e8b032bb 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError7.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/mappingTypeCheckError7.err @@ -2,6 +2,10 @@ Type constraint `length == 5` violated. Value: "hi" + length == 5 + │ │ + 2 false + x | res = new Mapping, String> { ^^^^^^^^^^^ at mappingTypeCheckError7#res (file:///$snippetsDir/input/errors/mappingTypeCheckError7.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints1.err new file mode 100644 index 00000000..0514c634 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints1.err @@ -0,0 +1,24 @@ +–– Pkl Error –– +Type constraint `this == "foo"` violated. +Value: "bar" + + this == "foo" + │ │ + │ false + "bar" + +x | foo: String(this == "foo") = "bar" + ^^^^^^^^^^^^^ +at typeConstraints1#foo (file:///$snippetsDir/input/errors/power/typeConstraints1.pkl) + +x | foo: String(this == "foo") = "bar" + ^^^^^ +at typeConstraints1#foo (file:///$snippetsDir/input/errors/power/typeConstraints1.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints10.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints10.err new file mode 100644 index 00000000..0568a93e --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints10.err @@ -0,0 +1,26 @@ +–– Pkl Error –– +Type constraint `this == new Dynamic { ...list }` violated. +Value: new Dynamic {} + + this == new Dynamic { ...list } + │ │ │ │ + │ │ │ List(1, 2, 3) + │ │ new Dynamic { 1; 2; 3 } + │ false + new Dynamic {} + +x | foo: Dynamic(this == new Dynamic { ...list }) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints10#foo (file:///$snippetsDir/input/errors/power/typeConstraints10.pkl) + +x | foo: Dynamic(this == new Dynamic { ...list }) + ^^^ +at typeConstraints10#foo (file:///$snippetsDir/input/errors/power/typeConstraints10.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints11.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints11.err new file mode 100644 index 00000000..9fa8173a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints11.err @@ -0,0 +1,25 @@ +–– Pkl Error –– +Type constraint `this == new Dynamic { for (elem in list) { elem + 1 } }` violated. +Value: new Dynamic {} + + this == new Dynamic { for (elem in list) { elem + 1 } } + │ │ │ │ + │ │ new Dynamic { 2 } List(1) + │ false + new Dynamic {} + +x | foo: Dynamic(this == new Dynamic { for (elem in list) { elem + 1 } }) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints11#foo (file:///$snippetsDir/input/errors/power/typeConstraints11.pkl) + +x | foo: Dynamic(this == new Dynamic { for (elem in list) { elem + 1 } }) + ^^^ +at typeConstraints11#foo (file:///$snippetsDir/input/errors/power/typeConstraints11.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints12.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints12.err new file mode 100644 index 00000000..106acca7 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints12.err @@ -0,0 +1,24 @@ +–– Pkl Error –– +Type constraint `this.length > 5` violated. +Value: "bob" + + this.length > 5 + │ │ │ + │ 3 false + "bob" + +x | foo: String(true, this.length > 5) = "bob" + ^^^^^^^^^^^^^^^ +at typeConstraints12#foo (file:///$snippetsDir/input/errors/power/typeConstraints12.pkl) + +x | foo: String(true, this.length > 5) = "bob" + ^^^^^ +at typeConstraints12#foo (file:///$snippetsDir/input/errors/power/typeConstraints12.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints13.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints13.err new file mode 100644 index 00000000..26f1ea5c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints13.err @@ -0,0 +1,26 @@ +–– Pkl Error –– +Type constraint `this == "Hello, \(name)"` violated. +Value: "Hello, Patty" + + this == "Hello, \(name)" + │ │ │ │ + │ │ │ "Bub" + │ │ "Hello, Bub" + │ false + "Hello, Patty" + +x | foo: String(this == "Hello, \(name)") = "Hello, Patty" + ^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints13#foo (file:///$snippetsDir/input/errors/power/typeConstraints13.pkl) + +x | foo: String(this == "Hello, \(name)") = "Hello, Patty" + ^^^^^^^^^^^^^^ +at typeConstraints13#foo (file:///$snippetsDir/input/errors/power/typeConstraints13.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints14.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints14.err new file mode 100644 index 00000000..35e058e2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints14.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Type constraint `startsWith(super.name)` violated. +Value: "Bubby" + + startsWith(super.name) + │ │ + false "Foo" + +x | bub: String(startsWith(super.name)) = "Bubby" + ^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints14#Bar.bub (file:///$snippetsDir/input/errors/power/typeConstraints14.pkl) + +x | bub: String(startsWith(super.name)) = "Bubby" + ^^^^^^^ +at typeConstraints14#Bar.bub (file:///$snippetsDir/input/errors/power/typeConstraints14.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints15.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints15.err new file mode 100644 index 00000000..134b351e --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints15.err @@ -0,0 +1,37 @@ +–– Pkl Error –– +Expected value of type `String(this == "foo") | String(this == "qux")`, but got a different `String`. +Value: "bar" + +x | foo: String(this == "foo") | String(this == "qux") = "bar" + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints15#foo (file:///$snippetsDir/input/errors/power/typeConstraints15.pkl) + +* Value is not of type `String(this == "foo")` because: + Type constraint `this == "foo"` violated. + Value: "bar" + + this == "foo" + │ │ + │ false + "bar" + +* Value is not of type `String(this == "qux")` because: + Type constraint `this == "qux"` violated. + Value: "bar" + + this == "qux" + │ │ + │ false + "bar" + +x | foo: String(this == "foo") | String(this == "qux") = "bar" + ^^^^^ +at typeConstraints15#foo (file:///$snippetsDir/input/errors/power/typeConstraints15.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints16.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints16.err new file mode 100644 index 00000000..d4561099 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints16.err @@ -0,0 +1,24 @@ +–– Pkl Error –– +Type constraint `name?.contains("Bob") ?? false` violated. +Value: new Bird { name = "Pigeon" } + + name?.contains("Bob") ?? false + │ │ │ + │ false false + "Pigeon" + +x | p: Bird(name?.contains("Bob") ?? false) = new { + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints16#p (file:///$snippetsDir/input/errors/power/typeConstraints16.pkl) + +x | p: Bird(name?.contains("Bob") ?? false) = new { + ^^^^^ +at typeConstraints16#p (file:///$snippetsDir/input/errors/power/typeConstraints16.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints17.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints17.err new file mode 100644 index 00000000..9ba63257 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints17.err @@ -0,0 +1,27 @@ +–– Pkl Error –– +Type constraint `greet(name) == "Hello, \(name)!"` violated. +Value: null + + greet(name) == "Hello, \(name)!" + │ │ │ │ │ + │ │ │ │ "Bob" + │ │ │ "Hello, Bob!" + │ │ false + │ "Bob" + "Hello, Bob" + +x | foo: Any(greet(name) == "Hello, \(name)!") = null + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints17#foo (file:///$snippetsDir/input/errors/power/typeConstraints17.pkl) + +x | foo: Any(greet(name) == "Hello, \(name)!") = null + ^^^^ +at typeConstraints17#foo (file:///$snippetsDir/input/errors/power/typeConstraints17.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints2.err new file mode 100644 index 00000000..699fd7af --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints2.err @@ -0,0 +1,24 @@ +–– Pkl Error –– +Type constraint `startsWith(bub.name)` violated. +Value: "Huzzah" + + startsWith(bub.name) + │ │ │ + false │ "Bub" + new Person { name = "Bub" } + +x | foo: String(startsWith(bub.name)) = "Huzzah" + ^^^^^^^^^^^^^^^^^^^^ +at typeConstraints2#foo (file:///$snippetsDir/input/errors/power/typeConstraints2.pkl) + +x | foo: String(startsWith(bub.name)) = "Huzzah" + ^^^^^^^^ +at typeConstraints2#foo (file:///$snippetsDir/input/errors/power/typeConstraints2.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints3.err new file mode 100644 index 00000000..ddcfcc32 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints3.err @@ -0,0 +1,41 @@ +–– Pkl Error –– +Type constraint `this + + two + / three + == four` violated. +Value: 4 + + this + │ + 4 + + + two + │ │ + │ 2 + 4.666666666666667 + + / three + │ │ + │ 3 + 0.6666666666666666 + + == four + │ │ + │ 4 + false + +x | foo: Int(this + ^^^^ +at typeConstraints3#foo (file:///$snippetsDir/input/errors/power/typeConstraints3.pkl) + +x | == four) = 4 + ^ +at typeConstraints3#foo (file:///$snippetsDir/input/errors/power/typeConstraints3.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints4.err new file mode 100644 index 00000000..2d588dde --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints4.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Type constraint `fold(0, (a, b) -> a + b) == 5` violated. +Value: List(1, 2, 3) + + fold(0, (a, b) -> a + b) == 5 + │ │ + 6 false + +x | foo: List(fold(0, (a, b) -> a + b) == 5) = myList + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints4#foo (file:///$snippetsDir/input/errors/power/typeConstraints4.pkl) + +x | foo: List(fold(0, (a, b) -> a + b) == 5) = myList + ^^^^^^ +at typeConstraints4#foo (file:///$snippetsDir/input/errors/power/typeConstraints4.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints5.err new file mode 100644 index 00000000..7dbcd32b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints5.err @@ -0,0 +1,26 @@ +–– Pkl Error –– +Type constraint `isCapitalized` violated. +Value: "hello" + + (it: String) -> it == it.capitalize() + │ │ │ │ + │ │ │ "Hello" + │ │ "hello" + │ false + "hello" + +x | foo: String(isCapitalized) = "hello" + ^^^^^^^^^^^^^ +at typeConstraints5#foo (file:///$snippetsDir/input/errors/power/typeConstraints5.pkl) + +x | foo: String(isCapitalized) = "hello" + ^^^^^^^ +at typeConstraints5#foo (file:///$snippetsDir/input/errors/power/typeConstraints5.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints6.err new file mode 100644 index 00000000..b1fe0877 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints6.err @@ -0,0 +1,32 @@ +–– Pkl Error –– +Type constraint `isCapitalized` violated. +Value: "hello" + + (it: String) -> + let (first = it.take(1).toUpperCase()) + │ │ │ + │ "h" "H" + "hello" + + it == "\(first)\(it.drop(1))" + │ │ │ │ │ │ + │ │ │ "H" │ "ello" + │ │ "Hello" "hello" + │ false + "hello" + +x | foo: String(isCapitalized) = "hello" + ^^^^^^^^^^^^^ +at typeConstraints6#foo (file:///$snippetsDir/input/errors/power/typeConstraints6.pkl) + +x | foo: String(isCapitalized) = "hello" + ^^^^^^^ +at typeConstraints6#foo (file:///$snippetsDir/input/errors/power/typeConstraints6.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints7.err new file mode 100644 index 00000000..3dd87ec6 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints7.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Type constraint `isBetween(0, 255)` violated. +Value: -5 + + isBetween(0, 255) + │ + false + +xxxx | typealias UInt8 = Int(isBetween(0, 255)) + ^^^^^^^^^^^^^^^^^ +at typeConstraints7#foo (pkl:base) + +x | foo: UInt8 = -5 + ^^ +at typeConstraints7#foo (file:///$snippetsDir/input/errors/power/typeConstraints7.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints8.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints8.err new file mode 100644 index 00000000..162e9ceb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints8.err @@ -0,0 +1,23 @@ +–– Pkl Error –– +Type constraint `!(this is Null)` violated. +Value: null + + !(this is Null) + │ + false + +xx | typealias NonNull = Any(!(this is Null)) + ^^^^^^^^^^^^^^^ +at typeConstraints8#foo (pkl:base) + +x | foo: NonNull = null + ^^^^ +at typeConstraints8#foo (file:///$snippetsDir/input/errors/power/typeConstraints8.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9.err new file mode 100644 index 00000000..d355a0fa --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9.err @@ -0,0 +1,59 @@ +–– Pkl Error –– +Type constraint `IntSeq(1, 5).toList().contains(this) + || Map(1, 2, 3, 4).values.contains(this) + || List(1, 2, 3, 4).contains(this) + || Set(1, 2, 3, 4).contains(this) + || Pair(1, 2).second == this + || Bytes(1, 2, 3, 4).toList().contains(this) + || Regex("1234").toString().length == this` violated. +Value: 10 + + IntSeq(1, 5).toList().contains(this) + │ │ │ + │ false 10 + List(1, 2, 3, 4, 5) + + || Map(1, 2, 3, 4).values.contains(this) + │ │ │ │ + false │ false 10 + List(2, 4) + + || List(1, 2, 3, 4).contains(this) + │ │ │ + false false 10 + + || Set(1, 2, 3, 4).contains(this) + │ │ │ + false false 10 + + || Pair(1, 2).second == this + │ │ │ │ + false 2 │ 10 + false + + || Bytes(1, 2, 3, 4).toList().contains(this) + │ │ │ │ + false │ false 10 + List(1, 2, 3, 4) + + || Regex("1234").toString().length == this + │ │ │ │ │ + false │ 13 │ 10 + │ false + "Regex(\"1234\")" + +x | IntSeq(1, 5).toList().contains(this) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints9#foo (file:///$snippetsDir/input/errors/power/typeConstraints9.pkl) + +xx | ) = 10 + ^^ +at typeConstraints9#foo (file:///$snippetsDir/input/errors/power/typeConstraints9.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9a.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9a.err new file mode 100644 index 00000000..e7a608de --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/power/typeConstraints9a.err @@ -0,0 +1,67 @@ +–– Pkl Error –– +Type constraint `IntSeq(num, 5).toList().contains(this) + || Map(num, 2, 3, 4).values.contains(this) + || List(num, 2, 3, 4).contains(this) + || Set(num, 2, 3, 4).contains(this) + || Pair(num, 2).second == this + || Bytes(num, 2, 3, 4).toList().contains(this) + || Regex("\(num)234").toString().length == this` violated. +Value: 10 + + IntSeq(num, 5).toList().contains(this) + │ │ │ │ │ + │ 1 │ false 10 + IntSeq(1, 5) List(1, 2, 3, 4, 5) + + || Map(num, 2, 3, 4).values.contains(this) + │ │ │ │ │ │ + │ │ 1 │ false 10 + │ Map(1, 2, 3, 4) List(2, 4) + false + + || List(num, 2, 3, 4).contains(this) + │ │ │ │ │ + │ │ 1 false 10 + │ List(1, 2, 3, 4) + false + + || Set(num, 2, 3, 4).contains(this) + │ │ │ │ │ + │ │ 1 false 10 + │ Set(1, 2, 3, 4) + false + + || Pair(num, 2).second == this + │ │ │ │ │ │ + │ │ 1 2 │ 10 + │ Pair(1, 2) false + false + + || Bytes(num, 2, 3, 4).toList().contains(this) + │ │ │ │ │ │ + │ │ 1 │ false 10 + │ Bytes(1, 2, 3, 4) List(1, 2, 3, 4) + false + + || Regex("\(num)234").toString().length == this + │ │ │ │ │ │ │ │ + │ │ │ 1 │ 13 │ 10 + │ │ "1234" │ false + │ Regex("1234") "Regex(\"1234\")" + false + +x | IntSeq(num, 5).toList().contains(this) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at typeConstraints9a#foo (file:///$snippetsDir/input/errors/power/typeConstraints9a.pkl) + +xx | ) = 10 + ^^ +at typeConstraints9a#foo (file:///$snippetsDir/input/errors/power/typeConstraints9a.pkl) + +xxx | renderer.renderDocument(value) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +at pkl.base#Module.output.text (pkl:base) + +xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8") + ^^^^ +at pkl.base#Module.output.bytes (pkl:base) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/modules/typedModuleProperties3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/typedModuleProperties3.err index 4c6e4944..d7eba49c 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/modules/typedModuleProperties3.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/modules/typedModuleProperties3.err @@ -2,6 +2,11 @@ Type constraint `!isEmpty` violated. Value: "" + !isEmpty + ││ + │true + false + x | res1: String(!isEmpty) = "" ^^^^^^^^ at typedModuleProperties3#res1 (file:///$snippetsDir/input/modules/typedModuleProperties3.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badProjectDeps4/bug.err b/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badProjectDeps4/bug.err index 3241b2b8..d29b1584 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badProjectDeps4/bug.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/projects/badProjectDeps4/bug.err @@ -1,6 +1,6 @@ –– Pkl Error –– Expected value of type `*RemoteDependency | Project(isValidLoadDependency)`, but got a different `pkl.Project`. -Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetting... +Value: new ModuleClass { package = ?; tests = ?; dependencies { ["badLocalProject"] ... xxx | dependencies: Mapping ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -8,8 +8,32 @@ at pkl.Project#dependencies (pkl:Project) * Value is not of type `Project(isValidLoadDependency)` because: Type constraint `isValidLoadDependency` violated. - Value: new ModuleClass { package = null; tests {}; dependencies {}; evaluatorSetti... + Value: new ModuleClass { package = ?; tests = ?; dependencies { ["badLocalProject"... -x | ["badLocalProject"] = import("../badLocalProject/PklProject") - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + (it: Project) -> + isUriLocal(projectFileUri, it.projectFileUri) + │ │ │ │ + true │ │ "file:///$snippetsDir/projects/badProjectDeps4/PklProject" + │ new ModuleClass { package = ?; tests = ?; dependencies { ["badLocalProject"] = ? }; evaluatorSett... + "file:///$snippetsDir/projects/badProjectDeps4/PklProject" + + && it.projectFileUri.endsWith("/PklProject") + │ │ │ │ + │ │ │ true + │ │ "file:///$snippetsDir/projects/badProjectDeps4/PklProject" + │ new ModuleClass { package = ?; tests = ?; dependencies { ["badLocalProject"] = ? }; evaluatorSett... + true + + && it != module + │ │ │ + │ │ false + │ new ModuleClass { package = ?; tests = ?; dependencies { ["badLocalProject"] = ? }; evaluatorSett... + false + + && it.package != null + │ + false + +x | ["badLocalProject"] = import("PklProject") + ^^^^^^^^^^^^^^^^^^^^ at PklProject#dependencies["badLocalProject"] (file:///$snippetsDir/input/projects/badProjectDeps4/PklProject) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err index e809c982..98b69f0b 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasContext.err @@ -2,6 +2,10 @@ Type constraint `endsWith(lastName)` violated. Value: "Jimmy Bird" + endsWith(lastName) + │ │ + false "Birdo" + x | typealias Birds = Listing ^^^^^^^^^^^^^^^^^^ at typeAliasContext#res (file:///$snippetsDir/input/types/helpers/originalTypealias.pkl) diff --git a/pkl-core/src/test/kotlin/org/pkl/core/EvaluateTestsTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/EvaluateTestsTest.kt index 924e5977..4ccee610 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/EvaluateTestsTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/EvaluateTestsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,10 +88,26 @@ class EvaluateTestsTest { assertThat(res.failures.size).isEqualTo(2) val fail1 = res.failures[0] - assertThat(fail1.message).isEqualTo("1 == 2 (repl:text)") + assertThat(fail1.message) + .isEqualTo( + """ + 1 == 2 (repl:text) + │ + false + """ + .trimIndent() + ) val fail2 = res.failures[1] - assertThat(fail2.message).isEqualTo(""""foo" == "bar" (repl:text)""") + assertThat(fail2.message) + .isEqualTo( + """ + "foo" == "bar" (repl:text) + │ + false + """ + .trimIndent() + ) } @Test diff --git a/pkl-core/src/test/kotlin/org/pkl/core/runtime/StackTraceRendererTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/runtime/StackTraceRendererTest.kt index 72d03e9a..ee55795a 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/runtime/StackTraceRendererTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/runtime/StackTraceRendererTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -192,7 +192,7 @@ class StackTraceRendererTest { val loop = StackTraceRenderer.StackFrameLoop(loopFrames, 1) val frames = listOf(createFrame("bar", 1), createFrame("baz", 2), loop) val formatter = AnsiStringBuilder(false) - renderer.doRender(frames, null, formatter, "", true) + renderer.doRender(frames, null, null, formatter, "", true) val renderedFrames = formatter.toString() assertThat(renderedFrames) .isEqualTo( diff --git a/pkl-gradle/src/test/kotlin/org/pkl/gradle/TestsTest.kt b/pkl-gradle/src/test/kotlin/org/pkl/gradle/TestsTest.kt index e088f578..1594e1b3 100644 --- a/pkl-gradle/src/test/kotlin/org/pkl/gradle/TestsTest.kt +++ b/pkl-gradle/src/test/kotlin/org/pkl/gradle/TestsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,7 +123,11 @@ class TestsTest : AbstractTest() { ✔ divide numbers ✘ fail 4 == 9 (file:///file, line x) + │ + false "foo" == "bar" (file:///file, line x) + │ + false examples ✔ user 0 ✘ user 1 @@ -233,14 +237,19 @@ class TestsTest : AbstractTest() { assertThat(report) .isEqualTo( + // language=xml """ - 4 == 9 (file:///file, line x) - "foo" == "bar" (file:///file, line x) + 4 == 9 (file:///file, line x) + │ + false + "foo" == "bar" (file:///file, line x) + │ + false @@ -259,7 +268,7 @@ class TestsTest : AbstractTest() { - + """ .trimIndent() ) diff --git a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java index ff6c74f2..cf76a8b5 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java +++ b/pkl-parser/src/main/java/org/pkl/parser/GenericParser.java @@ -1,5 +1,5 @@ /* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/pkl-parser/src/main/java/org/pkl/parser/Span.java b/pkl-parser/src/main/java/org/pkl/parser/Span.java index 460b2b3b..b7012d58 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Span.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Span.java @@ -1,5 +1,5 @@ /* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,10 @@ public record Span(int charIndex, int length) { return charIndex + length - 1; } + public int stopIndexExclusive() { + return charIndex + length; + } + public Span stopSpan() { return new Span(charIndex + length - 1, 1); } @@ -42,4 +46,9 @@ public record Span(int charIndex, int length) { public Span grow(int amount) { return new Span(charIndex, length + amount); } + + /** Tells if {@code other} is entirely within this span. */ + public boolean contains(Span other) { + return charIndex <= other.charIndex && other.charIndex + other.length <= charIndex + length; + } } diff --git a/pkl-parser/src/main/java/org/pkl/parser/Token.java b/pkl-parser/src/main/java/org/pkl/parser/Token.java index b20d8130..d80467b8 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/Token.java +++ b/pkl-parser/src/main/java/org/pkl/parser/Token.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -191,6 +191,35 @@ public enum Token { }; } + public boolean isOperator() { + return switch (this) { + case POW, + STAR, + DIV, + INT_DIV, + MOD, + PLUS, + MINUS, + GT, + GTE, + LT, + LTE, + IS, + AS, + EQUAL, + NOT_EQUAL, + AND, + OR, + PIPE, + COALESCE, + DOT, + QDOT, + LBRACK -> + true; + default -> false; + }; + } + public boolean isAffix() { return switch (this) { case LINE_COMMENT, BLOCK_COMMENT, SEMICOLON -> true; diff --git a/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java b/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java index 6b4da467..d9a3ccb8 100644 --- a/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java +++ b/pkl-parser/src/main/java/org/pkl/parser/syntax/Expr.java @@ -127,7 +127,22 @@ public abstract sealed class Expr extends AbstractNode { } } - public static final class SingleLineStringLiteralExpr extends Expr { + public abstract static sealed class StringLiteralExpr extends Expr + permits SingleLineStringLiteralExpr, MultiLineStringLiteralExpr { + public abstract List getParts(); + + public StringLiteralExpr(Span span, List parts) { + super(span, parts); + } + + public final boolean hasInterpolation() { + var children = children(); + assert children != null; + return children.size() > 1; + } + } + + public static final class SingleLineStringLiteralExpr extends StringLiteralExpr { private final Span startDelimiterSpan; private final Span endDelimiterSpan; @@ -158,7 +173,7 @@ public abstract sealed class Expr extends AbstractNode { } } - public static final class MultiLineStringLiteralExpr extends Expr { + public static final class MultiLineStringLiteralExpr extends StringLiteralExpr { private final Span startDelimiterSpan; private final Span endDelimiterSpan;