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)
|
val asmType = codegen.typeMapper.mapType(type)
|
||||||
asmConstructorParameters.append(asmType.descriptor)
|
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)
|
v.load(1, parcelAsmType)
|
||||||
serializer.readValue(v)
|
serializer.readValue(v)
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-5
@@ -16,20 +16,27 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.android.parcel
|
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.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.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
|
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
|
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
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.resolve.jvm.annotations.findJvmFieldAnnotation
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.types.isError
|
||||||
|
|
||||||
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
|
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
|
||||||
internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
|
internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
|
||||||
@@ -46,7 +53,7 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
|
|||||||
bindingContext: BindingContext
|
bindingContext: BindingContext
|
||||||
) {
|
) {
|
||||||
when (descriptor) {
|
when (descriptor) {
|
||||||
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder)
|
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder, bindingContext)
|
||||||
is SimpleFunctionDescriptor -> {
|
is SimpleFunctionDescriptor -> {
|
||||||
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
|
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
|
||||||
val ktFunction = declaration as? KtFunction
|
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 (!descriptor.isMagicParcelable) return
|
||||||
|
|
||||||
if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) {
|
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))
|
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()) {
|
for (parameter in primaryConstructor?.valueParameters.orEmpty()) {
|
||||||
if (!parameter.hasValOrVar()) {
|
checkParcelableClassProperty(parameter, diagnosticHolder, typeMapper)
|
||||||
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
|
}
|
||||||
parameter.nameIdentifier ?: parameter))
|
}
|
||||||
|
|
||||||
|
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.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.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
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
import org.jetbrains.kotlin.synthetic.isVisibleOutside
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.types.isError
|
||||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||||
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
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
|
||||||
|
|
||||||
interface ParcelSerializer {
|
interface ParcelSerializer {
|
||||||
val asmType: Type
|
val asmType: Type
|
||||||
|
|
||||||
@@ -41,16 +45,26 @@ interface ParcelSerializer {
|
|||||||
fun readValue(v: InstructionAdapter)
|
fun readValue(v: InstructionAdapter)
|
||||||
|
|
||||||
companion object {
|
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
|
val className = asmType.className
|
||||||
|
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
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), typeMapper, strict = strict())
|
||||||
|
|
||||||
wrapToNullAwareIfNeeded(
|
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
|
||||||
type,
|
|
||||||
ArrayParcelSerializer(asmType, get(elementType, typeMapper.mapType(elementType), typeMapper)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asmType.isPrimitive() -> {
|
asmType.isPrimitive() -> {
|
||||||
@@ -73,7 +87,8 @@ 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 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))
|
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +99,10 @@ interface ParcelSerializer {
|
|||||||
|| className == ConcurrentHashMap::class.java.canonicalName
|
|| className == ConcurrentHashMap::class.java.canonicalName
|
||||||
-> {
|
-> {
|
||||||
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
|
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
|
||||||
val keySerializer = get(keyType.type, typeMapper.mapType(keyType.type), typeMapper, forceBoxed = true)
|
val keySerializer = get(
|
||||||
val valueSerializer = get(valueType.type, typeMapper.mapType(valueType.type), typeMapper, forceBoxed = true)
|
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))
|
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +148,8 @@ interface ParcelSerializer {
|
|||||||
|
|
||||||
asmType.isSparseArray() -> {
|
asmType.isSparseArray() -> {
|
||||||
val elementType = type.arguments.single().type
|
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))
|
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +176,7 @@ interface ParcelSerializer {
|
|||||||
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
|
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
|
||||||
|
|
||||||
val creatorAsmType = when {
|
val creatorAsmType = when {
|
||||||
creatorVar != null -> typeMapper.mapType(creatorVar.type)
|
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
|
||||||
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
|
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -175,7 +193,12 @@ interface ParcelSerializer {
|
|||||||
Method("writeSerializable"),
|
Method("writeSerializable"),
|
||||||
Method("readSerializable"))
|
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 {
|
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when {
|
||||||
|
|||||||
+4
@@ -69,6 +69,10 @@ class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
|
|||||||
|
|
||||||
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN,
|
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN,
|
||||||
"'CREATOR' definition is forbidden. Use 'Parceler' companion object instead.")
|
"'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> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
|
||||||
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
|
||||||
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_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")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
Object _initializer = new Object() {
|
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