diff --git a/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt new file mode 100644 index 00000000000..183a1ba9cb9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/parcelize.common/src/org/jetbrains/kotlin/parcelize/BuiltinParcelableTypes.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.parcelize + +/* + * List of builtin Parcelable types. + * + * The Parcelize plugin has direct support for serializing all types in `PARCELABLE_BASE_TYPE_FQNAMES`, + * as well as the container types in `PARCELABLE_CONTAINER_FQNAMES` whenever all element types can be serialized. + * + * Apart from that, Parcelize can serialize all types with supertypes in `PARCELABLE_SUPERTYPE_FQNAMES` through + * reflection, as well as all objects, enums, and function types (since they are implicitly serializable). + */ +object BuiltinParcelableTypes { + val PARCELABLE_SUPERTYPE_FQNAMES = setOf( + "android.os.Parcelable", + "android.os.IBinder", + "java.io.Serializable" + ) + + val PARCELABLE_BASE_TYPE_FQNAMES = setOf( + "android.os.Bundle", + "android.os.PersistableBundle", + "android.util.Size", + "android.util.SizeF", + "android.util.SparseBooleanArray", + "android.util.SparseIntArray", + "android.util.SparseLongArray", + "java.io.FileDescriptor", + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.CharSequence", + "java.lang.Character", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Short", + "java.lang.String", + "kotlin.Boolean", + "kotlin.BooleanArray", + "kotlin.Byte", + "kotlin.ByteArray", + "kotlin.Char", + "kotlin.CharArray", + "kotlin.CharSequence", + "kotlin.Double", + "kotlin.DoubleArray", + "kotlin.Float", + "kotlin.FloatArray", + "kotlin.Int", + "kotlin.IntArray", + "kotlin.Long", + "kotlin.LongArray", + "kotlin.Short", + "kotlin.String", + "kotlin.UByte", + "kotlin.UByteArray", + "kotlin.UInt", + "kotlin.UIntArray", + "kotlin.ULong", + "kotlin.ULongArray", + "kotlin.UShort", + "kotlin.UShortArray", + ) + PARCELABLE_SUPERTYPE_FQNAMES + + val PARCELABLE_CONTAINER_FQNAMES = setOf( + "android.util.SparseArray", + "java.util.ArrayDeque", + "java.util.ArrayList", + "java.util.HashMap", + "java.util.HashSet", + "java.util.LinkedHashMap", + "java.util.LinkedHashSet", + "java.util.List", + "java.util.Map", + "java.util.NavigableMap", + "java.util.NavigableSet", + "java.util.Set", + "java.util.SortedMap", + "java.util.SortedSet", + "java.util.TreeMap", + "java.util.concurrent.ConcurrentHashMap", + "kotlin.Array", + "kotlin.collections.ArrayDeque", + "kotlin.collections.ArrayList", + "kotlin.collections.HashMap", + "kotlin.collections.HashSet", + "kotlin.collections.LinkedHashMap", + "kotlin.collections.LinkedHashSet", + "kotlin.collections.List", + "kotlin.collections.Map", + "kotlin.collections.MutableList", + "kotlin.collections.MutableMap", + "kotlin.collections.MutableSet", + "kotlin.collections.Set", + ) +} diff --git a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt index 7204dabc3e2..510e9162479 100644 --- a/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt +++ b/plugins/parcelize/parcelize-compiler/parcelize.k1/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt @@ -5,21 +5,20 @@ package org.jetbrains.kotlin.parcelize +import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype import org.jetbrains.kotlin.codegen.ClassBuilderMode -import org.jetbrains.kotlin.codegen.FrameMap import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.load.kotlin.TypeMappingMode import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.parcelize.ParcelizeNames.OLD_PARCELER_FQN import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELABLE_FQN import org.jetbrains.kotlin.parcelize.diagnostic.ErrorsParcelize -import org.jetbrains.kotlin.parcelize.serializers.ParcelSerializer import org.jetbrains.kotlin.parcelize.serializers.isParcelable +import org.jetbrains.kotlin.parcelize.serializers.matchesFqNameWithSupertypes import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker @@ -27,9 +26,12 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext 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.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.isError +import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound import org.jetbrains.kotlin.types.typeUtil.supertypes +import org.jetbrains.kotlin.utils.addToStdlib.safeAs open class ParcelizeDeclarationChecker : DeclarationChecker { private companion object { @@ -239,28 +241,58 @@ open class ParcelizeDeclarationChecker : DeclarationChecker { } val type = descriptor.type - if (!type.isError) { - val asmType = typeMapper.mapType(type, mode = TypeMappingMode.CLASS_DECLARATION) + val customParcelerTypes = + (getTypeParcelers(descriptor.annotations) + getTypeParcelers(containerClass.annotations)).map { (mappedType, _) -> + mappedType + }.toSet() - try { - val parcelers = getTypeParcelers(descriptor.annotations) + getTypeParcelers(containerClass.annotations) - val context = ParcelSerializer.ParcelSerializerContext( - typeMapper, - typeMapper.mapClass(containerClass), - parcelers, - FrameMap() - ) - - ParcelSerializer.get(type, asmType, context, strict = true) - } catch (e: IllegalArgumentException) { - // get() throws IllegalArgumentException on unknown types + if (!checkParcelableType(type, customParcelerTypes)) { val reportElement = parameter.typeReference ?: parameter.nameIdentifier ?: parameter diagnosticHolder.report(ErrorsParcelize.PARCELABLE_TYPE_NOT_SUPPORTED.on(reportElement)) } } } + private fun checkParcelableType(type: KotlinType, customParcelerTypes: Set): Boolean { + if (type.hasAnyAnnotation(ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES) + || type.hasAnyAnnotation(ParcelizeNames.WRITE_WITH_FQ_NAMES) + || type in customParcelerTypes) { + return true + } + + val upperBound = type.getErasedUpperBound() + val descriptor = upperBound.constructor.declarationDescriptor as? ClassDescriptor + ?: return false + + if (descriptor.kind.isSingleton || descriptor.kind.isEnumClass) { + return true + } + + val fqName = descriptor.fqNameSafe.asString() + if (fqName in BuiltinParcelableTypes.PARCELABLE_BASE_TYPE_FQNAMES) { + return true + } + + if (fqName in BuiltinParcelableTypes.PARCELABLE_CONTAINER_FQNAMES) { + return upperBound.arguments.all { + checkParcelableType(it.type, customParcelerTypes) + } + } + + for (superFqName in BuiltinParcelableTypes.PARCELABLE_SUPERTYPE_FQNAMES) { + if (type.matchesFqNameWithSupertypes(superFqName)) { + return true + } + } + + return type.isBuiltinFunctionalTypeOrSubtype + } + + private fun KotlinType.getErasedUpperBound(): KotlinType = + constructor.declarationDescriptor?.safeAs()?.representativeUpperBound?.getErasedUpperBound() + ?: this + private fun ClassDescriptor.hasCustomParceler(): Boolean { val companionObjectSuperTypes = companionObjectDescriptor?.let { TypeUtils.getAllSupertypes(it.defaultType) } ?: return false return companionObjectSuperTypes.any { it.isParceler }