CLOVIS
02/16/2026, 1:40 PMfun foo(a: Boolean)
fun foo(a: Boolean, () -> PowerAssertNode)
which would enable the exact same transformation as when it returns a String, but instead returning a PowerAssertNode, which would be a new interface like:
interface PowerAssertNode {
val value: Any? // the value at this particular node
fun toString(): String // the existing PowerAssert string representation
val children: Sequence<PowerAssertNode>
}
This would be very useful to integrate Power Assert into other projects, for example assertion libraries. Currently, assertion libraries can't embed the information they have into Power Assert diagrams, so they can't be used together easily. With this feature, it would become possible.bnorm
02/16/2026, 10:06 PM@PowerAssert
fun myAssert(condition: Boolean) {
if (!condition) {
val explanation: CallExplanation? = PowerAssert.explanation // intrinsic
throw AssertionError(explanation?.toDefaultMessage() ?: "Assertion failed") // extension function
}
}CLOVIS
02/17/2026, 8:07 AMassertThat(foo) {
isGreaterThan(emptyList())
get { this[0] } isEqualTo 5
}
I'd like to use Power Assert on each of these definitions, and within the assertion library be able to combine them with the intrinsic knowledge I have about the structure, so I can generate a bigger Power Assert output string that contains all that informationbnorm
02/17/2026, 1:05 PMassertThat, isGreaterThan, and isEqualTo are all annotated, you'll get information about foo, emptyList(), get { this[0] }, and 5 in 3 separate call explanations you can combine how see you see fit. Source information will be available in each explanation like file offset of source code and display offset of each expression.
I'll mention that lambda are extremely difficult to handle in any meaningful way, so the assertThat and get lambdas will not be analyzed, similar to how it works today with something like assert(list.filter { it <= 0 }.isEmpty)).CLOVIS
02/17/2026, 1:07 PMassertThat lambda is not an issue, because it contains function calls that are themselves annotated, so the information is recoverable that way. The get { this[0] } would be useful, I'll think of how that can be adaptedbnorm
02/17/2026, 2:53 PMassertThat(foo) {
isGreaterThan(emptyList())
subject[0] isEqualTo 5
}CLOVIS
02/17/2026, 3:57 PMCLOVIS
02/17/2026, 4:23 PMcheck from the stdlibbnorm
02/17/2026, 4:28 PM@PowerAssert.
In fact, we're also considering changing the current transformation to create a CallExplanation and then immediately call toDefaultMessage() on it for the message parameter, so we can improve things with runtime behavior (proper array rendering, flattening of the diagram, etc). Will likely happen, but we need to carefully review the API for any backwards compatibility concerns.CLOVIS
02/17/2026, 4:28 PMbnorm
02/17/2026, 4:30 PMExplanation and Expression is still under review and might change. But if you have use case requests or other comments/concerns, happy to hear them.CLOVIS
02/17/2026, 5:00 PMExpression an interface , to be able to use interface delegation.
TheI expect that libraries adopting this will want to work even if users haven't enabled Power Assert.property is a compiler-plugin intrinsic, and access will always result in a runtime error unless the compiler-plugin is applied.explanation
try…catch the explanation each time doesn't seem very convenient (but also, explanationOrNull would be confusing since it's already nullable).
Since it will generate two different functions with a different branch, how will that behave with code coverage tools?
For example, aI think this may be useful in many more situations than Power Assert itself. For example, string sanitization, similar to what #C07CVTQ6WBG is doing. I find the syntaxcould be introduced to help describe String concatenation and each of the arguments provided.StringTemplateExpression
PowerAssert.explanation to be a bit strange, it feels a bit alien. Though this is a very specific thing used in very specific situations, so maybe it's ok.
Overall, I like this, and I hope we see an actual KEEP of it one day 🙂bnorm
02/17/2026, 5:20 PMOne of them would be to makeWhat's the use case for creating your ownanExpression, to be able to use interface delegation.interface
Expression implementation?
[...] even if users haven't enabled Power Assert.So with this new annotation, library authors need to apply the power-assert compiler plugin to their library as well. This makes sure that the synthetic copy of the function is generated. And this also makes sure that calls to
PowerAssert.explanation are replaced with a hard-coded null in the original function and parameter access in the synthetic copy. This means that downstream users of the annotated function don't need to have power-assert applied to be able to use the original function, as the explanation will just always be null.
Since it will generate two different functions with a different branch, how will that behave with code coverage tools?I'm honestly not sure. But if the tool respects line numbers - which are preserved in the synthetic copy of the function - the coverage should be combined?
I think this may be useful in many more situations than Power Assert itself.Yes, there are some ideas along this line similar to python's f-string support for self-documenting expressions: KT-71211. It's sort of possible to do it now, as you can get the last expression of the argument and extract the template from the source code. Not pretty, so we're thinking about how we can expand on it in the future.
I find the syntaxIt's sort of similar toto be a bit strange, [...]PowerAssert.explanation
coroutineContext just not at the top-level. Or currentComposer in Compose. I don't think it should be at the top-level because I don't really want to pollute that scope, but I could certainly reconsider it during the KEEP review if there are good suggestions.CLOVIS
02/18/2026, 11:17 AMWhat's the use case for creating your ownCreating animplementation?Expression
Expression that combines others, while still being able to use all the built-in features (e.g. toDefaultMessage())bnorm
02/18/2026, 12:42 PMExplanation rather than Expression? So that you can combine `Explanation`s from multiple calls into a single diagram?CLOVIS
02/18/2026, 1:56 PMbnorm
02/18/2026, 6:49 PMAssertion failed:
* String `Kodee` does not have length `1`.
assertThat(name) {
|
Kodee
hasLength(expected)
|
1
}CLOVIS
02/18/2026, 6:54 PMCLOVIS
02/18/2026, 6:55 PMCLOVIS
02/18/2026, 6:57 PMassertThat(someComputationHere) {
check(subject != null)
check(subject.length > 3)
}bnorm
02/18/2026, 7:06 PMAssertion failed:
* String `Kodee` does not have length `1`.
* Condition failed: (subject?.length ?: 0) > 7
assertThat(name) {
|
Kodee
hasLength(1)
check(subject != null)
check((subject?.length ?: 0) > 7)
| |
5 false
}bnorm
02/18/2026, 7:07 PMcheck function. But maybe there could be a difference between a check (soft assertion) and a require that immediately fails.