Parcelable: Report error on unsupported parameter types, add @RawValue annotation support
This commit is contained in:
+1
-1
@@ -188,7 +188,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
|
||||
val asmType = codegen.typeMapper.mapType(type)
|
||||
asmConstructorParameters.append(asmType.descriptor)
|
||||
|
||||
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
|
||||
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper, forceBoxed = false, strict = false)
|
||||
v.load(1, parcelAsmType)
|
||||
serializer.readValue(v)
|
||||
}
|
||||
|
||||
+46
-5
@@ -16,20 +16,27 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.parcel
|
||||
|
||||
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
|
||||
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
||||
import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmFieldAnnotation
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
|
||||
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
|
||||
internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
|
||||
@@ -46,7 +53,7 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
|
||||
bindingContext: BindingContext
|
||||
) {
|
||||
when (descriptor) {
|
||||
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder)
|
||||
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder, bindingContext)
|
||||
is SimpleFunctionDescriptor -> {
|
||||
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
|
||||
val ktFunction = declaration as? KtFunction
|
||||
@@ -102,7 +109,12 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParcelableClass(descriptor: ClassDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {
|
||||
private fun checkParcelableClass(
|
||||
descriptor: ClassDescriptor,
|
||||
declaration: KtDeclaration,
|
||||
diagnosticHolder: DiagnosticSink,
|
||||
bindingContext: BindingContext
|
||||
) {
|
||||
if (!descriptor.isMagicParcelable) return
|
||||
|
||||
if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) {
|
||||
@@ -135,10 +147,39 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
|
||||
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(declaration.nameIdentifier ?: declaration))
|
||||
}
|
||||
|
||||
val typeMapper = KotlinTypeMapper(
|
||||
bindingContext,
|
||||
ClassBuilderMode.full(false),
|
||||
NoResolveFileClassesProvider,
|
||||
IncompatibleClassTracker.DoNothing,
|
||||
descriptor.module.name.asString(),
|
||||
/* isJvm8Target */ false,
|
||||
/* isJvm8TargetWithDefaults */ false)
|
||||
|
||||
for (parameter in primaryConstructor?.valueParameters.orEmpty()) {
|
||||
if (!parameter.hasValOrVar()) {
|
||||
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
|
||||
parameter.nameIdentifier ?: parameter))
|
||||
checkParcelableClassProperty(parameter, diagnosticHolder, typeMapper)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParcelableClassProperty(parameter: KtParameter, diagnosticHolder: DiagnosticSink, typeMapper: KotlinTypeMapper) {
|
||||
if (!parameter.hasValOrVar()) {
|
||||
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
|
||||
parameter.nameIdentifier ?: parameter))
|
||||
}
|
||||
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter] ?: return
|
||||
val type = descriptor.type
|
||||
|
||||
if (!type.isError) {
|
||||
val asmType = typeMapper.mapType(type)
|
||||
|
||||
try {
|
||||
ParcelSerializer.get(type, asmType, typeMapper, strict = true)
|
||||
}
|
||||
catch (e: IllegalArgumentException) {
|
||||
// get() throws IllegalArgumentException on unknown types
|
||||
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED.on(
|
||||
parameter.typeReference ?: parameter.nameIdentifier ?: parameter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-10
@@ -22,18 +22,22 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
|
||||
|
||||
interface ParcelSerializer {
|
||||
val asmType: Type
|
||||
|
||||
@@ -41,16 +45,26 @@ interface ParcelSerializer {
|
||||
fun readValue(v: InstructionAdapter)
|
||||
|
||||
companion object {
|
||||
fun get(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, forceBoxed: Boolean = false): ParcelSerializer {
|
||||
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType): Type {
|
||||
return if (type.isError) Type.getObjectType("java/lang/Object") else mapType(type)
|
||||
}
|
||||
|
||||
fun get(
|
||||
type: KotlinType,
|
||||
asmType: Type,
|
||||
typeMapper: KotlinTypeMapper,
|
||||
forceBoxed: Boolean = false,
|
||||
strict: Boolean = false
|
||||
): ParcelSerializer {
|
||||
val className = asmType.className
|
||||
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
|
||||
|
||||
return when {
|
||||
asmType.sort == Type.ARRAY -> {
|
||||
val elementType = type.builtIns.getArrayElementType(type)
|
||||
val elementSerializer = get(elementType, typeMapper.mapTypeSafe(elementType), typeMapper, strict = strict())
|
||||
|
||||
wrapToNullAwareIfNeeded(
|
||||
type,
|
||||
ArrayParcelSerializer(asmType, get(elementType, typeMapper.mapType(elementType), typeMapper)))
|
||||
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
|
||||
}
|
||||
|
||||
asmType.isPrimitive() -> {
|
||||
@@ -73,7 +87,8 @@ interface ParcelSerializer {
|
||||
|| className == TreeSet::class.java.canonicalName
|
||||
-> {
|
||||
val elementType = type.arguments.single().type
|
||||
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
|
||||
val elementSerializer = get(
|
||||
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
|
||||
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer))
|
||||
}
|
||||
|
||||
@@ -84,8 +99,10 @@ interface ParcelSerializer {
|
||||
|| className == ConcurrentHashMap::class.java.canonicalName
|
||||
-> {
|
||||
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
|
||||
val keySerializer = get(keyType.type, typeMapper.mapType(keyType.type), typeMapper, forceBoxed = true)
|
||||
val valueSerializer = get(valueType.type, typeMapper.mapType(valueType.type), typeMapper, forceBoxed = true)
|
||||
val keySerializer = get(
|
||||
keyType.type, typeMapper.mapTypeSafe(keyType.type), typeMapper, forceBoxed = true, strict = strict())
|
||||
val valueSerializer = get(
|
||||
valueType.type, typeMapper.mapTypeSafe(valueType.type), typeMapper, forceBoxed = true, strict = strict())
|
||||
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
|
||||
}
|
||||
|
||||
@@ -131,7 +148,8 @@ interface ParcelSerializer {
|
||||
|
||||
asmType.isSparseArray() -> {
|
||||
val elementType = type.arguments.single().type
|
||||
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
|
||||
val elementSerializer = get(
|
||||
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
|
||||
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
|
||||
}
|
||||
|
||||
@@ -158,7 +176,7 @@ interface ParcelSerializer {
|
||||
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
|
||||
|
||||
val creatorAsmType = when {
|
||||
creatorVar != null -> typeMapper.mapType(creatorVar.type)
|
||||
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
|
||||
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
|
||||
else -> null
|
||||
}
|
||||
@@ -175,7 +193,12 @@ interface ParcelSerializer {
|
||||
Method("writeSerializable"),
|
||||
Method("readSerializable"))
|
||||
|
||||
else -> GenericParcelSerializer
|
||||
else -> {
|
||||
if (strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME))
|
||||
throw IllegalArgumentException("Illegal type")
|
||||
else
|
||||
GenericParcelSerializer
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when {
|
||||
|
||||
+4
@@ -69,6 +69,10 @@ class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
|
||||
|
||||
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN,
|
||||
"'CREATOR' definition is forbidden. Use 'Parceler' companion object instead.")
|
||||
|
||||
MAP.put(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED,
|
||||
"Type is not supported by 'Parcelable'. " +
|
||||
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ public interface ErrorsAndroid {
|
||||
DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
|
||||
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
|
||||
+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
|
||||
|
||||
/**
|
||||
* Write the corresponding property value with [android.os.Parcel.writeValue].
|
||||
* Serialization may fail at runtime, depending on actual property value type.
|
||||
*/
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class RawValue
|
||||
Reference in New Issue
Block a user