[Parcelize] Allow parcelize to work on common code in multiplatform.

Since parcelize is Android specific, it should only be enabled for
Android compilation. In order to allow parcelize to generate code
for declarations in common code, we make checkers platform checkers
and we allow the registration of an additional annotations to
trigger parcelize processing.

That way, code such as:

```
package my.package

annotation class Parcelize

expect interface MyParcelable

@Parcelize
data class User(name: String): MyParcelable
```

Will work with an Android platform actual of the form:

```
actual typealias MyParcelable = android.os.Parcelable
```

And telling the plugin to trigger parcelize processing with the
additional annotation `my.package.Parcelize`.

Fixes https://issuetracker.google.com/315775835.
This commit is contained in:
Mads Ager
2024-02-26 14:29:55 +01:00
committed by Space Cloud
parent 5f971d6d9d
commit fa0d456850
26 changed files with 177 additions and 87 deletions
@@ -23,10 +23,10 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.containingPackage
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.TYPE_PARCELER_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_WITH_FQ_NAMES
@@ -43,7 +43,7 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.replaceAnnotations
import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeAnnotationChecker : CallChecker {
open class ParcelizeAnnotationChecker(val parcelizeAnnotations : List<FqName>) : CallChecker {
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
@@ -66,7 +66,7 @@ open class ParcelizeAnnotationChecker : CallChecker {
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
if (annotationClass.fqNameSafe in PARCELIZE_CLASS_FQ_NAMES || annotationClass.fqNameSafe in RAW_VALUE_ANNOTATION_FQ_NAMES) {
if (annotationClass.fqNameSafe in parcelizeAnnotations || annotationClass.fqNameSafe in RAW_VALUE_ANNOTATION_FQ_NAMES) {
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
}
@@ -177,7 +177,7 @@ open class ParcelizeAnnotationChecker : CallChecker {
) {
if (containingClass != null) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize) {
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize(parcelizeAnnotations)) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.report(ErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE.on(reportElement, containingClass))
}
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeDeclarationChecker : DeclarationChecker {
open class ParcelizeDeclarationChecker(val parcelizeAnnotations: List<FqName>) : DeclarationChecker {
private companion object {
private val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName("kotlinx.parcelize.IgnoredOnParcel"),
@@ -71,7 +71,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
declaration: KtFunction,
diagnosticHolder: DiagnosticSink
) {
if (!containingClass.isParcelize) {
if (!containingClass.isParcelize(parcelizeAnnotations)) {
return
}
@@ -97,7 +97,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false)
}
if (containingClass.isParcelize
if (containingClass.isParcelize(parcelizeAnnotations)
&& (declaration.hasDelegate() || bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property] == true)
&& !hasIgnoredOnParcel()
&& !containingClass.hasCustomParceler()
@@ -109,7 +109,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
// @JvmName is not applicable to property so we can check just the descriptor name
if (property.name.asString() == "CREATOR" && property.findJvmFieldAnnotation() != null && containingClass.isCompanionObject) {
val outerClass = containingClass.containingDeclaration as? ClassDescriptor
if (outerClass != null && outerClass.isParcelize) {
if (outerClass != null && outerClass.isParcelize(parcelizeAnnotations)) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement))
}
@@ -141,7 +141,7 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings
) {
if (!descriptor.isParcelize) {
if (!descriptor.isParcelize(parcelizeAnnotations)) {
return
}
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DESCRIBE_CONTENTS_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_FQN
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_FQ_NAMES
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCEL_ID
import org.jetbrains.kotlin.parcelize.ParcelizeNames.WRITE_TO_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS
@@ -46,7 +45,7 @@ import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.types.error.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils
open class ParcelizeResolveExtension : SyntheticResolveExtension {
open class ParcelizeResolveExtension(private val parcelizeAnnotations: List<FqName>) : SyntheticResolveExtension {
companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? {
return module.findClassAcrossModuleDependencies(PARCEL_ID)?.defaultType
@@ -107,7 +106,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? = null
override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List<Name> {
return if (thisDescriptor.isParcelize) parcelizeMethodNames else emptyList()
return if (thisDescriptor.isParcelize(parcelizeAnnotations)) parcelizeMethodNames else emptyList()
}
override fun generateSyntheticMethods(
@@ -123,7 +122,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
}
if (name.asString() == DESCRIBE_CONTENTS.methodName
&& thisDescriptor.isParcelize
&& thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isDescribeContents() }
@@ -131,7 +130,7 @@ open class ParcelizeResolveExtension : SyntheticResolveExtension {
) {
result += createMethod(thisDescriptor, DESCRIBE_CONTENTS, Modality.OPEN, thisDescriptor.builtIns.intType)
} else if (name.asString() == WRITE_TO_PARCEL.methodName
&& thisDescriptor.isParcelize
&& thisDescriptor.isParcelize(parcelizeAnnotations)
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isWriteToParcel() }
@@ -173,13 +172,13 @@ interface ParcelizeSyntheticComponent {
}
}
val ClassDescriptor.hasParcelizeAnnotation: Boolean
get() = PARCELIZE_CLASS_FQ_NAMES.any(annotations::hasAnnotation)
fun ClassDescriptor.hasParcelizeAnnotation(parcelizeAnnotations: List<FqName>): Boolean =
parcelizeAnnotations.any(annotations::hasAnnotation)
val ClassDescriptor.isParcelize: Boolean
get() = hasParcelizeAnnotation
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation }
fun ClassDescriptor.isParcelize(parcelizeAnnotations: List<FqName>): Boolean =
hasParcelizeAnnotation(parcelizeAnnotations)
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation(parcelizeAnnotations) == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation(parcelizeAnnotations) }
val KotlinType.isParceler: Boolean
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQN
@@ -38,6 +38,7 @@ interface ParcelSerializer {
fun readValue(v: InstructionAdapter)
data class ParcelSerializerContext(
val parcelizeAnnotations: List<FqName>,
val typeMapper: KotlinTypeMapper,
val containerClassType: Type,
val typeParcelers: List<TypeParcelerMapping>,
@@ -344,7 +345,7 @@ interface ParcelSerializer {
val creatorAsmType = when {
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true)
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
clazz.isParcelize(context.parcelizeAnnotations) -> Type.getObjectType(asmType.internalName + "\$Creator")
else -> null
}
@@ -36,7 +36,8 @@ interface ParcelizeExtensionBase {
.isNotEmpty()
}
val ClassDescriptor.isParcelizeClassDescriptor get() = kind in ALLOWED_CLASS_KINDS && isParcelize
fun ClassDescriptor.isParcelizeClassDescriptor(parcelizeAnnotations: List<FqName>)
= kind in ALLOWED_CLASS_KINDS && isParcelize(parcelizeAnnotations)
fun ClassDescriptor.hasSyntheticDescribeContents() = hasParcelizeSyntheticMethod(DESCRIBE_CONTENTS)