realm / realm-kotlin

Kotlin Multiplatform and Android SDK for the Realm Mobile Database: Build Better Apps Faster.
Apache License 2.0
968 stars 61 forks source link

RealmObject interface/class extension with generics crashes #999

Open Burtan opened 2 years ago

Burtan commented 2 years ago

Hi,

when using generics on an interface/class that extends RealmObject you get an error. Not sure if it is kotlin or realm though.

class BaseEntity<T> : RealmObject

org.jetbrains.kotlin.backend.common.CompilationException: Back-end: Please report this problem https://kotl.in/issue
...BaseEntity.kt:-1:-1
Details: Internal error in file lowering: java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
    at kotlin.collections.EmptyList.get(Collections.kt:36)
    at kotlin.collections.EmptyList.get(Collections.kt:24)
    at org.jetbrains.kotlin.backend.common.ir.IrUtilsKt.remapTypeParameters(IrUtils.kt:329)
    at org.jetbrains.kotlin.backend.common.ir.IrUtilsKt.remapTypeParameters(IrUtils.kt:342)
    at org.jetbrains.kotlin.backend.common.ir.IrUtilsKt.copyTo$default(IrUtils.kt:121)
    at io.realm.kotlin.compiler.RealmModelSyntheticPropertiesGeneration.addVariableProperty(RealmModelSyntheticPropertiesGeneration.kt:613)
    at io.realm.kotlin.compiler.RealmModelSyntheticPropertiesGeneration.addRealmObjectInternalProperties(RealmModelSyntheticPropertiesGeneration.kt:171)
    at io.realm.kotlin.compiler.RealmModelLowering.lower(RealmModelLoweringExtension.kt:89)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitClass(Lower.kt:101)
    at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitClass(IrElementVisitorVoid.kt:44)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitClass(Lower.kt:92)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitClass(Lower.kt:92)
    at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:46)
    at org.jetbrains.kotlin.ir.declarations.IrFile.acceptChildren(IrFile.kt:28)
    at org.jetbrains.kotlin.ir.visitors.IrVisitorsKt.acceptChildrenVoid(IrVisitors.kt:15)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitElement(Lower.kt:96)
    at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitPackageFragment(IrElementVisitorVoid.kt:30)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitPackageFragment(Lower.kt:92)
    at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitFile(IrElementVisitorVoid.kt:37)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitFile(Lower.kt:92)
    at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitFile(IrElementVisitorVoid.kt:38)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitFile(Lower.kt:92)
    at org.jetbrains.kotlin.backend.common.ClassLoweringVisitor.visitFile(Lower.kt:92)
    at org.jetbrains.kotlin.ir.declarations.IrFile.accept(IrFile.kt:22)
    at org.jetbrains.kotlin.ir.visitors.IrVisitorsKt.acceptVoid(IrVisitors.kt:11)
    at org.jetbrains.kotlin.backend.common.LowerKt.runOnFilePostfix(Lower.kt:89)
    at org.jetbrains.kotlin.backend.common.ClassLoweringPass$DefaultImpls.lower(Lower.kt:44)
    at io.realm.kotlin.compiler.RealmModelLowering.lower(RealmModelLoweringExtension.kt:47)
    at org.jetbrains.kotlin.backend.common.LowerKt.lower(Lower.kt:75)
    at io.realm.kotlin.compiler.RealmModelLoweringExtension.generate(RealmModelLoweringExtension.kt:43)
    at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr$lambda-1(JvmIrCodegenFactory.kt:162)
    at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:99)
    at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:194)
    at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:53)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.convertToIr(KotlinToJVMBytecodeCompiler.kt:232)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:115)
    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:60)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:157)
    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:94)
    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:43)
    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:477)
    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:127)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:366)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally$default(IncrementalCompilerRunner.kt:311)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:175)
    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:75)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:625)
    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:101)
    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1739)
    at jdk.internal.reflect.GeneratedMethodAccessor101.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
edualonso commented 2 years ago

Hi @Burtan. We will try to devise a fix for this problem since the code you wrote is syntactically correct and the error appears to be stemming from our compiler plugin.

Though I have an observation about the class definition you wrote. I assume you want to create an inheritance hierarchy by writing, say

open class NewBaseEntity<T> : RealmObject // I added the `open` modifier myself
class Foo : NewBaseEntity<String>()

While the class declaration you wrote and the one above are syntactically correct, inheritance/polymorphism is not supported by Realm yet and therefore you will not be able to use this in your schema - see https://github.com/realm/realm-java/issues/761 for more info. This constraint from our SDK would render your problem moot (if this is in fact what you are trying to do) as you would not be able to use said hierarchies in Realm.

Burtan commented 2 years ago

I think my case is a little special. I'm using multiple interfaces on my entity classes

class A: RealmObject, Bable<T>, Cable<T>, Dable<T>

To use generics on a combination of these interfaces I need a common interface that I don't inherit from.

MetaInterface<T>: RealmObject, Bable<T>, Cable<T>

fun doSomething<A: MetaInterface<T>>()

edualonso commented 2 years ago

Hi @Burtan.

Even though you are not using MetaInterface actively for inheritance purposes, our compiler plugin visits all model definitions implementing RealmObject and their fields to generate the code that allows us to create your database schema when you open a realm. This will ultimately fail as models implementing RealmObject need to have a zero-argument constructor for us to be able to instantiate them as managed Realm objects. Since interfaces do not have such a constructor we reject them as invalid - we do not support inline instantiation as we would not be able to handle compile time-defined properties in the schema. These constraints are established by Realm Core because they do not support polymorphic inheritance so we cannot lift this requirement yet.

Additionally:

// currently not working, requires a bug fix
class MyClass<T>: RealmObject, Bable<T>, Cable<T>, Dable<T> {
  @io.realm.kotlin.types.annotations.Ignore
  var myGenericField: T? = null // MUST BE IGNORED as this is a dynamic type and we cannot generate appropriate schema creation instructions in compile time
}

should be allowed as long as your Xable<T> interfaces do not inherit from RealmObject and you ignore any generic fields - see comment in the snippet.

Something along these lines should also be allowed, again, as long as you don't make your interface inherit from RealmObject:

interface MyInterface<T> {
  var genericField: T?
}

// currently not working, requires a bug fix
class MyGenericClass<T> : RealmObject, MyInterface<T> {
  @io.realm.kotlin.types.annotations.Ignore
  override var genericField: T? = null // MUST be ignored, same reason as above
}

class MyStringClass : RealmObject, MyInterface<String> {
  override var genericField: String? = null // OK
}

We will try to devise a bug fix for class MyClass<T>: RealmObject.

Burtan commented 2 years ago

Thanks for the effort and extensive explanation already!