vdshb
09/04/2023, 5:43 PMkmpTestPromise
function to make tests multiplatform. This function return Promise on JS-target (to make test framework process it) and process promise inside and return null on other platforms (which has multiple threads). Later it turned out that tests that return non-Unit results are not supported on all targeters except JS. That's when I decided that this is a good case for my first compiler plugin....
The question.
I've written a compiler plugin, which goal is to make test function return Unit on JVM and Native and keep it as is on JS.
For example it must convert this function:
@Test
fun should_test_async(): Any? {
println("test")
return kmpTestPromise { resolve, reject -> // (1)
resolve(null)
}
}
into this function:
@Test
fun should_test_async(): Unit {
println("test")
kmpTestPromise { resolve, reject ->
resolve(null)
}
return
}
on native and jvm.
The signature of kmpTestPromise:
expect fun kmpTestPromise(
block: (resolve: (Any?) -> Unit, reject: (Throwable) -> Unit) -> Any?
): Any?
The transformation itself takes place in IrElementTransformerVoidWithContext::visitReturn
implementation:
private class ReturnsToUnitTransformer(
private val function: IrFunction,
private val pluginContext: IrPluginContext
): IrElementTransformerVoidWithContext() {
override fun visitReturn(returnExpression: IrReturn): IrExpression {
// ignore irrelevant returns (return@let, return@launch, etc.)
if (returnExpression.returnTargetSymbol != function.symbol) return returnExpression
return DeclarationIrBuilder(
pluginContext,
function.symbol,
returnExpression.startOffset,
returnExpression.endOffset
).irBlock {
+returnExpression.value // (2) It seems this operation is not fully correct.
+DeclarationIrBuilder(
pluginContext,
function.symbol,
returnExpression.startOffset,
returnExpression.endOffset
).irReturnUnit()
}
}
}
It applyed inside IrElementTransformerVoidWithContext
(I've tried IrElementVisitorVoid
as well. Nothing changed).
Compiler plugin works as expected on native target, but generates broken code on JVM.
On decompiled code I can see, that expression (1)
is changed during (2)
operation. It's param become null instead of original lambda:
@Test
public final void should_test_async() {
System.out.println("test");
KmKt.kmpTestPromise((Function2)null.INSTANCE); // null is not expected to be here.
}
The test runtime exception is:
java.lang.NoSuchMethodError:
'java.lang.Object com.exl.KmKt.kmpTestPromise(kotlin.jvm.functions.Function2)'
Am I missing something as a newbie in compiler plugin development? Or maybe it's a bug?
Different results on Native (works fine) and JVM(generates broken code) confuse me.
I use kotlin 1.9.10. I've tried to use plugin on K2 - no difference.
Full reproducer: https://github.com/vdshb/kcp-reproducershikasd
09/04/2023, 9:22 PMvdshb
09/05/2023, 5:57 AMshikasd
09/05/2023, 8:52 PM