Parcelize: Add support for unsigned types on the IR backend
This commit is contained in:
committed by
Alexander Udalov
parent
34a200bf09
commit
9fd777cb7d
+11
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-5
@@ -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()
|
||||
|
||||
+16
@@ -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.
|
||||
|
||||
+14
@@ -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 {
|
||||
|
||||
+9
@@ -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;",
|
||||
|
||||
+15
@@ -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)
|
||||
}
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user