Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using @Serializable(with = …) crashes the compiler #1496

Closed
Ribesg opened this issue May 18, 2021 · 4 comments
Closed

Using @Serializable(with = …) crashes the compiler #1496

Ribesg opened this issue May 18, 2021 · 4 comments

Comments

@Ribesg
Copy link

Ribesg commented May 18, 2021

Describe the bug
Maybe I was doing something wrong, but something I was doing before Kotlin 1.5.0 / serialization 1.2.0+ no longer compiles.
See the reproducer.

To Reproduce
I used to do this, and it no longer compiles

@Serializable(with = X.Companion::class)
data class X(val a: String) {

    @Serializer(forClass = X::class)
    companion object : KSerializer<X> {

        override val descriptor = PrimitiveSerialDescriptor("X", PrimitiveKind.STRING)

        override fun serialize(encoder: Encoder, value: X) {
            encoder.encodeString(value.a)
        }
    }

}

Trace:

e: java.lang.NullPointerException
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator$generateLoad$1.invoke(SerializerIrGenerator.kt:521)
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator$generateLoad$1.invoke(SerializerIrGenerator.kt:382)
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.IrBuilderExtension$DefaultImpls.contributeFunction(GeneratorHelpers.kt:68)
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator.contributeFunction(SerializerIrGenerator.kt:46)
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator.generateLoad(SerializerIrGenerator.kt:382)
	at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generateLoadIfNeeded(SerializerCodegen.kt:116)
	at org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen.generate(SerializerCodegen.kt:38)
	at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator$Companion.generate(SerializerIrGenerator.kt:626)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializerClassLowering.lower(SerializationLoweringExtension.kt:53)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:37)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitClass(IrElementVisitorVoid.kt:44)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:56)
	at org.jetbrains.kotlin.ir.declarations.IrClass.acceptChildren(IrClass.kt:61)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoidKt.acceptChildrenVoid(IrElementVisitorVoid.kt:275)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:38)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitClass(IrElementVisitorVoid.kt:44)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitClass(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlin.ir.declarations.IrClass.accept(IrClass.kt:56)
	at org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl.acceptChildren(IrFileImpl.kt:66)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoidKt.acceptChildrenVoid(IrElementVisitorVoid.kt:275)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitElement(SerializationLoweringExtension.kt:33)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitPackageFragment(IrElementVisitorVoid.kt:30)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitPackageFragment(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitFile(IrElementVisitorVoid.kt:37)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitFile(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid$DefaultImpls.visitFile(IrElementVisitorVoid.kt:38)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitFile(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt$runOnFileInOrder$1.visitFile(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl.accept(IrFileImpl.kt:63)
	at org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoidKt.acceptVoid(IrElementVisitorVoid.kt:271)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtensionKt.runOnFileInOrder(SerializationLoweringExtension.kt:31)
	at org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationLoweringExtension.generate(SerializationLoweringExtension.kt:71)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory$convertToIr$1.invoke(JvmIrCodegenFactory.kt:120)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory$convertToIr$1.invoke(JvmIrCodegenFactory.kt:116)
	at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:91)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:140)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr$default(JvmIrCodegenFactory.kt:66)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.generateModule(JvmIrCodegenFactory.kt:61)
	at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:35)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.generate(KotlinToJVMBytecodeCompiler.kt:592)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:212)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:155)
	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:303)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl$rebuild(IncrementalCompilerRunner.kt:99)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:124)
	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 java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
	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(Native Method)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

Expected behavior
This worked in previous version. Now, I think that in this case, because there is @Serializer(forClass = X::class), maybe the with was unneeded and I can just remove it to work around the issue immediately.

Removing just (with = X.Companion::class) in the previous snippet seems to compile and work just fine.

Environment

  • Kotlin version: 1.5.0
  • Library version: 1.2.1
  • Kotlin platforms: Multiplatform
  • Gradle version: 7.0.2
@shanshin
Copy link
Contributor

This compilation error for external serializers fixed in 1.5.20. Removing (with = X.Companion::class) is a workaround for 1.5.0.
Also you may use custom serializer - it's necessary to define deserialize function, e.g.

@Serializable(with = X.Companion::class)
data class X(val a: String) {

    companion object : KSerializer<X> {
        override val descriptor = PrimitiveSerialDescriptor("X", PrimitiveKind.STRING)

        override fun serialize(encoder: Encoder, value: X) {
            encoder.encodeString(value.a)
        }

        override fun deserialize(decoder: Decoder): X {
            return X(decoder.decodeString())
        }
    }
}

For this example encoding and decoding works consistent:

    val origin = X("Hello world!")
    Json.decodeFromString<X>(Json.encodeToString(origin)) == origin // true

@Ribesg
Copy link
Author

Ribesg commented May 18, 2021

This is kinda unrelated to the issue but…

In my case I didn't need a deserialize function different than the default implementation, my real serialize function actually mimics the default implementation without serializing nulls, so the default generated deserialize function actually matches.
Obviously this doesn't make any sense in my minimized reproducer as the default deserialize wouldn't match, but I didn't really care about the Deserializer object content itself as that was not the issue, I just wanted it minimal :)

Also, I can't use encodeDefaults because:

  • Defaults and nulls are different things, I'm not sure why this is not taken into account, why there is no encodeNulls
  • I do need to encode defaults most of the time, but not for this specific model, while always using the same Json instance, so a global option like that can't work. An annotation like @OmitNulls (or something) mutating the serializer of a specific field would be nice here I guess

So I think what I did work for my use case, or is there some better way to achieve this?

@shanshin
Copy link
Contributor

In this case for 1.5.0 you may use workaround with removing (with = X.Companion::class) and keep the descriptor property up-to-date, corresponding to the class structure.

About omit nulls see #195.

@shanshin
Copy link
Contributor

Fixed in 1.5.20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants