Parcelize: Add support for unsigned types on the IR backend

This commit is contained in:
Steven Schäfer
2021-08-27 13:30:38 +02:00
committed by Alexander Udalov
parent 34a200bf09
commit 9fd777cb7d
9 changed files with 161 additions and 5 deletions
@@ -4,9 +4,12 @@
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
// An IR builder with access to AndroidSymbols and convenience methods to build calls to some of these methods.
class AndroidIrBuilder internal constructor(
@@ -87,4 +90,12 @@ class AndroidIrBuilder internal constructor(
fun getTextUtilsCharSequenceCreator(): IrExpression {
return irGetField(null, androidSymbols.textUtilsCharSequenceCreator.owner)
}
fun unsafeCoerce(value: IrExpression, fromType: IrType, toType: IrType): IrExpression {
return IrCallImpl.fromSymbolOwner(UNDEFINED_OFFSET, UNDEFINED_OFFSET, toType, androidSymbols.unsafeCoerceIntrinsic).apply {
putTypeArgument(0, fromType)
putTypeArgument(1, toType)
putValueArgument(0, value)
}
}
}
@@ -6,20 +6,19 @@ package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.InlineClassRepresentation
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -36,7 +35,9 @@ class AndroidSymbols(
private val javaLang: IrPackageFragment = createPackage("java.lang")
private val javaUtil: IrPackageFragment = createPackage("java.util")
private val kotlin: IrPackageFragment = createPackage("kotlin")
private val kotlinJvm: IrPackageFragment = createPackage("kotlin.jvm")
private val kotlinJvmInternalPackage: IrPackageFragment = createPackage("kotlin.jvm.internal")
private val androidOs: IrPackageFragment = createPackage("android.os")
private val androidUtil: IrPackageFragment = createPackage("android.util")
@@ -102,6 +103,26 @@ class AndroidSymbols(
private val javaUtilTreeSet: IrClassSymbol =
createClass(javaUtil, "TreeSet", ClassKind.CLASS, Modality.OPEN)
val kotlinUByte: IrClassSymbol =
createClass(kotlin, "UByte", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.inlineClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.byteType as IrSimpleType)
}
val kotlinUShort: IrClassSymbol =
createClass(kotlin, "UShort", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.inlineClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.shortType as IrSimpleType)
}
val kotlinUInt: IrClassSymbol =
createClass(kotlin, "UInt", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.inlineClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.intType as IrSimpleType)
}
val kotlinULong: IrClassSymbol =
createClass(kotlin, "ULong", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.inlineClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.longType as IrSimpleType)
}
val androidOsParcelableCreator: IrClassSymbol = irFactory.buildClass {
name = Name.identifier("Creator")
kind = ClassKind.INTERFACE
@@ -436,6 +457,18 @@ class AndroidSymbols(
isStatic = true
}.symbol
val unsafeCoerceIntrinsic: IrSimpleFunctionSymbol =
irFactory.buildFun {
name = Name.special("<unsafe-coerce>")
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
}.apply {
parent = kotlinJvmInternalPackage
val src = addTypeParameter("T", irBuiltIns.anyNType)
val dst = addTypeParameter("R", irBuiltIns.anyNType)
addValueParameter("v", src.defaultType)
returnType = dst.defaultType
}.symbol
private fun createPackage(packageName: String): IrPackageFragment =
IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment(
moduleFragment.descriptor,
@@ -446,11 +479,13 @@ class AndroidSymbols(
irPackage: IrPackageFragment,
shortName: String,
classKind: ClassKind,
classModality: Modality
classModality: Modality,
isInlineClass: Boolean = false
): IrClassSymbol = irFactory.buildClass {
name = Name.identifier(shortName)
kind = classKind
modality = classModality
isInline = isInlineClass
}.apply {
parent = irPackage
createImplicitParameterDeclarationWithWrappedDescriptor()
@@ -71,6 +71,16 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) {
"android.util.SizeF" ->
return wrapNullableSerializerIfNeeded(irType, sizeFSerializer)
// Unsigned primitive types
"kotlin.UByte" ->
return wrapNullableSerializerIfNeeded(irType, ubyteSerializer)
"kotlin.UShort" ->
return wrapNullableSerializerIfNeeded(irType, ushortSerializer)
"kotlin.UInt" ->
return wrapNullableSerializerIfNeeded(irType, uintSerializer)
"kotlin.ULong" ->
return wrapNullableSerializerIfNeeded(irType, ulongSerializer)
// Built-in non-parameterized container types.
"kotlin.IntArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.intType))
@@ -275,6 +285,12 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) {
private val shortSerializer = IrWrappedIntParcelSerializer(irBuiltIns.shortType)
private val charSerializer = IrWrappedIntParcelSerializer(irBuiltIns.charType)
// Unsigned primitive types
private val ubyteSerializer = IrUnsafeCoerceWrappedSerializer(byteSerializer, symbols.kotlinUByte.defaultType, irBuiltIns.byteType)
private val ushortSerializer = IrUnsafeCoerceWrappedSerializer(shortSerializer, symbols.kotlinUShort.defaultType, irBuiltIns.shortType)
private val uintSerializer = IrUnsafeCoerceWrappedSerializer(intSerializer, symbols.kotlinUInt.defaultType, irBuiltIns.intType)
private val ulongSerializer = IrUnsafeCoerceWrappedSerializer(longSerializer, symbols.kotlinULong.defaultType, irBuiltIns.longType)
private val charSequenceSerializer = IrCharSequenceParcelSerializer()
// TODO The old backend uses the hidden "read/writeRawFileDescriptor" methods.
@@ -79,6 +79,20 @@ class IrWrappedIntParcelSerializer(private val parcelType: IrType) : IrParcelSer
)
}
class IrUnsafeCoerceWrappedSerializer(
private val serializer: IrParcelSerializer,
private val wrappedType: IrType,
private val underlyingType: IrType,
) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return unsafeCoerce(readParcelWith(serializer, parcel), underlyingType, wrappedType)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return writeParcelWith(serializer, parcel, flags, unsafeCoerce(value, wrappedType, underlyingType))
}
}
// Wraps a non-null aware parceler to handle nullable types.
class IrNullAwareParcelSerializer(private val serializer: IrParcelSerializer) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
@@ -148,6 +148,10 @@ interface ParcelSerializer {
PrimitiveTypeParcelSerializer.getInstance(asmType)
}
asmType.isUnsigned() -> {
ParcelSerializerStub(asmType, type)
}
asmType.isString() -> {
NullCompliantObjectParcelSerializer(
asmType,
@@ -422,6 +426,11 @@ interface ParcelSerializer {
else -> false
}
private fun Type.isUnsigned(): Boolean = when (descriptor) {
"Lkotlin/UByte;", "Lkotlin/UShort;", "Lkotlin/UInt;", "Lkotlin/ULong;" -> true
else -> false
}
private fun Type.isBoxedPrimitive(): Boolean = when (this.descriptor) {
"Ljava/lang/Boolean;",
"Ljava/lang/Character;",
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.useTmpVar
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.parcelize.serializers.BoxedPrimitiveTypeParcelSerializer.Companion.BOXED_VALUE_METHOD_NAMES
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
@@ -785,6 +786,20 @@ internal open class PrimitiveTypeParcelSerializer private constructor(final over
}
}
internal class ParcelSerializerStub(override val asmType: Type, private val kotlinType: KotlinType) : ParcelSerializer {
private fun throwError() {
TODO("Type is only supported in the IR backend: ${DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(kotlinType)}")
}
override fun writeValue(v: InstructionAdapter) {
throwError()
}
override fun readValue(v: InstructionAdapter) {
throwError()
}
}
private fun readValueNullAware(v: InstructionAdapter, block: () -> Unit) {
val labelNull = Label()
val labelReturn = Label()
@@ -0,0 +1,46 @@
// WITH_RUNTIME
// IGNORE_BACKEND: JVM
@file:JvmName("TestKt")
package test
import kotlinx.parcelize.*
import android.os.Parcel
import android.os.Parcelable
@Parcelize
data class UnsignedTypes(
val ub: UByte, val us: UShort, val ui: UInt, val ul: ULong,
val nub: UByte?, val nus: UShort?, val nui: UInt?, val nul: ULong?
) : Parcelable
fun box() = parcelTest { parcel ->
val first = UnsignedTypes(
3.toUByte(), 10.toUShort(), 300.toUInt(), 3000UL,
3.toUByte(), 10.toUShort(), 300.toUInt(), 3000UL,
)
val second = UnsignedTypes(
UByte.MAX_VALUE, UShort.MAX_VALUE, UInt.MAX_VALUE, ULong.MAX_VALUE,
UByte.MAX_VALUE, UShort.MAX_VALUE, UInt.MAX_VALUE, ULong.MAX_VALUE,
)
val third = UnsignedTypes(
UByte.MIN_VALUE, UShort.MIN_VALUE, UInt.MIN_VALUE, ULong.MIN_VALUE,
null, null, null, null,
)
first.writeToParcel(parcel, 0)
second.writeToParcel(parcel, 0)
third.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0)
val first2 = readFromParcel<UnsignedTypes>(parcel)
val second2 = readFromParcel<UnsignedTypes>(parcel)
val third2 = readFromParcel<UnsignedTypes>(parcel)
assert(first == first2)
assert(second == second2)
assert(third == third2)
}
@@ -35,6 +35,11 @@ public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest {
runTest("plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt");
}
@TestMetadata("allUnsignedTypes.kt")
public void testAllUnsignedTypes() throws Exception {
runTest("plugins/parcelize/parcelize-compiler/testData/box/allUnsignedTypes.kt");
}
@TestMetadata("arraySimple.kt")
public void testArraySimple() throws Exception {
runTest("plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt");
@@ -35,6 +35,11 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest {
runTest("plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt");
}
@TestMetadata("allUnsignedTypes.kt")
public void testAllUnsignedTypes() throws Exception {
runTest("plugins/parcelize/parcelize-compiler/testData/box/allUnsignedTypes.kt");
}
@TestMetadata("arraySimple.kt")
public void testArraySimple() throws Exception {
runTest("plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt");