diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt index 8fb0177c7ac..c5eb6d6cd84 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt @@ -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) + } + } } diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt index 97c06f8ba52..9c225cb7407 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt @@ -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("") + 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() diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt index 8b79cd2e6c3..9c6d26601c6 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt @@ -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. diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt index 67d355b9d28..ad38769dc81 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt @@ -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 { diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt index 321b79c5cce..f1b1a38d91e 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt @@ -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;", diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt index 3765d5f3643..e09554de787 100644 --- a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt @@ -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() diff --git a/plugins/parcelize/parcelize-compiler/testData/box/allUnsignedTypes.kt b/plugins/parcelize/parcelize-compiler/testData/box/allUnsignedTypes.kt new file mode 100644 index 00000000000..d9e2fa979f5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/allUnsignedTypes.kt @@ -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(parcel) + val second2 = readFromParcel(parcel) + val third2 = readFromParcel(parcel) + + assert(first == first2) + assert(second == second2) + assert(third == third2) +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java index 5414c207fab..4fe1620470c 100644 --- a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java @@ -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"); diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java index b761ae660f8..be5ca7556dd 100644 --- a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java @@ -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");