-
Notifications
You must be signed in to change notification settings - Fork 626
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
Boxed primitives for value classes in structures #2738
Comments
Hello there, I tried to understand where could be the issue, but the plugin code is quite complex. I think the issue is that the value class is not handled as an inlined element. I suppose the issue is around here https://github.com/JetBrains/kotlin/blob/f9de685b2feebf5e7021b7e769ae5751cde7dc46/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt#L266 Do you have any idea on how to fix it ? |
Yes, I've re-checked the code and it is indeed is not generated. Likely you can change functions being called in the |
We also experienced this performance problem. It gets even worse if you use value classes around other value classes (such as an The idea that the library should unconditionally unwrap value classes is surprising, though. Is that only done if there is no custom serializer? What if the type is in another compilation unit and its serializer could change without recompilation? I was coming to ask for some kind of opt-in to the unwrapping behavior when I discovered this issue. For now we've migrated away from unsigned types and manually unwrap/re-wrap inside the carrier types. e.g., @Poko
@Serializable
public class Create private constructor(
@SerialName("id")
private val _id: Int,
@SerialName("tag")
private val _tag: Int,
) : Change {
override val id: Id get() = Id(_id)
public val tag: WidgetTag get() = WidgetTag(_tag)
public companion object {
public operator fun invoke(
id: Id,
tag: WidgetTag,
): Create = Create(id.value, tag.value)
}
} This still affects |
@JakeWharton custom serializer = on your own, so you are the owner of how to serialize, using directly
You missed the purpose of kotlinx-serialization plug-in when annotating with But still, it's interesting that you experienced big performance issues and found a "solution". Anyway, thanks @sandwwraith, that could be an interesting day to dive a bit in the serialization plugin, I'll try to play with formEncodeDecodePropertyCall or serializeAllProperties as you said. |
There's so much to unpack in your comment. Custom serializers do not mean you are "on your own". Switching to and/or from a custom serializer on the type definition from a separate compilation unit should absolutely change the behavior of callers without recompilation. This fact should immediately preclude the strategy of the plugin automatically unwrapping on its own. Moreover, the backing property is not necessarily public nor would its type necessarily be intrinsically representable in the serialization format. Removing the ability for a value class to dictate its own serialized form despite being required to be annotated as But sure, let's say we want to completely ignore this fact and split the behavior of the library for value classes. There are so many implications to this:
The behavior also completely breaks with the presumed forthcoming concept of multi-property value classes. You could possibly try to argue that the behavior should only be for The library absolutely needs the ability to serialize value classes, but there are multiple axes to the behavior which are not being adequately handled today. First, the unwrapping of a value class to directly serialize a single, public backing property should be something the enclosing type can choose to do on a per-property basis and it should only work on value classes with a single, public backing property without it needing to be marked as Second, value classes should be able to define their own serialized form using the normal Both of these behaviors would allow custom value class representation without the enclosing class to do any work. It will also allow multi-property value classes to seamlessly integrate into the system with the only new concept being that its default representation is an object with multiple key-value pairs rather than unwrapped to its single-property scalar value. Additionally, these two behaviors also retain the current behavior of the library making it a compatible migration, unlike the current proposed solution to this bug to skip the serializer. Finally, as a bonus, and the superior solution to this bug in my opinion: it would be nice if serialization was specialized to the primitive storage types to avoid boxing in the case of delegating to the value class's serializer (similar to how the stdlib specializes things like iterators for primitives). I think it's unclear if this is viable strategy long-term, but it definitely solves the case of user value classes wrapping At this point, this should probably be its own bug... |
Describe the bug
Given a serializable data class having a value class wrapping a primitive type, the field is serialized using
.encodeSerializableElement(serialDesc, 0, (SerializationStrategy)myData.$serializer.INSTANCE, myData.box-impl(self.value))
instead ofencodeInlineElement(serialDesc, 0).encodeInt(self.value)
, forcing primitive values to be boxed, and also doesn't follow the expected as said in the docs:To Reproduce
Check the bytecode generated from this code:
And here is the generated serializer (only the interesting part) retrieved using
Kotlin bytecode
IDEA tooling:Expected behavior
Should call
encodeInlineElement
and generate the following (not sure for the nullable value class' field):Environment
EDIT: shortened the description
The text was updated successfully, but these errors were encountered: