realm / realm-kotlin

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

Model class constructor not supported by Compiler Plugin #184

Closed cmelchior closed 2 years ago

cmelchior commented 3 years ago

The following model class is not currently supported by our Compiler Plugin. If there are restrictions on constructors we should attempt to lift them or document them.

class Expression(expression: String) : RealmObject {
    var expressionString: String = expression
}
e: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering

File being compiled: /Users/realm/workspace/realm_realm-kotlin_PR-174/examples/kmm-sample/shared/src/commonMain/kotlin/io/realm/example/kmmsample/Expression.kt

The root cause java.lang.AssertionError was thrown at: org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpressionKt.throwNoSuchArgumentSlotException(IrMemberAccessExpression.kt:66)

    at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:239)

    at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:235)

    at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(PhaseBuilders.kt:124)

    at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(PhaseBuilders.kt:112)

    at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)

    at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:30)

    at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)

    at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:41)

    at org.jetbrains.kotlin.backend.jvm.JvmLower.lower(JvmLower.kt:407)

    at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.doGenerateFilesInternal$backend_jvm(JvmIrCodegenFactory.kt:147)

    at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.generateModule(JvmIrCodegenFactory.kt:120)

    at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)

    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:595)

    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:211)

    at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:154)

    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:169)

    at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)

    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:88)

    at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:44)

    at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:98)

    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:386)

    at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:110)

    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileIncrementally(IncrementalCompilerRunner.kt:286)

    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl$rebuild(IncrementalCompilerRunner.kt:99)

    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:114)

    at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:74)

    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:607)

    at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:96)

    at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1659)

    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:357)

    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(AccessController.java:770)

    at sun.rmi.transport.Transport.serviceCall(Transport.java:196)

    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)

    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)

    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)

    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$11/000000000000000000.run(Unknown Source)

    at java.security.AccessController.doPrivileged(AccessController.java:704)

    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

    at java.lang.Thread.run(Thread.java:823)

Caused by: java.lang.AssertionError: No such value argument slot in IrConstructorCallImpl: 0 (total=0).

