Parcelable: Add CREATOR field (and other generated declarations) in light classes (KT-19300, KT-19853)

This commit is contained in:
Yan Zhulanow
2017-12-16 01:45:54 +09:00
parent 90dff281ba
commit 145ddf3b1f
29 changed files with 129 additions and 66 deletions
@@ -144,7 +144,7 @@ class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptor
iv.anew(creatorType)
iv.dup()
iv.invokespecial(creatorName, "<init>", "()V", false)
iv.putstatic(parcelableName, "CREATOR", creatorType.descriptor)
iv.putstatic(parcelableName, "CREATOR", "Landroid/os/Parcelable\$Creator;")
}
super.visitInsn(opcode)
@@ -1,3 +1,5 @@
package org.jetbrains.kotlin.android.parcel
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
@@ -14,10 +16,9 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel
import kotlinx.android.parcel.TypeParceler
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.*
import org.jetbrains.kotlin.android.parcel.serializers.*
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -57,10 +58,14 @@ import java.io.FileDescriptor
open class ParcelableCodegenExtension : ExpressionCodegenExtension {
private companion object {
private val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
private val CREATOR_NAME = Name.identifier("CREATOR")
}
protected open fun isExperimental(element: KtElement) = true
override val shouldGenerateClassSyntheticPartsInLightClassesMode: Boolean
get() = true
override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) {
val parcelableClass = codegen.descriptor
if (!parcelableClass.isParcelize) return
@@ -68,24 +73,60 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
val sourceElement = (codegen.myClass as? KtClassOrObject) ?: return
if (!isExperimental(sourceElement)) return
assert(parcelableClass.kind == ClassKind.CLASS || parcelableClass.kind == ClassKind.OBJECT)
if (parcelableClass.kind != ClassKind.CLASS && parcelableClass.kind != ClassKind.OBJECT) return
val propertiesToSerialize = getPropertiesToSerialize(codegen, parcelableClass)
val parcelClassType = ParcelableResolveExtension.resolveParcelClassType(parcelableClass.module)
val parcelAsmType = codegen.typeMapper.mapType(parcelClassType)
val parcelerObject = parcelableClass.companionObjectDescriptor?.takeIf {
TypeUtils.getAllSupertypes(it.defaultType).any { it.isParceler }
}
with (parcelableClass) {
writeDescribeContentsFunction(codegen, propertiesToSerialize)
writeWriteToParcel(codegen, propertiesToSerialize, parcelAsmType, parcelerObject)
if (hasSyntheticDescribeContents()) {
writeDescribeContentsFunction(codegen, propertiesToSerialize)
}
if (hasSyntheticWriteToParcel()) {
writeWriteToParcel(codegen, propertiesToSerialize, PARCEL_TYPE, parcelerObject)
}
if (!hasCreatorField()) {
writeCreatorAccessField(codegen)
}
}
writeCreatorAccessField(codegen, parcelableClass)
writeCreatorClass(codegen, parcelableClass, parcelClassType, parcelAsmType, parcelerObject, propertiesToSerialize)
if (codegen.state.classBuilderMode != ClassBuilderMode.LIGHT_CLASSES) {
val parcelClassType = ParcelableResolveExtension.resolveParcelClassType(parcelableClass.module)
?: error("Can't resolve 'android.os.Parcel' class")
writeCreatorClass(codegen, parcelableClass, parcelClassType, PARCEL_TYPE, parcelerObject, propertiesToSerialize)
}
}
private fun ClassDescriptor.hasCreatorField(): Boolean {
val companionObject = companionObjectDescriptor ?: return false
if (companionObject.name == CREATOR_NAME) {
return true
}
return companionObject.unsubstitutedMemberScope
.getContributedVariables(CREATOR_NAME, NoLookupLocation.FROM_BACKEND)
.isNotEmpty()
}
private fun ClassDescriptor.hasSyntheticDescribeContents() = hasParcelizeSyntheticMethod(ComponentKind.DESCRIBE_CONTENTS)
private fun ClassDescriptor.hasSyntheticWriteToParcel() = hasParcelizeSyntheticMethod(ComponentKind.WRITE_TO_PARCEL)
private fun ClassDescriptor.hasParcelizeSyntheticMethod(componentKind: ParcelableSyntheticComponent.ComponentKind): Boolean {
val methodName = Name.identifier(componentKind.methodName)
val writeToParcelMethods = unsubstitutedMemberScope
.getContributedFunctions(methodName, NoLookupLocation.FROM_BACKEND)
.filter { it is ParcelableSyntheticComponent && it.componentKind == componentKind }
return writeToParcelMethods.size == 1
}
private fun getCompanionClassType(containerAsmType: Type, parcelerObject: ClassDescriptor): Pair<Type, String> {
@@ -166,11 +207,10 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor
): List<PropertyToSerialize> {
val constructor = parcelableClass.constructors.first { it.isPrimary }
val constructor = parcelableClass.constructors.firstOrNull { it.isPrimary } ?: return emptyList()
val propertiesToSerialize = constructor.valueParameters.map { param ->
val propertiesToSerialize = constructor.valueParameters.mapNotNull { param ->
codegen.bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, param]
?: error("Value parameter should have 'val' or 'var' keyword")
}
val classParcelers = getTypeParcelers(parcelableClass.annotations)
@@ -228,12 +268,11 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
}
}
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) {
val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val creatorAsmType = Type.getObjectType(parcelableAsmType.internalName + "\$Creator")
private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen) {
val creatorType = Type.getObjectType("android/os/Parcelable\$Creator")
codegen.v.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_STATIC or ACC_PUBLIC or ACC_FINAL, "CREATOR",
creatorAsmType.descriptor, null, null)
creatorType.descriptor, null, null)
}
private fun writeCreatorClass(
@@ -33,14 +33,14 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
open class ParcelableResolveExtension : SyntheticResolveExtension {
companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType {
return module.findClassAcrossModuleDependencies(
ClassId.topLevel(FqName("android.os.Parcel")))?.defaultType ?: error("Can't resolve 'android.os.Parcel' class")
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? {
return module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("android.os.Parcel")))?.defaultType
}
fun createMethod(
@@ -103,7 +103,7 @@ open class ParcelableResolveExtension : SyntheticResolveExtension {
&& result.none { it.isWriteToParcel() }
) {
val builtIns = clazz.builtIns
val parcelClassType = resolveParcelClassType(clazz.module)
val parcelClassType = resolveParcelClassType(clazz.module) ?: ErrorUtils.createErrorType("Unresolved 'Parcel' type")
result += createMethod(clazz, WRITE_TO_PARCEL, builtIns.unitType, "parcel" to parcelClassType, "flags" to builtIns.intType)
}
}
@@ -120,12 +120,11 @@ open class ParcelableResolveExtension : SyntheticResolveExtension {
internal fun SimpleFunctionDescriptor.isWriteToParcel(): Boolean {
return typeParameters.isEmpty()
&& valueParameters.size == 2
&& valueParameters[0].type.isParcel()
// Unfortunately, we can't check the first parameter cause it can be unresolved
&& KotlinBuiltIns.isInt(valueParameters[1].type)
&& returnType?.let { KotlinBuiltIns.isUnit(it) } == true
}
private fun KotlinType.isParcel() = constructor.declarationDescriptor?.fqNameSafe == ANDROID_PARCEL_CLASS_FQNAME
interface ParcelableSyntheticComponent {
val componentKind: ComponentKind
@@ -534,7 +534,7 @@ internal class EfficientParcelableParcelSerializer(override val asmType: Type, p
override fun readValue(v: InstructionAdapter) {
// -> parcel
v.getstatic(asmType.internalName, "CREATOR", creatorAsmType.descriptor) // -> parcel, creator
v.getstatic(asmType.internalName, "CREATOR", "Landroid/os/Parcelable\$Creator;") // -> parcel, creator
v.swap() // -> creator, parcel
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
v.castIfNeeded(asmType)
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy
import org.jetbrains.kotlin.android.synthetic.descriptors.AndroidSyntheticPackageFragmentDescriptor
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticFunction
import org.jetbrains.kotlin.android.synthetic.res.AndroidSyntheticProperty
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.FunctionCodegen
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*