-
Notifications
You must be signed in to change notification settings - Fork 636
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
Add polymorphic default serializers (as opposed to deserializers) #1686
Merged
sandwwraith
merged 4 commits into
Kotlin:dev
from
Earthcomputer:polymorphic-serializers
Nov 22, 2021
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need type parameter? I think
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
is more appropriate here.Imagine the situation: we have
Base; A: Base(); B:Base()
. If we registerdefaultSerializer<A> { return A.serializer() }
which is allowed by this signature, when we try to serialize B, we'd get ClassCastException, because our lambda can accept onlyA
type. Therefore, default serializer should be able to select between all subclasses of base, i.e. acceptvalue: Base
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to use
@UnsafeVariance
for that, but sure.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the problem is more complicated than it looks at the first sight.
@UnsafeVariance
is needed here becausePolymorphicModuleBuilder
has IN variance:<in Base : Any>
. Why does it need it? The answer lies in this sample: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#registering-multiple-superclassesIf you have a vast hierarchy (in the sample, it is
Any
andProject
, but instead ofAny
, it can be different multiple super-interfaces), it is logical to register subclasses of the lowest common interface for polymorphic serialization in all bases (registerProjectSubclasses
method in the sample). Naturally, it should acceptPolymorphicModuleBuilder<Project>
, because any of theProject
subclasses can be serialized and deserialized in bigger scopes (sayAny
or other super-interface). It is also possible to return some deserializer as default — if it always returns a subclass of Project, it is possible to assign it into any variable up toAny
.However, it is not the case for the default serializer: since it accepts an instance, if we register it using some lambda that accepts a
Project
, we can't accept arbitraryAny
.SerializationStrategy
would also break: we know how to serializeProject
, but not our super-interface. Both problems will lead to ClassCastException. If we add this function with@UnsafeVariance
, the following code is error-prone:There are multiple ways to solve this problem:
@UnsafeVariance
and document that this function may cause problems, probably annotate it with special opt-in annotation. Not a clean solution, since most people don't read the documentation. It probably would be more helpful if we can suppress CCE and throw SerializationException instead about smth like 'serializer not found, default serializer is not applicable', but I'm not sure if this can be done accurately — need further investigation. In any case, stacktrace of the exception won't pinpoint the actual line with the problem.in Base: Any
in polymorphic module builder. It would solve the problem, because Kotlin compiler is smart enough. We still can declare the helper function asfun PolymorphicModuleBuilder<in Project>.registerProjectSubclasses()
(use-site variance), but compiler would infer that actualBase
indefaultSerializer
isAny
and thus would require to accept Any and returnSerializationStrategy<Any>
. This is a good solution, but unfortunately removing variance is a source-incompatible breaking change we can't afford to do. (In the sample,polymorphic(Any::class) { registerProjectSubclasses() }
would not compile with 'Unresolved reference' )defaultSerializer
with fixed types, e.g.defaultSerializer(defaultSerializerProvider: (value: Any) -> SerializationStrategy<Any?>?)
. Possible and type-safe, but very inconvenient to use.defaultSerializer
at all. Note thatpolymorphicDefaultSerializer
on the regularSerializersModuleBuilder
is still a thing as it doesn't have such problems. By doing this, we're causing minor inconvenience — people are forced to writepolymorphicDefaultSerializer
outside ofpolymorphic {}
scope, but we're saving them from accidental exceptions that are hard to grasp.I think that option 4 is the way to go, despite all inconveniences. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, sorry I read your comment and then got sidetracked and forgot about it. I agree, 4 is the best solution