[Parcelize] Reorganize module structure of Parcelize plugin

Also mark parcelize as compatible with K2
This commit is contained in:
Dmitriy Novozhilov
2022-05-17 15:44:02 +03:00
committed by teamcity
parent 2a7dc1cc0c
commit 259862d294
45 changed files with 193 additions and 127 deletions
@@ -0,0 +1,30 @@
description = "Parcelize compiler plugin (Backend)"
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":plugins:parcelize:parcelize-compiler:parcelize.common"))
implementation(project(":plugins:parcelize:parcelize-compiler:parcelize.k1"))
implementation(project(":plugins:parcelize:parcelize-compiler:parcelize.k2"))
compileOnly(project(":compiler:backend"))
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:backend.jvm"))
compileOnly(project(":compiler:ir.tree.impl"))
compileOnly(project(":compiler:fir:tree"))
compileOnly(project(":compiler:fir:fir2ir"))
compileOnly(intellijCore())
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
runtimeJar()
javadocJar()
sourcesJar()
@@ -0,0 +1,144 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.AbstractClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.RecordComponentVisitor
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class ParcelizeClinitClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension {
override fun interceptClassBuilderFactory(
interceptedFactory: ClassBuilderFactory,
bindingContext: BindingContext,
diagnostics: DiagnosticSink
): ClassBuilderFactory {
return ParcelableClinitClassBuilderFactory(interceptedFactory, bindingContext)
}
private inner class ParcelableClinitClassBuilderFactory(
private val delegateFactory: ClassBuilderFactory,
val bindingContext: BindingContext
) : ClassBuilderFactory {
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder {
return AndroidOnDestroyCollectorClassBuilder(origin, delegateFactory.newClassBuilder(origin), bindingContext)
}
override fun getClassBuilderMode() = delegateFactory.classBuilderMode
override fun asText(builder: ClassBuilder?): String? {
return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun asBytes(builder: ClassBuilder?): ByteArray? {
return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder)
}
override fun close() {
delegateFactory.close()
}
}
private class AndroidOnDestroyCollectorClassBuilder(
val declarationOrigin: JvmDeclarationOrigin,
val delegateClassBuilder: ClassBuilder,
val bindingContext: BindingContext
) : DelegatingClassBuilder() {
private var currentClass: KtClassOrObject? = null
private var currentClassName: String? = null
private var isClinitGenerated = false
override fun getDelegate() = delegateClassBuilder
override fun defineClass(
origin: PsiElement?,
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array<out String>
) {
currentClass = origin as? KtClassOrObject
currentClassName = name
isClinitGenerated = false
super.defineClass(origin, version, access, name, signature, superName, interfaces)
}
override fun done() {
if (!isClinitGenerated && currentClass != null && currentClassName != null) {
val descriptor = bindingContext[BindingContext.CLASS, currentClass]
if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) {
val baseVisitor = super.newMethod(JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_STATIC, "<clinit>", "()V", null, null)
val visitor = ClinitAwareMethodVisitor(currentClassName!!, baseVisitor)
visitor.visitCode()
visitor.visitInsn(Opcodes.RETURN)
visitor.visitEnd()
}
}
super.done()
}
override fun newMethod(
origin: JvmDeclarationOrigin,
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
if (name == "<clinit>" && currentClass != null && currentClassName != null) {
isClinitGenerated = true
val descriptor = bindingContext[BindingContext.CLASS, currentClass]
if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) {
return ClinitAwareMethodVisitor(
currentClassName!!,
super.newMethod(origin, access, name, desc, signature, exceptions)
)
}
}
return super.newMethod(origin, access, name, desc, signature, exceptions)
}
override fun newRecordComponent(name: String, desc: String, signature: String?): RecordComponentVisitor {
return AbstractClassBuilder.EMPTY_RECORD_VISITOR
}
}
private class ClinitAwareMethodVisitor(val parcelableName: String, mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) {
override fun visitInsn(opcode: Int) {
if (opcode == Opcodes.RETURN) {
val iv = InstructionAdapter(this)
val creatorName = "$parcelableName\$Creator"
val creatorType = Type.getObjectType(creatorName)
iv.anew(creatorType)
iv.dup()
iv.invokespecial(creatorName, "<init>", "()V", false)
iv.putstatic(parcelableName, "CREATOR", "Landroid/os/Parcelable\$Creator;")
}
super.visitInsn(opcode)
}
}
}
@@ -0,0 +1,426 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy.CodegenBased
import org.jetbrains.kotlin.codegen.context.ClassContext
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.parcelize.serializers.*
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase.Companion.FILE_DESCRIPTOR_FQNAME
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorFactory
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
open class ParcelizeCodegenExtension : ParcelizeExtensionBase, ExpressionCodegenExtension {
open fun isAvailable(element: PsiElement): Boolean {
return true
}
override val shouldGenerateClassSyntheticPartsInLightClassesMode: Boolean
get() = true
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
val declaration = codegen.myClass as? PsiElement ?: return
if (!isAvailable(declaration)) {
return
}
val parcelableClass = codegen.descriptor
if (!parcelableClass.isParcelizeClassDescriptor) {
return
}
val propertiesToSerialize = getPropertiesToSerialize(codegen, parcelableClass)
val parcelerObject = parcelableClass.companionObjectDescriptor?.takeIf {
TypeUtils.getAllSupertypes(it.defaultType).any { supertype -> supertype.isParceler }
}
with(parcelableClass) {
if (hasSyntheticDescribeContents()) {
writeDescribeContentsFunction(codegen, propertiesToSerialize)
}
if (hasSyntheticWriteToParcel()) {
writeWriteToParcel(codegen, propertiesToSerialize, PARCEL_TYPE, parcelerObject)
}
if (!hasCreatorField()) {
writeCreatorAccessField(codegen, parcelableClass)
}
}
if (codegen.state.classBuilderMode != ClassBuilderMode.LIGHT_CLASSES) {
val parcelClassType = ParcelizeResolveExtension.resolveParcelClassType(parcelableClass.module)
?: error("Can't resolve 'android.os.Parcel' class")
val parcelableCreatorClassType = ParcelizeResolveExtension.resolveParcelableCreatorClassType(parcelableClass.module)
?: error("Can't resolve 'android.os.Parcelable.Creator' class")
writeCreatorClass(
codegen,
parcelableClass, parcelClassType, parcelableCreatorClassType,
PARCEL_TYPE, parcelerObject, propertiesToSerialize
)
}
}
private fun getCompanionClassType(containerAsmType: Type, parcelerObject: ClassDescriptor): Pair<Type, String> {
val shortName = parcelerObject.name
return Pair(Type.getObjectType(containerAsmType.internalName + "\$$shortName"), shortName.asString())
}
private fun ClassDescriptor.writeWriteToParcel(
codegen: ImplementationBodyCodegen,
properties: List<PropertyToSerialize>,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?
): Unit? {
val containerAsmType = codegen.typeMapper.mapType(this.defaultType)
return findFunction(WRITE_TO_PARCEL)?.write(codegen) {
if (parcelerObject != null) {
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(0, containerAsmType)
v.load(1, PARCEL_TYPE)
v.load(2, Type.INT_TYPE)
v.invokevirtual(
companionAsmType.internalName, "write",
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false
)
} else {
val frameMap = FrameMap().apply {
enterTemp(containerAsmType)
enterTemp(PARCEL_TYPE)
enterTemp(Type.INT_TYPE)
}
val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList(), frameMap)
if (properties.isEmpty()) {
val entityType = this@writeWriteToParcel.defaultType
val asmType = codegen.state.typeMapper.mapType(entityType)
val serializer = if (this@writeWriteToParcel.kind == ClassKind.CLASS) {
NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType))
} else {
ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
}
v.load(1, parcelAsmType)
v.load(0, containerAsmType)
serializer.writeValue(v)
} else {
for ((fieldName, type, parcelers) in properties) {
val asmType = codegen.typeMapper.mapType(type)
v.load(1, parcelAsmType)
v.load(0, containerAsmType)
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers))
serializer.writeValue(v)
}
}
}
v.areturn(Type.VOID_TYPE)
}
}
private fun ClassDescriptor.writeDescribeContentsFunction(
codegen: ImplementationBodyCodegen,
propertiesToSerialize: List<PropertyToSerialize>
): Unit? {
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.type.containsFileDescriptor() }
return findFunction(DESCRIBE_CONTENTS)?.write(codegen) {
v.aconst(if (hasFileDescriptorAnywhere) 1 /* CONTENTS_FILE_DESCRIPTOR */ else 0)
v.areturn(Type.INT_TYPE)
}
}
private fun KotlinType.containsFileDescriptor(): Boolean {
val declarationDescriptor = this.constructor.declarationDescriptor
if (declarationDescriptor != null) {
if (declarationDescriptor.fqNameSafe == FILE_DESCRIPTOR_FQNAME) {
return true
}
}
return this.arguments.any { it.type.containsFileDescriptor() }
}
data class PropertyToSerialize(val name: String, val type: KotlinType, val parcelers: List<TypeParcelerMapping>)
private fun getPropertiesToSerialize(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor
): List<PropertyToSerialize> {
if (parcelableClass.kind != ClassKind.CLASS) {
return emptyList()
}
val constructor = parcelableClass.constructors.firstOrNull { it.isPrimary } ?: return emptyList()
val propertiesToSerialize = constructor.valueParameters.mapNotNull { param ->
codegen.bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, param]
}
val classParcelers = getTypeParcelers(parcelableClass.annotations)
return propertiesToSerialize.map {
PropertyToSerialize(it.name.asString(), it.type, classParcelers + getTypeParcelers(it.annotations))
}
}
private fun writeCreateFromParcel(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
parcelableCreatorClassType: KotlinType,
creatorClass: ClassDescriptorImpl,
parcelClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<PropertyToSerialize>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
val creatorAsmType = codegen.typeMapper.mapType(creatorClass)
val overriddenFunction = parcelableCreatorClassType.findFunction(CREATE_FROM_PARCEL)
?: error("Can't resolve 'android.os.Parcelable.Creator.${CREATE_FROM_PARCEL.methodName}' method")
createMethod(
creatorClass, CREATE_FROM_PARCEL, Modality.FINAL,
parcelableClass.defaultType.replaceArgumentsWithStarProjections(), "in" to parcelClassType
).write(codegen, overriddenFunction) {
if (parcelerObject != null) {
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(1, PARCEL_TYPE)
v.invokevirtual(companionAsmType.internalName, "create", "(${PARCEL_TYPE.descriptor})$containerAsmType", false)
} else {
v.anew(containerAsmType)
v.dup()
val asmConstructorParameters = StringBuilder()
val frameMap = FrameMap().apply {
enterTemp(creatorAsmType)
enterTemp(PARCEL_TYPE)
}
val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList(), frameMap)
if (properties.isEmpty()) {
val entityType = parcelableClass.defaultType
val asmType = codegen.state.typeMapper.mapType(entityType)
val serializer = if (parcelableClass.kind == ClassKind.CLASS) {
NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType))
} else {
ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
}
v.load(1, parcelAsmType)
serializer.readValue(v)
} else {
for ((_, type, parcelers) in properties) {
val asmType = codegen.typeMapper.mapType(type)
asmConstructorParameters.append(asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers))
v.load(1, parcelAsmType)
serializer.readValue(v)
}
v.invokespecial(containerAsmType.internalName, "<init>", "($asmConstructorParameters)V", false)
}
}
v.areturn(containerAsmType)
}
}
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) {
val creatorType = Type.getObjectType("android/os/Parcelable\$Creator")
val parcelableType = codegen.typeMapper.mapType(parcelableClass)
val fieldSignature = "L${creatorType.internalName}<${parcelableType.descriptor}>;"
codegen.v.newField(
JvmDeclarationOrigin.NO_ORIGIN,
ACC_STATIC or ACC_PUBLIC or ACC_FINAL,
"CREATOR",
creatorType.descriptor,
fieldSignature,
null
)
}
private fun writeCreatorClass(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
parcelClassType: KotlinType,
parcelableCreatorClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<PropertyToSerialize>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$Creator")
val creatorClass = ClassDescriptorImpl(
parcelableClass, Name.identifier("Creator"), Modality.FINAL, ClassKind.CLASS, listOf(parcelableCreatorClassType),
parcelableClass.source, false, LockBasedStorageManager.NO_LOCKS
)
creatorClass.initialize(
MemberScope.Empty, emptySet(),
DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source)
)
val classBuilderForCreator = codegen.state.factory.newVisitor(
JvmDeclarationOrigin(JvmDeclarationOriginKind.OTHER, null, creatorClass),
Type.getObjectType(creatorAsmType.internalName),
codegen.myClass.containingKtFile
)
val classContextForCreator = ClassContext(
codegen.typeMapper, creatorClass, OwnerKind.IMPLEMENTATION, codegen.context.parentContext, null
)
val codegenForCreator = ImplementationBodyCodegen(
codegen.myClass, classContextForCreator, classBuilderForCreator, codegen.state, codegen.parentCodegen, false
)
val classSignature = "Ljava/lang/Object;Landroid/os/Parcelable\$Creator<${containerAsmType.descriptor}>;"
classBuilderForCreator.defineClass(
null, V1_6, ACC_PUBLIC or ACC_FINAL or ACC_SUPER,
creatorAsmType.internalName, classSignature, "java/lang/Object",
arrayOf("android/os/Parcelable\$Creator")
)
codegen.v.visitInnerClass(creatorAsmType.internalName, containerAsmType.internalName, "Creator", ACC_PUBLIC or ACC_STATIC)
codegenForCreator.v.visitInnerClass(creatorAsmType.internalName, containerAsmType.internalName, "Creator", ACC_PUBLIC or ACC_STATIC)
writeSyntheticClassMetadata(classBuilderForCreator, codegen.state, InlineUtil.isInPublicInlineScope(parcelableClass))
writeCreatorConstructor(codegenForCreator, creatorClass, creatorAsmType)
writeNewArrayMethod(codegenForCreator, parcelableClass, parcelableCreatorClassType, creatorClass, parcelerObject)
writeCreateFromParcel(
codegenForCreator,
parcelableClass, parcelableCreatorClassType, creatorClass, parcelClassType,
parcelAsmType, parcelerObject, properties
)
classBuilderForCreator.done()
}
private fun writeCreatorConstructor(codegen: ImplementationBodyCodegen, creatorClass: ClassDescriptor, creatorAsmType: Type) {
DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source)
.apply {
returnType = creatorClass.defaultType
}.write(codegen) {
v.load(0, creatorAsmType)
v.invokespecial("java/lang/Object", "<init>", "()V", false)
v.areturn(Type.VOID_TYPE)
}
}
private fun writeNewArrayMethod(
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor,
parcelableCreatorClassType: KotlinType,
creatorClass: ClassDescriptorImpl,
parcelerObject: ClassDescriptor?
) {
val builtIns = parcelableClass.builtIns
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass)
val overriddenFunction = parcelableCreatorClassType.findFunction(NEW_ARRAY)
?: error("Can't resolve 'android.os.Parcelable.Creator.${NEW_ARRAY.methodName}' method")
createMethod(
creatorClass, NEW_ARRAY, Modality.FINAL,
builtIns.getArrayType(Variance.INVARIANT, parcelableClass.defaultType.replaceArgumentsWithStarProjections()),
"size" to builtIns.intType
).write(codegen, overriddenFunction) {
if (parcelerObject != null) {
val newArrayMethod = parcelerObject.unsubstitutedMemberScope
.getContributedFunctions(NEW_ARRAY_NAME, NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS)
.firstOrNull {
it.typeParameters.isEmpty()
&& it.kind == CallableMemberDescriptor.Kind.DECLARATION
&& (it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type))
&& !((it.containingDeclaration as? ClassDescriptor)?.defaultType?.isParceler ?: true)
}
if (newArrayMethod != null) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject)
v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor)
v.load(1, Type.INT_TYPE)
v.invokevirtual(companionAsmType.internalName, "newArray", "(I)[$parcelableAsmType", false)
v.areturn(Type.getType("[$parcelableAsmType"))
return@write
}
}
v.load(1, Type.INT_TYPE)
v.newarray(parcelableAsmType)
v.areturn(Type.getType("[$parcelableAsmType"))
}
}
private fun FunctionDescriptor.write(
codegen: ImplementationBodyCodegen,
overriddenDescriptor: FunctionDescriptor? = null,
code: ExpressionCodegen.() -> Unit
) {
val declarationOrigin = JvmDeclarationOrigin(JvmDeclarationOriginKind.OTHER, null, this)
if (overriddenDescriptor != null) {
this.overriddenDescriptors = listOf(overriddenDescriptor)
}
codegen.functionCodegen.generateMethod(declarationOrigin, this, object : CodegenBased(codegen.state) {
override fun doGenerateBody(e: ExpressionCodegen, signature: JvmMethodSignature) = with(e) {
e.code()
}
})
}
private fun KotlinType.findFunction(componentKind: ParcelizeSyntheticComponent.ComponentKind): SimpleFunctionDescriptor? {
return memberScope
.getContributedFunctions(Name.identifier(componentKind.methodName), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS)
.firstOrNull()
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.parcelize.ir.AndroidSymbols
import org.jetbrains.kotlin.parcelize.ir.ParcelizeFirIrTransformer
class ParcelizeFirIrGeneratorExtension : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext.irBuiltIns, moduleFragment)
ParcelizeFirIrTransformer(pluginContext, androidSymbols).transform(moduleFragment)
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.parcelize.ir.AndroidSymbols
import org.jetbrains.kotlin.parcelize.ir.ParcelizeIrTransformer
class ParcelizeIrGeneratorExtension : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
val androidSymbols = AndroidSymbols(pluginContext.irBuiltIns, moduleFragment)
ParcelizeIrTransformer(pluginContext, androidSymbols).transform(moduleFragment)
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
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(
val androidSymbols: AndroidSymbols,
symbol: IrSymbol,
startOffset: Int,
endOffset: Int
) : IrBuilderWithScope(IrGeneratorContextBase(androidSymbols.irBuiltIns), Scope(symbol), startOffset, endOffset) {
fun parcelReadInt(receiver: IrExpression): IrExpression {
return irCall(androidSymbols.parcelReadInt).apply {
dispatchReceiver = receiver
}
}
fun parcelReadParcelable(receiver: IrExpression, loader: IrExpression): IrExpression {
return irCall(androidSymbols.parcelReadParcelable).apply {
dispatchReceiver = receiver
putValueArgument(0, loader)
}
}
fun parcelReadString(receiver: IrExpression): IrExpression {
return irCall(androidSymbols.parcelReadString).apply {
dispatchReceiver = receiver
}
}
fun parcelWriteInt(receiver: IrExpression, value: IrExpression): IrExpression {
return irCall(androidSymbols.parcelWriteInt).apply {
dispatchReceiver = receiver
putValueArgument(0, value)
}
}
fun parcelWriteParcelable(receiver: IrExpression, p: IrExpression, parcelableFlags: IrExpression): IrExpression {
return irCall(androidSymbols.parcelWriteParcelable).apply {
dispatchReceiver = receiver
putValueArgument(0, p)
putValueArgument(1, parcelableFlags)
}
}
fun parcelWriteString(receiver: IrExpression, value: IrExpression): IrExpression {
return irCall(androidSymbols.parcelWriteString).apply {
dispatchReceiver = receiver
putValueArgument(0, value)
}
}
fun textUtilsWriteToParcel(cs: IrExpression, p: IrExpression, parcelableFlags: IrExpression): IrExpression {
return irCall(androidSymbols.textUtilsWriteToParcel).apply {
putValueArgument(0, cs)
putValueArgument(1, p)
putValueArgument(2, parcelableFlags)
}
}
fun classGetClassLoader(receiver: IrExpression): IrExpression {
return irCall(androidSymbols.classGetClassLoader).apply {
dispatchReceiver = receiver
}
}
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)
}
}
}
@@ -0,0 +1,540 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.ir.addExtensionReceiver
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.*
import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
// All of the IR declarations needed by the parcelize plugin. Note that the declarations are generated based on JVM descriptors and
// hence contain just enough information to produce correct JVM bytecode for *calls*. In particular, we omit generic types and
// supertypes, which are not needed to produce correct bytecode.
class AndroidSymbols(
val irBuiltIns: IrBuiltIns,
private val moduleFragment: IrModuleFragment
) {
private val irFactory: IrFactory = IrFactoryImpl
private val javaIo: IrPackageFragment = createPackage("java.io")
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")
private val androidText: IrPackageFragment = createPackage("android.text")
private val androidOsBundle: IrClassSymbol =
createClass(androidOs, "Bundle", ClassKind.CLASS, Modality.FINAL)
private val androidOsIBinder: IrClassSymbol =
createClass(androidOs, "IBinder", ClassKind.INTERFACE, Modality.ABSTRACT)
val androidOsParcel: IrClassSymbol =
createClass(androidOs, "Parcel", ClassKind.CLASS, Modality.FINAL)
private val androidOsParcelFileDescriptor: IrClassSymbol =
createClass(androidOs, "ParcelFileDescriptor", ClassKind.CLASS, Modality.OPEN)
private val androidOsParcelable: IrClassSymbol =
createClass(androidOs, "Parcelable", ClassKind.INTERFACE, Modality.ABSTRACT)
private val androidOsPersistableBundle: IrClassSymbol =
createClass(androidOs, "PersistableBundle", ClassKind.CLASS, Modality.FINAL)
private val androidTextTextUtils: IrClassSymbol =
createClass(androidText, "TextUtils", ClassKind.CLASS, Modality.OPEN)
private val androidUtilSize: IrClassSymbol =
createClass(androidUtil, "Size", ClassKind.CLASS, Modality.FINAL)
private val androidUtilSizeF: IrClassSymbol =
createClass(androidUtil, "SizeF", ClassKind.CLASS, Modality.FINAL)
private val androidUtilSparseBooleanArray: IrClassSymbol =
createClass(androidUtil, "SparseBooleanArray", ClassKind.CLASS, Modality.OPEN)
private val javaIoFileDescriptor: IrClassSymbol =
createClass(javaIo, "FileDescriptor", ClassKind.CLASS, Modality.FINAL)
private val javaIoSerializable: IrClassSymbol =
createClass(javaIo, "Serializable", ClassKind.INTERFACE, Modality.ABSTRACT)
val javaLangClass: IrClassSymbol =
createClass(javaLang, "Class", ClassKind.CLASS, Modality.FINAL)
private val javaLangClassLoader: IrClassSymbol =
createClass(javaLang, "ClassLoader", ClassKind.CLASS, Modality.ABSTRACT)
private val javaUtilArrayList: IrClassSymbol =
createClass(javaUtil, "ArrayList", ClassKind.CLASS, Modality.OPEN)
private val javaUtilLinkedHashMap: IrClassSymbol =
createClass(javaUtil, "LinkedHashMap", ClassKind.CLASS, Modality.OPEN)
private val javaUtilLinkedHashSet: IrClassSymbol =
createClass(javaUtil, "LinkedHashSet", ClassKind.CLASS, Modality.OPEN)
private val javaUtilList: IrClassSymbol =
createClass(javaUtil, "List", ClassKind.INTERFACE, Modality.ABSTRACT)
private val javaUtilTreeMap: IrClassSymbol =
createClass(javaUtil, "TreeMap", ClassKind.CLASS, Modality.OPEN)
private val javaUtilTreeSet: IrClassSymbol =
createClass(javaUtil, "TreeSet", ClassKind.CLASS, Modality.OPEN)
val kotlinUByte: IrClassSymbol =
createClass(kotlin, "UByte", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.byteType as IrSimpleType)
}
val kotlinUShort: IrClassSymbol =
createClass(kotlin, "UShort", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.shortType as IrSimpleType)
}
val kotlinUInt: IrClassSymbol =
createClass(kotlin, "UInt", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.intType as IrSimpleType)
}
val kotlinULong: IrClassSymbol =
createClass(kotlin, "ULong", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(Name.identifier("data"), irBuiltIns.longType as IrSimpleType)
}
val kotlinUByteArray: IrClassSymbol =
createClass(kotlin, "UByteArray", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(
Name.identifier("storage"),
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).owner.defaultType
)
}
val kotlinUShortArray: IrClassSymbol =
createClass(kotlin, "UShortArray", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(
Name.identifier("storage"),
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.shortType).owner.defaultType
)
}
val kotlinUIntArray: IrClassSymbol =
createClass(kotlin, "UIntArray", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(
Name.identifier("storage"),
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).owner.defaultType
)
}
val kotlinULongArray: IrClassSymbol =
createClass(kotlin, "ULongArray", ClassKind.CLASS, Modality.FINAL, true).apply {
owner.valueClassRepresentation = InlineClassRepresentation(
Name.identifier("storage"),
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).owner.defaultType
)
}
val androidOsParcelableCreator: IrClassSymbol = irFactory.buildClass {
name = Name.identifier("Creator")
kind = ClassKind.INTERFACE
modality = Modality.ABSTRACT
}.apply {
createImplicitParameterDeclarationWithWrappedDescriptor()
val t = addTypeParameter("T", irBuiltIns.anyNType)
parent = androidOsParcelable.owner
addFunction(CREATE_FROM_PARCEL_NAME.identifier, t.defaultType, Modality.ABSTRACT).apply {
addValueParameter("source", androidOsParcel.defaultType)
}
addFunction(
NEW_ARRAY_NAME.identifier, irBuiltIns.arrayClass.typeWith(t.defaultType.makeNullable()),
Modality.ABSTRACT
).apply {
addValueParameter("size", irBuiltIns.intType)
}
}.symbol
val kotlinKClassJava: IrPropertySymbol = irFactory.buildProperty {
name = Name.identifier("java")
}.apply {
parent = kotlinJvm
addGetter().apply {
addExtensionReceiver(irBuiltIns.kClassClass.starProjectedType)
returnType = javaLangClass.defaultType
}
}.symbol
val parcelCreateBinderArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createBinderArray", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType)).symbol
val parcelCreateBinderArrayList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createBinderArrayList", javaUtilArrayList.defaultType).symbol
val parcelCreateBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createBooleanArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType
).symbol
val parcelCreateByteArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createByteArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType
).symbol
val parcelCreateCharArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createCharArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType
).symbol
val parcelCreateDoubleArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createDoubleArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType
).symbol
val parcelCreateFloatArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createFloatArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType
).symbol
val parcelCreateIntArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createIntArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType
).symbol
val parcelCreateLongArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createLongArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType
).symbol
val parcelCreateStringArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createStringArray", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType)).symbol
val parcelCreateStringArrayList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createStringArrayList", javaUtilArrayList.defaultType).symbol
val parcelReadBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readBundle", androidOsBundle.defaultType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelReadByte: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readByte", irBuiltIns.byteType).symbol
val parcelReadDouble: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readDouble", irBuiltIns.doubleType).symbol
val parcelReadFileDescriptor: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readFileDescriptor", androidOsParcelFileDescriptor.defaultType).symbol
val parcelReadFloat: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readFloat", irBuiltIns.floatType).symbol
val parcelReadInt: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readInt", irBuiltIns.intType).symbol
val parcelReadLong: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readLong", irBuiltIns.longType).symbol
val parcelReadParcelable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readParcelable", androidOsParcelable.defaultType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelReadPersistableBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readPersistableBundle", androidOsPersistableBundle.defaultType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelReadSerializable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSerializable", javaIoSerializable.defaultType).symbol
val parcelReadSize: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSize", androidUtilSize.defaultType).symbol
val parcelReadSizeF: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSizeF", androidUtilSizeF.defaultType).symbol
val parcelReadSparseBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSparseBooleanArray", androidUtilSparseBooleanArray.defaultType).symbol
val parcelReadString: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readString", irBuiltIns.stringType).symbol
val parcelReadStrongBinder: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readStrongBinder", androidOsIBinder.defaultType).symbol
val parcelReadValue: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readValue", irBuiltIns.anyNType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelWriteBinderArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBinderArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType))
}.symbol
val parcelWriteBinderList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBinderList", irBuiltIns.unitType).apply {
addValueParameter("val", javaUtilList.defaultType)
}.symbol
val parcelWriteBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBooleanArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType)
}.symbol
val parcelWriteBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBundle", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsBundle.defaultType)
}.symbol
val parcelWriteByte: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeByte", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.byteType)
}.symbol
val parcelWriteByteArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeByteArray", irBuiltIns.unitType).apply {
addValueParameter("b", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType)
}.symbol
val parcelWriteCharArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeCharArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType)
}.symbol
val parcelWriteDouble: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeDouble", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.doubleType)
}.symbol
val parcelWriteDoubleArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeDoubleArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType)
}.symbol
val parcelWriteFileDescriptor: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFileDescriptor", irBuiltIns.unitType).apply {
addValueParameter("val", javaIoFileDescriptor.defaultType)
}.symbol
val parcelWriteFloat: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFloat", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.floatType)
}.symbol
val parcelWriteFloatArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFloatArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType)
}.symbol
val parcelWriteInt: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeInt", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.intType)
}.symbol
val parcelWriteIntArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeIntArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType)
}.symbol
val parcelWriteLong: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeLong", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.longType)
}.symbol
val parcelWriteLongArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeLongArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType)
}.symbol
val parcelWriteParcelable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeParcelable", irBuiltIns.unitType).apply {
addValueParameter("p", androidOsParcelable.defaultType)
addValueParameter("parcelableFlags", irBuiltIns.intType)
}.symbol
val parcelWritePersistableBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writePersistableBundle", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsPersistableBundle.defaultType)
}.symbol
val parcelWriteSerializable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSerializable", irBuiltIns.unitType).apply {
addValueParameter("s", javaIoSerializable.defaultType)
}.symbol
val parcelWriteSize: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSize", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSize.defaultType)
}.symbol
val parcelWriteSizeF: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSizeF", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSizeF.defaultType)
}.symbol
val parcelWriteSparseBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSparseBooleanArray", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSparseBooleanArray.defaultType)
}.symbol
val parcelWriteString: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeString", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.stringType)
}.symbol
val parcelWriteStringArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStringArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType))
}.symbol
val parcelWriteStringList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStringList", irBuiltIns.unitType).apply {
addValueParameter("val", javaUtilList.defaultType)
}.symbol
val parcelWriteStrongBinder: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStrongBinder", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsIBinder.defaultType)
}.symbol
val parcelWriteValue: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeValue", irBuiltIns.unitType).apply {
addValueParameter("v", irBuiltIns.anyNType)
}.symbol
val textUtilsWriteToParcel: IrSimpleFunctionSymbol =
androidTextTextUtils.owner.addFunction(WRITE_TO_PARCEL_NAME.identifier, irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("cs", irBuiltIns.charSequenceClass.defaultType)
addValueParameter("p", androidOsParcel.defaultType)
addValueParameter("parcelableFlags", irBuiltIns.intType)
}.symbol
val classGetClassLoader: IrSimpleFunctionSymbol =
javaLangClass.owner.addFunction("getClassLoader", javaLangClassLoader.defaultType).symbol
val arrayListConstructor: IrConstructorSymbol = javaUtilArrayList.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val arrayListAdd: IrSimpleFunctionSymbol =
javaUtilArrayList.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val linkedHashMapConstructor: IrConstructorSymbol =
javaUtilLinkedHashMap.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val linkedHashMapPut: IrSimpleFunctionSymbol =
javaUtilLinkedHashMap.owner.addFunction("put", irBuiltIns.anyNType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
addValueParameter("p_1", irBuiltIns.anyNType)
}.symbol
val linkedHashSetConstructor: IrConstructorSymbol =
javaUtilLinkedHashSet.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val linkedHashSetAdd: IrSimpleFunctionSymbol =
javaUtilLinkedHashSet.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val treeMapConstructor: IrConstructorSymbol = javaUtilTreeMap.owner.addConstructor().symbol
val treeMapPut: IrSimpleFunctionSymbol =
javaUtilTreeMap.owner.addFunction("put", irBuiltIns.anyNType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
addValueParameter("p_1", irBuiltIns.anyNType)
}.symbol
val treeSetConstructor: IrConstructorSymbol = javaUtilTreeSet.owner.addConstructor().symbol
val treeSetAdd: IrSimpleFunctionSymbol =
javaUtilTreeSet.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val textUtilsCharSequenceCreator: IrFieldSymbol = androidTextTextUtils.owner.addField {
name = Name.identifier("CHAR_SEQUENCE_CREATOR")
type = androidOsParcelableCreator.defaultType
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,
FqName(packageName)
)
private fun createClass(
irPackage: IrPackageFragment,
shortName: String,
classKind: ClassKind,
classModality: Modality,
isValueClass: Boolean = false,
): IrClassSymbol = irFactory.buildClass {
name = Name.identifier(shortName)
kind = classKind
modality = classModality
isValue = isValueClass
}.apply {
parent = irPackage
createImplicitParameterDeclarationWithWrappedDescriptor()
}.symbol
fun createBuilder(
symbol: IrSymbol,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET
) = AndroidIrBuilder(this, symbol, startOffset, endOffset)
}
@@ -0,0 +1,352 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.psiElement
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.inlineClassRepresentation
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES
class IrParcelSerializerFactory(private val symbols: AndroidSymbols) {
/**
* Resolve the given [irType] to a corresponding [IrParcelSerializer]. This depends on the TypeParcelers which
* are currently in [scope], as well as the type of the enclosing Parceleable class [parcelizeType], which is needed
* to get a class loader for reflection based serialization. Beyond this, we need to know whether to allow
* using read/writeValue for serialization (if [strict] is false). Beyond this, we need to know whether we are
* producing parcelers for properties of a Parcelable (if [toplevel] is true), or for a complete Parcelable.
*/
fun get(
irType: IrType,
scope: IrParcelerScope?,
parcelizeType: IrType,
strict: Boolean = false,
toplevel: Boolean = false
): IrParcelSerializer {
fun strict() = strict && !irType.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES)
scope.getCustomSerializer(irType)?.let { parceler ->
return IrCustomParcelSerializer(parceler)
}
val classifier = irType.erasedUpperBound
val classifierFqName = classifier.fqNameWhenAvailable?.asString()
when (classifierFqName) {
// Built-in parcel serializers
"kotlin.String", "java.lang.String" ->
return stringSerializer
"kotlin.CharSequence", "java.lang.CharSequence" ->
return charSequenceSerializer
"android.os.Bundle" ->
return IrParcelSerializerWithClassLoader(parcelizeType, symbols.parcelReadBundle, symbols.parcelWriteBundle)
"android.os.PersistableBundle" ->
return IrParcelSerializerWithClassLoader(
parcelizeType,
symbols.parcelReadPersistableBundle,
symbols.parcelWritePersistableBundle
)
// Non-nullable built-in serializers
"kotlin.Byte", "java.lang.Byte" ->
return wrapNullableSerializerIfNeeded(irType, byteSerializer)
"kotlin.Boolean", "java.lang.Boolean" ->
return wrapNullableSerializerIfNeeded(irType, booleanSerializer)
"kotlin.Char", "java.lang.Character" ->
return wrapNullableSerializerIfNeeded(irType, charSerializer)
"kotlin.Short", "java.lang.Short" ->
return wrapNullableSerializerIfNeeded(irType, shortSerializer)
"kotlin.Int", "java.lang.Integer" ->
return wrapNullableSerializerIfNeeded(irType, intSerializer)
"kotlin.Long", "java.lang.Long" ->
return wrapNullableSerializerIfNeeded(irType, longSerializer)
"kotlin.Float", "java.lang.Float" ->
return wrapNullableSerializerIfNeeded(irType, floatSerializer)
"kotlin.Double", "java.lang.Double" ->
return wrapNullableSerializerIfNeeded(irType, doubleSerializer)
"java.io.FileDescriptor" ->
return wrapNullableSerializerIfNeeded(irType, fileDescriptorSerializer)
"android.util.Size" ->
return wrapNullableSerializerIfNeeded(irType, sizeSerializer)
"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))
return intArraySerializer
"kotlin.BooleanArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.booleanType))
return booleanArraySerializer
"kotlin.ByteArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.byteType))
return byteArraySerializer
"kotlin.CharArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.charType))
return charArraySerializer
"kotlin.FloatArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.floatType))
return floatArraySerializer
"kotlin.DoubleArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.doubleType))
return doubleArraySerializer
"kotlin.LongArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.longType))
return longArraySerializer
"android.util.SparseBooleanArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.booleanType))
return sparseBooleanArraySerializer
// Unsigned array types
"kotlin.UByteArray" ->
return ubyteArraySerializer
"kotlin.UShortArray" ->
return wrapNullableSerializerIfNeeded(
irType,
ushortArraySerializer
)
"kotlin.UIntArray" ->
return uintArraySerializer
"kotlin.ULongArray" ->
return ulongArraySerializer
}
// Generic container types
when (classifierFqName) {
// Apart from kotlin.Array and kotlin.ShortArray, these will only be hit if we have a
// special parceler for the element type.
"kotlin.Array", "kotlin.ShortArray", "kotlin.IntArray",
"kotlin.BooleanArray", "kotlin.ByteArray", "kotlin.CharArray",
"kotlin.FloatArray", "kotlin.DoubleArray", "kotlin.LongArray" -> {
val elementType = irType.getArrayElementType(irBuiltIns)
if (!scope.hasCustomSerializer(elementType)) {
when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) {
"java.lang.String", "kotlin.String" ->
return stringArraySerializer
"android.os.IBinder" ->
return iBinderArraySerializer
}
}
val arrayType =
if (classifier.defaultType.isPrimitiveArray()) classifier.defaultType else irBuiltIns.arrayClass.typeWith(elementType)
return wrapNullableSerializerIfNeeded(
irType,
IrArrayParcelSerializer(arrayType, elementType, get(elementType, scope, parcelizeType, strict()))
)
}
// This will only be hit if we have a custom serializer for booleans
"android.util.SparseBooleanArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.booleanType,
get(irBuiltIns.booleanType, scope, parcelizeType, strict())
)
"android.util.SparseIntArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.intType,
get(irBuiltIns.intType, scope, parcelizeType, strict())
)
"android.util.SparseLongArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.longType,
get(irBuiltIns.longType, scope, parcelizeType, strict())
)
"android.util.SparseArray" -> {
val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns)
return IrSparseArrayParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict()))
}
// TODO: More java collections?
// TODO: Add tests for all of these types, not just some common ones...
// FIXME: Is the support for ArrayDeque missing in the old BE?
"kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List",
"kotlin.collections.ArrayList", "java.util.ArrayList",
"kotlin.collections.ArrayDeque", "java.util.ArrayDeque",
"kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set",
"kotlin.collections.HashSet", "java.util.HashSet",
"kotlin.collections.LinkedHashSet", "java.util.LinkedHashSet",
"java.util.NavigableSet", "java.util.SortedSet" -> {
val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns)
if (!scope.hasCustomSerializer(elementType) && classifierFqName in setOf(
"kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList",
"java.util.List", "java.util.ArrayList"
)
) {
when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) {
"android.os.IBinder" ->
return iBinderListSerializer
"kotlin.String", "java.lang.String" ->
return stringListSerializer
}
}
return wrapNullableSerializerIfNeeded(
irType,
IrListParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict()))
)
}
"kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map",
"kotlin.collections.HashMap", "java.util.HashMap",
"kotlin.collections.LinkedHashMap", "java.util.LinkedHashMap",
"java.util.SortedMap", "java.util.NavigableMap", "java.util.TreeMap",
"java.util.concurrent.ConcurrentHashMap" -> {
val keyType = (irType as IrSimpleType).arguments[0].upperBound(irBuiltIns)
val valueType = irType.arguments[1].upperBound(irBuiltIns)
val parceler =
IrMapParcelSerializer(
classifier,
keyType,
valueType,
get(keyType, scope, parcelizeType, strict()),
get(valueType, scope, parcelizeType, strict())
)
return wrapNullableSerializerIfNeeded(irType, parceler)
}
}
// Generic parcelable types
when {
classifier.isSubclassOfFqName("android.os.Parcelable")
// Avoid infinite loops when deriving parcelers for enum or object classes.
&& !(toplevel && (classifier.isObject || classifier.isEnumClass)) -> {
// We try to use writeToParcel/createFromParcel directly whenever possible, but there are some caveats.
//
// According to the JLS, changing a class from final to non-final is a binary compatible change, hence we
// cannot use the writeToParcel/createFromParcel methods directly when serializing classes from external
// dependencies. This issue was originally reported in KT-20029.
//
// Conversely, we can and should apply this optimization to all classes in the current module which
// implement Parcelable (KT-20030). There are two cases to consider. If the class is annotated with
// @Parcelize, we will create the corresponding methods/fields ourselves, before we generate the code
// for writeToParcel/createFromParcel. For Java classes (or compiled Kotlin classes annotated with
// @Parcelize), we'll have a field in the class itself. Finally, with Parcelable instances which were
// manually implemented in Kotlin, we'll instead have an @JvmField property getter in the companion object.
return if (classifier.modality == Modality.FINAL && classifier.psiElement != null
&& (classifier.isParcelize || classifier.hasCreatorField)
) {
wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier))
} else {
// In all other cases, we have to use the generic methods in Parcel, which use reflection internally.
IrGenericParcelableParcelSerializer(parcelizeType)
}
}
classifier.isSubclassOfFqName("android.os.IBinder") ->
return iBinderSerializer
classifier.isObject ->
return IrObjectParcelSerializer(classifier)
classifier.isEnumClass ->
return wrapNullableSerializerIfNeeded(irType, IrEnumParcelSerializer(classifier))
classifier.isSubclassOfFqName("java.io.Serializable")
// Functions and Continuations are always serializable.
|| irType.isFunctionTypeOrSubtype() || irType.isSuspendFunctionTypeOrSubtype() ->
return serializableSerializer
strict() ->
throw IllegalArgumentException("Illegal type, could not find a specific serializer for ${irType.render()}")
else ->
return IrParcelSerializerWithClassLoader(parcelizeType, symbols.parcelReadValue, symbols.parcelWriteValue)
}
}
private fun wrapNullableSerializerIfNeeded(irType: IrType, serializer: IrParcelSerializer) =
if (irType.isNullable()) IrNullAwareParcelSerializer(serializer) else serializer
private val irBuiltIns: IrBuiltIns
get() = symbols.irBuiltIns
private val stringArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArray, symbols.parcelWriteStringArray)
private val stringListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArrayList, symbols.parcelWriteStringList)
private val iBinderSerializer = IrSimpleParcelSerializer(symbols.parcelReadStrongBinder, symbols.parcelWriteStrongBinder)
private val iBinderArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArray, symbols.parcelWriteBinderArray)
private val iBinderListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArrayList, symbols.parcelWriteBinderList)
private val serializableSerializer = IrSimpleParcelSerializer(symbols.parcelReadSerializable, symbols.parcelWriteSerializable)
private val stringSerializer = IrSimpleParcelSerializer(symbols.parcelReadString, symbols.parcelWriteString)
private val byteSerializer = IrSimpleParcelSerializer(symbols.parcelReadByte, symbols.parcelWriteByte)
private val intSerializer = IrSimpleParcelSerializer(symbols.parcelReadInt, symbols.parcelWriteInt)
private val longSerializer = IrSimpleParcelSerializer(symbols.parcelReadLong, symbols.parcelWriteLong)
private val floatSerializer = IrSimpleParcelSerializer(symbols.parcelReadFloat, symbols.parcelWriteFloat)
private val doubleSerializer = IrSimpleParcelSerializer(symbols.parcelReadDouble, symbols.parcelWriteDouble)
private val intArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateIntArray, symbols.parcelWriteIntArray)
private val booleanArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBooleanArray, symbols.parcelWriteBooleanArray)
private val byteArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateByteArray, symbols.parcelWriteByteArray)
private val charArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateCharArray, symbols.parcelWriteCharArray)
private val doubleArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateDoubleArray, symbols.parcelWriteDoubleArray)
private val floatArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateFloatArray, symbols.parcelWriteFloatArray)
private val longArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateLongArray, symbols.parcelWriteLongArray)
// Primitive types without dedicated read/write methods need an additional cast.
private val booleanSerializer = IrWrappedIntParcelSerializer(irBuiltIns.booleanType)
private val shortSerializer = IrWrappedIntParcelSerializer(irBuiltIns.shortType)
private val charSerializer = IrWrappedIntParcelSerializer(irBuiltIns.charType)
private val shortArraySerializer = IrArrayParcelSerializer(
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.shortType).defaultType,
irBuiltIns.shortType,
shortSerializer
)
// 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)
// Unsigned array types
private val ubyteArraySerializer = IrUnsafeCoerceWrappedSerializer(
byteArraySerializer,
symbols.kotlinUByteArray.owner.defaultType,
symbols.kotlinUByteArray.owner.inlineClassRepresentation!!.underlyingType
)
private val ushortArraySerializer = IrUnsafeCoerceWrappedSerializer(
shortArraySerializer,
symbols.kotlinUShortArray.owner.defaultType,
symbols.kotlinUShortArray.owner.inlineClassRepresentation!!.underlyingType
)
private val uintArraySerializer = IrUnsafeCoerceWrappedSerializer(
intArraySerializer,
symbols.kotlinUIntArray.owner.defaultType,
symbols.kotlinUIntArray.owner.inlineClassRepresentation!!.underlyingType
)
private val ulongArraySerializer = IrUnsafeCoerceWrappedSerializer(
longArraySerializer,
symbols.kotlinULongArray.owner.defaultType,
symbols.kotlinULongArray.owner.inlineClassRepresentation!!.underlyingType
)
private val charSequenceSerializer = IrCharSequenceParcelSerializer()
// TODO The old backend uses the hidden "read/writeRawFileDescriptor" methods.
private val fileDescriptorSerializer = IrSimpleParcelSerializer(symbols.parcelReadFileDescriptor, symbols.parcelWriteFileDescriptor)
private val sizeSerializer = IrSimpleParcelSerializer(symbols.parcelReadSize, symbols.parcelWriteSize)
private val sizeFSerializer = IrSimpleParcelSerializer(symbols.parcelReadSizeF, symbols.parcelWriteSizeF)
private val sparseBooleanArraySerializer =
IrSimpleParcelSerializer(symbols.parcelReadSparseBooleanArray, symbols.parcelWriteSparseBooleanArray)
}
@@ -0,0 +1,553 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.isJvmInterface
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
interface IrParcelSerializer {
fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression
fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression
}
fun AndroidIrBuilder.readParcelWith(serializer: IrParcelSerializer, parcel: IrValueDeclaration): IrExpression {
return with(serializer) { readParcel(parcel) }
}
fun AndroidIrBuilder.writeParcelWith(
serializer: IrParcelSerializer,
parcel: IrValueDeclaration,
flags: IrValueDeclaration,
value: IrExpression
): IrExpression {
return with(serializer) { writeParcel(parcel, flags, value) }
}
// Creates a serializer from a pair of parcel methods of the form reader()T and writer(T)V.
class IrSimpleParcelSerializer(private val reader: IrSimpleFunctionSymbol, private val writer: IrSimpleFunctionSymbol) :
IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irCall(reader).apply { dispatchReceiver = irGet(parcel) }
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return irCall(writer).apply {
dispatchReceiver = irGet(parcel)
putValueArgument(0, value)
}
}
}
// Serialize a value of the primitive [parcelType] by coercion to int.
class IrWrappedIntParcelSerializer(private val parcelType: IrType) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return if (parcelType.isBoolean()) {
irNotEquals(parcelReadInt(irGet(parcel)), irInt(0))
} else {
val conversion = context.irBuiltIns.intClass.functions.first { function ->
function.owner.name.asString() == "to${parcelType.getClass()!!.name}"
}
irCall(conversion).apply {
dispatchReceiver = parcelReadInt(irGet(parcel))
}
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteInt(
irGet(parcel),
if (parcelType.isBoolean()) {
irIfThenElse(context.irBuiltIns.intType, value, irInt(1), irInt(0))
} else {
val conversion = parcelType.classOrNull!!.functions.first { function ->
function.owner.name.asString() == "toInt"
}
irCall(conversion).apply { dispatchReceiver = value }
}
)
}
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 {
val nonNullResult = readParcelWith(serializer, parcel)
return irIfThenElse(
nonNullResult.type.makeNullable(),
irEquals(parcelReadInt(irGet(parcel)), irInt(0)),
irNull(),
nonNullResult
)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return irLetS(value) { irValueSymbol ->
irIfNull(
context.irBuiltIns.unitType,
irGet(irValueSymbol.owner),
parcelWriteInt(irGet(parcel), irInt(0)),
irBlock {
+parcelWriteInt(irGet(parcel), irInt(1))
+writeParcelWith(serializer, parcel, flags, irGet(irValueSymbol.owner))
}
)
}
}
}
// Parcel serializer for object classes. We avoid empty parcels by writing a dummy value. Not null-safe.
class IrObjectParcelSerializer(private val objectClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression
// Avoid empty parcels
{
return irBlock {
+parcelReadInt(irGet(parcel))
+irGetObject(objectClass.symbol)
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelWriteInt(irGet(parcel), irInt(1))
}
}
// Parcel serializer for classes with a default constructor. We avoid empty parcels by writing a dummy value. Not null-safe.
class IrNoParameterClassParcelSerializer(private val irClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
val defaultConstructor = irClass.primaryConstructor!!
return irBlock {
+parcelReadInt(irGet(parcel))
+irCall(defaultConstructor)
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelWriteInt(irGet(parcel), irInt(1))
}
}
// Parcel serializer for enum classes. Not null-safe.
class IrEnumParcelSerializer(enumClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irCall(enumValueOf).apply {
putValueArgument(0, parcelReadString(irGet(parcel)))
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelWriteString(irGet(parcel), irCall(enumName).apply {
dispatchReceiver = value
})
}
private val enumValueOf: IrFunctionSymbol =
enumClass.functions.single { function ->
function.name.asString() == "valueOf" && function.dispatchReceiverParameter == null
&& function.extensionReceiverParameter == null && function.valueParameters.size == 1
&& function.valueParameters.single().type.isString()
}.symbol
private val enumName: IrFunctionSymbol = enumClass.getPropertyGetter("name")!!
}
// Parcel serializer for the java CharSequence interface.
class IrCharSequenceParcelSerializer : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return parcelableCreatorCreateFromParcel(getTextUtilsCharSequenceCreator(), irGet(parcel))
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return textUtilsWriteToParcel(value, irGet(parcel), irGet(flags))
}
}
// Parcel serializer for Parcelables in the same module, which accesses the writeToParcel/createFromParcel methods without reflection.
class IrEfficientParcelableParcelSerializer(private val irClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return parcelableCreatorCreateFromParcel(getParcelableCreator(irClass), irGet(parcel))
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelableWriteToParcel(irClass, value, irGet(parcel), irGet(flags))
}
}
// Parcel serializer for Parcelables using reflection.
// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027.
class IrGenericParcelableParcelSerializer(private val parcelizeType: IrType) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return parcelReadParcelable(irGet(parcel), classGetClassLoader(javaClassReference(parcelizeType)))
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelWriteParcelable(irGet(parcel), value, irGet(flags))
}
}
// Creates a serializer from a pair of parcel methods of the form reader(ClassLoader)T and writer(T)V.
// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027.
class IrParcelSerializerWithClassLoader(
private val parcelizeType: IrType,
private val reader: IrSimpleFunctionSymbol,
private val writer: IrSimpleFunctionSymbol
) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irCall(reader).apply {
dispatchReceiver = irGet(parcel)
putValueArgument(0, classGetClassLoader(javaClassReference(parcelizeType)))
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return irCall(writer).apply {
dispatchReceiver = irGet(parcel)
putValueArgument(0, value)
}
}
}
// Parcel serializer using a custom Parceler object.
class IrCustomParcelSerializer(private val parcelerObject: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return parcelerCreate(parcelerObject, parcel)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return parcelerWrite(parcelerObject, parcel, flags, value)
}
}
// Parcel serializer for array types. This handles both primitive array types (for ShortArray and for primitive arrays using custom element
// parcelers) as well as boxed arrays.
class IrArrayParcelSerializer(
private val arrayType: IrType,
private val elementType: IrType,
private val elementSerializer: IrParcelSerializer
) : IrParcelSerializer {
private fun AndroidIrBuilder.newArray(size: IrExpression): IrExpression {
val arrayConstructor: IrFunctionSymbol = if (arrayType.isBoxedArray) {
context.irBuiltIns.arrayOfNulls
} else {
arrayType.classOrNull!!.constructors.single { it.owner.valueParameters.size == 1 }
}
return irCall(arrayConstructor, arrayType).apply {
if (typeArgumentsCount != 0)
putTypeArgument(0, elementType)
putValueArgument(0, size)
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irBlock {
val arraySize = irTemporary(parcelReadInt(irGet(parcel)))
val arrayTemporary = irTemporary(newArray(irGet(arraySize)))
forUntil(irGet(arraySize)) { index ->
val setter = arrayType.classOrNull!!.getSimpleFunction("set")!!
+irCall(setter).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
putValueArgument(1, readParcelWith(elementSerializer, parcel))
}
}
+irGet(arrayTemporary)
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return irBlock {
val arrayTemporary = irTemporary(value)
val arraySizeSymbol = arrayType.classOrNull!!.getPropertyGetter("size")!!
val arraySize = irTemporary(irCall(arraySizeSymbol).apply {
dispatchReceiver = irGet(arrayTemporary)
})
+parcelWriteInt(irGet(parcel), irGet(arraySize))
forUntil(irGet(arraySize)) { index ->
val getter = context.irBuiltIns.arrayClass.getSimpleFunction("get")!!
val element = irCall(getter, elementType).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
}
+writeParcelWith(elementSerializer, parcel, flags, element)
}
}
}
}
// Parcel serializer for android SparseArrays. Note that this also needs to handle BooleanSparseArray, in case of a custom element parceler.
class IrSparseArrayParcelSerializer(
private val sparseArrayClass: IrClass,
private val elementType: IrType,
private val elementSerializer: IrParcelSerializer
) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irBlock {
val remainingSizeTemporary = irTemporary(parcelReadInt(irGet(parcel)), isMutable = true)
val sparseArrayConstructor = sparseArrayClass.constructors.first { irConstructor ->
irConstructor.valueParameters.size == 1 && irConstructor.valueParameters.single().type.isInt()
}
val constructorCall = if (sparseArrayClass.typeParameters.isEmpty())
irCall(sparseArrayConstructor)
else
irCallConstructor(sparseArrayConstructor.symbol, listOf(elementType))
val arrayTemporary = irTemporary(constructorCall.apply {
putValueArgument(0, irGet(remainingSizeTemporary))
})
+irWhile().apply {
condition = irNotEquals(irGet(remainingSizeTemporary), irInt(0))
body = irBlock {
val sparseArrayPut = sparseArrayClass.functions.first { function ->
function.name.asString() == "put" && function.valueParameters.size == 2
}
+irCall(sparseArrayPut).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, parcelReadInt(irGet(parcel)))
putValueArgument(1, readParcelWith(elementSerializer, parcel))
}
val dec = context.irBuiltIns.intClass.getSimpleFunction("dec")!!
+irSet(remainingSizeTemporary.symbol, irCall(dec).apply {
dispatchReceiver = irGet(remainingSizeTemporary)
})
}
}
+irGet(arrayTemporary)
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
return irBlock {
val sizeFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "size" && function.valueParameters.isEmpty()
}
val keyAtFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "keyAt" && function.valueParameters.size == 1
}
val valueAtFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "valueAt" && function.valueParameters.size == 1
}
val arrayTemporary = irTemporary(value)
val sizeTemporary = irTemporary(irCall(sizeFunction).apply {
dispatchReceiver = irGet(arrayTemporary)
})
+parcelWriteInt(irGet(parcel), irGet(sizeTemporary))
forUntil(irGet(sizeTemporary)) { index ->
+parcelWriteInt(irGet(parcel), irCall(keyAtFunction).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
})
+writeParcelWith(elementSerializer, parcel, flags, irCall(valueAtFunction.symbol, elementType).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
})
}
}
}
}
// Parcel serializer for all lists supported by Parcelize. List interfaces use hard-coded default implementations for deserialization.
// List maps to ArrayList, Set maps to LinkedHashSet, NavigableSet and SortedSet map to TreeSet.
class IrListParcelSerializer(
private val irClass: IrClass,
private val elementType: IrType,
private val elementSerializer: IrParcelSerializer
) : IrParcelSerializer {
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
val sizeFunction = irClass.getPropertyGetter("size")!!
val iteratorFunction = irClass.getMethodWithoutArguments("iterator")
val iteratorClass = iteratorFunction.returnType.erasedUpperBound
val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext")
val iteratorNext = iteratorClass.getMethodWithoutArguments("next")
return irBlock {
val list = irTemporary(value)
+parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply {
dispatchReceiver = irGet(list)
})
val iterator = irTemporary(irCall(iteratorFunction).apply {
dispatchReceiver = irGet(list)
})
+irWhile().apply {
condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) }
body = writeParcelWith(elementSerializer, parcel, flags, irCall(iteratorNext.symbol, elementType).apply {
dispatchReceiver = irGet(iterator)
})
}
}
}
private fun listSymbols(symbols: AndroidSymbols): Pair<IrConstructorSymbol, IrSimpleFunctionSymbol> {
// If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back
// the the default constructor if none exist.
if (!irClass.isJvmInterface) {
val constructor = irClass.constructors.find { constructor ->
constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt()
} ?: irClass.constructors.first { constructor -> constructor.valueParameters.isEmpty() }
val add = irClass.functions.first { function ->
function.name.asString() == "add" && function.valueParameters.size == 1
}
return constructor.symbol to add.symbol
}
return when (irClass.fqNameWhenAvailable?.asString()) {
"kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List" ->
symbols.arrayListConstructor to symbols.arrayListAdd
"kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set" ->
symbols.linkedHashSetConstructor to symbols.linkedHashSetAdd
"java.util.NavigableSet", "java.util.SortedSet" ->
symbols.treeSetConstructor to symbols.treeSetAdd
else -> error("Unknown list interface type: ${irClass.render()}")
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irBlock {
val (constructorSymbol, addSymbol) = listSymbols(androidSymbols)
val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel)))
val list = irTemporary(irCall(constructorSymbol).apply {
if (constructorSymbol.owner.valueParameters.isNotEmpty())
putValueArgument(0, irGet(sizeTemporary))
})
forUntil(irGet(sizeTemporary)) {
+irCall(addSymbol).apply {
dispatchReceiver = irGet(list)
putValueArgument(0, readParcelWith(elementSerializer, parcel))
}
}
+irGet(list)
}
}
}
// Parcel serializer for all maps supported by Parcelize. Map interfaces use hard-coded default implementations for deserialization.
// Map uses LinkedHashMap, while NavigableMap and SortedMap use to TreeMap.
class IrMapParcelSerializer(
private val irClass: IrClass,
private val keyType: IrType,
private val valueType: IrType,
private val keySerializer: IrParcelSerializer,
private val valueSerializer: IrParcelSerializer
) : IrParcelSerializer {
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
val sizeFunction = irClass.getPropertyGetter("size")!!
val entriesFunction = irClass.getPropertyGetter("entries")!!
val entrySetClass = entriesFunction.owner.returnType.erasedUpperBound
val iteratorFunction = entrySetClass.getMethodWithoutArguments("iterator")
val iteratorClass = iteratorFunction.returnType.erasedUpperBound
val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext")
val iteratorNext = iteratorClass.getMethodWithoutArguments("next")
val elementClass =
(entriesFunction.owner.returnType as IrSimpleType).arguments.single().upperBound(context.irBuiltIns).erasedUpperBound
val elementKey = elementClass.getPropertyGetter("key")!!
val elementValue = elementClass.getPropertyGetter("value")!!
return irBlock {
val list = irTemporary(value)
+parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply {
dispatchReceiver = irGet(list)
})
val iterator = irTemporary(irCall(iteratorFunction).apply {
dispatchReceiver = irCall(entriesFunction).apply {
dispatchReceiver = irGet(list)
}
})
+irWhile().apply {
condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) }
body = irBlock {
val element = irTemporary(irCall(iteratorNext).apply {
dispatchReceiver = irGet(iterator)
})
+writeParcelWith(keySerializer, parcel, flags, irCall(elementKey, keyType).apply {
dispatchReceiver = irGet(element)
})
+writeParcelWith(valueSerializer, parcel, flags, irCall(elementValue, valueType).apply {
dispatchReceiver = irGet(element)
})
}
}
}
}
private fun mapSymbols(symbols: AndroidSymbols): Pair<IrConstructorSymbol, IrSimpleFunctionSymbol> {
// If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back
// the the default constructor if none exist.
if (!irClass.isJvmInterface) {
val constructor = irClass.constructors.find { constructor ->
constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt()
} ?: irClass.constructors.find { constructor ->
constructor.valueParameters.isEmpty()
}!!
val put = irClass.functions.first { function ->
function.name.asString() == "put" && function.valueParameters.size == 2
}
return constructor.symbol to put.symbol
}
return when (irClass.fqNameWhenAvailable?.asString()) {
"kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map" ->
symbols.linkedHashMapConstructor to symbols.linkedHashMapPut
"java.util.SortedMap", "java.util.NavigableMap" ->
symbols.treeMapConstructor to symbols.treeMapPut
else -> error("Unknown map interface type: ${irClass.render()}")
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
return irBlock {
val (constructorSymbol, putSymbol) = mapSymbols(androidSymbols)
val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel)))
val map = irTemporary(irCall(constructorSymbol).apply {
if (constructorSymbol.owner.valueParameters.isNotEmpty())
putValueArgument(0, irGet(sizeTemporary))
})
forUntil(irGet(sizeTemporary)) {
+irCall(putSymbol).apply {
dispatchReceiver = irGet(map)
putValueArgument(0, readParcelWith(keySerializer, parcel))
putValueArgument(1, readParcelWith(valueSerializer, parcel))
}
}
+irGet(map)
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_FQ_NAMES
// Keep track of all custom parcelers which are currently in scope.
// Note that custom parcelers are resolved in *reverse* lexical order.
class IrParcelerScope(private val parent: IrParcelerScope? = null) {
private val typeParcelers = mutableMapOf<IrType, IrClass>()
fun add(type: IrType, parceler: IrClass) {
typeParcelers.putIfAbsent(type, parceler)
}
fun get(type: IrType): IrClass? =
parent?.get(type) ?: typeParcelers[type]
}
fun IrParcelerScope?.getCustomSerializer(irType: IrType): IrClass? {
return irType.getAnyAnnotation(WRITE_WITH_FQ_NAMES)?.let { writeWith ->
(writeWith.type as IrSimpleType).arguments.single().typeOrNull!!.getClass()!!
} ?: this?.get(irType)
}
fun IrParcelerScope?.hasCustomSerializer(irType: IrType): Boolean {
return getCustomSerializer(irType) != null
}
fun IrAnnotationContainer.getParcelerScope(parent: IrParcelerScope? = null): IrParcelerScope? {
val typeParcelerAnnotations = annotations.filterTo(mutableListOf()) {
it.symbol.owner.constructedClass.fqNameWhenAvailable in TYPE_PARCELER_FQ_NAMES
}
if (typeParcelerAnnotations.isEmpty())
return parent
val scope = IrParcelerScope(parent)
for (annotation in typeParcelerAnnotations) {
val (mappedType, parcelerType) = (annotation.type as IrSimpleType).arguments.map { it.typeOrNull!! }
scope.add(mappedType, parcelerType.getClass()!!)
}
return scope
}
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.backend.IrPluginDeclarationOrigin
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent
import org.jetbrains.kotlin.parcelize.fir.FirParcelizePluginKey
class ParcelizeFirIrTransformer(
context: IrPluginContext,
androidSymbols: AndroidSymbols
) : ParcelizeIrTransformerBase(context, androidSymbols) {
fun transform(moduleFragment: IrModuleFragment) {
moduleFragment.accept(this, null)
deferredOperations.forEach { it() }
}
override fun visitElement(element: IrElement) = element.acceptChildren(this, null)
override fun visitClass(declaration: IrClass) {
declaration.acceptChildren(this, null)
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED)
return
val parcelableProperties = declaration.parcelableProperties
// If the companion extends Parceler, it can override parts of the generated implementation.
val parcelerObject = declaration.companionObject()?.takeIf {
it.isSubclassOfFqName(PARCELER_FQN.asString())
}
for (function in declaration.functions) {
val origin = function.origin
if (origin !is IrPluginDeclarationOrigin || origin.pluginKey != FirParcelizePluginKey) continue
when (function.name.identifier) {
ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS.methodName -> {
function.generateDescribeContentsBody(parcelableProperties)
}
ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL.methodName -> {
function.apply {
val receiverParameter = dispatchReceiverParameter!!
val (parcelParameter, flagsParameter) = function.valueParameters
// We need to defer the construction of the writer, since it may refer to the [writeToParcel] methods in other
// @Parcelize classes in the current module, which might not be constructed yet at this point.
defer {
generateWriteToParcelBody(
declaration,
parcelerObject,
parcelableProperties,
receiverParameter,
parcelParameter,
flagsParameter
)
}
}
}
else -> error("Generated declaration with unknown name: ${function.render()}")
}
}
generateCreator(declaration, parcelerObject, parcelableProperties)
}
}
@@ -0,0 +1,207 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.DescriptorMetadataSource
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.copyTypeAndValueArgumentsFrom
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.FLAGS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@OptIn(ObsoleteDescriptorBasedAPI::class)
class ParcelizeIrTransformer(
context: IrPluginContext,
androidSymbols: AndroidSymbols
) : ParcelizeIrTransformerBase(context, androidSymbols) {
private val symbolMap = mutableMapOf<IrSimpleFunctionSymbol, IrSimpleFunctionSymbol>()
fun transform(moduleFragment: IrModuleFragment) {
moduleFragment.accept(this, null)
deferredOperations.forEach { it() }
// Remap broken stubs, which psi2ir generates for the synthetic descriptors coming from the ParcelizeResolveExtension.
// Replace the `parcelableCreator` intrinsic with a direct field access.
moduleFragment.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
// Handle the `parcelableCreator` intrinsic
val callee = expression.symbol.owner
if (
callee.dispatchReceiverParameter == null
&& callee.extensionReceiverParameter == null
&& callee.valueParameters.isEmpty()
&& callee.isInline
&& callee.fqNameWhenAvailable?.asString() == "kotlinx.parcelize.ParcelableCreatorKt.parcelableCreator"
&& callee.typeParameters.singleOrNull()?.let {
it.isReified && it.superTypes.singleOrNull()?.classFqName == PARCELABLE_FQN
} == true
) {
expression.getTypeArgument(0)?.getClass()?.let { parcelableClass ->
androidSymbols.createBuilder(expression.symbol).apply {
return getParcelableCreator(parcelableClass)
}
}
}
// Remap calls to `describeContents` and `writeToParcel`
val remappedSymbol = symbolMap[expression.symbol]
?: return super.visitCall(expression)
return IrCallImpl(
expression.startOffset, expression.endOffset, expression.type, remappedSymbol,
expression.typeArgumentsCount, expression.valueArgumentsCount, expression.origin,
expression.superQualifierSymbol
).apply {
copyTypeAndValueArgumentsFrom(expression)
}
}
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
val remappedSymbol = symbolMap[expression.symbol]
val remappedReflectionTarget = expression.reflectionTarget?.let { symbolMap[it] }
if (remappedSymbol == null && remappedReflectionTarget == null)
return super.visitFunctionReference(expression)
return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type, remappedSymbol ?: expression.symbol,
expression.typeArgumentsCount, expression.valueArgumentsCount, remappedReflectionTarget,
expression.origin
).apply {
copyTypeAndValueArgumentsFrom(expression)
}
}
override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {
// Remap overridden symbols, otherwise the code might break in BridgeLowering
declaration.overriddenSymbols = declaration.overriddenSymbols.map { symbol ->
symbolMap[symbol] ?: symbol
}
return super.visitSimpleFunction(declaration)
}
})
}
override fun visitElement(element: IrElement) = element.acceptChildren(this, null)
override fun visitClass(declaration: IrClass) {
declaration.acceptChildren(this, null)
// Sealed classes can be annotated with `@Parcelize`, but that only implies that we
// should process their immediate subclasses.
if (!declaration.isParcelize || declaration.modality == Modality.SEALED)
return
val parcelableProperties = declaration.parcelableProperties
// If the companion extends Parceler, it can override parts of the generated implementation.
val parcelerObject = declaration.companionObject()?.takeIf {
it.isSubclassOfFqName(PARCELER_FQN.asString())
}
if (declaration.descriptor.hasSyntheticDescribeContents()) {
val describeContents = declaration.addOverride(
PARCELABLE_FQN,
DESCRIBE_CONTENTS_NAME.identifier,
context.irBuiltIns.intType,
modality = Modality.OPEN
).apply {
generateDescribeContentsBody(parcelableProperties)
metadata = DescriptorMetadataSource.Function(
declaration.descriptor.findFunction(ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS)!!
)
}
declaration.functions.find {
it.descriptor.safeAs<ParcelizeSyntheticComponent>()?.componentKind == ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS
}?.let { stub ->
symbolMap[stub.symbol] = describeContents.symbol
declaration.declarations.remove(stub)
}
}
if (declaration.descriptor.hasSyntheticWriteToParcel()) {
val writeToParcel = declaration.addOverride(
PARCELABLE_FQN,
WRITE_TO_PARCEL_NAME.identifier,
context.irBuiltIns.unitType,
modality = Modality.OPEN
).apply {
val receiverParameter = dispatchReceiverParameter!!
val parcelParameter = addValueParameter("out", androidSymbols.androidOsParcel.defaultType)
val flagsParameter = addValueParameter(FLAGS_NAME, context.irBuiltIns.intType)
// We need to defer the construction of the writer, since it may refer to the [writeToParcel] methods in other
// @Parcelize classes in the current module, which might not be constructed yet at this point.
defer {
generateWriteToParcelBody(
declaration,
parcelerObject,
parcelableProperties,
receiverParameter,
parcelParameter,
flagsParameter
)
}
metadata = DescriptorMetadataSource.Function(
declaration.descriptor.findFunction(ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL)!!
)
}
declaration.functions.find {
it.descriptor.safeAs<ParcelizeSyntheticComponent>()?.componentKind == ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL
}?.let { stub ->
symbolMap[stub.symbol] = writeToParcel.symbol
declaration.declarations.remove(stub)
}
}
if (!declaration.descriptor.hasCreatorField()) {
generateCreator(declaration, parcelerObject, parcelableProperties)
}
}
private fun IrClass.addOverride(
baseFqName: FqName,
name: String,
returnType: IrType,
modality: Modality = Modality.FINAL
): IrSimpleFunction = addFunction(name, returnType, modality).apply {
overriddenSymbols = superTypes.mapNotNull { superType ->
superType.classOrNull?.owner?.takeIf { superClass -> superClass.isSubclassOfFqName(baseFqName.asString()) }
}.flatMap { superClass ->
superClass.functions.filter { function ->
function.name.asString() == name && function.overridesFunctionIn(baseFqName)
}.map { it.symbol }.toList()
}
}
}
@@ -0,0 +1,208 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
abstract class ParcelizeIrTransformerBase(
protected val context: IrPluginContext,
protected val androidSymbols: AndroidSymbols
) : ParcelizeExtensionBase, IrElementVisitorVoid {
private val irFactory: IrFactory = IrFactoryImpl
protected val deferredOperations = mutableListOf<() -> Unit>()
protected fun defer(block: () -> Unit) = deferredOperations.add(block)
protected fun IrSimpleFunction.generateDescribeContentsBody(parcelableProperties: List<ParcelableProperty?>) {
val flags = if (parcelableProperties.any { it != null && it.field.type.containsFileDescriptors }) 1 else 0
body = context.createIrBuilder(symbol).run {
irExprBody(irInt(flags))
}
}
protected fun IrSimpleFunction.generateWriteToParcelBody(
irClass: IrClass,
parcelerObject: IrClass?,
parcelableProperties: List<ParcelableProperty?>,
receiverParameter: IrValueParameter,
parcelParameter: IrValueParameter,
flagsParameter: IrValueParameter
) {
body = androidSymbols.createBuilder(symbol).run {
irBlockBody {
when {
parcelerObject != null ->
+parcelerWrite(parcelerObject, parcelParameter, flagsParameter, irGet(receiverParameter))
parcelableProperties.isNotEmpty() ->
for (property in parcelableProperties) {
if (property != null) {
+writeParcelWith(
property.parceler,
parcelParameter,
flagsParameter,
irGetField(irGet(receiverParameter), property.field)
)
}
}
else ->
+writeParcelWith(
irClass.classParceler,
parcelParameter,
flagsParameter,
irGet(receiverParameter)
)
}
}
}
}
protected fun generateCreator(declaration: IrClass, parcelerObject: IrClass?, parcelableProperties: List<ParcelableProperty?>) {
// Since the `CREATOR` object cannot refer to the type parameters of the parcelable class we use a star projected type
val declarationType = declaration.symbol.starProjectedType
val creatorType = androidSymbols.androidOsParcelableCreator.typeWith(declarationType)
declaration.addField {
name = CREATOR_NAME
type = creatorType
isStatic = true
isFinal = true
}.apply {
val irField = this
val creatorClass = irFactory.buildClass {
name = Name.identifier("Creator")
visibility = DescriptorVisibilities.LOCAL
}.apply {
parent = irField
superTypes = listOf(creatorType)
createImplicitParameterDeclarationWithWrappedDescriptor()
addConstructor {
isPrimary = true
}.apply {
body = context.createIrBuilder(symbol).irBlockBody {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
}
}
val arrayType = context.irBuiltIns.arrayClass.typeWith(declarationType.makeNullable())
addFunction(NEW_ARRAY_NAME.identifier, arrayType).apply {
overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!)
val sizeParameter = addValueParameter("size", context.irBuiltIns.intType)
body = context.createIrBuilder(symbol).run {
irExprBody(
parcelerNewArray(parcelerObject, sizeParameter)
?: irCall(context.irBuiltIns.arrayOfNulls, arrayType).apply {
putTypeArgument(0, arrayType)
putValueArgument(0, irGet(sizeParameter))
}
)
}
}
addFunction(CREATE_FROM_PARCEL_NAME.identifier, declarationType).apply {
overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!)
val parcelParameter = addValueParameter("parcel", androidSymbols.androidOsParcel.defaultType)
// We need to defer the construction of the create method, since it may refer to the [Parcelable.Creator]
// instances in other @Parcelize classes in the current module, which may not exist yet.
defer {
body = androidSymbols.createBuilder(symbol).run {
irExprBody(
when {
parcelerObject != null ->
parcelerCreate(parcelerObject, parcelParameter)
parcelableProperties.isNotEmpty() ->
irCall(declaration.primaryConstructor!!).apply {
for ((index, property) in parcelableProperties.withIndex()) {
if (property != null) {
putValueArgument(index, readParcelWith(property.parceler, parcelParameter))
}
}
}
else ->
readParcelWith(declaration.classParceler, parcelParameter)
}
)
}
}
}
}
initializer = context.createIrBuilder(symbol).run {
irExprBody(irBlock {
+creatorClass
+irCall(creatorClass.primaryConstructor!!)
})
}
}
}
private val IrClass.classParceler: IrParcelSerializer
get() = if (kind == ClassKind.CLASS) {
IrNoParameterClassParcelSerializer(this)
} else {
serializerFactory.get(defaultType, parcelizeType = defaultType, strict = true, toplevel = true, scope = getParcelerScope())
}
protected class ParcelableProperty(val field: IrField, parcelerThunk: () -> IrParcelSerializer) {
val parceler by lazy(parcelerThunk)
}
private val serializerFactory = IrParcelSerializerFactory(androidSymbols)
protected val IrClass.parcelableProperties: List<ParcelableProperty?>
get() {
if (kind != ClassKind.CLASS) return emptyList()
val constructor = primaryConstructor ?: return emptyList()
val topLevelScope = getParcelerScope()
return constructor.valueParameters.map { parameter ->
val property = properties.firstOrNull { it.name == parameter.name }
if (property == null || property.hasAnyAnnotation(IGNORED_ON_PARCEL_FQ_NAMES)) {
null
} else {
val localScope = property.getParcelerScope(topLevelScope)
ParcelableProperty(property.backingField!!) {
serializerFactory.get(parameter.type, parcelizeType = defaultType, scope = localScope)
}
}
}
}
// *Heuristic* to determine if a Parcelable contains file descriptors.
private val IrType.containsFileDescriptors: Boolean
get() = erasedUpperBound.fqNameWhenAvailable == ParcelizeExtensionBase.FILE_DESCRIPTOR_FQNAME ||
(this as? IrSimpleType)?.arguments?.any { argument ->
argument.typeOrNull?.containsFileDescriptors == true
} == true
private fun IrPluginContext.createIrBuilder(symbol: IrSymbol): DeclarationIrBuilder {
return DeclarationIrBuilder(this, symbol, symbol.owner.startOffset, symbol.owner.endOffset)
}
}
@@ -0,0 +1,234 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
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.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
import org.jetbrains.kotlin.types.Variance
// true if the class should be processed by the parcelize plugin
val IrClass.isParcelize: Boolean
get() = kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS &&
(hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES) || superTypes.any { superType ->
superType.classOrNull?.owner?.let {
it.modality == Modality.SEALED && it.hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES)
} == true
})
// Finds the getter for a pre-existing CREATOR field on the class companion, which is used for manual Parcelable implementations in Kotlin.
val IrClass.creatorGetter: IrSimpleFunctionSymbol?
get() = companionObject()?.getPropertyGetter(CREATOR_NAME.asString())?.takeIf {
it.owner.correspondingPropertySymbol?.owner?.backingField?.hasAnnotation(FqName("kotlin.jvm.JvmField")) == true
}
// true if the class has a static CREATOR field
val IrClass.hasCreatorField: Boolean
get() = fields.any { field -> field.name == CREATOR_NAME } || creatorGetter != null
// object P : Parceler<T> { fun T.write(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelerWrite(
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 =
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? =
parceler?.parcelerSymbolByName(NEW_ARRAY_NAME.identifier)?.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_FQN
}?.let { newArraySymbol ->
irCall(newArraySymbol).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(size))
}
}
// class Parcelable { fun writeToParcel(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelableWriteToParcel(
parcelableClass: IrClass,
parcelable: IrExpression,
parcel: IrExpression,
flags: IrExpression
): IrExpression {
val writeToParcel = parcelableClass.functions.first { function ->
function.name == WRITE_TO_PARCEL_NAME && function.overridesFunctionIn(PARCELABLE_FQN)
}
return irCall(writeToParcel).apply {
dispatchReceiver = parcelable
putValueArgument(0, parcel)
putValueArgument(1, flags)
}
}
// class C : Parcelable.Creator<T> { fun createFromParcel(parcel: Parcel): T ...}
fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression, parcel: IrExpression): IrExpression {
val createFromParcel = creator.type.getClass()!!.functions.first { function ->
function.name == CREATE_FROM_PARCEL_NAME && function.overridesFunctionIn(CREATOR_FQN)
}
return irCall(createFromParcel).apply {
dispatchReceiver = creator
putValueArgument(0, parcel)
}
}
// Construct an expression to access the parcelable creator field in the given class.
fun AndroidIrBuilder.getParcelableCreator(irClass: IrClass): IrExpression {
// For classes annotated with `Parcelize` in the same module we can
// use the creator field directly, since we add it ourselves.
irClass.fields.find { it.name == CREATOR_NAME }?.let { creatorField ->
return irGetField(null, creatorField)
}
// For parcelable classes which do not use `Parcelize`, the creator field
// will be present as a `JvmField` on the companion object.
irClass.creatorGetter?.let { getter ->
return irCall(getter).apply {
dispatchReceiver = irGetObject(irClass.companionObject()!!.symbol)
}
}
// For classes in different modules, the creator field may not exist,
// since static fields are not present in the Kotlin metadata.
// As a workaround we create a synthetic field here together with a
// field access.
val creatorType = androidSymbols.androidOsParcelableCreator.typeWith(irClass.symbol.starProjectedType)
val creatorField = irClass.factory.createField(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB,
IrFieldSymbolImpl(), CREATOR_NAME, creatorType, DescriptorVisibilities.PUBLIC,
isFinal = true, isExternal = false, isStatic = true,
).also { it.parent = irClass }
return irGetField(null, creatorField)
}
// 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? =
functions.firstOrNull { function ->
function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQN)
}?.symbol
fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean =
parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName }
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 =
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 =
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)
+irWhile().apply {
condition = irNotEquals(irGet(indexTemporary), upperBound)
body = irBlock {
loopBody(indexTemporary)
val inc = context.irBuiltIns.intClass.getSimpleFunction("inc")!!
+irSet(indexTemporary.symbol, irCall(inc).apply {
dispatchReceiver = irGet(indexTemporary)
})
}
}
}
fun IrTypeArgument.upperBound(builtIns: IrBuiltIns): IrType =
when (this) {
is IrStarProjection -> builtIns.anyNType
is IrTypeProjection -> {
if (variance == Variance.OUT_VARIANCE || variance == Variance.INVARIANT)
type
else
builtIns.anyNType
}
else -> error("Unknown type argument: ${render()}")
}
private fun IrClass.getSimpleFunction(name: String): IrSimpleFunctionSymbol? =
findDeclaration<IrSimpleFunction> { it.name.asString() == name }?.symbol
// This is a version of getPropertyGetter which does not throw when applied to broken lazy classes, such as java.util.HashMap,
// which contains two "size" properties with different visibilities.
fun IrClass.getPropertyGetter(name: String): IrSimpleFunctionSymbol? =
declarations.filterIsInstance<IrProperty>().firstOrNull { it.name.asString() == name && it.getter != null }?.getter?.symbol
?: getSimpleFunction("<get-$name>")
fun IrClass.getMethodWithoutArguments(name: String): IrSimpleFunction =
functions.first { function ->
function.name.asString() == name && function.dispatchReceiverParameter != null
&& function.extensionReceiverParameter == null && function.valueParameters.isEmpty()
}
internal fun IrAnnotationContainer.hasAnyAnnotation(fqNames: List<FqName>): Boolean {
for (fqName in fqNames) {
if (hasAnnotation(fqName)) {
return true
}
}
return false
}
internal fun IrAnnotationContainer.getAnyAnnotation(fqNames: List<FqName>): IrConstructorCall? {
for (fqName in fqNames) {
val annotation = getAnnotation(fqName)
if (annotation != null) {
return annotation
}
}
return null
}