Parcelable: Support custom Parcelers in compiler plugin
This commit is contained in:
+163
@@ -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)
|
||||
}
|
||||
+34
-13
@@ -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
|
||||
}
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+37
-9
@@ -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
|
||||
}
|
||||
|
||||
+77
-13
@@ -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",
|
||||
|
||||
+2
@@ -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())
|
||||
}
|
||||
}
|
||||
+22
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
+10
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user