Parcelize: Support old (kotlinx.android.parcel) annotations in Parcelize plugin

This commit is contained in:
Yan Zhulanow
2020-09-18 01:52:14 +09:00
parent f0b93bf352
commit 1d5ab192f5
8 changed files with 108 additions and 41 deletions
@@ -18,9 +18,6 @@ package org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.TypeParceler
import kotlinx.parcelize.WriteWith
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -40,10 +37,22 @@ import org.jetbrains.kotlin.types.typeUtil.replaceAnnotations
import org.jetbrains.kotlin.types.typeUtil.supertypes
class ParcelizeAnnotationChecker : CallChecker {
@Suppress("DEPRECATION")
companion object {
val TYPE_PARCELER_FQNAME = FqName(TypeParceler::class.java.name)
val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name)
val IGNORED_ON_PARCEL_FQNAME = FqName(IgnoredOnParcel::class.java.name)
val TYPE_PARCELER_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.TypeParceler::class.java.name),
FqName(kotlinx.android.parcel.TypeParceler::class.java.name)
)
val WRITE_WITH_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.WriteWith::class.java.name),
FqName(kotlinx.android.parcel.WriteWith::class.java.name)
)
val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.IgnoredOnParcel::class.java.name),
FqName(kotlinx.android.parcel.IgnoredOnParcel::class.java.name)
)
}
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
@@ -53,15 +62,15 @@ class ParcelizeAnnotationChecker : CallChecker {
val annotationEntry = resolvedCall.call.callElement.getNonStrictParentOfType<KtAnnotationEntry>() ?: return
val annotationOwner = annotationEntry.getStrictParentOfType<KtModifierListOwner>() ?: return
if (annotationClass.fqNameSafe == TYPE_PARCELER_FQNAME) {
if (annotationClass.fqNameSafe in TYPE_PARCELER_FQ_NAMES) {
checkTypeParcelerUsage(resolvedCall, annotationEntry, context, annotationOwner)
}
if (annotationClass.fqNameSafe == WRITE_WITH_FQNAME) {
if (annotationClass.fqNameSafe in WRITE_WITH_FQ_NAMES) {
checkWriteWithUsage(resolvedCall, annotationEntry, context, annotationOwner)
}
if (annotationClass.fqNameSafe == IGNORED_ON_PARCEL_FQNAME) {
if (annotationClass.fqNameSafe in IGNORED_ON_PARCEL_FQ_NAMES) {
checkIgnoredOnParcelUsage(annotationEntry, context, annotationOwner)
}
}
@@ -84,7 +93,7 @@ class ParcelizeAnnotationChecker : CallChecker {
val thisMappedType = resolvedCall.typeArguments.values.takeIf { it.size == 2 }?.first() ?: return
val duplicatingAnnotationCount = descriptor.annotations
.filter { it.fqName == TYPE_PARCELER_FQNAME }
.filter { it.fqName in TYPE_PARCELER_FQ_NAMES }
.mapNotNull { it.type.arguments.takeIf { args -> args.size == 2 }?.first()?.type }
.count { it == thisMappedType }
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import kotlinx.parcelize.TypeParceler
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy.CodegenBased
@@ -17,8 +16,8 @@ import org.jetbrains.kotlin.descriptors.*
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.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeAnnotationChecker.Companion.TYPE_PARCELER_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.parcelize.serializers.*
@@ -427,10 +426,9 @@ open class ParcelizeCodegenExtension : ParcelizeExtensionBase, ExpressionCodegen
}
internal fun getTypeParcelers(annotations: Annotations): List<TypeParcelerMapping> {
val typeParcelerFqName = FqName(TypeParceler::class.java.name)
val serializers = mutableListOf<TypeParcelerMapping>()
for (annotation in annotations.filter { it.fqName == typeParcelerFqName }) {
for (annotation in annotations.filter { it.fqName in TYPE_PARCELER_FQ_NAMES }) {
val (mappedType, parcelerType) = annotation.type.arguments.takeIf { it.size == 2 } ?: continue
serializers += TypeParcelerMapping(mappedType.type, parcelerType.type)
}
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.parcelize
import kotlinx.parcelize.IgnoredOnParcel
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.FrameMap
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
@@ -37,7 +36,10 @@ val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
class ParcelizeDeclarationChecker : DeclarationChecker {
private companion object {
private val IGNORED_ON_PARCEL_FQNAME = FqName(IgnoredOnParcel::class.java.canonicalName)
private val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.IgnoredOnParcel::class.java.canonicalName),
FqName(kotlinx.android.parcel.IgnoredOnParcel::class.java.canonicalName)
)
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
@@ -91,7 +93,7 @@ class ParcelizeDeclarationChecker : DeclarationChecker {
bindingContext: BindingContext
) {
fun hasIgnoredOnParcel(): Boolean {
fun Annotations.hasIgnoredOnParcel() = any { it.fqName == IGNORED_ON_PARCEL_FQNAME }
fun Annotations.hasIgnoredOnParcel() = any { it.fqName in IGNORED_ON_PARCEL_FQ_NAMES }
return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false)
}
@@ -17,10 +17,10 @@
package org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import kotlinx.parcelize.Parceler
import kotlinx.parcelize.Parcelize
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
@@ -169,11 +169,44 @@ interface ParcelizeSyntheticComponent {
}
}
val PARCELIZE_CLASS_FQNAME: FqName = FqName(Parcelize::class.java.canonicalName)
internal val PARCELER_FQNAME: FqName = FqName(Parceler::class.java.canonicalName)
val PARCELIZE_CLASS_FQ_NAMES: List<FqName> = listOf(
FqName(kotlinx.parcelize.Parcelize::class.java.canonicalName),
FqName(kotlinx.android.parcel.Parcelize::class.java.canonicalName)
)
internal val PARCELER_FQNAME = FqName(kotlinx.parcelize.Parceler::class.java.canonicalName)
val ClassDescriptor.isParcelize: Boolean
get() = this.annotations.hasAnnotation(PARCELIZE_CLASS_FQNAME)
get() {
for (fqName in PARCELIZE_CLASS_FQ_NAMES) {
if (this.annotations.hasAnnotation(fqName)) {
return true
}
}
val KotlinType.isParceler
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQNAME
return false
}
val KotlinType.isParceler: Boolean
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQNAME
fun Annotated.findAnyAnnotation(fqNames: List<FqName>): AnnotationDescriptor? {
for (fqName in fqNames) {
val annotation = annotations.findAnnotation(fqName)
if (annotation != null) {
return annotation
}
}
return null
}
fun Annotated.hasAnyAnnotation(fqNames: List<FqName>): Boolean {
for (fqName in fqNames) {
if (annotations.hasAnnotation(fqName)) {
return true
}
}
return false
}
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.parcelize.serializers.RAW_VALUE_ANNOTATION_FQNAME
import org.jetbrains.kotlin.parcelize.serializers.RAW_VALUE_ANNOTATION_FQ_NAMES
class IrParcelSerializerFactory(symbols: AndroidSymbols) {
/**
@@ -28,7 +28,7 @@ class IrParcelSerializerFactory(symbols: AndroidSymbols) {
strict: Boolean = false,
toplevel: Boolean = false
): IrParcelSerializer {
fun strict() = strict && !irType.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME)
fun strict() = strict && !irType.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES)
scope.getCustomSerializer(irType)?.let { parceler ->
return IrCustomParcelSerializer(parceler)
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.parcelize.ParcelizeAnnotationChecker
// Keep track of all custom parcelers which are currently in scope.
@@ -30,7 +29,7 @@ class IrParcelerScope(private val parent: IrParcelerScope? = null) {
}
fun IrParcelerScope?.getCustomSerializer(irType: IrType): IrClass? {
return irType.getAnnotation(ParcelizeAnnotationChecker.WRITE_WITH_FQNAME)?.let { writeWith ->
return irType.getAnyAnnotation(ParcelizeAnnotationChecker.WRITE_WITH_FQ_NAMES)?.let { writeWith ->
(writeWith.type as IrSimpleType).arguments.single().typeOrNull!!.getClass()!!
} ?: this?.get(irType)
}
@@ -41,7 +40,7 @@ fun IrParcelerScope?.hasCustomSerializer(irType: IrType): Boolean {
fun IrAnnotationContainer.getParcelerScope(parent: IrParcelerScope? = null): IrParcelerScope? {
val typeParcelerAnnotations = annotations.filterTo(mutableListOf()) {
it.symbol.owner.constructedClass.fqNameWhenAvailable == ParcelizeAnnotationChecker.TYPE_PARCELER_FQNAME
it.symbol.owner.constructedClass.fqNameWhenAvailable in ParcelizeAnnotationChecker.TYPE_PARCELER_FQ_NAMES
}
if (typeParcelerAnnotations.isEmpty())
@@ -8,12 +8,10 @@ package org.jetbrains.kotlin.parcelize.ir
import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
@@ -23,14 +21,14 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ANDROID_PARCELABLE_CLASS_FQNAME
import org.jetbrains.kotlin.parcelize.ANDROID_PARCELABLE_CREATOR_CLASS_FQNAME
import org.jetbrains.kotlin.parcelize.PARCELER_FQNAME
import org.jetbrains.kotlin.parcelize.PARCELIZE_CLASS_FQNAME
import org.jetbrains.kotlin.parcelize.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase.Companion.CREATOR_NAME
import org.jetbrains.kotlin.types.Variance
// true if the class should be processed by the parcelize plugin
val IrClass.isParcelize: Boolean
get() = kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS && hasAnnotation(PARCELIZE_CLASS_FQNAME)
get() = kind in ParcelizeExtensionBase.ALLOWED_CLASS_KINDS && hasAnyAnnotation(PARCELIZE_CLASS_FQ_NAMES)
// Finds the getter for a pre-existing CREATOR field on the class companion, which is used for manual Parcelable implementations in Kotlin.
val IrClass.creatorGetter: IrSimpleFunctionSymbol?
@@ -175,3 +173,24 @@ fun IrClass.getMethodWithoutArguments(name: String): IrSimpleFunction =
function.name.asString() == name && function.dispatchReceiverParameter != null
&& function.extensionReceiverParameter == null && function.valueParameters.isEmpty()
}
internal fun IrAnnotationContainer.hasAnyAnnotation(fqNames: List<FqName>): Boolean {
for (fqName in fqNames) {
if (hasAnnotation(fqName)) {
return true
}
}
return false
}
internal fun IrAnnotationContainer.getAnyAnnotation(fqNames: List<FqName>): IrConstructorCall? {
for (fqName in fqNames) {
val annotation = getAnnotation(fqName)
if (annotation != null) {
return annotation
}
}
return null
}
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.parcelize.serializers
import kotlinx.parcelize.WriteWith
import org.jetbrains.kotlin.codegen.FrameMap
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -27,6 +26,8 @@ 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.parcelize.findAnyAnnotation
import org.jetbrains.kotlin.parcelize.hasAnyAnnotation
import org.jetbrains.kotlin.parcelize.isParcelize
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -42,7 +43,10 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.*
import java.util.concurrent.ConcurrentHashMap
val RAW_VALUE_ANNOTATION_FQNAME = FqName("kotlinx.parcelize.RawValue")
val RAW_VALUE_ANNOTATION_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.RawValue::class.java.name),
FqName(kotlinx.android.parcel.RawValue::class.java.name)
)
internal typealias TypeParcelerMapping = Pair<KotlinType, KotlinType>
@@ -64,7 +68,10 @@ interface ParcelSerializer {
}
companion object {
private val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name)
private val WRITE_WITH_FQ_NAMES = listOf(
FqName(kotlinx.parcelize.WriteWith::class.java.name),
FqName(kotlinx.android.parcel.WriteWith::class.java.name),
)
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType, forceBoxed: Boolean) = when {
type.isError -> Type.getObjectType("java/lang/Object")
@@ -81,10 +88,10 @@ interface ParcelSerializer {
val typeMapper = context.typeMapper
val className = asmType.className
fun strict() = strict && !type.annotations.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME)
fun strict() = strict && !type.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES)
fun findCustomParcelerType(type: KotlinType): KotlinType? {
type.annotations.findAnnotation(WRITE_WITH_FQNAME)?.let { writeWith ->
type.findAnyAnnotation(WRITE_WITH_FQ_NAMES)?.let { writeWith ->
val parceler = writeWith.type.arguments.singleOrNull()?.type
if (parceler != null && !parceler.isError) {
return parceler
@@ -371,7 +378,7 @@ interface ParcelSerializer {
}
else -> {
if (strict && !type.annotations.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME))
if (strict && !type.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES))
throw IllegalArgumentException("Illegal type")
else
GenericParcelSerializer(asmType)