Parcelable: Support custom Parcelers in compiler plugin

This commit is contained in:
Yan Zhulanow
2017-09-14 23:00:33 +03:00
parent 7dbefc1613
commit a63aca08f2
16 changed files with 603 additions and 38 deletions
@@ -0,0 +1,163 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.android.parcel
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import kotlinx.android.parcel.TypeParceler
import kotlinx.android.parcel.WriteWith
import org.jetbrains.kotlin.android.synthetic.diagnostic.DefaultErrorMessagesAndroid
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.reportFromPlugin
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.replaceAnnotations
import org.jetbrains.kotlin.types.typeUtil.supertypes
class ParcelableAnnotationChecker : CallChecker {
companion object {
val TYPE_PARCELER_FQNAME = FqName(TypeParceler::class.java.name)
val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name)
}
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val constructorDescriptor = resolvedCall.resultingDescriptor as? ClassConstructorDescriptor ?: return
val annotationClass = constructorDescriptor.constructedClass.takeIf { it.kind == ClassKind.ANNOTATION_CLASS } ?: return
val annotationEntry = resolvedCall.call.callElement.getNonStrictParentOfType<KtAnnotationEntry>() ?: return
val annotationOwner = annotationEntry.getStrictParentOfType<KtModifierListOwner>() ?: return
if (annotationClass.fqNameSafe == TYPE_PARCELER_FQNAME) {
checkTypeParcelerUsage(resolvedCall, annotationEntry, context, annotationOwner)
}
if (annotationClass.fqNameSafe == WRITE_WITH_FQNAME) {
checkWriteWithUsage(resolvedCall, annotationEntry, context, annotationOwner)
}
}
private fun checkTypeParcelerUsage(
resolvedCall: ResolvedCall<*>,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext,
element: KtModifierListOwner
) {
val descriptor = context.trace[BindingContext.DECLARATION_TO_DESCRIPTOR, element] ?: return
val thisMappedType = resolvedCall.typeArguments.values.takeIf { it.size == 2 }?.first() ?: return
val duplicatingAnnotationCount = descriptor.annotations.getAllAnnotations()
.filter { it.annotation.fqName == TYPE_PARCELER_FQNAME }
.mapNotNull { it.annotation.type.arguments.takeIf { it.size == 2 }?.first()?.type }
.count { it == thisMappedType }
if (duplicatingAnnotationCount > 1) {
val reportElement = annotationEntry.typeArguments.firstOrNull() ?: annotationEntry
context.trace.reportFromPlugin(ErrorsAndroid.DUPLICATING_TYPE_PARCELERS.on(reportElement), DefaultErrorMessagesAndroid)
return
}
val containingClass = when (element) {
is KtClassOrObject -> element
is KtParameter -> element.containingClassOrObject
else -> null
}
checkIfTheContainingClassIsParcelize(containingClass, annotationEntry, context)
if (element is KtParameter && element.getStrictParentOfType<KtDeclaration>() is KtPrimaryConstructor) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
val thisAnnotationDescriptor = context.trace[BindingContext.ANNOTATION, annotationEntry]
if (containingClass != null && containingClassDescriptor != null && thisAnnotationDescriptor != null) {
// We can ignore value arguments here cause @TypeParceler is a zero-parameter annotation
if (containingClassDescriptor.annotations.any { it.type == thisAnnotationDescriptor.type }) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.reportFromPlugin(
ErrorsAndroid.REDUNDANT_TYPE_PARCELER.on(reportElement, containingClass), DefaultErrorMessagesAndroid)
}
}
}
}
private fun checkWriteWithUsage(
resolvedCall: ResolvedCall<*>,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext,
element: KtModifierListOwner
) {
element as? KtTypeReference ?: return
val actualType = context.trace[BindingContext.TYPE, element]?.replaceAnnotations(Annotations.EMPTY) ?: return
val parcelerType = resolvedCall.typeArguments.values.singleOrNull() ?: return
val parcelerClass = parcelerType.constructor.declarationDescriptor as? ClassDescriptor ?: return
val containingClass = element.getStrictParentOfType<KtClassOrObject>()
checkIfTheContainingClassIsParcelize(containingClass, annotationEntry, context)
fun reportElement() = annotationEntry.typeArguments.singleOrNull() ?: annotationEntry
if (parcelerClass.kind != ClassKind.OBJECT) {
context.trace.reportFromPlugin(ErrorsAndroid.PARCELER_SHOULD_BE_OBJECT.on(reportElement()), DefaultErrorMessagesAndroid)
return
}
fun KotlinType.fqName() = constructor.declarationDescriptor?.fqNameSafe
val parcelerSuperType = parcelerClass.defaultType.supertypes().firstOrNull { it.fqName() == PARCELER_FQNAME } ?: return
val expectedType = parcelerSuperType.arguments.singleOrNull()?.type ?: return
if (!actualType.isSubtypeOf(expectedType)) {
context.trace.reportFromPlugin(ErrorsAndroid.PARCELER_TYPE_INCOMPATIBLE.on(reportElement(), expectedType, actualType),
DefaultErrorMessagesAndroid)
}
}
private fun checkIfTheContainingClassIsParcelize(
containingClass: KtClassOrObject?,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext
) {
if (containingClass != null) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.reportFromPlugin(ErrorsAndroid.CLASS_SHOULD_BE_PARCELIZE.on(reportElement, containingClass),
DefaultErrorMessagesAndroid)
}
}
}
}
internal inline fun <reified T : PsiElement> PsiElement.getStrictParentOfType(): T? {
return PsiTreeUtil.getParentOfType(this, T::class.java, true)
}
internal inline fun <reified T : PsiElement> PsiElement.getNonStrictParentOfType(): T? {
return PsiTreeUtil.getParentOfType(this, T::class.java, false)
}
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.android.parcel
import kotlinx.android.parcel.TypeParceler
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.android.parcel.serializers.PARCEL_TYPE
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
import org.jetbrains.kotlin.android.parcel.serializers.TypeParcelerMapping
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
@@ -35,6 +37,7 @@ import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.context.ClassContext
import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor
import org.jetbrains.kotlin.codegen.writeSyntheticClassMetadata
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.*
@@ -94,7 +97,7 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
private fun ClassDescriptor.writeWriteToParcel(
codegen: ImplementationBodyCodegen,
properties: List<Pair<String, KotlinType>>,
properties: List<PropertyToSerialize>,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?
): Unit? {
@@ -114,16 +117,16 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false)
}
else {
val context = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType)
val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList())
for ((fieldName, type) in properties) {
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, context)
val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers))
serializer.writeValue(v)
}
}
@@ -134,9 +137,9 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
private fun ClassDescriptor.writeDescribeContentsFunction(
codegen: ImplementationBodyCodegen,
propertiesToSerialize: List<Pair<String, KotlinType>>
propertiesToSerialize: List<PropertyToSerialize>
): Unit? {
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.second.containsFileDescriptor() }
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.type.containsFileDescriptor() }
return findFunction(DESCRIBE_CONTENTS)?.write(codegen) {
v.aconst(if (hasFileDescriptorAnywhere) 1 /* CONTENTS_FILE_DESCRIPTOR */ else 0)
@@ -155,10 +158,12 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
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<Pair<String, KotlinType>> {
): List<PropertyToSerialize> {
val constructor = parcelableClass.constructors.first { it.isPrimary }
val propertiesToSerialize = constructor.valueParameters.map { param ->
@@ -166,7 +171,11 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
?: error("Value parameter should have 'val' or 'var' keyword")
}
return propertiesToSerialize.map { it.name.asString() /* TODO */ to it.type }
val classParcelers = getTypeParcelers(parcelableClass.annotations)
return propertiesToSerialize.map {
PropertyToSerialize(it.name.asString(), it.type, classParcelers + getTypeParcelers(it.annotations))
}
}
private fun writeCreateFromParcel(
@@ -176,7 +185,7 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
parcelClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<Pair<String, KotlinType>>
properties: List<PropertyToSerialize>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
@@ -195,13 +204,13 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
v.dup()
val asmConstructorParameters = StringBuilder()
val context = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType)
val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList())
for ((_, type) in properties) {
for ((_, type, parcelers) in properties) {
val asmType = codegen.typeMapper.mapType(type)
asmConstructorParameters.append(asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, context)
val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers))
v.load(1, parcelAsmType)
serializer.readValue(v)
}
@@ -228,7 +237,7 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
parcelClassType: KotlinType,
parcelAsmType: Type,
parcelerObject: ClassDescriptor?,
properties: List<Pair<String, KotlinType>>
properties: List<PropertyToSerialize>
) {
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
val creatorAsmType = Type.getObjectType(
@@ -335,4 +344,16 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
.getContributedFunctions(Name.identifier(componentKind.methodName), WHEN_GET_ALL_DESCRIPTORS)
.firstOrNull { (it as? ParcelableSyntheticComponent)?.componentKind == componentKind }
}
}
internal fun getTypeParcelers(annotations: Annotations): List<TypeParcelerMapping> {
val typeParcelerFqName = FqName(TypeParceler::class.java.name)
val serializers = mutableListOf<TypeParcelerMapping>()
for (anno in annotations.filter { it.fqName == typeParcelerFqName }) {
val (mappedType, parcelerType) = anno.type.arguments.takeIf { it.size == 2 } ?: continue
serializers += TypeParcelerMapping(mappedType.type, parcelerType.type)
}
return serializers
}
@@ -212,7 +212,8 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
val asmType = typeMapper.mapType(type)
try {
val context = ParcelSerializer.ParcelSerializerContext(typeMapper, typeMapper.mapType(containerClass.defaultType))
val parcelers = getTypeParcelers(descriptor.annotations) + getTypeParcelers(containerClass.annotations)
val context = ParcelSerializer.ParcelSerializerContext(typeMapper, typeMapper.mapType(containerClass.defaultType), parcelers)
ParcelSerializer.get(type, asmType, context, strict = true)
}
catch (e: IllegalArgumentException) {
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.android.parcel.serializers
import kotlinx.android.parcel.WriteWith
import org.jetbrains.kotlin.android.parcel.isParcelize
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -41,17 +43,30 @@ import java.util.concurrent.ConcurrentHashMap
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
internal typealias TypeParcelerMapping = Pair<KotlinType, KotlinType>
interface ParcelSerializer {
val asmType: Type
fun writeValue(v: InstructionAdapter)
fun readValue(v: InstructionAdapter)
class ParcelSerializerContext(val typeMapper: KotlinTypeMapper, val containerClassType: Type)
data class ParcelSerializerContext(
val typeMapper: KotlinTypeMapper,
val containerClassType: Type,
val typeParcelers: List<TypeParcelerMapping>
) {
fun findParcelerClass(type: KotlinType): KotlinType? {
return typeParcelers.firstOrNull { it.first == type }?.second
}
}
companion object {
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType): Type {
return if (type.isError) Type.getObjectType("java/lang/Object") else mapType(type)
private val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name)
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType, forceBoxed: Boolean) = when {
type.isError -> Type.getObjectType("java/lang/Object")
else -> mapType(type, null, if (forceBoxed) TypeMappingMode.GENERIC_ARGUMENT else TypeMappingMode.DEFAULT)
}
fun get(
@@ -66,6 +81,19 @@ interface ParcelSerializer {
val className = asmType.className
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
type.annotations.findAnnotation(WRITE_WITH_FQNAME)?.let { writeWith ->
val parceler = writeWith.type.arguments.singleOrNull()?.type
if (parceler != null && !parceler.isError) {
return TypeParcelerParcelSerializer(asmType, parceler, context.typeMapper)
}
}
context.findParcelerClass(type)?.let { typeParceler ->
if (!typeParceler.isError) {
return TypeParcelerParcelSerializer(asmType, typeParceler, context.typeMapper)
}
}
return when {
asmType.descriptor == "[I"
|| asmType.descriptor == "[Z"
@@ -83,7 +111,7 @@ interface ParcelSerializer {
asmType.sort == Type.ARRAY -> {
val elementType = type.builtIns.getArrayElementType(type)
val elementSerializer = get(elementType, typeMapper.mapTypeSafe(elementType), context, strict = strict())
val elementSerializer = get(elementType, typeMapper.mapTypeSafe(elementType, forceBoxed = false), context, strict = strict())
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
}
@@ -110,7 +138,7 @@ interface ParcelSerializer {
|| className == TreeSet::class.java.canonicalName
-> {
val elementType = type.arguments.single().type
val elementAsmType = typeMapper.mapTypeSafe(elementType)
val elementAsmType = typeMapper.mapTypeSafe(elementType, forceBoxed = true)
if (className == List::class.java.canonicalName) {
// Don't care if the element type is nullable cause both writeStrongBinder() and writeString() support null values
@@ -137,9 +165,9 @@ interface ParcelSerializer {
-> {
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
val keySerializer = get(
keyType.type, typeMapper.mapTypeSafe(keyType.type), context, forceBoxed = true, strict = strict())
keyType.type, typeMapper.mapTypeSafe(keyType.type, forceBoxed = true), context, forceBoxed = true, strict = strict())
val valueSerializer = get(
valueType.type, typeMapper.mapTypeSafe(valueType.type), context, forceBoxed = true, strict = strict())
valueType.type, typeMapper.mapTypeSafe(valueType.type, forceBoxed = true), context, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
}
@@ -186,7 +214,7 @@ interface ParcelSerializer {
asmType.isSparseArray() -> {
val elementType = type.arguments.single().type
val elementSerializer = get(
elementType, typeMapper.mapTypeSafe(elementType), context, forceBoxed = true, strict = strict())
elementType, typeMapper.mapTypeSafe(elementType, forceBoxed = true), context, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
}
@@ -214,7 +242,7 @@ interface ParcelSerializer {
}
val creatorAsmType = when {
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true)
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
else -> null
}
@@ -16,14 +16,19 @@
package org.jetbrains.kotlin.android.parcel.serializers
import kotlinx.android.parcel.Parceler
import org.jetbrains.kotlin.android.parcel.serializers.BoxedPrimitiveTypeParcelSerializer.Companion.BOXED_VALUE_METHOD_NAMES
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
internal val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
internal val PARCELER_TYPE = Type.getObjectType(Parceler::class.java.name.replace(".", "/"))
internal class GenericParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
@@ -38,6 +43,63 @@ internal class GenericParcelSerializer(override val asmType: Type) : ParcelSeria
}
}
internal class TypeParcelerParcelSerializer(
override val asmType: Type,
private val parcelerType: KotlinType,
private val typeMapper: KotlinTypeMapper
) : ParcelSerializer {
private val parcelerAsmType = typeMapper.mapType(parcelerType)
override fun writeValue(v: InstructionAdapter) {
// -> parcel, value(?)
boxTypeIfNeeded(v) // -> parcel, (boxed)value
v.swap() // -> value, parcel
putObjectInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> value, parcel, parceler
v.dupX2() // -> parceler, value, parcel, parceler
v.pop() // -> parceler, value, parcel
v.load(2, Type.INT_TYPE) // -> parceler, value, parcel, flags
v.invokeinterface(PARCELER_TYPE.internalName, "write", "(Ljava/lang/Object;Landroid/os/Parcel;I)V")
}
override fun readValue(v: InstructionAdapter) {
// -> parcel
putObjectInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> parcel, parceler
v.swap() // -> parceler, parcel
v.invokeinterface(PARCELER_TYPE.internalName, "create", "(Landroid/os/Parcel;)Ljava/lang/Object;") // -> obj
unboxTypeIfNeeded(v)
v.castIfNeeded(asmType)
}
private fun handleSpecialBoxingCases(v: InstructionAdapter): Type? {
assert(asmType.sort != Type.METHOD)
if (asmType.sort == Type.OBJECT || asmType.sort == Type.ARRAY) {
return null
}
if (asmType == Type.VOID_TYPE) {
v.pop()
v.aconst(null)
return null
}
return AsmUtil.boxType(asmType)
}
private fun boxTypeIfNeeded(v: InstructionAdapter) {
val boxedType = handleSpecialBoxingCases(v) ?: return
v.invokestatic(boxedType.internalName, "valueOf", "(${asmType.descriptor})${boxedType.descriptor}", false)
}
private fun unboxTypeIfNeeded(v: InstructionAdapter) {
val boxedType = handleSpecialBoxingCases(v) ?: return
val getValueMethodName = BOXED_VALUE_METHOD_NAMES.getValue(boxedType.internalName)
v.castIfNeeded(boxedType)
v.invokevirtual(boxedType.internalName, getValueMethodName, "()${asmType.descriptor}", false)
}
}
internal class ArrayParcelSerializer(override val asmType: Type, private val elementSerializer: ParcelSerializer) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.dupX1() // -> arr, parcel, arr
@@ -401,21 +463,23 @@ internal class ObjectParcelSerializer(
override fun readValue(v: InstructionAdapter) {
v.pop()
// Handle companion object
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
if (clazz != null && clazz.isCompanionObject) {
val outerClass = clazz.containingDeclaration as? ClassDescriptor
if (outerClass != null) {
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
return
}
}
v.getstatic(asmType.internalName, "INSTANCE", asmType.descriptor)
putObjectInstanceOnStack(type, asmType, typeMapper, v)
}
}
private fun putObjectInstanceOnStack(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, v: InstructionAdapter) {
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
if (clazz != null && clazz.isCompanionObject) {
val outerClass = clazz.containingDeclaration as? ClassDescriptor
if (outerClass != null) {
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
return
}
}
v.getstatic(asmType.internalName, "INSTANCE", asmType.descriptor)
}
internal class EnumParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual("java/lang/Enum", "name", "()Ljava/lang/String;", false)
@@ -553,7 +617,7 @@ internal class BoxedPrimitiveTypeParcelSerializer private constructor(val unboxe
private val UNBOXED_TO_BOXED_TYPE_MAPPINGS = BOXED_TO_UNBOXED_TYPE_MAPPINGS.map { it.value to it.key }.toMap()
private val BOXED_VALUE_METHOD_NAMES = mapOf(
internal val BOXED_VALUE_METHOD_NAMES = mapOf(
"java/lang/Boolean" to "booleanValue",
"java/lang/Character" to "charValue",
"java/lang/Byte" to "byteValue",
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.android.synthetic
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import kotlinx.android.extensions.CacheImplementation
import org.jetbrains.kotlin.android.parcel.ParcelableAnnotationChecker
import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension
import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
@@ -135,5 +136,6 @@ class AndroidExtensionPropertiesComponentContainerContributor : StorageComponent
container.useInstance(AndroidExtensionPropertiesCallChecker())
container.useInstance(ParcelableDeclarationChecker())
container.useInstance(ParcelableAnnotationChecker())
}
}
@@ -19,6 +19,8 @@ package org.jetbrains.kotlin.android.synthetic.diagnostic
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_CLASS_OR_OBJECT
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_TYPE
object DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
private val MAP = DiagnosticFactoryToRendererMap("Android")
@@ -73,13 +75,31 @@ object DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
"Property would not be serialized into a 'Parcel'. Add '@Transient' annotation to remove the warning")
MAP.put(ErrorsAndroid.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED,
"Overriding 'writeToParcel' is not allowed. Use 'Parceler' companion object instead.")
"Overriding 'writeToParcel' is not allowed. Use 'Parceler' companion object instead")
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_NOT_ALLOWED,
"'CREATOR' definition is not allowed. Use 'Parceler' companion object instead.")
"'CREATOR' definition is not allowed. Use 'Parceler' companion object instead")
MAP.put(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED,
"Type is not directly supported by 'Parcelize'. " +
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'")
MAP.put(ErrorsAndroid.PARCELER_SHOULD_BE_OBJECT,
"Parceler should be an object")
MAP.put(ErrorsAndroid.PARCELER_TYPE_INCOMPATIBLE,
"Parceler type {0} is incompatible with {1}",
RENDER_TYPE, RENDER_TYPE)
MAP.put(ErrorsAndroid.DUPLICATING_TYPE_PARCELERS,
"Duplicating ''TypeParceler'' annotations")
MAP.put(ErrorsAndroid.REDUNDANT_TYPE_PARCELER,
"This ''TypeParceler'' is already provided for {0}",
RENDER_CLASS_OR_OBJECT)
MAP.put(ErrorsAndroid.CLASS_SHOULD_BE_PARCELIZE,
"{0} should be annotated with ''@Parcelize''",
RENDER_CLASS_OR_OBJECT)
}
}
@@ -19,8 +19,13 @@ package org.jetbrains.kotlin.android.synthetic.diagnostic;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.psi.KtAnnotationEntry;
import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.types.KotlinType;
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
@@ -45,6 +50,11 @@ public interface ErrorsAndroid {
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELER_SHOULD_BE_OBJECT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> PARCELER_TYPE_INCOMPATIBLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<PsiElement> DUPLICATING_TYPE_PARCELERS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, KtClassOrObject> REDUNDANT_TYPE_PARCELER = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, KtClassOrObject> CLASS_SHOULD_BE_PARCELIZE = DiagnosticFactory1.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {