Kotlin implement a method from an interface that's already present in super class - kotlin

Let's say I have this interface:
interface Things {
fun size(): Int
}
And I want to subclass a List and implement this interface.
class Cars : ArrayList<String>, Things {}
I get a compilation error:
Inherited platform declarations clash: The following declarations have
the same JVM signature (size()I): fun (): Int defined in
Things fun size(): Int defined in Things
I can get around the compilation error by changing the size contract to a var implicit getter:
interface Things {
var size: Int
}
But then I get a long and complicated error that looks like a runtime error (IllegalStateException) but seems to happen when Kotlin is compiling :shrug:
I suspect I know why this is happening - ArrayList already has int size() and Kotlin now tries to add another of the same signature, but even if I'm right (10% chance?) it doesn't help much.
I'll also mention that it allows me to have get as part of the Things interface, which is satisfied by the list. I'm guessing the operator operator let's this be just different enough to slide?
So anyway, no IDE errors, but when I build I get this:
java.lang.IllegalStateException: Backend Internal error: Exception during code generation
Cause: Concrete fake override public open fun <set-size>(<set-?>: kotlin.Int): kotlin.Unit defined in org.blah.Stuff[PropertySetterDescriptorImpl#673f2280] should have exactly one concrete super-declaration: []
File being compiled at position: file:///blah/Stuff.kt
The root cause was thrown at: bridges.kt:122
at org.jetbrains.kotlin.codegen.CompilationErrorHandler.lambda$static$0(CompilationErrorHandler.java:24)
at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:76)
at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generatePackage(CodegenFactory.kt:96)
at org.jetbrains.kotlin.codegen.DefaultCodegenFactory.generateModule(CodegenFactory.kt:67)
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.doGenerateFiles(KotlinCodegenFacade.java:47)
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:39)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:476)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:164)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:166)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:56)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:84)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:42)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:104)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:349)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:105)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:237)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.access$compileIncrementally(IncrementalCompilerRunner.kt:37)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner$compile$2.invoke(IncrementalCompilerRunner.kt:79)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:91)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.execIncrementalCompiler(CompileServiceImpl.kt:579)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.access$execIncrementalCompiler(CompileServiceImpl.kt:102)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:455)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:102)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:1005)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:102)
at org.jetbrains.kotlin.daemon.common.DummyProfiler.withMeasure(PerfUtils.kt:138)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.checkedCompile(CompileServiceImpl.kt:1047)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.doCompile(CompileServiceImpl.kt:1004)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:454)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:346)
at sun.rmi.transport.Transport$1.run(Transport.java:200)
at sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Concrete fake override public open fun <set-size>(<set-?>: kotlin.Int): kotlin.Unit defined in org.blah.Stuff[PropertySetterDescriptorImpl#673f2280] should have exactly one concrete super-declaration: []
at org.jetbrains.kotlin.backend.common.bridges.BridgesKt.findConcreteSuperDeclaration(bridges.kt:122)
at org.jetbrains.kotlin.backend.common.bridges.BridgesKt.generateBridges(bridges.kt:59)
at org.jetbrains.kotlin.codegen.JvmBridgesImplKt.generateBridgesForFunctionDescriptorForJvm(JvmBridgesImpl.kt:92)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateBridges(FunctionCodegen.java:1041)
at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateBridges(ClassBodyCodegen.java:138)
at org.jetbrains.kotlin.codegen.ClassBodyCodegen.generateBody(ClassBodyCodegen.java:116)
at org.jetbrains.kotlin.codegen.MemberCodegen.generate(MemberCodegen.java:129)
at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:302)
at org.jetbrains.kotlin.codegen.MemberCodegen.genClassOrObject(MemberCodegen.java:286)
at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateClassesAndObjectsInFile(PackageCodegenImpl.java:118)
at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generateFile(PackageCodegenImpl.java:137)
at org.jetbrains.kotlin.codegen.PackageCodegenImpl.generate(PackageCodegenImpl.java:68)
... 44 more
> Task :app:buildInfoGeneratorDebug
Anything I'm missing. I've spent most of my life in Java and am late to the Kotlin party. Any workarounds, alternatives... or is this a known thing?
TYIA

The problem is because kotlin have extension val property for List: size.
The first error says that you have two different kotlin "things" (method and property) which in jvm are the same. And the second error is because you have val implementation in ArrayList and var field in interface so your class Cars needs to implement both setter and getter but implements only getter (from List val size).
The solution is simple: just change your interface property to val size: Int

Related

Byte Buddy: Creating class by implementing interface leads to NoClassFoundException

I am trying to create a class in a unit test that implements an Interface by using byte buddy
interface SomeInterface {}
class ByteBuddyTest {
#Test
fun byteBuddyTest(){
val instrumentation = ByteBuddyAgent.install()
val bb = ByteBuddy()
val loadedRestController = bb
.subclass(SomeInterface::class.java)
.make()
.load(Object::class.java.classLoader, ClassLoadingStrategy.Default.WRAPPER)
.loaded
}
}
Unfortunately I am getting a NoClassFoundException when trying to implement the Interface
org/example/SomeInterface
java.lang.NoClassDefFoundError: org/example/SomeInterface
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.access$300(ByteArrayClassLoader.java:56)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader$ClassDefinitionAction.run(ByteArrayClassLoader.java:686)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader$ClassDefinitionAction.run(ByteArrayClassLoader.java:638)
at java.security.AccessController.doPrivileged(Native Method)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.doPrivileged(ByteArrayClassLoader.java)
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:405)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
Am I missing something during agent initialization?
You are loading the class into a child loader of the boot loader by using
Object::class.java.classLoader
This class loader is of course unaware of SomeInterface. Instead, load the class into a child of:
SomeInterface::class.java.classLoader

Creating TypeDescryption of Array in ByteBuddy

