-
Notifications
You must be signed in to change notification settings - Fork 758
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
Remove kotlin-reflect, replace with kotlinx-metadata-jvm #1076
Conversation
It's used there
for (property in allPropertiesSequence.distinctBy { it.name }) { | ||
val propertyField = property.fieldSignature?.let { signature -> | ||
val signatureString = signature.asString() | ||
rawType.declaredFields.find { it.jvmFieldSignature == signatureString } |
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 think I need to do an allFields() style function for this as well to be safe.
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.
return classMetadata.toKmClass() | ||
} | ||
|
||
private fun Class<*>.allMethods(): Sequence<Method> { |
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 wonder if this is worth sticking a lazily-filled cache in front of
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.
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.
This all sounds good to me. I haven't used these libraries extensively so I'm not sure I have the expertise to thoroughly evaluate the implementation, but don't see any red flags on review.
get() { | ||
return buildString { | ||
append('(') | ||
parameterTypes.joinTo(this, separator = "", transform = { it.descriptor }) |
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.
Minor, but you can use one-liners for this
internal val Constructor.descriptor: String
get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }
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.
Good call! 6cff563
kotlin/reflect/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt
Outdated
Show resolved
Hide resolved
return when { | ||
this === other -> true | ||
this != null && other != null -> { | ||
abbreviatedType == other.abbreviatedType |
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.
Note that this will imply that types which are represented with the same runtime type are different, if they have a different abbreviation. Not sure if you'd like this, but if you would, then never mind:
typealias ListOfString = List<String>
typealias MyList<T> = List<T>
KmType of ListOfString
isEqualTo KmType of MyList<String>
-> false
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.
Hmm, that's a good point. Since we're just doing this for checking equality of the parameter and property. I guess since it's a legal assignment we can drop the alias check. Done in 36f945b
variance == other.variance | ||
&& type isEqualTo other.type | ||
} | ||
this == null && other == null -> true |
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 suppose this is already checked by this === other -> true
?
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.
Hmm, good point! I added the this === other check later 🙃 d3e6e36
val annotations: List<Annotation> | ||
) { | ||
val name get() = km.name | ||
val isOptional get() = Flag.ValueParameter.DECLARES_DEFAULT_VALUE(km.flags) |
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.
Note that semantics of "is optional" are bit different from "declares default value". Not sure how important it is for your use case:
interface A {
fun foo(x: String = "...")
}
interface B : A {
override fun foo(x: String)
}
x
in B.foo
is optional, although it does not declare default value.
KParameter.isOptional
implements the "is optional" semantics.
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.
Interesting, that's a good point. I think we can stick with just our default check since this is only ever checked on constructor parameters. Updated the naming of this though to reflect the default value semantic though! 164bd08
Co-Authored-By: Alexander Udalov <udalov@users.noreply.github.com>
Optimizes for happy path
Updated with modernized proguard rules for this approach that also leverage R8's features where appropriate (based off of this PR for kotlin-reflect rules JetBrains/kotlin#2893) @swankjesse want to get your sign-off before merging, especially with the platform types behavior change. |
# Keep names of Kotlin classes but allow shrinking if unused | ||
-keepnames @kotlin.Metadata class * | ||
|
||
# Keep fields, constructors, and methods in Kotlin classes. | ||
-keepclassmembers @kotlin.Metadata class * { | ||
<fields>; | ||
<init>(...); | ||
<methods>; | ||
} |
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.
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.
For some reason I thought that the -keep class kotlin.Metadata
rule would also keep all members. If that's not true, then your proposal seems reasonable.
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.
This is slightly different, and rather it applies this rule only to classes annotated with Metadata, not the metadata annotation class itself
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.
thinking on it more, I think it's probably best to omit these for now and have the guidance be that users apply keep rules as needed for their exact cases. Same with kotlin-reflect - the rules in place now make sure its machinery works, and users can just make sure they handle appropriate rules for classes that they plan to reflect on
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.
Cleaned up in ad47a56
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.
Oops, I should have paid attention to github alarms more often and carefully. Sorry for the late game.
I thought that the -keep class kotlin.Metadata rule would also keep all members.
That's not true. The rule doesn't specify members, and thus all members are still eligible for shrinking and minification. Since members, such as k
, d1
, etc., are read all together (via metadata library), the correct rule is -keep class kotlin.Metadata { *; }
.
But, as @ZacSweers already mentioned, rules here point to classes with metadata annotation, not the metadata definition itself.
Regarding having similar rules in kotlin-reflect, I would say no, because it's still up to users to add such rules if some classes (w/ or w/o metadata annotation) have reflective uses. Glad to see that you ended up with the same conclusion.
Just a note for posterity - @swankjesse requested this not be merged until he can review as well |
Will reopen a new PR, I can't push to this branch anymore since I stepped back from the project 😬 |
Continued here #1183 |
Kotlin-reflect has been a long-standing thorn for
KotlinJsonAdapter
for a number of reasons:This PR replaces kotlin-reflect entirely with kotlinx-metadata-reflect, the low-level metadata library we use in code gen (via kotlinpoet-metadata). It's also what the
kotlinx.reflect.lite
project is in the process of being migrated to, and used in a number of other major toolchains now (Dagger, R8, etc).Effectively, this skips past
kotlinx.reflect.lite
and replaceskotlin-reflect
with something closer to an "ultralight" setup, set up specific to Moshi's needs. It's a significant change to the adapter, but the core logic remains the same with one notable exception (documented at the end).This supersedes #307, but effectively resolves #307 in spirit.
Stats
Quick androidx-benchmark on my serialization benchmarks project shows this is ~21.5% faster for buffer reads as well! ZacSweers/json-serialization-benchmarking#10
Two open questions
What are the appropriate proguard rules for this?