Skip to content

Commit d9e8369

Browse files
committed
SPICE-0026: Power Assertions
1 parent 585598c commit d9e8369

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

images/power-assertions.png

68.8 KB
Loading
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
= Power assertions
2+
3+
* Proposal: link:./SPICE-0021-binary-renderer-and-parser.adoc[SPICE-0021]
4+
* Author: https://github.com/bioball[Dan Chao]
5+
* Status: Accepted or Rejected
6+
* Implemented in: TBD
7+
* Category: Language
8+
9+
== Introduction
10+
11+
We will enhance Pkl's error messages with power assertions.
12+
13+
Power assertions are error messages that display the values produced through executing source code in a visual diagram.
14+
15+
== Motivation
16+
17+
Pkl has some places that are assertions:
18+
19+
1. Type constraint expressions
20+
2. The `facts` block of `pkl:test`
21+
22+
When these assertions fail, Pkl shows a limited amount of information about the failure.
23+
24+
For example, given the following program:
25+
26+
[source,pkl]
27+
----
28+
class Person {
29+
name: String
30+
}
31+
32+
kiddo: Person(name.endsWith(lastName))
33+
34+
lastName: String = "Smith"
35+
----
36+
37+
A failing typecheck simply tells that the type constraint check failed:
38+
39+
[source,text]
40+
----
41+
–– Pkl Error ––
42+
Type constraint `name.endsWith(lastName)` violated.
43+
Value: new Person { name = "Bub Johnson" }
44+
45+
6 | passenger: Person(name.endsWith(lastName))
46+
----
47+
48+
This doesn't tell explain _why_ the type constraint failed.
49+
For example, what is `lastName`?
50+
What is the expectation?
51+
52+
== Proposed Solution
53+
54+
These assertions will be decorated with values produced by the AST nodes during execution.
55+
56+
With power assertions, the above error becomes:
57+
58+
[source,text]
59+
----
60+
–– Pkl Error ––
61+
Type constraint `name.endsWith(lastName)` violated.
62+
Value: new Person { name = "Bub Johnson" }
63+
64+
name.endsWith(lastName)
65+
| | |
66+
| false "Smith"
67+
"Bub Johnson"
68+
69+
5 | kiddo: Person(name.endsWith(lastName))
70+
^^^^^^^^^^^^^^^^^^^^^^^
71+
----
72+
73+
== Detailed design
74+
75+
=== How the diagram works
76+
77+
The design of the diagram follows prior art:
78+
79+
* https://docs.groovy-lang.org/next/html/documentation/core-testing-guide.html#_power_assertions[Groovy]
80+
* https://kotlinlang.org/docs/power-assert.html[Kotlin]
81+
* https://github.com/kishikawakatsumi/swift-power-assert[swift-power-assert]
82+
* https://github.com/power-assert-js/power-assert[power-assert-js]
83+
84+
The rules for displaying power asserts are:
85+
86+
Values are appended to the source graph using the `|` character.
87+
88+
[source,text]
89+
----
90+
myName.startsWith(prefix)
91+
| | |
92+
"bub" false "g"
93+
----
94+
95+
If two values cannot fit, the right-most value wins, and the left-most value gets moved down one line.
96+
97+
[source,text]
98+
----
99+
num1 == num2
100+
| | |
101+
5 | 6
102+
false
103+
----
104+
105+
Continually overlapping values will cascade.
106+
107+
[source,text]
108+
----
109+
foo == bar
110+
| | |
111+
| | "barrey"
112+
| false
113+
"foooey"
114+
----
115+
116+
Literal values are omitted from the source.
117+
118+
[source,text]
119+
----
120+
foo == "barrey"
121+
| |
122+
| false
123+
"foooey"
124+
----
125+
126+
Literal values include stdlib types like IntSeq, List, Map whose members are also literal.
127+
Here, `List(1, 2, 3)` is excluded because it's considered a literal:
128+
129+
[source,text]
130+
----
131+
List(1, 2, 3).contains(num)
132+
| |
133+
false 5
134+
----
135+
136+
Nodes from within things that can be executed multiple times are excluded.
137+
These are:
138+
139+
* Lambdas
140+
* For-generators
141+
* Member predicates
142+
143+
Here, the nodes within the lambda are not part of the diagram.
144+
145+
[source,text]
146+
----
147+
myList.fold(0, (a, b) -> a + b) == 5
148+
| | |
149+
| 6 false
150+
List(1, 2, 3)
151+
----
152+
153+
Expressions that span multiple lines have a blank line inserted in between.
154+
155+
[source,text]
156+
----
157+
one
158+
|
159+
1
160+
161+
+ two
162+
| |
163+
| 2
164+
1.6666666666666665
165+
166+
/ three
167+
| |
168+
| 3
169+
0.6666666666666666
170+
171+
== four
172+
| |
173+
| 4
174+
false
175+
----
176+
177+
=== Colors
178+
179+
If colors are enabled, the source nodes are syntax highlighted, and the `|` character is emitted with ANSI code 2 (faint).
180+
181+
Sample:
182+
183+
image::../images/power-assertions.png[]
184+
185+
=== Runtime implementation
186+
187+
Values are collected through truffle instrumentation, which is machinery to wrap AST nodes to observe their execution.
188+
189+
Instrumentation is disabled by default, and only enabled if an assertion fails.
190+
191+
Essentially, failing assertions are executed twice.
192+
The algorithm works as follows:
193+
194+
1. Run the assertion; assertion fails
195+
2. Enable instrumentation
196+
3. Run the assertion again
197+
4. Disable instrumentation
198+
199+
Running instrumentation has a runtime cost.
200+
By only enabling instrumentation for failing assertions, we only pay this cost for the error path.
201+
202+
=== `test.catch`
203+
204+
Power assertions are hidden from the error passed to `test.catch` and `test.catchOrNull`; these two APIs work exactly like they do today.
205+
206+
== Compatibility
207+
208+
There is no impact on compatibility.
209+
This design only impacts error messages, which is not considered an API.
210+
211+
== Future directions
212+
213+
=== Assertions as an API
214+
215+
Possibly, we can provide an in-language assertion API.
216+
For example:
217+
218+
[source,pkl]
219+
----
220+
assert(1 == 2)
221+
----
222+
223+
The assertion would also run display power assertions in the resulting thrown error.
224+
225+
This might play nicely with custom error messages:
226+
227+
[source,pkl]
228+
----
229+
local startingStartsWithBub = (it: String) ->
230+
assert(it.starsWith("bub"), "Foo should start with bub.")
231+
----
232+
233+
=== Richer power assertions
234+
235+
We can possibly provide richer diagrams.
236+
For example, in the case of failing string comparison, to display a unified diff.
237+
238+
== Alternatives considered
239+
240+
N/A
241+
242+
== Acknowledgements
243+
244+
Power assertions was initially introduced in https://spockframework.org[Spock Framework] by Peter Niederwieser.

0 commit comments

Comments
 (0)