I want to create classes like this in Byte-Buddy:
class A{
public B b;
}
class B{
public A[] a_array;
}
this is my code on how I imagine it could be done:
InstrumentedType arrayType = InstrumentedType.Default.of("[A",
TypeDescription.Generic.Builder.rawType(Object.class).build(),
Modifier.PUBLIC);
DynamicType.Unloaded B_made = new ByteBuddy()
.subclass(Object.class)
.name("B")
.defineField("a_array", arrayType, Modifier.PUBLIC)
.make();
DynamicType.Unloaded A_made = new ByteBuddy()
.subclass(Object.class)
.name("A")
.defineField("b", B_made.getTypeDescription(), Modifier.PUBLIC).make();
Class A = A_made.include(B_made).load(Test.class.getClassLoader()).getLoaded();
Class B = A.getField("b").getType();
System.out.println(B.getField("a_array"));
But this does not work even though Byte-Buddy will make the classes and load the them.
This code gives me this when evaluating B.getField("a_array")
Exception in thread "main" java.lang.NoClassDefFoundError: L[A;
at java.lang.Class.getDeclaredFields0(Native Method)
at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
at java.lang.Class.getField0(Class.java:2975)
at java.lang.Class.getField(Class.java:1701)
at Test.main(Test.java:88)
Caused by: java.lang.ClassNotFoundException: [A
at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:396)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
... 5 more
I was wondering how can I fix this?
is it even possible for me to create TypeDescription of [A from TypeDescription of A??
To construct an array type, use TypeDescription.Generic.Builder which allows for creating array representations. Do not use InstrumentedType.Default for creating arrays as they are intended to represent definable types.

ByteBuddy creating enums with constructors

Using ByteBuddy how can I create enum with constructors such as this one :
public enum EnumConstructorSample {
STATE1(10),
STATE2(15);
public int count;
EnumConstructorSample(int count){
this.count = count;
}
}
I tried this code and it gives me error.
Class enumClass = new ByteBuddy().makeEnumeration("STATE1", "STATE2")
.name("DynamicEnum")
.defineConstructor(Visibility.PACKAGE_PRIVATE)
.withParameters(int.class)
.intercept(FixedValue.value(1))
.make()
.load(EnumWithConstructor.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
System.out.println(enumClass.getDeclaredConstructors()[0]);
This is the Error and it is happening in enumClass.getDeclaredConstructors()
Exception in thread "main" java.lang.VerifyError: Constructor must call super() or this() before return
Exception Details:
Location:
DynamicEnum.<init>(I)V #2: return
Reason:
Error exists in the bytecode
Bytecode:
0x0000000: 0457 b1
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getDeclaredConstructors(Class.java:2020)
at EnumWithConstructor.main(EnumWithConstructor.java:19)
For constructors, it is required to invoke the super method within the constructor. For enumerations, you'd need to invoke the Enum(String, int) constructor. You can implement this using MethodCall.invoke(...).onSuper().
If you wanted to achieve this, I'd recommend you to subclass Enum manually since you'd otherwise define multiple constructors for the enum you are creating where Byte Buddy would invoke its own enum constructor and the fields would all have its default value.
Rather, implement the method and return the value based on its name. You can for example use a MethodDelegation and then use a #This Enum<?> val injection where you switch over the name to return the correct value as if it was stored in a field.

How can I make Kryo Serializer

I tried to solve the following problem by providing Kryo Serializer but it still doesn't work. It could not recognize the serializer of ModelCom. Also, any messages by print function don't show up.
I used Apache Flink 1.9.0 and Apache Jena 3.10.0
My code in Kotlin:
val serializer = object : Serializer<Model>(){
override fun write(kryo: Kryo, output: Output?, obj : Model?) {
print("write")
kryo.writeClassAndObject(output, obj)
}
override fun read(kryo: Kryo, input: Input?, type: Class<Model>?): Model {
print("read")
val m = kryo.readObject(input, Model::class.java)
return m
}
}
ExecutionContext.see.config.registerTypeWithKryoSerializer(ModelCom::class.java, serializer::class.java)
Error
Exception in thread "main" org.apache.flink.streaming.runtime.tasks.StreamTaskException: Cannot serialize operator object class org.apache.flink.streaming.api.operators.SimpleUdfStreamOperatorFactory.
at org.apache.flink.streaming.api.graph.StreamConfig.setStreamOperatorFactory(StreamConfig.java:222)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.setVertexConfig(StreamingJobGraphGenerator.java:460)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.createChain(StreamingJobGraphGenerator.java:272)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.createChain(StreamingJobGraphGenerator.java:243)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.createChain(StreamingJobGraphGenerator.java:243)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.setChaining(StreamingJobGraphGenerator.java:207)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.createJobGraph(StreamingJobGraphGenerator.java:159)
at org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator.createJobGraph(StreamingJobGraphGenerator.java:94)
at org.apache.flink.streaming.api.graph.StreamGraph.getJobGraph(StreamGraph.java:737)
at org.apache.flink.optimizer.plan.StreamingPlan.getJobGraph(StreamingPlan.java:40)
at org.apache.flink.streaming.api.environment.LocalStreamEnvironment.execute(LocalStreamEnvironment.java:86)
at org.apache.flink.streaming.api.environment.StreamExecutionEnvironment.execute(StreamExecutionEnvironment.java:1507)
at org.apache.flink.streaming.api.environment.StreamExecutionEnvironment.execute(StreamExecutionEnvironment.java:1489)
at core.EgressEngine.start(EgressEngine.kt:187)
at core.EgressEngineKt.main(EgressEngine.kt:45)
Caused by: java.io.NotSerializableException: org.apache.jena.rdf.model.impl.ModelCom
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)
at org.apache.flink.util.InstantiationUtil.serializeObject(InstantiationUtil.java:586)
at org.apache.flink.util.InstantiationUtil.writeObjectToConfig(InstantiationUtil.java:515)
at org.apache.flink.streaming.api.graph.StreamConfig.setStreamOperatorFactory(StreamConfig.java:219)
... 14 more
Jena models are not serializable, so this approach isn't going to work. What you could do instead would be to send around just enough serialized data so that each instance that needs a model can instantiate one.
See this thread from the jena-users list about how to resolve this for Spark; the underlying issues are the same for any JVM-based framework that distributes computation.

Mocking extensions from Continuation

I want to mock resume and resumeWithException from the standard library Continuation class. Both are extension functions.
Here's my JUnit setup function:
#MockK
private lateinit var mockContinuation: Continuation<Unit>
#Before
fun setup() {
MockKAnnotations.init(this)
mockkStatic("kotlin.coroutines.ContinuationKt")
every { mockContinuation.resume(any()) } just Runs
every { mockContinuation.resumeWithException(any()) } just Runs
}
However this does not work, throwing the following exception at the mocking of resumeWithException function:
io.mockk.MockKException: Failed matching mocking signature for
SignedCall(retValue=java.lang.Void#5b057c8c, isRetValueMock=false, retType=class java.lang.Void, self=Continuation(mockContinuation#1), method=resumeWith(Any), args=[null], invocationStr=Continuation(mockContinuation#1).resumeWith(null))
left matchers: [any()]
at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:59)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalEvery(API.kt:92)
at io.mockk.MockKKt.every(MockK.kt:104)
at com.blablabla.data.pair.TestConnectSDKDeviceListener.setup(TestConnectSDKDeviceListener.kt:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
And this is the code for resumeWithException which is very similar to resume:
/**
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
* last suspension point.
*/
#SinceKotlin("1.3")
#InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
Here is a small investigation. If you are just looking for solution for inline functions and inline classes, scroll to solution section.
Long explanation:
These are tricky consequences of modern kotlin features. Let's decompile this code to java with help of kotlin plugin.
This mockContinuation.resumeWithException(any()) becomes something like this (shortened and beautified version)
Matcher matcher = (new ConstantMatcher(true));
Throwable anyThrowable = (Throwable)getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(Throwable.class));
Object result = kotlin.Result.constructor-impl(ResultKt.createFailure(anyThrowable));
mockContinuation.resumeWith(result);
As you can see a few things happened. First, there is no call to resumeWithException anymore. Because it is an inline function, it was inlined by the compiler, so now it's a resumeWith call. Second, matcher returned by any() was wrapped with a mysterious call
kotlin.Result.constructor-impl(ResultKt.createFailure(anyThrowable)), and it's not an argument of a function, called on the mock. That's why mockk can't match the signature and the matcher.
Obviously we can try to fix it by mocking resumeWith function itself:
every { mockContinuation.resumeWith(any()) } just Runs
And it does not work either! Here is the decompiled code:
Matcher matcher = (new ConstantMatcher(true));
Object anyValue = getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(kotlin.Result.class));
mockContinuation.resumeWith(((kotlin.Result)anyValue).unbox-impl());
And here is another mysterious call unbox-impl(). Let's look at Result class definition
public inline class Result<out T> #PublishedApi internal constructor(
#PublishedApi
internal val value: Any?
)
It's an inline class! And ubox-impl() is a compiler-generated function like this:
public final Object unbox-impl() {
return this.value;
}
Basically, the compiler inlines Result object, by replacing it with it's value.
So again, instead of calling resumeWith(any()) in the end we call resumeWith(any().value) and mocking library is confused.
So how to mock it? Remember, mockContinuation.resume(any()) worked for some reason, even though resume is just another inline function
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
Decompiling mockContinuation.resume(any()) gives us
Object anyValue = getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(Unit.class));
Object result = kotlin.Result.constructor-impl(anyValue);
mockContinuation.resumeWith(result);
As we can see, it was inlined indeed and resumeWith was called with a result object, not with anyValue, which is our matcher. But, let's take a look at this mysterious kotlin.Result.constructor-impl:
public static Object constructor-impl(Object value) {
return value;
}
So it actually does not wrap the value, juts return it! That's why it actually works and gives us a solution how to mock resumeWith:
every { mockContinuation.resumeWith(Result.success(any())) } just Runs
Yes, we are wrapping our matcher into Result, which as we saw, gets inlined. But what if we want to distinguish between Result.success() and Result.failure()? We still can't mock mockContinuation.resumeWith(Result.failure(any())), because failure() call wraps the argument into something else (check the source code or decompiled code above).
So I can think about something like that:
every { mockContinuation.resumeWith(Result.success(any())) } answers {
val result = arg<Any>(0)
if (result is Unit) {
println("success")
} else {
println("fail")
}
}
The result value is instance of either our type (Unit in this case) or Result.Failure type, which is an internal type.
Solution:
Mocking inline function is generally impossible, because they are inlined at compile time and mocking runs later at runtime. Mock functions, which are called inside inlined function instead.
When dealing with inline classes, match inlined value, not the wrapper. So instead of mock.testFunction(any<InlinedClass>()) use mock.testFunction(InlinedClass(any<Value>())).
Here is a feature request for mockk to support inline classes, which is currently in Opened state.