Skip to content
Merged
84 changes: 83 additions & 1 deletion docs/modules/release-notes/pages/0.31.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]#🎶#

Expand Down
61 changes: 36 additions & 25 deletions pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -81,7 +81,7 @@ class CliTestRunnerTest {
facts {
["fail"] {
4 == 9
"foo" != "bar"
1 == 5
}
}
"""
Expand All @@ -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()
Expand Down Expand Up @@ -283,12 +288,14 @@ class CliTestRunnerTest {
<testsuite name="test" tests="2" failures="1">
<testcase classname="test.facts" name="foo"></testcase>
<testcase classname="test.facts" name="bar">
<failure message="Fact Failure">5 == 9 (/tempDir/test.pkl, line xx)</failure>
<failure message="Fact Failure">5 == 9 (/tempDir/test.pkl, line xx)
false</failure>
</testcase>
<system-err><![CDATA[9 = 9
]]></system-err>
</testsuite>
"""
.trimIndent()
)
Expand Down Expand Up @@ -481,26 +488,30 @@ class CliTestRunnerTest {
assertThat(junitReport)
.isEqualTo(
"""
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="pkl-tests" tests="5" failures="3">
<testsuite name="test1" tests="2" failures="1">
<testcase classname="test1.facts" name="foo"></testcase>
<testcase classname="test1.facts" name="bar">
<failure message="Fact Failure">5 == 9 (/tempDir/test1.pkl, line xx)</failure>
</testcase>
</testsuite>
<testsuite name="test2" tests="3" failures="2">
<testcase classname="test2.facts" name="xxx">
<failure message="Fact Failure">false (/tempDir/test2.pkl, line xx)</failure>
</testcase>
<testcase classname="test2.facts" name="yyy">
<failure message="Fact Failure">false (/tempDir/test2.pkl, line xx)</failure>
</testcase>
<testcase classname="test2.facts" name="zzz"></testcase>
</testsuite>
</testsuites>
"""
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="pkl-tests" tests="5" failures="3">
<testsuite name="test1" tests="2" failures="1">
<testcase classname="test1.facts" name="foo"></testcase>
<testcase classname="test1.facts" name="bar">
<failure message="Fact Failure">5 == 9 (/tempDir/test1.pkl, line xx)
false</failure>
</testcase>
</testsuite>
<testsuite name="test2" tests="3" failures="2">
<testcase classname="test2.facts" name="xxx">
<failure message="Fact Failure">false (/tempDir/test2.pkl, line xx)
</failure>
</testcase>
<testcase classname="test2.facts" name="yyy">
<failure message="Fact Failure">false (/tempDir/test2.pkl, line xx)
</failure>
</testcase>
<testcase classname="test2.facts" name="zzz"></testcase>
</testsuite>
</testsuites>
"""
.trimIndent()
)
}
Expand Down
25 changes: 23 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/ast/ExpressionNode.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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);
}
Expand All @@ -43,4 +49,19 @@ public double executeFloat(VirtualFrame frame) throws UnexpectedResultException
public boolean executeBoolean(VirtualFrame frame) throws UnexpectedResultException {
return VmTypesGen.expectBoolean(executeGeneric(frame));
}

@Override
public boolean hasTag(Class<? extends Tag> tag) {
return tag == PklTags.Expression.class;
}

@Override
public boolean isInstrumentable() {
return true;
}

@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new ExpressionNodeWrapper(this, probe);
}
}
8 changes: 6 additions & 2 deletions pkl-core/src/main/java/org/pkl/core/ast/PklNode.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,6 +17,7 @@

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;
Expand All @@ -38,7 +39,10 @@ protected PklNode() {
}

@Override
public SourceSection getSourceSection() {
public final SourceSection getSourceSection() {
if (this instanceof WrapperNode wrapperNode) {
return wrapperNode.getDelegateNode().getSourceSection();
}
return sourceSection;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ private URI resolveImport(String importUri, StringConstant ctx) {
throw exceptionBuilder()
.evalError(e.getMessage(), e.getMessageArguments())
.withCause(e.getCause())
.withHint(e.getHint())
.withHintBuilder(e.getHintBuilder())
.withSourceSection(createSourceSection(ctx))
.build();
} catch (ExternalReaderProcessException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,12 +17,15 @@

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);
Expand Down Expand Up @@ -63,4 +66,9 @@ protected boolean eval(VmDuration left, VmDuration right) {
protected boolean eval(VmDataSize left, VmDataSize right) {
return left.compareTo(right) > 0;
}

@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new GreaterThanNodeWrapper(sourceSection, this, probe);
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,12 +17,15 @@

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);
Expand Down Expand Up @@ -63,4 +66,9 @@ protected boolean eval(VmDuration left, VmDuration right) {
protected boolean eval(VmDataSize left, VmDataSize right) {
return left.compareTo(right) < 0;
}

@Override
public WrapperNode createWrapper(ProbeNode probe) {
return new LessThanNodeWrapper(sourceSection, this, probe);
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,8 @@
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;
Expand All @@ -40,6 +42,7 @@
@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;
Expand Down Expand Up @@ -173,6 +176,12 @@ protected ClassMethod resolveMethod(VmClass receiverClass) {
.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();
Expand Down
Loading
Loading