Symbol: public io.realm.example.kmmsample/Expression.<init>|1280618353163213788[0]

    at org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpressionKt.throwNoSuchArgumentSlotException(IrMemberAccessExpression.kt:66)

    at org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression.getValueArgument(IrFunctionAccessExpression.kt:24)

    at org.jetbrains.kotlin.ir.util.IrUtilsKt.usesDefaultArguments(IrUtils.kt:177)

    at org.jetbrains.kotlin.backend.jvm.lower.SyntheticAccessorLowering.visitFunctionAccess(SyntheticAccessorLowering.kt:76)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitConstructorCall(IrElementTransformerVoid.kt:201)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitConstructorCall(IrElementTransformerVoid.kt:202)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitConstructorCall(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl.accept(IrConstructorCallImpl.kt:28)

    at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)

    at org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl.transformChildren(IrReturnImpl.kt:41)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitExpression(IrElementTransformerVoid.kt:131)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitReturn(IrElementTransformerVoid.kt:292)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitReturn(IrElementTransformerVoid.kt:293)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitReturn(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl.accept(IrReturnImpl.kt:34)

    at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:33)

    at org.jetbrains.kotlin.ir.expressions.IrExpression.transform(IrExpression.kt:26)

    at org.jetbrains.kotlin.ir.expressions.IrBlockBody.transformChildren(IrBody.kt:62)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitBody(IrElementTransformerVoid.kt:108)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitBlockBody(IrElementTransformerVoid.kt:117)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitBlockBody(IrElementTransformerVoid.kt:118)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitBlockBody(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.expressions.IrBlockBody.accept(IrBody.kt:54)

    at org.jetbrains.kotlin.ir.expressions.IrBody.transform(IrBody.kt:27)

    at org.jetbrains.kotlin.ir.declarations.IrFunction.transformChildren(IrFunction.kt:71)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitFunction(IrElementTransformerVoid.kt:69)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitFunctionNew(IrElementTransformerVoidWithContext.kt:115)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitFunction(IrElementTransformerVoidWithContext.kt:68)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitSimpleFunction(IrElementTransformerVoid.kt:72)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitSimpleFunction(IrElementTransformerVoid.kt:73)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitSimpleFunction(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.declarations.IrSimpleFunction.accept(IrSimpleFunction.kt:28)

    at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)

    at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)

    at org.jetbrains.kotlin.ir.util.TransformKt.transformInPlace(transform.kt:35)

    at org.jetbrains.kotlin.ir.declarations.IrClass.transformChildren(IrClass.kt:66)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:66)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClassNew(IrElementTransformerVoidWithContext.kt:111)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClass(IrElementTransformerVoidWithContext.kt:47)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:67)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:55)

    at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)

    at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)

    at org.jetbrains.kotlin.ir.util.TransformKt.transformInPlace(transform.kt:35)

    at org.jetbrains.kotlin.ir.declarations.IrClass.transformChildren(IrClass.kt:66)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitDeclaration(IrElementTransformerVoid.kt:57)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:66)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClassNew(IrElementTransformerVoidWithContext.kt:111)

    at org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext.visitClass(IrElementTransformerVoidWithContext.kt:47)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:67)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitClass(IrElementTransformerVoid.kt:24)

    at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:55)

    at org.jetbrains.kotlin.ir.IrElement$DefaultImpls.transform(IrElement.kt:32)

    at org.jetbrains.kotlin.ir.IrElementBase.transform(IrElementBase.kt:19)

    at org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl.transformChildren(IrFileImpl.kt:71)

    at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoidKt.transformChildrenVoid(IrElementTransformerVoid.kt:330)

    at org.jetbrains.kotlin.backend.jvm.lower.SyntheticAccessorLowering.lower(SyntheticAccessorLowering.kt:59)

    at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:155)

    at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:151)

    at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:94)

    at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(PhaseBuilders.kt:121)
jakobkmar commented 2 years ago

Would be great if this gets fixed, because doing

val person = Person().apply {
    name = "Carlo"
    dog = Dog().apply { name = "Fido"; age = 16 }
}

works, but it is easy to forget a property there without the compiler warning you, unlike with constructors.

rorbech commented 2 years ago

Hi @jakobkmar. Just a quick thought. I think you should be able to get away with defining a secondary constructor like

class Dog() : RealmObject {

    var name: String? = null
    var age: Int? = null

    constructor(name: String, age: Int) : this() {
        this.name = name
        this.age = age
    }
}

This still enables you to forget calling the right constructor, but at least you will not miss single arguments and is way more compact. We don't have explicit unit test covering this, so might be that there can be corner cases that won't work, but I can not immediately think of why it shouldn't.

jakobkmar commented 2 years ago

Thank you, this is a good workaround for the time being!

nhachicha commented 2 years ago

@cmelchior This is tricky to support since the compiler plugin provides the mediator with the ability to instantiate classes dynamically (without using reflection). The IR lowering references the first zero arg (primary) constructor, if the user doesn't define one it will be difficult to call the non-zero arg ctor with sensible default values (for example avoid generating default object with the same PK value)

cmelchior commented 2 years ago

Yes, but last I checked, I think we only support the no-arg constructor as the primary one...I suspect we could also support it as a secondary one, like this:

class Expression(expression: String) : RealmObject {
    constructor(): this("")
    var expressionString: String = expression
}
nhachicha commented 2 years ago

Yes we can support models like

class Expression(expression: String) : RealmObject {
    constructor(): this("")
    var expressionString: String = expression
}

but that's different from the initial issue use case

jakobkmar commented 2 years ago

Does this mean

class ConstructorTest(var firstName: String, var lastName: String, var age: Int) : RealmObject

is now supported as well?

nhachicha commented 2 years ago

@jakobkmar no, you still need to provide a default zero arg constructor like explained in https://github.com/realm/realm-kotlin/issues/184#issuecomment-1031626034