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
|
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.ParcelableSyntheticComponent.ComponentKind.*
|
||||||
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
|
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.PARCEL_TYPE
|
||||||
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
|
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.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||||
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
|
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.context.ClassContext
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor
|
import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor
|
||||||
import org.jetbrains.kotlin.codegen.writeSyntheticClassMetadata
|
import org.jetbrains.kotlin.codegen.writeSyntheticClassMetadata
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
|
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
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(
|
private fun ClassDescriptor.writeWriteToParcel(
|
||||||
codegen: ImplementationBodyCodegen,
|
codegen: ImplementationBodyCodegen,
|
||||||
properties: List<Pair<String, KotlinType>>,
|
properties: List<PropertyToSerialize>,
|
||||||
parcelAsmType: Type,
|
parcelAsmType: Type,
|
||||||
parcelerObject: ClassDescriptor?
|
parcelerObject: ClassDescriptor?
|
||||||
): Unit? {
|
): Unit? {
|
||||||
@@ -114,16 +117,16 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false)
|
"(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false)
|
||||||
}
|
}
|
||||||
else {
|
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)
|
val asmType = codegen.typeMapper.mapType(type)
|
||||||
|
|
||||||
v.load(1, parcelAsmType)
|
v.load(1, parcelAsmType)
|
||||||
v.load(0, containerAsmType)
|
v.load(0, containerAsmType)
|
||||||
v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor)
|
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)
|
serializer.writeValue(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,9 +137,9 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
|
|
||||||
private fun ClassDescriptor.writeDescribeContentsFunction(
|
private fun ClassDescriptor.writeDescribeContentsFunction(
|
||||||
codegen: ImplementationBodyCodegen,
|
codegen: ImplementationBodyCodegen,
|
||||||
propertiesToSerialize: List<Pair<String, KotlinType>>
|
propertiesToSerialize: List<PropertyToSerialize>
|
||||||
): Unit? {
|
): Unit? {
|
||||||
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.second.containsFileDescriptor() }
|
val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.type.containsFileDescriptor() }
|
||||||
|
|
||||||
return findFunction(DESCRIBE_CONTENTS)?.write(codegen) {
|
return findFunction(DESCRIBE_CONTENTS)?.write(codegen) {
|
||||||
v.aconst(if (hasFileDescriptorAnywhere) 1 /* CONTENTS_FILE_DESCRIPTOR */ else 0)
|
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() }
|
return this.arguments.any { it.type.containsFileDescriptor() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class PropertyToSerialize(val name: String, val type: KotlinType, val parcelers: List<TypeParcelerMapping>)
|
||||||
|
|
||||||
private fun getPropertiesToSerialize(
|
private fun getPropertiesToSerialize(
|
||||||
codegen: ImplementationBodyCodegen,
|
codegen: ImplementationBodyCodegen,
|
||||||
parcelableClass: ClassDescriptor
|
parcelableClass: ClassDescriptor
|
||||||
): List<Pair<String, KotlinType>> {
|
): List<PropertyToSerialize> {
|
||||||
val constructor = parcelableClass.constructors.first { it.isPrimary }
|
val constructor = parcelableClass.constructors.first { it.isPrimary }
|
||||||
|
|
||||||
val propertiesToSerialize = constructor.valueParameters.map { param ->
|
val propertiesToSerialize = constructor.valueParameters.map { param ->
|
||||||
@@ -166,7 +171,11 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
?: error("Value parameter should have 'val' or 'var' keyword")
|
?: 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(
|
private fun writeCreateFromParcel(
|
||||||
@@ -176,7 +185,7 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
parcelClassType: KotlinType,
|
parcelClassType: KotlinType,
|
||||||
parcelAsmType: Type,
|
parcelAsmType: Type,
|
||||||
parcelerObject: ClassDescriptor?,
|
parcelerObject: ClassDescriptor?,
|
||||||
properties: List<Pair<String, KotlinType>>
|
properties: List<PropertyToSerialize>
|
||||||
) {
|
) {
|
||||||
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
|
val containerAsmType = codegen.typeMapper.mapType(parcelableClass)
|
||||||
|
|
||||||
@@ -195,13 +204,13 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
v.dup()
|
v.dup()
|
||||||
|
|
||||||
val asmConstructorParameters = StringBuilder()
|
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)
|
val asmType = codegen.typeMapper.mapType(type)
|
||||||
asmConstructorParameters.append(asmType.descriptor)
|
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)
|
v.load(1, parcelAsmType)
|
||||||
serializer.readValue(v)
|
serializer.readValue(v)
|
||||||
}
|
}
|
||||||
@@ -228,7 +237,7 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
parcelClassType: KotlinType,
|
parcelClassType: KotlinType,
|
||||||
parcelAsmType: Type,
|
parcelAsmType: Type,
|
||||||
parcelerObject: ClassDescriptor?,
|
parcelerObject: ClassDescriptor?,
|
||||||
properties: List<Pair<String, KotlinType>>
|
properties: List<PropertyToSerialize>
|
||||||
) {
|
) {
|
||||||
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
|
val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType)
|
||||||
val creatorAsmType = Type.getObjectType(
|
val creatorAsmType = Type.getObjectType(
|
||||||
@@ -335,4 +344,16 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
|||||||
.getContributedFunctions(Name.identifier(componentKind.methodName), WHEN_GET_ALL_DESCRIPTORS)
|
.getContributedFunctions(Name.identifier(componentKind.methodName), WHEN_GET_ALL_DESCRIPTORS)
|
||||||
.firstOrNull { (it as? ParcelableSyntheticComponent)?.componentKind == componentKind }
|
.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)
|
val asmType = typeMapper.mapType(type)
|
||||||
|
|
||||||
try {
|
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)
|
ParcelSerializer.get(type, asmType, context, strict = true)
|
||||||
}
|
}
|
||||||
catch (e: IllegalArgumentException) {
|
catch (e: IllegalArgumentException) {
|
||||||
|
|||||||
+37
-9
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.android.parcel.serializers
|
package org.jetbrains.kotlin.android.parcel.serializers
|
||||||
|
|
||||||
|
import kotlinx.android.parcel.WriteWith
|
||||||
import org.jetbrains.kotlin.android.parcel.isParcelize
|
import org.jetbrains.kotlin.android.parcel.isParcelize
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
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.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
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.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
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")
|
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
|
||||||
|
|
||||||
|
internal typealias TypeParcelerMapping = Pair<KotlinType, KotlinType>
|
||||||
|
|
||||||
interface ParcelSerializer {
|
interface ParcelSerializer {
|
||||||
val asmType: Type
|
val asmType: Type
|
||||||
|
|
||||||
fun writeValue(v: InstructionAdapter)
|
fun writeValue(v: InstructionAdapter)
|
||||||
fun readValue(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 {
|
companion object {
|
||||||
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType): Type {
|
private val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name)
|
||||||
return if (type.isError) Type.getObjectType("java/lang/Object") else mapType(type)
|
|
||||||
|
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(
|
fun get(
|
||||||
@@ -66,6 +81,19 @@ interface ParcelSerializer {
|
|||||||
val className = asmType.className
|
val className = asmType.className
|
||||||
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
|
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 {
|
return when {
|
||||||
asmType.descriptor == "[I"
|
asmType.descriptor == "[I"
|
||||||
|| asmType.descriptor == "[Z"
|
|| asmType.descriptor == "[Z"
|
||||||
@@ -83,7 +111,7 @@ interface ParcelSerializer {
|
|||||||
|
|
||||||
asmType.sort == Type.ARRAY -> {
|
asmType.sort == Type.ARRAY -> {
|
||||||
val elementType = type.builtIns.getArrayElementType(type)
|
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))
|
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
|
||||||
}
|
}
|
||||||
@@ -110,7 +138,7 @@ interface ParcelSerializer {
|
|||||||
|| className == TreeSet::class.java.canonicalName
|
|| className == TreeSet::class.java.canonicalName
|
||||||
-> {
|
-> {
|
||||||
val elementType = type.arguments.single().type
|
val elementType = type.arguments.single().type
|
||||||
val elementAsmType = typeMapper.mapTypeSafe(elementType)
|
val elementAsmType = typeMapper.mapTypeSafe(elementType, forceBoxed = true)
|
||||||
|
|
||||||
if (className == List::class.java.canonicalName) {
|
if (className == List::class.java.canonicalName) {
|
||||||
// Don't care if the element type is nullable cause both writeStrongBinder() and writeString() support null values
|
// 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 (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
|
||||||
val keySerializer = get(
|
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(
|
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))
|
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +214,7 @@ interface ParcelSerializer {
|
|||||||
asmType.isSparseArray() -> {
|
asmType.isSparseArray() -> {
|
||||||
val elementType = type.arguments.single().type
|
val elementType = type.arguments.single().type
|
||||||
val elementSerializer = get(
|
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))
|
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +242,7 @@ interface ParcelSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val creatorAsmType = when {
|
val creatorAsmType = when {
|
||||||
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
|
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true)
|
||||||
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
|
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-13
@@ -16,14 +16,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.android.parcel.serializers
|
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.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
|
|
||||||
internal val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
|
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 {
|
internal class GenericParcelSerializer(override val asmType: Type) : ParcelSerializer {
|
||||||
override fun writeValue(v: InstructionAdapter) {
|
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 {
|
internal class ArrayParcelSerializer(override val asmType: Type, private val elementSerializer: ParcelSerializer) : ParcelSerializer {
|
||||||
override fun writeValue(v: InstructionAdapter) {
|
override fun writeValue(v: InstructionAdapter) {
|
||||||
v.dupX1() // -> arr, parcel, arr
|
v.dupX1() // -> arr, parcel, arr
|
||||||
@@ -401,21 +463,23 @@ internal class ObjectParcelSerializer(
|
|||||||
|
|
||||||
override fun readValue(v: InstructionAdapter) {
|
override fun readValue(v: InstructionAdapter) {
|
||||||
v.pop()
|
v.pop()
|
||||||
|
putObjectInstanceOnStack(type, asmType, typeMapper, v)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
internal class EnumParcelSerializer(override val asmType: Type) : ParcelSerializer {
|
||||||
override fun writeValue(v: InstructionAdapter) {
|
override fun writeValue(v: InstructionAdapter) {
|
||||||
v.invokevirtual("java/lang/Enum", "name", "()Ljava/lang/String;", false)
|
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 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/Boolean" to "booleanValue",
|
||||||
"java/lang/Character" to "charValue",
|
"java/lang/Character" to "charValue",
|
||||||
"java/lang/Byte" to "byteValue",
|
"java/lang/Byte" to "byteValue",
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.android.synthetic
|
|||||||
import com.intellij.mock.MockProject
|
import com.intellij.mock.MockProject
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import kotlinx.android.extensions.CacheImplementation
|
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.ParcelableCodegenExtension
|
||||||
import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker
|
import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker
|
||||||
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
|
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
|
||||||
@@ -135,5 +136,6 @@ class AndroidExtensionPropertiesComponentContainerContributor : StorageComponent
|
|||||||
|
|
||||||
container.useInstance(AndroidExtensionPropertiesCallChecker())
|
container.useInstance(AndroidExtensionPropertiesCallChecker())
|
||||||
container.useInstance(ParcelableDeclarationChecker())
|
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.DefaultErrorMessages
|
||||||
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
|
||||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
|
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 {
|
object DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
|
||||||
private val MAP = DiagnosticFactoryToRendererMap("Android")
|
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")
|
"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,
|
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,
|
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,
|
MAP.put(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED,
|
||||||
"Type is not directly supported by 'Parcelize'. " +
|
"Type is not directly supported by 'Parcelize'. " +
|
||||||
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'")
|
"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 com.intellij.psi.PsiElement;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
|
||||||
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
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.psi.KtExpression;
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
|
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
|
||||||
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
|
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> OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_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> 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")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
Object _initializer = new Object() {
|
Object _initializer = new Object() {
|
||||||
|
|||||||
plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerBoxing.kt
Vendored
+55
@@ -0,0 +1,55 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
@file:JvmName("TestKt")
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlinx.android.parcel.*
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
object Parceler1 : Parceler<Int> {
|
||||||
|
override fun create(parcel: Parcel) = -parcel.readInt()
|
||||||
|
|
||||||
|
override fun Int.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeInt(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Parceler2 : Parceler<Long> {
|
||||||
|
override fun create(parcel: Parcel) = parcel.readString().length.toLong()
|
||||||
|
|
||||||
|
override fun Long.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString("Abc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Test(
|
||||||
|
val a: Int,
|
||||||
|
@TypeParceler<Int, Parceler1> val b: Int,
|
||||||
|
@TypeParceler<Long, Parceler2> val c: Long,
|
||||||
|
@TypeParceler<Int, Parceler1> val d: List<Int>,
|
||||||
|
@TypeParceler<Long, Parceler2> val e: LongArray
|
||||||
|
)
|
||||||
|
|
||||||
|
fun box() = parcelTest { parcel ->
|
||||||
|
val test = Test(5, 5, 50L, listOf(1, 2, 3), longArrayOf(3, 2, 1))
|
||||||
|
test.writeToParcel(parcel, 0)
|
||||||
|
|
||||||
|
val bytes = parcel.marshall()
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.size)
|
||||||
|
|
||||||
|
val test2 = readFromParcel<Test>(parcel)
|
||||||
|
|
||||||
|
println(test.toString())
|
||||||
|
println(test2.toString())
|
||||||
|
|
||||||
|
with (test) {
|
||||||
|
assert(a == 5 && b == 5 && c == 50L && d == listOf(1, 2, 3) && Arrays.equals(e, longArrayOf(3, 2, 1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
with (test2) {
|
||||||
|
assert(a == 5 && b == -5 && c == 3L && d == listOf(-1, -2, -3) && Arrays.equals(e, longArrayOf(3, 3, 3)))
|
||||||
|
}
|
||||||
|
}
|
||||||
plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerSimple.kt
Vendored
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
@file:JvmName("TestKt")
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlinx.android.parcel.*
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
object Parceler1 : Parceler<String> {
|
||||||
|
override fun create(parcel: Parcel) = parcel.readInt().toString()
|
||||||
|
|
||||||
|
override fun String.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeInt(length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias Parceler2 = Parceler1
|
||||||
|
|
||||||
|
object Parceler3 : Parceler<String> {
|
||||||
|
override fun create(parcel: Parcel) = parcel.readString().toUpperCase()
|
||||||
|
|
||||||
|
override fun String.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<String, Parceler2>
|
||||||
|
data class Test(
|
||||||
|
val a: String,
|
||||||
|
@TypeParceler<String, Parceler1> val b: String,
|
||||||
|
@TypeParceler<String, Parceler3> val c: CharSequence,
|
||||||
|
val d: @WriteWith<Parceler3> String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun box() = parcelTest { parcel ->
|
||||||
|
val test = Test("Abc", "Abc", "Abc", "Abc")
|
||||||
|
test.writeToParcel(parcel, 0)
|
||||||
|
|
||||||
|
val bytes = parcel.marshall()
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.size)
|
||||||
|
|
||||||
|
val test2 = readFromParcel<Test>(parcel)
|
||||||
|
|
||||||
|
assert(test.a == "Abc" && test.b == "Abc" && test.c == "Abc" && test.d == "Abc")
|
||||||
|
assert(test2.a == "3" && test2.b == "3" && test2.c == "Abc" && test2.d == "ABC")
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
@file:JvmName("TestKt")
|
||||||
|
package test
|
||||||
|
|
||||||
|
import kotlinx.android.parcel.*
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
object Parceler1 : Parceler<String> {
|
||||||
|
override fun create(parcel: Parcel) = parcel.readInt().toString()
|
||||||
|
|
||||||
|
override fun String.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeInt(length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Parceler2 : Parceler<List<String>> {
|
||||||
|
override fun create(parcel: Parcel) = listOf(parcel.readString())
|
||||||
|
|
||||||
|
override fun List<String>.write(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(this.joinToString(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Test(
|
||||||
|
val a: String,
|
||||||
|
val b: @WriteWith<Parceler1> String,
|
||||||
|
val c: List<@WriteWith<Parceler1> String>,
|
||||||
|
val d: @WriteWith<Parceler2> List<String>,
|
||||||
|
val e: @WriteWith<Parceler2> List<@WriteWith<Parceler1> String>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun box() = parcelTest { parcel ->
|
||||||
|
val test = Test("Abc", "Abc", listOf("A", "bc"), listOf("A", "bc"), listOf("A", "bc"))
|
||||||
|
test.writeToParcel(parcel, 0)
|
||||||
|
|
||||||
|
val bytes = parcel.marshall()
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.size)
|
||||||
|
|
||||||
|
val test2 = readFromParcel<Test>(parcel)
|
||||||
|
|
||||||
|
with (test) {
|
||||||
|
assert(a == "Abc" && b == "Abc" && c == listOf("A", "bc") && d == listOf("A", "bc") && e == listOf("A", "bc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
with (test2) {
|
||||||
|
assert(a == "Abc" && b == "3" && c == listOf("A", "bc") && d == listOf("A,bc") && e == listOf("A,bc"))
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import kotlinx.android.parcel.*
|
||||||
|
import android.os.*
|
||||||
|
|
||||||
|
object StringParceler : Parceler<String> {
|
||||||
|
override fun create(parcel: Parcel) = TODO()
|
||||||
|
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
object CharSequenceParceler : Parceler<CharSequence> {
|
||||||
|
override fun create(parcel: Parcel) = TODO()
|
||||||
|
override fun CharSequence.write(parcel: Parcel, flags: Int) = TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringClassParceler : Parceler<String> {
|
||||||
|
override fun create(parcel: Parcel) = TODO()
|
||||||
|
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
@<error descr="[CLASS_SHOULD_BE_PARCELIZE] Class 'MissingParcelizeAnnotation' should be annotated with '@Parcelize'">TypeParceler</error><String, StringParceler>
|
||||||
|
class MissingParcelizeAnnotation(val a: @<error descr="[CLASS_SHOULD_BE_PARCELIZE] Class 'MissingParcelizeAnnotation' should be annotated with '@Parcelize'">WriteWith</error><StringParceler> String)
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<String, StringClassParceler>
|
||||||
|
class ShouldBeClass(val a: @WriteWith<<error descr="[PARCELER_SHOULD_BE_OBJECT] Parceler should be an object">StringClassParceler</error>> String) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class Test(
|
||||||
|
val a: @WriteWith<<error descr="[PARCELER_TYPE_INCOMPATIBLE] Parceler type String is incompatible with Int">StringParceler</error>> Int,
|
||||||
|
val b: @WriteWith<StringParceler> String,
|
||||||
|
val c: @WriteWith<<error descr="[PARCELER_TYPE_INCOMPATIBLE] Parceler type String is incompatible with CharSequence">StringParceler</error>> CharSequence,
|
||||||
|
val d: @WriteWith<CharSequenceParceler> String,
|
||||||
|
val e: @WriteWith<CharSequenceParceler> CharSequence
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<String, StringParceler>
|
||||||
|
class Test2(@<warning descr="[REDUNDANT_TYPE_PARCELER] This annotation duplicates the one for Class 'Test2'">TypeParceler</warning><String, StringParceler> val a: String) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@TypeParceler<<error descr="[DUPLICATING_TYPE_PARCELERS] Duplicating ''Parceler'' annotations">String</error>, StringParceler>
|
||||||
|
@TypeParceler<<error descr="[DUPLICATING_TYPE_PARCELERS] Duplicating ''Parceler'' annotations">String</error>, CharSequenceParceler>
|
||||||
|
class Test3(val a: String) : Parcelable
|
||||||
+6
@@ -48,6 +48,12 @@ public class ParcelCheckerTestGenerated extends AbstractParcelCheckerTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("customParcelers.kt")
|
||||||
|
public void testCustomParcelers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customParcelers.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("customWriteToParcel.kt")
|
@TestMetadata("customWriteToParcel.kt")
|
||||||
public void testCustomWriteToParcel() throws Exception {
|
public void testCustomWriteToParcel() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customWriteToParcel.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customWriteToParcel.kt");
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlinx.android.parcel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies what [Parceler] should be used for a particular type [T].
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Repeatable
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||||
|
annotation class TypeParceler<T, P: Parceler<in T>>
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlinx.android.parcel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies what [Parceler] should be used for the annotated type.
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Target(AnnotationTarget.TYPE)
|
||||||
|
annotation class WriteWith<P : Parceler<*>>
|
||||||
@@ -43,4 +43,7 @@ class ParcelBoxTest : AbstractParcelBoxTest() {
|
|||||||
fun testKt19747_2() = doTest("kt19747_2")
|
fun testKt19747_2() = doTest("kt19747_2")
|
||||||
fun test20002() = doTest("kt20002")
|
fun test20002() = doTest("kt20002")
|
||||||
fun test20021() = doTest("kt20021")
|
fun test20021() = doTest("kt20021")
|
||||||
|
fun testCustomSerializerSimple() = doTest("customSerializerSimple")
|
||||||
|
fun testCustomSerializerWriteWith() = doTest("customSerializerWriteWith")
|
||||||
|
fun testCustomSerializerBoxing() = doTest("customSerializerBoxing")
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user