From a63aca08f2062d16cead28672cd0849b9c2a8b34 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 14 Sep 2017 23:00:33 +0300 Subject: [PATCH] Parcelable: Support custom Parcelers in compiler plugin --- .../parcel/ParcelableAnnotationChecker.kt | 163 ++++++++++++++++++ .../parcel/ParcelableCodegenExtension.kt | 47 +++-- .../parcel/ParcelableDeclarationChecker.kt | 3 +- .../parcel/serializers/ParcelSerializer.kt | 46 ++++- .../parcel/serializers/ParcelSerializers.kt | 90 ++++++++-- .../synthetic/AndroidComponentRegistrar.kt | 2 + .../diagnostic/DefaultErrorMessagesAndroid.kt | 24 ++- .../synthetic/diagnostic/ErrorsAndroid.java | 10 ++ .../parcel/box/customSerializerBoxing.kt | 55 ++++++ .../parcel/box/customSerializerSimple.kt | 48 ++++++ .../parcel/box/customSerializerWriteWith.kt | 51 ++++++ .../android/parcel/checker/customParcelers.kt | 44 +++++ .../android/ParcelCheckerTestGenerated.java | 6 + .../kotlinx/android/parcel/TypeParceler.kt | 25 +++ .../src/kotlinx/android/parcel/WriteWith.kt | 24 +++ .../kotlin/android/parcel/ParcelBoxTest.kt | 3 + 16 files changed, 603 insertions(+), 38 deletions(-) create mode 100644 plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableAnnotationChecker.kt create mode 100644 plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerBoxing.kt create mode 100644 plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerSimple.kt create mode 100644 plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerWriteWith.kt create mode 100644 plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customParcelers.kt create mode 100644 plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/TypeParceler.kt create mode 100644 plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/WriteWith.kt diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableAnnotationChecker.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableAnnotationChecker.kt new file mode 100644 index 00000000000..77a286156fd --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableAnnotationChecker.kt @@ -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() ?: return + val annotationOwner = annotationEntry.getStrictParentOfType() ?: 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() 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() + 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 PsiElement.getStrictParentOfType(): T? { + return PsiTreeUtil.getParentOfType(this, T::class.java, true) +} + +internal inline fun PsiElement.getNonStrictParentOfType(): T? { + return PsiTreeUtil.getParentOfType(this, T::class.java, false) +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt index 6f869552f38..e1d9d37d2e3 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt @@ -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>, + properties: List, 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> + propertiesToSerialize: List ): 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) + private fun getPropertiesToSerialize( codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor - ): List> { + ): List { 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> + properties: List ) { 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> + properties: List ) { 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 { + val typeParcelerFqName = FqName(TypeParceler::class.java.name) + val serializers = mutableListOf() + + 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 } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableDeclarationChecker.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableDeclarationChecker.kt index 83a2b0abd5c..4e63a80b473 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableDeclarationChecker.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableDeclarationChecker.kt @@ -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) { diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt index 024ef4201a2..8e84524659d 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt @@ -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 + 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 + ) { + 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 } diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializers.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializers.kt index 058534212bc..f97e1793297 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializers.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializers.kt @@ -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", diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index 337299b7c97..0421c76b21e 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -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()) } } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt index a4b1595cb26..7ee8042f181 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt @@ -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) } } \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java index efbff9a9d11..28829f1c233 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java @@ -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 OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CREATOR_DEFINITION_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELER_SHOULD_BE_OBJECT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory2 PARCELER_TYPE_INCOMPATIBLE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory0 DUPLICATING_TYPE_PARCELERS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 REDUNDANT_TYPE_PARCELER = DiagnosticFactory1.create(WARNING); + DiagnosticFactory1 CLASS_SHOULD_BE_PARCELIZE = DiagnosticFactory1.create(ERROR); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerBoxing.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerBoxing.kt new file mode 100644 index 00000000000..357da96c8b6 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerBoxing.kt @@ -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 { + override fun create(parcel: Parcel) = -parcel.readInt() + + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeInt(this) + } +} + +object Parceler2 : Parceler { + 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 val b: Int, + @TypeParceler val c: Long, + @TypeParceler val d: List, + @TypeParceler 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(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))) + } +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerSimple.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerSimple.kt new file mode 100644 index 00000000000..a3220bbf871 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerSimple.kt @@ -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 { + 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 { + override fun create(parcel: Parcel) = parcel.readString().toUpperCase() + + override fun String.write(parcel: Parcel, flags: Int) { + parcel.writeString(this) + } +} + +@Parcelize +@TypeParceler +data class Test( + val a: String, + @TypeParceler val b: String, + @TypeParceler val c: CharSequence, + val d: @WriteWith 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(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") +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerWriteWith.kt b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerWriteWith.kt new file mode 100644 index 00000000000..5c3662079ac --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/testData/parcel/box/customSerializerWriteWith.kt @@ -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 { + override fun create(parcel: Parcel) = parcel.readInt().toString() + + override fun String.write(parcel: Parcel, flags: Int) { + parcel.writeInt(length) + } +} + +object Parceler2 : Parceler> { + override fun create(parcel: Parcel) = listOf(parcel.readString()) + + override fun List.write(parcel: Parcel, flags: Int) { + parcel.writeString(this.joinToString(",")) + } +} + +@Parcelize +data class Test( + val a: String, + val b: @WriteWith String, + val c: List<@WriteWith String>, + val d: @WriteWith List, + val e: @WriteWith List<@WriteWith 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(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")) + } +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customParcelers.kt b/plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customParcelers.kt new file mode 100644 index 00000000000..2d67c21cd15 --- /dev/null +++ b/plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customParcelers.kt @@ -0,0 +1,44 @@ +package test + +import kotlinx.android.parcel.* +import android.os.* + +object StringParceler : Parceler { + override fun create(parcel: Parcel) = TODO() + override fun String.write(parcel: Parcel, flags: Int) = TODO() +} + +object CharSequenceParceler : Parceler { + override fun create(parcel: Parcel) = TODO() + override fun CharSequence.write(parcel: Parcel, flags: Int) = TODO() +} + +class StringClassParceler : Parceler { + override fun create(parcel: Parcel) = TODO() + override fun String.write(parcel: Parcel, flags: Int) = TODO() +} + +@TypeParceler +class MissingParcelizeAnnotation(val a: @WriteWith String) + +@Parcelize +@TypeParceler +class ShouldBeClass(val a: @WriteWith<StringClassParceler> String) : Parcelable + +@Parcelize +class Test( + val a: @WriteWith<StringParceler> Int, + val b: @WriteWith String, + val c: @WriteWith<StringParceler> CharSequence, + val d: @WriteWith String, + val e: @WriteWith CharSequence +) : Parcelable + +@Parcelize +@TypeParceler +class Test2(@TypeParceler val a: String) : Parcelable + +@Parcelize +@TypeParceler<String, StringParceler> +@TypeParceler<String, CharSequenceParceler> +class Test3(val a: String) : Parcelable \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-idea/tests/org/jetbrains/kotlin/android/ParcelCheckerTestGenerated.java b/plugins/android-extensions/android-extensions-idea/tests/org/jetbrains/kotlin/android/ParcelCheckerTestGenerated.java index fae80edb89e..bb059cc5cd4 100644 --- a/plugins/android-extensions/android-extensions-idea/tests/org/jetbrains/kotlin/android/ParcelCheckerTestGenerated.java +++ b/plugins/android-extensions/android-extensions-idea/tests/org/jetbrains/kotlin/android/ParcelCheckerTestGenerated.java @@ -48,6 +48,12 @@ public class ParcelCheckerTestGenerated extends AbstractParcelCheckerTest { 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") public void testCustomWriteToParcel() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-idea/testData/android/parcel/checker/customWriteToParcel.kt"); diff --git a/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/TypeParceler.kt b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/TypeParceler.kt new file mode 100644 index 00000000000..0e47732ae25 --- /dev/null +++ b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/TypeParceler.kt @@ -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> \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/WriteWith.kt b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/WriteWith.kt new file mode 100644 index 00000000000..d1cfac39095 --- /dev/null +++ b/plugins/android-extensions/android-extensions-runtime/src/kotlinx/android/parcel/WriteWith.kt @@ -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

> \ No newline at end of file diff --git a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt index 4950eabf616..718ed1c4f8d 100644 --- a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt +++ b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt @@ -43,4 +43,7 @@ class ParcelBoxTest : AbstractParcelBoxTest() { fun testKt19747_2() = doTest("kt19747_2") fun test20002() = doTest("kt20002") fun test20021() = doTest("kt20021") + fun testCustomSerializerSimple() = doTest("customSerializerSimple") + fun testCustomSerializerWriteWith() = doTest("customSerializerWriteWith") + fun testCustomSerializerBoxing() = doTest("customSerializerBoxing") } \ No newline at end of file