Skip to content

Commit

Permalink
Parcelize: Handle class hierarchies of Parcelers (KT-46567)
Browse files Browse the repository at this point in the history
(cherry picked from commit bf7db84)
  • Loading branch information
Steven Schäfer authored and Space committed May 19, 2021
1 parent 87b2cc1 commit f0f2b92
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclarat

// object P: Parceler<T> { fun newArray(size: Int): Array<T> }
fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? =
parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol ->
parceler?.parcelerSymbolByName("newArray")?.takeIf {
// The `newArray` method in `kotlinx.parcelize.Parceler` is stubbed out and we
// have to produce a new implementation, unless the user overrides it.
!it.owner.isFakeOverride || it.owner.resolveFakeOverride()?.parentClassOrNull?.fqNameWhenAvailable != PARCELER_FQNAME
}?.let { newArraySymbol ->
irCall(newArraySymbol).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(size))
Expand Down Expand Up @@ -101,7 +105,7 @@ fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression,
// has already done the work.
private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? =
functions.firstOrNull { function ->
!function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME)
function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME)
}?.symbol

fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean =
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// WITH_RUNTIME

@file:JvmName("TestKt")
package test

import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
import java.util.Arrays

/**
* Generic pair parceler
* Create concrete object to use (see below)
*/
open class PairParceler<F: Any, S: Any>(private val firstParceler: Parceler<F>, private val secondParceler: Parceler<S>): Parceler<Pair<F, S>> {
/**
* Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it.
*/
override fun create(parcel: Parcel): Pair<F, S> =
firstParceler.create(parcel) to secondParceler.create(parcel)

/**
* Writes the [T] instance state to the [parcel].
*/
override fun Pair<F, S>.write(parcel: Parcel, flags: Int) {
with(firstParceler) { this@write.first.write(parcel, 0) }
with(secondParceler) { this@write.second.write(parcel, 0) }
}
}

object IntParceler: Parceler<Int> {
/**
* Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it.
*/
override fun create(parcel: Parcel): Int = parcel.readInt()

/**
* Writes the [T] instance state to the [parcel].
*/
override fun Int.write(parcel: Parcel, flags: Int) {
parcel.writeInt(this)
}
}

/**
* [Int] to [Int] pair parceler
*/
object IntToIntParceler: PairParceler<Int, Int>(IntParceler, IntParceler)

@Parcelize
@TypeParceler<Pair<Int, Int>, IntToIntParceler>
class A(val pair: Pair<Int, Int>): Parcelable

fun box() = parcelTest { parcel ->
val a1 = A(1 to 2)
a1.writeToParcel(parcel, 0)

val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0)

val a2 = readFromParcel<A>(parcel)
assert(a1.pair == a2.pair)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// WITH_RUNTIME
// IGNORE_BACKEND: JVM

@file:JvmName("TestKt")
package test

import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable

abstract class UserParceler : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
parcel.writeString(name)
}

override fun newArray(size: Int): Array<User> {
return Array(size + 1) { User(null) }
}
}

@Parcelize
class User(val name: String?) : Parcelable {
companion object : UserParceler() {
override fun create(parcel: Parcel) = User(parcel.readString())
}
}

fun box() = parcelTest { parcel ->
val user = User("John")
val user2 = User("Joe")
val array = arrayOf(user, user2)
parcel.writeTypedArray(array, 0)

val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0)

val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator<User>
val result = parcel.createTypedArray(creator)

assert(result.size == 3)
assert(result[0].name == user.name)
assert(result[1].name == user2.name)
assert(result[2].name == null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,4 @@ public final class test/Foo : java/lang/Object, android/os/Parcelable {
RETURN
LABEL (L1)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,34 +42,36 @@ val IrClass.hasCreatorField: Boolean

// object P : Parceler<T> { fun T.write(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelerWrite(
parceler: IrClass, parcel: IrValueDeclaration,
flags: IrValueDeclaration, value: IrExpression
): IrCall {
return irCall(parceler.parcelerSymbolByName("write")!!).apply {
dispatchReceiver = irGetObject(parceler.symbol)
extensionReceiver = value
putValueArgument(0, irGet(parcel))
putValueArgument(1, irGet(flags))
}
parceler: IrClass,
parcel: IrValueDeclaration,
flags: IrValueDeclaration,
value: IrExpression,
) = irCall(parceler.parcelerSymbolByName("write")!!).apply {
dispatchReceiver = irGetObject(parceler.symbol)
extensionReceiver = value
putValueArgument(0, irGet(parcel))
putValueArgument(1, irGet(flags))
}

// object P : Parceler<T> { fun create(parcel: Parcel): T }
fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression {
return irCall(parceler.parcelerSymbolByName("create")!!).apply {
fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression =
irCall(parceler.parcelerSymbolByName("create")!!).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(parcel))
}
}

// object P: Parceler<T> { fun newArray(size: Int): Array<T> }
fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? {
return parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol ->
fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? =
parceler?.parcelerSymbolByName("newArray")?.takeIf {
// The `newArray` method in `kotlinx.parcelize.Parceler` is stubbed out and we
// have to produce a new implementation, unless the user overrides it.
!it.owner.isFakeOverride || it.owner.resolveFakeOverride()?.parentClassOrNull?.fqNameWhenAvailable != PARCELER_FQNAME
}?.let { newArraySymbol ->
irCall(newArraySymbol).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(size))
}
}
}

// class Parcelable { fun writeToParcel(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelableWriteToParcel(
Expand Down Expand Up @@ -104,34 +106,29 @@ fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression,
// Find a named function declaration which overrides the corresponding function in [Parceler].
// This is more reliable than trying to match the functions signature ourselves, since the frontend
// has already done the work.
private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? {
return functions.firstOrNull { function ->
!function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME)
private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? =
functions.firstOrNull { function ->
function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME)
}?.symbol
}

fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean {
return parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName }
}
fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean =
parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName }

private fun IrBuilderWithScope.kClassReference(classType: IrType): IrClassReferenceImpl {
return IrClassReferenceImpl(
private fun IrBuilderWithScope.kClassReference(classType: IrType): IrClassReferenceImpl =
IrClassReferenceImpl(
startOffset, endOffset, context.irBuiltIns.kClassClass.starProjectedType, context.irBuiltIns.kClassClass, classType
)
}

private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression): IrCall {
return irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply {
private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression): IrCall =
irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply {
extensionReceiver = kClassReference
}
}

// Produce a static reference to the java class of the given type.
fun AndroidIrBuilder.javaClassReference(classType: IrType): IrCall = kClassToJavaClass(kClassReference(classType))

fun IrClass.isSubclassOfFqName(fqName: String): Boolean {
return fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) }
}
fun IrClass.isSubclassOfFqName(fqName: String): Boolean =
fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) }

inline fun IrBlockBuilder.forUntil(upperBound: IrExpression, loopBody: IrBlockBuilder.(IrValueDeclaration) -> Unit) {
val indexTemporary = irTemporary(irInt(0), isMutable = true)
Expand Down
64 changes: 64 additions & 0 deletions plugins/parcelize/parcelize-compiler/testData/box/kt46567.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// WITH_RUNTIME

@file:JvmName("TestKt")
package test

import kotlinx.parcelize.*
import android.os.Parcel
import android.os.Parcelable
import java.util.Arrays

/**
* Generic pair parceler
* Create concrete object to use (see below)
*/
open class PairParceler<F: Any, S: Any>(private val firstParceler: Parceler<F>, private val secondParceler: Parceler<S>): Parceler<Pair<F, S>> {
/**
* Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it.
*/
override fun create(parcel: Parcel): Pair<F, S> =
firstParceler.create(parcel) to secondParceler.create(parcel)

/**
* Writes the [T] instance state to the [parcel].
*/
override fun Pair<F, S>.write(parcel: Parcel, flags: Int) {
with(firstParceler) { this@write.first.write(parcel, 0) }
with(secondParceler) { this@write.second.write(parcel, 0) }
}
}

object IntParceler: Parceler<Int> {
/**
* Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it.
*/
override fun create(parcel: Parcel): Int = parcel.readInt()

/**
* Writes the [T] instance state to the [parcel].
*/
override fun Int.write(parcel: Parcel, flags: Int) {
parcel.writeInt(this)
}
}

/**
* [Int] to [Int] pair parceler
*/
object IntToIntParceler: PairParceler<Int, Int>(IntParceler, IntParceler)

@Parcelize
@TypeParceler<Pair<Int, Int>, IntToIntParceler>
class A(val pair: Pair<Int, Int>): Parcelable

fun box() = parcelTest { parcel ->
val a1 = A(1 to 2)
a1.writeToParcel(parcel, 0)

val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0)

val a2 = readFromParcel<A>(parcel)
assert(a1.pair == a2.pair)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// WITH_RUNTIME
// IGNORE_BACKEND: JVM

@file:JvmName("TestKt")
package test

import kotlinx.parcelize.*
import android.os.Parcel
import android.os.Parcelable

abstract class UserParceler : Parceler<User> {
override fun User.write(parcel: Parcel, flags: Int) {
parcel.writeString(name)
}

override fun newArray(size: Int): Array<User> {
return Array(size + 1) { User(null) }
}
}

@Parcelize
class User(val name: String?) : Parcelable {
companion object : UserParceler() {
override fun create(parcel: Parcel) = User(parcel.readString())
}
}

fun box() = parcelTest { parcel ->
val user = User("John")
val user2 = User("Joe")
val array = arrayOf(user, user2)
parcel.writeTypedArray(array, 0)

val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0)

val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator<User>
val result = parcel.createTypedArray(creator)

assert(result.size == 3)
assert(result[0].name == user.name)
assert(result[1].name == user2.name)
assert(result[2].name == null)
}
Loading

0 comments on commit f0f2b92

Please sign in to comment.