[Parcelize] Reorganize module structure of Parcelize plugin

Also mark parcelize as compatible with K2
This commit is contained in:
Dmitriy Novozhilov
2022-05-17 15:44:02 +03:00
committed by teamcity
parent 2a7dc1cc0c
commit 259862d294
45 changed files with 193 additions and 127 deletions
@@ -0,0 +1,25 @@
description = "Parcelize compiler plugin (Backend)"
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":plugins:parcelize:parcelize-compiler:parcelize.common"))
compileOnly(project(":compiler:util"))
compileOnly(project(":compiler:frontend"))
compileOnly(project(":compiler:frontend.java"))
compileOnly(project(":compiler:backend")) // for KotlinTypeMapper
compileOnly(intellijCore())
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
runtimeJar()
javadocJar()
sourcesJar()
@@ -0,0 +1,194 @@
/*
* 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 org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
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.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
import org.jetbrains.kotlin.parcelize.diagnostic.ErrorsParcelize
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
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 {
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
val annotationEntry = resolvedCall.call.callElement.getNonStrictParentOfType<KtAnnotationEntry>() ?: return
val annotationOwner = annotationEntry.getStrictParentOfType<KtModifierListOwner>() ?: return
if (annotationClass.fqNameSafe in TYPE_PARCELER_FQ_NAMES) {
checkTypeParcelerUsage(resolvedCall, annotationEntry, context, annotationOwner)
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = true)
}
if (annotationClass.fqNameSafe in WRITE_WITH_FQ_NAMES) {
checkWriteWithUsage(resolvedCall, annotationEntry, context, annotationOwner)
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = true)
}
if (annotationClass.fqNameSafe in IGNORED_ON_PARCEL_FQ_NAMES) {
checkIgnoredOnParcelUsage(annotationEntry, context, annotationOwner)
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
if (annotationClass.fqNameSafe in PARCELIZE_CLASS_FQ_NAMES || annotationClass.fqNameSafe in RAW_VALUE_ANNOTATION_FQ_NAMES) {
checkDeprecatedAnnotations(resolvedCall, annotationEntry, context, isForbidden = false)
}
}
private fun checkDeprecatedAnnotations(
resolvedCall: ResolvedCall<*>,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext,
isForbidden: Boolean
) {
val descriptorPackage = resolvedCall.resultingDescriptor.containingPackage()
if (descriptorPackage == DEPRECATED_RUNTIME_PACKAGE) {
val factory = if (isForbidden) ErrorsParcelize.FORBIDDEN_DEPRECATED_ANNOTATION else ErrorsParcelize.DEPRECATED_ANNOTATION
context.trace.report(factory.on(annotationEntry))
}
}
private fun checkIgnoredOnParcelUsage(annotationEntry: KtAnnotationEntry, context: CallCheckerContext, element: KtModifierListOwner) {
if (element is KtParameter && PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) is KtPrimaryConstructor) {
if (!element.hasDefaultValue()) {
context.trace.report(ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY.on(annotationEntry))
}
} else if (element !is KtProperty || PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) !is KtClassOrObject) {
context.trace.report(ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL.on(annotationEntry))
}
}
private fun checkTypeParcelerUsage(
resolvedCall: ResolvedCall<*>,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext,
element: KtModifierListOwner
) {
val descriptor = context.trace[BindingContext.DECLARATION_TO_DESCRIPTOR, element] ?: return
val thisMappedType = resolvedCall.typeArguments.values.takeIf { it.size == 2 }?.first() ?: return
val duplicatingAnnotationCount = descriptor.annotations
.filter { it.fqName in TYPE_PARCELER_FQ_NAMES }
.mapNotNull { it.type.arguments.takeIf { args -> args.size == 2 }?.first()?.type }
.count { it == thisMappedType }
if (duplicatingAnnotationCount > 1) {
val reportElement = annotationEntry.typeArguments.firstOrNull() ?: annotationEntry
context.trace.report(ErrorsParcelize.DUPLICATING_TYPE_PARCELERS.on(reportElement))
return
}
val containingClass = when (element) {
is KtClassOrObject -> element
is KtParameter -> element.containingClassOrObject
else -> null
}
checkIfTheContainingClassIsParcelize(containingClass, annotationEntry, context)
if (element is KtParameter && element.getStrictParentOfType<KtDeclaration>() is KtPrimaryConstructor) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
val thisAnnotationDescriptor = context.trace[BindingContext.ANNOTATION, annotationEntry]
if (containingClass != null && containingClassDescriptor != null && thisAnnotationDescriptor != null) {
// We can ignore value arguments here cause @TypeParceler is a zero-parameter annotation
if (containingClassDescriptor.annotations.any { it.type == thisAnnotationDescriptor.type }) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.report(ErrorsParcelize.REDUNDANT_TYPE_PARCELER.on(reportElement, containingClass))
}
}
}
}
private fun checkWriteWithUsage(
resolvedCall: ResolvedCall<*>,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext,
element: KtModifierListOwner
) {
if (element !is KtTypeReference) {
return
}
val actualType = context.trace[BindingContext.TYPE, element]?.replaceAnnotations(Annotations.EMPTY) ?: return
val parcelerType = resolvedCall.typeArguments.values.singleOrNull() ?: return
val parcelerClass = parcelerType.constructor.declarationDescriptor as? ClassDescriptor ?: return
val containingClass = element.getStrictParentOfType<KtClassOrObject>()
checkIfTheContainingClassIsParcelize(containingClass, annotationEntry, context)
fun reportElement() = annotationEntry.typeArguments.singleOrNull() ?: annotationEntry
if (parcelerClass.kind != ClassKind.OBJECT) {
context.trace.report(ErrorsParcelize.PARCELER_SHOULD_BE_OBJECT.on(reportElement()))
return
}
fun KotlinType.fqName() = constructor.declarationDescriptor?.fqNameSafe
val parcelerSuperType = parcelerClass.defaultType.supertypes().firstOrNull { it.fqName() == PARCELER_FQN } ?: return
val expectedType = parcelerSuperType.arguments.singleOrNull()?.type ?: return
if (!actualType.isSubtypeOf(expectedType)) {
context.trace.report(ErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE.on(reportElement(), expectedType, actualType))
}
}
private fun checkIfTheContainingClassIsParcelize(
containingClass: KtClassOrObject?,
annotationEntry: KtAnnotationEntry,
context: CallCheckerContext
) {
if (containingClass != null) {
val containingClassDescriptor = context.trace[BindingContext.CLASS, containingClass]
if (containingClassDescriptor != null && !containingClassDescriptor.isParcelize) {
val reportElement = (annotationEntry.typeReference?.typeElement as? KtUserType)?.referenceExpression ?: annotationEntry
context.trace.report(ErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE.on(reportElement, containingClass))
}
}
}
}
internal inline fun <reified T : PsiElement> PsiElement.getStrictParentOfType(): T? {
return PsiTreeUtil.getParentOfType(this, T::class.java, true)
}
internal inline fun <reified T : PsiElement> PsiElement.getNonStrictParentOfType(): T? {
return PsiTreeUtil.getParentOfType(this, T::class.java, false)
}
@@ -0,0 +1,268 @@
/*
* Copyright 2010-2018 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
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.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
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.TypeUtils
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.typeUtil.supertypes
open class ParcelizeDeclarationChecker : DeclarationChecker {
private companion object {
private val IGNORED_ON_PARCEL_FQ_NAMES = listOf(
FqName("kotlinx.parcelize.IgnoredOnParcel"),
FqName("kotlinx.android.parcel.IgnoredOnParcel")
)
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val trace = context.trace
when (descriptor) {
is ClassDescriptor -> {
checkParcelableClass(descriptor, declaration, trace, trace.bindingContext, context.languageVersionSettings)
checkParcelerClass(descriptor, declaration, trace)
}
is SimpleFunctionDescriptor -> {
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
val ktFunction = declaration as? KtFunction
if (containingClass != null && ktFunction != null) {
checkParcelableClassMethod(descriptor, containingClass, ktFunction, trace)
}
}
is PropertyDescriptor -> {
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
val ktProperty = declaration as? KtProperty
if (containingClass != null && ktProperty != null) {
checkParcelableClassProperty(descriptor, containingClass, ktProperty, trace, trace.bindingContext)
}
}
}
}
private fun checkParcelableClassMethod(
method: SimpleFunctionDescriptor,
containingClass: ClassDescriptor,
declaration: KtFunction,
diagnosticHolder: DiagnosticSink
) {
if (!containingClass.isParcelize) {
return
}
if (method.isWriteToParcel() && declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
val reportElement = declaration.modifierList?.getModifier(KtTokens.OVERRIDE_KEYWORD)
?: declaration.nameIdentifier
?: declaration
diagnosticHolder.report(ErrorsParcelize.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED.on(reportElement))
}
}
private fun checkParcelableClassProperty(
property: PropertyDescriptor,
containingClass: ClassDescriptor,
declaration: KtProperty,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext
) {
fun hasIgnoredOnParcel(): Boolean {
fun Annotations.hasIgnoredOnParcel() = any { it.fqName in IGNORED_ON_PARCEL_FQ_NAMES }
return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false)
}
if (containingClass.isParcelize
&& (declaration.hasDelegate() || bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property] == true)
&& !hasIgnoredOnParcel()
&& !containingClass.hasCustomParceler()
) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PROPERTY_WONT_BE_SERIALIZED.on(reportElement))
}
// @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) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement))
}
}
}
private fun checkParcelerClass(
descriptor: ClassDescriptor,
declaration: KtDeclaration,
diagnosticHolder: DiagnosticSink,
) {
if (!descriptor.isCompanionObject || declaration !is KtObjectDeclaration) {
return
}
for (type in descriptor.defaultType.supertypes()) {
if (type.constructor.declarationDescriptor?.fqNameSafe == OLD_PARCELER_FQN) {
val reportElement = declaration.nameIdentifier ?: declaration.getObjectKeyword() ?: declaration
diagnosticHolder.report(ErrorsParcelize.DEPRECATED_PARCELER.on(reportElement))
break
}
}
}
private fun checkParcelableClass(
descriptor: ClassDescriptor,
declaration: KtDeclaration,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext,
languageVersionSettings: LanguageVersionSettings
) {
if (!descriptor.isParcelize) {
return
}
if (declaration !is KtClassOrObject) {
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS.on(declaration))
return
}
if (declaration is KtClass && (declaration.isAnnotation() || declaration.isInterface() && !declaration.isSealed())) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS.on(reportElement))
return
}
for (companion in declaration.companionObjects) {
if (companion.name == "CREATOR") {
val reportElement = companion.nameIdentifier ?: companion
diagnosticHolder.report(ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED.on(reportElement))
}
}
val abstractModifier = declaration.modifierList?.let { it.getModifier(KtTokens.ABSTRACT_KEYWORD) }
if (abstractModifier != null) {
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_SHOULD_BE_INSTANTIABLE.on(abstractModifier))
}
if (declaration is KtClass && declaration.isInner()) {
val reportElement = declaration.modifierList?.getModifier(KtTokens.INNER_KEYWORD) ?: declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_CANT_BE_INNER_CLASS.on(reportElement))
}
if (declaration.isLocal) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_CANT_BE_LOCAL_CLASS.on(reportElement))
}
val superTypes = TypeUtils.getAllSupertypes(descriptor.defaultType)
if (superTypes.none { it.constructor.declarationDescriptor?.fqNameSafe == PARCELABLE_FQN }) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.NO_PARCELABLE_SUPERTYPE.on(reportElement))
}
for (supertypeEntry in declaration.superTypeListEntries) {
supertypeEntry as? KtDelegatedSuperTypeEntry ?: continue
val delegateExpression = supertypeEntry.delegateExpression ?: continue
val type = bindingContext[BindingContext.TYPE, supertypeEntry.typeReference] ?: continue
if (type.isParcelable()) {
val reportElement = supertypeEntry.byKeywordNode?.psi ?: delegateExpression
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_DELEGATE_IS_NOT_ALLOWED.on(reportElement))
}
}
// The constructor checks are irrelevant for custom parcelers
if (descriptor.hasCustomParceler()) {
return
}
val primaryConstructor = declaration.primaryConstructor
if (primaryConstructor == null && declaration.secondaryConstructors.isNotEmpty()) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(reportElement))
} else if (primaryConstructor != null && primaryConstructor.valueParameters.isEmpty()) {
val reportElement = declaration.nameIdentifier ?: declaration
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY.on(reportElement))
}
val typeMapper = KotlinTypeMapper(
bindingContext,
ClassBuilderMode.FULL,
descriptor.module.name.asString(),
languageVersionSettings,
useOldInlineClassesManglingScheme = false
)
for (parameter in primaryConstructor?.valueParameters.orEmpty<KtParameter>()) {
checkParcelableClassProperty(parameter, descriptor, diagnosticHolder, typeMapper)
}
}
private fun checkParcelableClassProperty(
parameter: KtParameter,
containerClass: ClassDescriptor,
diagnosticHolder: DiagnosticSink,
typeMapper: KotlinTypeMapper
) {
if (!parameter.hasValOrVar()) {
val reportElement = parameter.nameIdentifier ?: parameter
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(reportElement))
}
val descriptor = typeMapper.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter] ?: return
// Don't check parameters which won't be serialized
if (descriptor.annotations.any { it.fqName in IGNORED_ON_PARCEL_FQ_NAMES }) {
return
}
val type = descriptor.type
if (!type.isError) {
val asmType = typeMapper.mapType(type, mode = TypeMappingMode.CLASS_DECLARATION)
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
val reportElement = parameter.typeReference ?: parameter.nameIdentifier ?: parameter
diagnosticHolder.report(ErrorsParcelize.PARCELABLE_TYPE_NOT_SUPPORTED.on(reportElement))
}
}
}
private fun ClassDescriptor.hasCustomParceler(): Boolean {
val companionObjectSuperTypes = companionObjectDescriptor?.let { TypeUtils.getAllSupertypes(it.defaultType) } ?: return false
return companionObjectSuperTypes.any { it.isParceler }
}
}
@@ -0,0 +1,223 @@
/*
* 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 org.jetbrains.kotlin.parcelize
import com.intellij.psi.PsiElement
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
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_ID
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
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL
import org.jetbrains.kotlin.parcelize.serializers.TypeParcelerMapping
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.types.error.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
open class ParcelizeResolveExtension : SyntheticResolveExtension {
companion object {
fun resolveParcelClassType(module: ModuleDescriptor): SimpleType? {
return module.findClassAcrossModuleDependencies(PARCEL_ID)?.defaultType
}
fun resolveParcelableCreatorClassType(module: ModuleDescriptor): SimpleType? {
val creatorClassId = CREATOR_ID
return module.findClassAcrossModuleDependencies(creatorClassId)?.defaultType
}
fun createMethod(
classDescriptor: ClassDescriptor,
componentKind: ParcelizeSyntheticComponent.ComponentKind,
modality: Modality,
returnType: KotlinType,
vararg parameters: Pair<String, KotlinType>
): SimpleFunctionDescriptor {
val functionDescriptor = object : ParcelizeSyntheticComponent, SimpleFunctionDescriptorImpl(
classDescriptor,
null,
Annotations.EMPTY,
Name.identifier(componentKind.methodName),
CallableMemberDescriptor.Kind.SYNTHESIZED,
classDescriptor.source
) {
override val componentKind = componentKind
}
val valueParameters = parameters.mapIndexed { index, (name, type) -> functionDescriptor.makeValueParameter(name, type, index) }
functionDescriptor.initialize(
null, classDescriptor.thisAsReceiverParameter, emptyList(), emptyList(), valueParameters,
returnType, modality, DescriptorVisibilities.PUBLIC
)
return functionDescriptor
}
private fun FunctionDescriptor.makeValueParameter(name: String, type: KotlinType, index: Int): ValueParameterDescriptor {
return ValueParameterDescriptorImpl(
containingDeclaration = this,
original = null,
index = index,
annotations = Annotations.EMPTY,
name = Name.identifier(name),
outType = type,
declaresDefaultValue = false,
isCrossinline = false,
isNoinline = false,
varargElementType = null,
source = this.source
)
}
private val parcelizeMethodNames: List<Name> =
listOf(Name.identifier(DESCRIBE_CONTENTS.methodName), Name.identifier(WRITE_TO_PARCEL.methodName))
}
open fun isAvailable(element: PsiElement): Boolean {
return true
}
override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? = null
override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List<Name> {
return if (thisDescriptor.isParcelize) parcelizeMethodNames else emptyList()
}
override fun generateSyntheticMethods(
thisDescriptor: ClassDescriptor,
name: Name,
bindingContext: BindingContext,
fromSupertypes: List<SimpleFunctionDescriptor>,
result: MutableCollection<SimpleFunctionDescriptor>
) {
fun isParcelizePluginEnabled(): Boolean {
val sourceElement = (thisDescriptor.source as? PsiSourceElement)?.psi ?: return false
return isAvailable(sourceElement)
}
if (name.asString() == DESCRIBE_CONTENTS.methodName
&& thisDescriptor.isParcelize
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isDescribeContents() }
&& fromSupertypes.none { it.isDescribeContents() }
) {
result += createMethod(thisDescriptor, DESCRIBE_CONTENTS, Modality.OPEN, thisDescriptor.builtIns.intType)
} else if (name.asString() == WRITE_TO_PARCEL.methodName
&& thisDescriptor.isParcelize
&& !thisDescriptor.isSealed()
&& isParcelizePluginEnabled()
&& result.none { it.isWriteToParcel() }
) {
val builtIns = thisDescriptor.builtIns
val parcelClassType = resolveParcelClassType(thisDescriptor.module) ?: ErrorUtils.createErrorType(ErrorTypeKind.UNRESOLVED_PARCEL_TYPE)
result += createMethod(
thisDescriptor, WRITE_TO_PARCEL, Modality.OPEN,
builtIns.unitType, "parcel" to parcelClassType, "flags" to builtIns.intType
)
}
}
private fun SimpleFunctionDescriptor.isDescribeContents(): Boolean {
return this.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE
&& modality != Modality.ABSTRACT
&& typeParameters.isEmpty()
&& valueParameters.isEmpty()
// Unfortunately, we can't check the return type as it's unresolved in IDE light classes
}
}
internal fun SimpleFunctionDescriptor.isWriteToParcel(): Boolean {
return typeParameters.isEmpty()
&& valueParameters.size == 2
// Unfortunately, we can't check the first parameter type as it's unresolved in IDE light classes
&& KotlinBuiltIns.isInt(valueParameters[1].type)
&& returnType?.let { KotlinBuiltIns.isUnit(it) } == true
}
interface ParcelizeSyntheticComponent {
val componentKind: ComponentKind
enum class ComponentKind(val methodName: String) {
WRITE_TO_PARCEL(WRITE_TO_PARCEL_NAME.identifier),
DESCRIBE_CONTENTS(DESCRIBE_CONTENTS_NAME.identifier),
NEW_ARRAY(NEW_ARRAY_NAME.identifier),
CREATE_FROM_PARCEL(CREATE_FROM_PARCEL_NAME.identifier)
}
}
val ClassDescriptor.hasParcelizeAnnotation: Boolean
get() = PARCELIZE_CLASS_FQ_NAMES.any(annotations::hasAnnotation)
val ClassDescriptor.isParcelize: Boolean
get() = hasParcelizeAnnotation
|| getSuperClassNotAny()?.takeIf(DescriptorUtils::isSealedClass)?.hasParcelizeAnnotation == true
|| getSuperInterfaces().any { DescriptorUtils.isSealedClass(it) && it.hasParcelizeAnnotation }
val KotlinType.isParceler: Boolean
get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQN
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
}
fun getTypeParcelers(annotations: Annotations): List<TypeParcelerMapping> {
val serializers = mutableListOf<TypeParcelerMapping>()
for (annotation in annotations.filter { it.fqName in ParcelizeNames.TYPE_PARCELER_FQ_NAMES }) {
val (mappedType, parcelerType) = annotation.type.arguments.takeIf { it.size == 2 } ?: continue
serializers += TypeParcelerMapping(mappedType.type, parcelerType.type)
}
return serializers
}
@@ -0,0 +1,153 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.parcelize.diagnostic
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_CLASS_OR_OBJECT
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_TYPE_WITH_ANNOTATIONS
object DefaultErrorMessagesParcelize : DefaultErrorMessages.Extension {
private val MAP = DiagnosticFactoryToRendererMap("Parcelize")
override fun getMap() = MAP
init {
MAP.put(
ErrorsParcelize.PARCELABLE_SHOULD_BE_CLASS,
"'Parcelable' should be a class"
)
MAP.put(
ErrorsParcelize.PARCELABLE_DELEGATE_IS_NOT_ALLOWED,
"Delegating 'Parcelable' is not allowed"
)
MAP.put(
ErrorsParcelize.PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS,
"'Parcelable' should not be a 'enum class'"
)
MAP.put(
ErrorsParcelize.PARCELABLE_SHOULD_BE_INSTANTIABLE,
"'Parcelable' should not be an 'abstract' class"
)
MAP.put(
ErrorsParcelize.PARCELABLE_CANT_BE_INNER_CLASS,
"'Parcelable' can't be an inner class"
)
MAP.put(
ErrorsParcelize.PARCELABLE_CANT_BE_LOCAL_CLASS,
"'Parcelable' can't be a local class"
)
MAP.put(
ErrorsParcelize.NO_PARCELABLE_SUPERTYPE,
"No 'Parcelable' supertype"
)
MAP.put(
ErrorsParcelize.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR,
"'Parcelable' should have a primary constructor"
)
MAP.put(
ErrorsParcelize.PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY,
"The primary constructor is empty, no data will be serialized to 'Parcel'"
)
MAP.put(
ErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR,
"'Parcelable' constructor parameter should be 'val' or 'var'"
)
MAP.put(
ErrorsParcelize.PROPERTY_WONT_BE_SERIALIZED,
"Property would not be serialized into a 'Parcel'. Add '@IgnoredOnParcel' annotation to remove the warning"
)
MAP.put(
ErrorsParcelize.OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED,
"Overriding 'writeToParcel' is not allowed. Use 'Parceler' companion object instead"
)
MAP.put(
ErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED,
"'CREATOR' definition is not allowed. Use 'Parceler' companion object instead"
)
MAP.put(
ErrorsParcelize.PARCELABLE_TYPE_NOT_SUPPORTED,
"Type is not directly supported by 'Parcelize'. " +
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'"
)
MAP.put(
ErrorsParcelize.PARCELER_SHOULD_BE_OBJECT,
"Parceler should be an object"
)
MAP.put(
ErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE,
"Parceler type {0} is incompatible with {1}",
RENDER_TYPE_WITH_ANNOTATIONS, RENDER_TYPE_WITH_ANNOTATIONS
)
MAP.put(
ErrorsParcelize.DUPLICATING_TYPE_PARCELERS,
"Duplicating ''TypeParceler'' annotations"
)
MAP.put(
ErrorsParcelize.REDUNDANT_TYPE_PARCELER,
"This ''TypeParceler'' is already provided for {0}",
RENDER_CLASS_OR_OBJECT
)
MAP.put(
ErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE,
"{0} should be annotated with ''@Parcelize''",
RENDER_CLASS_OR_OBJECT
)
MAP.put(
ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL,
"'@IgnoredOnParcel' is only applicable to class properties"
)
MAP.put(
ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
"'@IgnoredOnParcel' is inapplicable to properties without default value declared in the primary constructor"
)
MAP.put(
ErrorsParcelize.FORBIDDEN_DEPRECATED_ANNOTATION,
"Parceler-related annotations from package 'kotlinx.android.parcel' are forbidden. Change package to 'kotlinx.parcelize'"
)
MAP.put(
ErrorsParcelize.DEPRECATED_ANNOTATION,
"Parcelize annotations from package 'kotlinx.android.parcel' are deprecated. Change package to 'kotlinx.parcelize'"
)
MAP.put(
ErrorsParcelize.DEPRECATED_PARCELER,
"'kotlinx.android.parcel.Parceler' is deprecated. Use 'kotlinx.parcelize.Parceler' instead"
)
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.parcelize.diagnostic;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.types.KotlinType;
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
public interface ErrorsParcelize {
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_BE_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_DELEGATE_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_BE_INSTANTIABLE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_CANT_BE_INNER_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_CANT_BE_LOCAL_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NO_PARCELABLE_SUPERTYPE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELER_SHOULD_BE_OBJECT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> PARCELER_TYPE_INCOMPATIBLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<PsiElement> DUPLICATING_TYPE_PARCELERS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, KtClassOrObject> REDUNDANT_TYPE_PARCELER = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, KtClassOrObject> CLASS_SHOULD_BE_PARCELIZE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> FORBIDDEN_DEPRECATED_ANNOTATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> DEPRECATED_ANNOTATION = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> DEPRECATED_PARCELER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_IGNORED_ON_PARCEL = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY = DiagnosticFactory0.create(WARNING);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages(ErrorsParcelize.class, DefaultErrorMessagesParcelize.INSTANCE);
}
};
}
@@ -0,0 +1,434 @@
/*
* 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.serializers
import org.jetbrains.kotlin.codegen.FrameMap
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
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.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.ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES
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.scopes.MemberScope
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.synthetic.isVisibleOutside
import org.jetbrains.kotlin.types.KotlinType
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
interface ParcelSerializer {
val asmType: Type
fun writeValue(v: InstructionAdapter)
fun readValue(v: InstructionAdapter)
data class ParcelSerializerContext(
val typeMapper: KotlinTypeMapper,
val containerClassType: Type,
val typeParcelers: List<TypeParcelerMapping>,
val frameMap: FrameMap
) {
fun findParcelerClass(type: KotlinType): KotlinType? {
return typeParcelers.firstOrNull { it.first == type }?.second
}
}
companion object {
private val WRITE_WITH_FQ_NAMES = listOf(
FqName("kotlinx.parcelize.WriteWith"),
FqName("kotlinx.android.parcel.WriteWith"),
)
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType, forceBoxed: Boolean) = when {
type.isError -> Type.getObjectType("java/lang/Object")
else -> mapType(type, null, if (forceBoxed) TypeMappingMode.GENERIC_ARGUMENT else TypeMappingMode.DEFAULT)
}
fun get(
type: KotlinType,
asmType: Type,
context: ParcelSerializerContext,
forceBoxed: Boolean = false,
strict: Boolean = false
): ParcelSerializer {
val typeMapper = context.typeMapper
val className = asmType.className
fun strict() = strict && !type.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES)
fun findCustomParcelerType(type: KotlinType): KotlinType? {
type.findAnyAnnotation(WRITE_WITH_FQ_NAMES)?.let { writeWith ->
val parceler = writeWith.type.arguments.singleOrNull()?.type
if (parceler != null && !parceler.isError) {
return parceler
}
}
return context.findParcelerClass(type)?.takeIf { !it.isError }
}
findCustomParcelerType(type)?.let { return TypeParcelerParcelSerializer(asmType, it, context.typeMapper) }
return when {
// Somewhat inconsistently, there are no `Parcel.writeShortArray/createShortArray` methods,
// so we need to exclude "[S" here.
asmType.descriptor == "[I"
|| asmType.descriptor == "[Z"
|| asmType.descriptor == "[B"
|| asmType.descriptor == "[C"
|| asmType.descriptor == "[D"
|| asmType.descriptor == "[F"
|| asmType.descriptor == "[J"
-> {
val customElementParcelerType = findCustomParcelerType(type.builtIns.getArrayElementType(type))
if (customElementParcelerType != null) {
val elementType = asmType.elementType
val elementParceler = TypeParcelerParcelSerializer(elementType, customElementParcelerType, context.typeMapper)
ArrayParcelSerializer(asmType, elementParceler)
} else {
PrimitiveArrayParcelSerializer(asmType)
}
}
asmType.descriptor == "[Landroid/os/IBinder;" -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeBinderArray"), Method("createBinderArray")
)
}
asmType.descriptor == "[Ljava/lang/String;" -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeStringArray"), Method("createStringArray")
)
}
asmType.sort == Type.ARRAY -> {
val elementType = type.builtIns.getArrayElementType(type)
val elementSerializer =
get(elementType, typeMapper.mapTypeSafe(elementType, forceBoxed = true), context, strict = strict())
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
}
asmType.isPrimitive() -> {
if (forceBoxed || type.isMarkedNullable)
wrapToNullAwareIfNeeded(type, BoxedPrimitiveTypeParcelSerializer.forUnboxedType(asmType))
else
PrimitiveTypeParcelSerializer.getInstance(asmType)
}
asmType.isUnsigned() -> {
ParcelSerializerStub(asmType, type)
}
asmType.isString() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeString"),
Method("readString")
)
}
className == List::class.java.canonicalName
|| className == ArrayList::class.java.canonicalName
|| className == LinkedList::class.java.canonicalName
|| className == Set::class.java.canonicalName
|| className == SortedSet::class.java.canonicalName
|| className == NavigableSet::class.java.canonicalName
|| className == HashSet::class.java.canonicalName
|| className == LinkedHashSet::class.java.canonicalName
|| className == TreeSet::class.java.canonicalName
-> {
val elementType = type.arguments.single().type
val elementAsmType = typeMapper.mapTypeSafe(elementType, forceBoxed = true)
if (className == List::class.java.canonicalName) {
// Don't care if the element type is nullable cause both writeStrongBinder() and writeString() support null values
if (elementAsmType.descriptor == "Landroid/os/IBinder;") {
return NullCompliantObjectParcelSerializer(
asmType,
Method("writeBinderList"), Method("createBinderArrayList", "()Ljava/util/ArrayList;")
)
} else if (elementAsmType.descriptor == "Ljava/lang/String;") {
return NullCompliantObjectParcelSerializer(
asmType,
Method("writeStringList"), Method("createStringArrayList", "()Ljava/util/ArrayList;")
)
}
}
val elementSerializer = get(elementType, elementAsmType, context, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer, context.frameMap))
}
className == Map::class.java.canonicalName
|| className == SortedMap::class.java.canonicalName
|| className == NavigableMap::class.java.canonicalName
|| className == HashMap::class.java.canonicalName
|| className == LinkedHashMap::class.java.canonicalName
|| className == TreeMap::class.java.canonicalName
|| className == ConcurrentHashMap::class.java.canonicalName
-> {
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
val keySerializer = get(
keyType.type, typeMapper.mapTypeSafe(keyType.type, forceBoxed = true), context, forceBoxed = true, strict = strict()
)
val valueSerializer = get(
valueType.type,
typeMapper.mapTypeSafe(valueType.type, forceBoxed = true),
context,
forceBoxed = true,
strict = strict()
)
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer, context.frameMap))
}
asmType.isBoxedPrimitive() -> {
wrapToNullAwareIfNeeded(type, BoxedPrimitiveTypeParcelSerializer.forBoxedType(asmType))
}
asmType.isBlob() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeBlob"),
Method("readBlob")
)
}
asmType.isSize() -> {
wrapToNullAwareIfNeeded(
type, NullCompliantObjectParcelSerializer(
asmType,
Method("writeSize"),
Method("readSize")
)
)
}
asmType.isSizeF() -> wrapToNullAwareIfNeeded(
type, NullCompliantObjectParcelSerializer(
asmType,
Method("writeSizeF"),
Method("readSizeF")
)
)
asmType.isBundle() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeBundle"),
Method("readBundle")
)
}
type.isIBinder() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeStrongBinder", "(Landroid/os/IBinder;)V"),
Method("readStrongBinder", "()Landroid/os/IBinder;")
)
}
type.isIInterface() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeStrongInterface", "(Landroid/os/IInterface;)V"),
Method("readStrongInterface", "()Landroid/os/IInterface;")
)
}
asmType.isPersistableBundle() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeBundle"),
Method("readBundle")
)
}
asmType.isSparseBooleanArray() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeSparseBooleanArray"),
Method("readSparseBooleanArray")
)
}
asmType.isSparseIntArray() -> {
wrapToNullAwareIfNeeded(
type, SparseArrayParcelSerializer(
asmType, PrimitiveTypeParcelSerializer.getInstance(Type.INT_TYPE), context.frameMap
)
)
}
asmType.isSparseLongArray() -> {
wrapToNullAwareIfNeeded(
type, SparseArrayParcelSerializer(
asmType, PrimitiveTypeParcelSerializer.getInstance(Type.LONG_TYPE), context.frameMap
)
)
}
asmType.isSparseArray() -> {
val elementType = type.arguments.single().type
val elementSerializer = get(
elementType, typeMapper.mapTypeSafe(elementType, forceBoxed = true), context, forceBoxed = true, strict = strict()
)
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer, context.frameMap))
}
type.isCharSequence() -> {
CharSequenceParcelSerializer(asmType)
}
type.isException() -> {
wrapToNullAwareIfNeeded(
type, NullCompliantObjectParcelSerializer(
asmType,
Method("writeException"),
Method("readException")
)
)
}
asmType.isFileDescriptor() -> {
wrapToNullAwareIfNeeded(
type, NullCompliantObjectParcelSerializer(
asmType,
Method("writeRawFileDescriptor"),
Method("readRawFileDescriptor")
)
)
}
// Write at least a nullability byte.
// We don't want parcel to be empty in case if all constructor parameters are objects
type.isNamedObject() -> {
NullAwareParcelSerializerWrapper(ObjectParcelSerializer(asmType, type, typeMapper))
}
type.isEnum() -> {
wrapToNullAwareIfNeeded(type, EnumParcelSerializer(asmType))
}
type.isParcelable() -> {
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
if (clazz != null && clazz.modality == Modality.FINAL && clazz.source is PsiSourceElement) {
fun MemberScope.findCreatorField() = getContributedVariables(
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS
).firstOrNull()
val creatorVar = when (clazz) {
is JavaClassDescriptor -> clazz.staticScope.findCreatorField()
else -> clazz.companionObjectDescriptor?.unsubstitutedMemberScope?.findCreatorField()
?.takeIf { it.annotations.hasAnnotation(FqName(JvmField::class.java.name)) }
}
val creatorAsmType = when {
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type, forceBoxed = true)
clazz.isParcelize -> Type.getObjectType(asmType.internalName + "\$Creator")
else -> null
}
creatorAsmType?.let { wrapToNullAwareIfNeeded(type, EfficientParcelableParcelSerializer(asmType)) }
?: GenericParcelableParcelSerializer(asmType, context.containerClassType)
} else {
GenericParcelableParcelSerializer(asmType, context.containerClassType)
}
}
type.isSerializable() -> {
NullCompliantObjectParcelSerializer(
asmType,
Method("writeSerializable", "(Ljava/io/Serializable;)V"),
Method("readSerializable", "()Ljava/io/Serializable;")
)
}
else -> {
if (strict && !type.hasAnyAnnotation(RAW_VALUE_ANNOTATION_FQ_NAMES))
throw IllegalArgumentException("Illegal type")
else
GenericParcelSerializer(asmType)
}
}
}
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer): ParcelSerializer {
return when {
type.isMarkedNullable -> NullAwareParcelSerializerWrapper(serializer)
else -> serializer
}
}
private fun Type.isBlob() = this.sort == Type.ARRAY && this.elementType == Type.BYTE_TYPE
private fun Type.isString() = this.descriptor == "Ljava/lang/String;"
private fun Type.isSize() = this.descriptor == "Landroid/util/Size;"
private fun Type.isSizeF() = this.descriptor == "Landroid/util/SizeF;"
private fun Type.isFileDescriptor() = this.descriptor == "Ljava/io/FileDescriptor;"
private fun Type.isBundle() = this.descriptor == "Landroid/os/Bundle;"
private fun Type.isPersistableBundle() = this.descriptor == "Landroid/os/PersistableBundle;"
private fun Type.isSparseBooleanArray() = this.descriptor == "Landroid/util/SparseBooleanArray;"
private fun Type.isSparseIntArray() = this.descriptor == "Landroid/util/SparseIntArray;"
private fun Type.isSparseLongArray() = this.descriptor == "Landroid/util/SparseLongArray;"
private fun Type.isSparseArray() = this.descriptor == "Landroid/util/SparseArray;"
private fun KotlinType.isException() = matchesFqNameWithSupertypes("java.lang.Exception")
private fun KotlinType.isIBinder() = matchesFqNameWithSupertypes("android.os.IBinder")
private fun KotlinType.isIInterface() = matchesFqNameWithSupertypes("android.os.IInterface")
private fun KotlinType.isCharSequence() = matchesFqName("kotlin.CharSequence") || matchesFqName("java.lang.CharSequence")
private fun KotlinType.isSerializable() = matchesFqNameWithSupertypes("java.io.Serializable")
|| matchesFqNameWithSupertypes("kotlin.Function")
private fun KotlinType.isNamedObject(): Boolean {
val classDescriptor = constructor.declarationDescriptor as? ClassDescriptor ?: return false
if (!classDescriptor.visibility.isVisibleOutside()) return false
if (DescriptorUtils.isAnonymousObject(classDescriptor)) return false
return classDescriptor.kind == ClassKind.OBJECT
}
private fun KotlinType.isEnum() = (constructor.declarationDescriptor as? ClassDescriptor)?.kind == ClassKind.ENUM_CLASS
private fun Type.isPrimitive(): Boolean = when (this.sort) {
Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT, Type.FLOAT, Type.LONG, Type.DOUBLE -> true
else -> false
}
private fun Type.isUnsigned(): Boolean = when (descriptor) {
"Lkotlin/UByte;", "Lkotlin/UShort;", "Lkotlin/UInt;", "Lkotlin/ULong;",
"Lkotlin/UByteArray;", "Lkotlin/UShortArray;", "Lkotlin/UIntArray;", "Lkotlin/ULongArray;" -> true
else -> false
}
private fun Type.isBoxedPrimitive(): Boolean = when (this.descriptor) {
"Ljava/lang/Boolean;",
"Ljava/lang/Character;",
"Ljava/lang/Byte;",
"Ljava/lang/Short;",
"Ljava/lang/Integer;",
"Ljava/lang/Float;",
"Ljava/lang/Long;",
"Ljava/lang/Double;"
-> true
else -> false
}
}
}
@@ -0,0 +1,837 @@
/*
* 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.serializers
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.FrameMap
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.codegen.useTmpVar
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.parcelize.serializers.BoxedPrimitiveTypeParcelSerializer.Companion.BOXED_VALUE_METHOD_NAMES
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
val PARCEL_TYPE = Type.getObjectType("android/os/Parcel")
val PARCELER_TYPE = Type.getObjectType("kotlinx/parcelize/Parceler")
internal class GenericParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "writeValue", "(Ljava/lang/Object;)V", false)
}
override fun readValue(v: InstructionAdapter) {
v.aconst(asmType) // -> parcel, type
v.invokevirtual("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false) // -> parcel, classloader
v.invokevirtual(PARCEL_TYPE.internalName, "readValue", "(Ljava/lang/ClassLoader;)Ljava/lang/Object;", false)
v.castIfNeeded(asmType)
}
}
internal class TypeParcelerParcelSerializer(
override val asmType: Type,
private val parcelerType: KotlinType,
private val typeMapper: KotlinTypeMapper
) : ParcelSerializer {
private val parcelerAsmType = typeMapper.mapType(parcelerType)
override fun writeValue(v: InstructionAdapter) {
// -> parcel, value(?)
boxTypeIfNeeded(v) // -> parcel, (boxed)value
v.swap() // -> value, parcel
putObjectOrClassInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> value, parcel, parceler
v.dupX2() // -> parceler, value, parcel, parceler
v.pop() // -> parceler, value, parcel
v.load(2, Type.INT_TYPE) // -> parceler, value, parcel, flags
v.invokeinterface(PARCELER_TYPE.internalName, "write", "(Ljava/lang/Object;Landroid/os/Parcel;I)V")
}
override fun readValue(v: InstructionAdapter) {
// -> parcel
putObjectOrClassInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> parcel, parceler
v.swap() // -> parceler, parcel
v.invokeinterface(PARCELER_TYPE.internalName, "create", "(Landroid/os/Parcel;)Ljava/lang/Object;") // -> obj
unboxTypeIfNeeded(v)
v.castIfNeeded(asmType)
}
private fun handleSpecialBoxingCases(v: InstructionAdapter): Type? {
assert(asmType.sort != Type.METHOD)
if (asmType.sort == Type.OBJECT || asmType.sort == Type.ARRAY) {
return null
}
if (asmType == Type.VOID_TYPE) {
v.pop()
v.aconst(null)
return null
}
return AsmUtil.boxType(asmType)
}
private fun boxTypeIfNeeded(v: InstructionAdapter) {
val boxedType = handleSpecialBoxingCases(v) ?: return
v.invokestatic(boxedType.internalName, "valueOf", "(${asmType.descriptor})${boxedType.descriptor}", false)
}
private fun unboxTypeIfNeeded(v: InstructionAdapter) {
val boxedType = handleSpecialBoxingCases(v) ?: return
val getValueMethodName = BOXED_VALUE_METHOD_NAMES.getValue(boxedType.internalName)
v.castIfNeeded(boxedType)
v.invokevirtual(boxedType.internalName, getValueMethodName, "()${asmType.descriptor}", false)
}
}
internal class ArrayParcelSerializer(override val asmType: Type, private val elementSerializer: ParcelSerializer) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.dupX1() // -> arr, parcel, arr
v.arraylength() // -> arr, parcel, length
v.dupX2() // -> length, arr, parcel, length
// Write array size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> length, arr
v.swap() // -> arr, length
v.aconst(0) // -> arr, length, <index>
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
// Loop
v.dup2() // -> arr, length, index, length, index
v.ificmple(loopIsOver) // -> arr, length, index
v.swap() // -> arr, index, length
v.dupX2() // -> length, arr, index, length
v.pop() // -> length, arr, index
v.dup2() // -> length, arr, index, arr, index
v.load(1, PARCEL_TYPE) // -> length, arr, index, arr, index, parcel
v.dupX2() // -> length, arr, index, parcel, arr, index, parcel
v.pop() // -> length, arr, index, parcel, arr, index
v.aload(elementSerializer.asmType) // -> length, arr, index, parcel, obj
v.castIfNeeded(elementSerializer.asmType)
elementSerializer.writeValue(v) // -> length, arr, index
v.aconst(1) // -> length, arr, index, (1)
v.add(Type.INT_TYPE) // -> length, arr, (index + 1)
v.swap() // -> length, (index + 1), arr
v.dupX2() // -> arr, length, (index + 1), arr
v.pop() // -> arr, length, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2() // -> arr
v.pop()
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> length
v.dup() // -> length, length
v.newarray(elementSerializer.asmType) // -> length, arr
v.swap() // -> arr, length
v.aconst(0) // -> arr, length, index
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup2() // -> arr, length, index, length, index
v.ificmple(loopIsOver) // -> arr, length, index
v.swap() // -> arr, index, length
v.dupX2() // -> length, arr, index, length
v.pop() // -> length, arr, index
v.dup2() // -> length, arr, index, arr, index
v.load(1, PARCEL_TYPE) // -> length, arr, index, arr, index, parcel
elementSerializer.readValue(v) // -> length, arr, index, arr, index, obj
v.castIfNeeded(elementSerializer.asmType)
v.astore(elementSerializer.asmType) // -> length, arr, index
v.aconst(1) // -> length, arr, index, (1)
v.add(Type.INT_TYPE) // -> length, arr, (index + 1)
v.swap() // -> length, (index + 1), arr
v.dupX2() // -> arr, length, (index + 1), arr
v.pop() // -> arr, length, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2() // -> arr
}
}
internal fun InstructionAdapter.castIfNeeded(targetType: Type) {
if (targetType.sort != Type.OBJECT && targetType.sort != Type.ARRAY) return
if (targetType.descriptor == "Ljava/lang/Object;") return
checkcast(targetType)
}
internal class ListSetParcelSerializer(
asmType: Type,
elementSerializer: ParcelSerializer,
frameMap: FrameMap
) : AbstractCollectionParcelSerializer(asmType, elementSerializer, frameMap) {
override fun getSize(v: InstructionAdapter) {
v.invokeinterface("java/util/Collection", "size", "()I")
}
override fun getIterator(v: InstructionAdapter) {
v.invokeinterface("java/util/Collection", "iterator", "()Ljava/util/Iterator;")
}
override fun doWriteValue(v: InstructionAdapter) {
// -> parcel, obj
v.castIfNeeded(elementSerializer.asmType)
elementSerializer.writeValue(v)
}
override fun doReadValue(v: InstructionAdapter) {
// -> collection, parcel
elementSerializer.readValue(v) // -> collection, element
v.castIfNeeded(elementSerializer.asmType)
v.invokevirtual(collectionType.internalName, "add", "(Ljava/lang/Object;)Z", false) // -> bool
v.pop()
}
}
internal class MapParcelSerializer(
asmType: Type,
private val keySerializer: ParcelSerializer,
elementSerializer: ParcelSerializer,
frameMap: FrameMap
) : AbstractCollectionParcelSerializer(asmType, elementSerializer, frameMap) {
override fun getSize(v: InstructionAdapter) {
v.invokeinterface("java/util/Map", "size", "()I")
}
override fun getIterator(v: InstructionAdapter) {
v.invokeinterface("java/util/Map", "entrySet", "()Ljava/util/Set;")
v.invokeinterface("java/util/Set", "iterator", "()Ljava/util/Iterator;")
}
override fun doWriteValue(v: InstructionAdapter) {
// -> parcel, obj
v.dup2() // -> parcel, obj, parcel, obj
v.invokeinterface("java/util/Map\$Entry", "getKey", "()Ljava/lang/Object;") // -> parcel, obj, parcel, key
v.castIfNeeded(keySerializer.asmType)
keySerializer.writeValue(v) // -> parcel, obj
v.invokeinterface("java/util/Map\$Entry", "getValue", "()Ljava/lang/Object;") // -> parcel, value
v.castIfNeeded(elementSerializer.asmType)
elementSerializer.writeValue(v)
}
override fun doReadValue(v: InstructionAdapter) {
// -> map, parcel
v.dup() // -> map, parcel, parcel
keySerializer.readValue(v) // -> map, parcel, key
v.castIfNeeded(keySerializer.asmType)
v.swap() // -> map, key, parcel
elementSerializer.readValue(v) // -> map, key, value
v.castIfNeeded(elementSerializer.asmType)
v.invokeinterface("java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") // -> obj
v.pop()
}
}
internal abstract class AbstractCollectionParcelSerializer(
final override val asmType: Type,
protected val elementSerializer: ParcelSerializer,
private val frameMap: FrameMap
) : ParcelSerializer {
protected val collectionType: Type = Type.getObjectType(
when (asmType.internalName) {
"java/util/List" -> "java/util/ArrayList"
"java/util/Set" -> "java/util/LinkedHashSet"
"java/util/SortedSet" -> "java/util/TreeSet"
"java/util/NavigableSet" -> "java/util/TreeSet"
"java/util/Map" -> "java/util/LinkedHashMap"
"java/util/SortedMap" -> "java/util/TreeMap"
"java/util/NavigableMap" -> "java/util/TreeMap"
else -> asmType.internalName
}
)
private var hasConstructorWithCapacity = when (collectionType.internalName) {
"java/util/LinkedList", "java/util/TreeSet", "java/util/TreeMap" -> false
else -> true
}
/**
* Stack before: collection
* Stack after: size
*/
protected abstract fun getSize(v: InstructionAdapter)
/**
* Stack before: collection
* Stack after: iterator
*/
protected abstract fun getIterator(v: InstructionAdapter)
/**
* Stack before: parcel, obj
* Stack after: <empty>
*/
protected abstract fun doWriteValue(v: InstructionAdapter)
/**
* Stack before: collection, parcel
* Stack after: <empty>
*/
protected abstract fun doReadValue(v: InstructionAdapter)
override fun writeValue(v: InstructionAdapter) {
val labelIteratorLoop = Label()
val labelReturn = Label()
v.dupX1() // -> collection, parcel, collection
getSize(v) // -> collection, parcel, size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // collection
getIterator(v) // -> iterator
v.visitLabel(labelIteratorLoop)
v.dup() // -> iterator, iterator
v.invokeinterface("java/util/Iterator", "hasNext", "()Z") // -> iterator, hasNext
v.ifeq(labelReturn) // -> iterator
v.dup() // -> iterator, iterator
v.load(1, PARCEL_TYPE) // iterator, iterator, parcel
v.swap() // iterator, parcel, iterator
v.invokeinterface("java/util/Iterator", "next", "()Ljava/lang/Object;") // -> iterator, parcel, obj
doWriteValue(v) // -> iterator
v.goTo(labelIteratorLoop)
v.visitLabel(labelReturn)
v.pop()
}
override fun readValue(v: InstructionAdapter) {
frameMap.useTmpVar(Type.INT_TYPE) { sizeVarIndex ->
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> size
v.store(sizeVarIndex, Type.INT_TYPE)
v.anew(collectionType) // -> list
v.dup() // -> list, list
if (hasConstructorWithCapacity) {
v.load(sizeVarIndex, Type.INT_TYPE)
v.invokespecial(collectionType.internalName, "<init>", "(I)V", false) // -> list
} else {
v.invokespecial(collectionType.internalName, "<init>", "()V", false) // -> list
}
v.load(sizeVarIndex, Type.INT_TYPE) // -> list, size
}
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dupX1() // -> size, list, size
v.ifeq(loopIsOver) // -> size, list
v.dup() // -> size, list, list
v.load(1, PARCEL_TYPE) // -> size, list, list, parcel
doReadValue(v) // -> size, list
v.swap() // -> list, size
v.aconst(-1) // -> list, size, (-1)
v.add(Type.INT_TYPE) // -> list, (size - 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.swap() // -> list, size
v.pop()
}
}
internal class SparseArrayParcelSerializer(
override val asmType: Type,
private val valueSerializer: ParcelSerializer,
private val frameMap: FrameMap
) : ParcelSerializer {
private val valueType = (valueSerializer as? PrimitiveTypeParcelSerializer)?.asmType ?: Type.getObjectType("java/lang/Object")
override fun writeValue(v: InstructionAdapter) {
v.dup() // -> parcel, arr, arr
v.invokevirtual(asmType.internalName, "size", "()I", false) // -> parcel, arr, size
v.dup2X1() // -> arr, size, parcel, arr, size
v.swap() // -> arr, size, parcel, size, arr
v.pop() // -> arr, size, parcel, size
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> arr, size
v.aconst(0) // -> arr, size, <index>
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup2() // -> arr, size, index, size, index
v.ificmple(loopIsOver) // -> arr, size, index
v.swap() // -> arr, index, size
v.dupX2() // -> size, arr, index, size
v.pop() // -> size, arr, index
v.dup2() // -> size, arr, index, arr, index
v.invokevirtual(asmType.internalName, "keyAt", "(I)I", false) // -> size, arr, index, key
v.load(1, PARCEL_TYPE) // size, arr, index, key, parcel
v.dupX2() // -> size, arr, parcel, index, key, parcel
v.swap() // -> size, arr, parcel, index, parcel, key
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false) // -> size, arr, parcel, index
v.swap() // -> size, arr, index, parcel
v.dupX2() // -> size, parcel, arr, index, parcel
v.pop() // -> size, parcel, arr, index
v.dup2X1() // -> size, arr, index, parcel, arr, index
v.invokevirtual(asmType.internalName, "valueAt", "(I)${valueType.descriptor}", false) // -> size, arr, index, parcel, value
valueSerializer.writeValue(v) // -> size, arr, index
v.aconst(1) // -> size, arr, index, (1)
v.add(Type.INT_TYPE) // -> size, arr, (index + 1)
v.dup2X1() // -> arr, (index + 1), size, arr, (index + 1)
v.pop2() // -> arr, (index + 1), size
v.swap() // -> arr, size, (index + 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop2()
v.pop()
}
override fun readValue(v: InstructionAdapter) {
frameMap.useTmpVar(Type.INT_TYPE) { sizeVarIndex ->
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> size
v.store(sizeVarIndex, Type.INT_TYPE) // -> (empty)
v.anew(asmType) // -> arr
v.dup() // -> arr, arr
v.load(sizeVarIndex, Type.INT_TYPE) // -> arr, arr, size
v.invokespecial(asmType.internalName, "<init>", "(I)V", false) // -> arr
v.load(sizeVarIndex, Type.INT_TYPE) // -> arr, size
}
val nextLoopIteration = Label()
val loopIsOver = Label()
v.visitLabel(nextLoopIteration)
v.dup() // -> arr, size, size
v.ifle(loopIsOver) // -> arr, size
v.swap() // -> size, arr
v.dupX1() // -> arr, size, arr
v.load(1, PARCEL_TYPE) // -> arr, size, arr, parcel
v.dup() // -> arr, size, arr, parcel, parcel
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false) // -> arr, size, arr, parcel, key
v.swap() // -> arr, size, arr, key, parcel
valueSerializer.readValue(v) // -> arr, size, arr, key, value
v.invokevirtual(asmType.internalName, "put", "(I${valueType.descriptor})V", false) // -> arr, size
v.aconst(-1) // -> arr, size, (-1)
v.add(Type.INT_TYPE) // -> arr, (size - 1)
v.goTo(nextLoopIteration)
v.visitLabel(loopIsOver)
v.pop() // -> arr
}
}
internal class ObjectParcelSerializer(
override val asmType: Type,
private val type: KotlinType,
private val typeMapper: KotlinTypeMapper
) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.pop2()
}
override fun readValue(v: InstructionAdapter) {
v.pop()
putObjectOrClassInstanceOnStack(type, asmType, typeMapper, v)
}
}
class ZeroParameterClassSerializer(
override val asmType: Type,
type: KotlinType
) : ParcelSerializer {
private val clazz = type.constructor.declarationDescriptor as ClassDescriptor
init {
assert(clazz.kind == ClassKind.CLASS)
}
override fun writeValue(v: InstructionAdapter) {
v.pop2()
}
override fun readValue(v: InstructionAdapter) {
v.pop()
val constructor = clazz.unsubstitutedPrimaryConstructor
assert(constructor == null || constructor.valueParameters.isEmpty())
v.anew(asmType)
v.dup()
v.invokespecial(asmType.internalName, "<init>", "()V", false)
}
}
private fun putObjectOrClassInstanceOnStack(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, v: InstructionAdapter) {
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
if (clazz != null) {
if (clazz.isCompanionObject) {
val outerClass = clazz.containingDeclaration as? ClassDescriptor
if (outerClass != null) {
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
return
}
}
}
v.getstatic(asmType.internalName, "INSTANCE", asmType.descriptor)
}
internal class EnumParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual("java/lang/Enum", "name", "()Ljava/lang/String;", false)
v.invokevirtual(PARCEL_TYPE.internalName, "writeString", "(Ljava/lang/String;)V", false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, "readString", "()Ljava/lang/String;", false)
v.aconst(asmType)
v.swap()
v.invokestatic("java/lang/Enum", "valueOf", "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", false)
v.castIfNeeded(asmType)
}
}
internal class CharSequenceParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
// -> parcel, seq
v.swap() // -> seq, parcel
v.aconst(0) // -> seq, parcel, flags
v.invokestatic("android/text/TextUtils", "writeToParcel", "(Ljava/lang/CharSequence;Landroid/os/Parcel;I)V", false)
}
override fun readValue(v: InstructionAdapter) {
// -> parcel
v.getstatic("android/text/TextUtils", "CHAR_SEQUENCE_CREATOR", "Landroid/os/Parcelable\$Creator;") // -> parcel, creator
v.swap() // -> creator, parcel
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
v.castIfNeeded(asmType)
}
}
internal class EfficientParcelableParcelSerializer(override val asmType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
// -> parcel, parcelable
v.swap() // -> parcelable, parcel
v.aconst(0) // -> parcelable, parcel, flags
v.invokeinterface("android/os/Parcelable", "writeToParcel", "(Landroid/os/Parcel;I)V")
}
override fun readValue(v: InstructionAdapter) {
// -> parcel
v.getstatic(asmType.internalName, "CREATOR", "Landroid/os/Parcelable\$Creator;") // -> parcel, creator
v.swap() // -> creator, parcel
v.invokeinterface("android/os/Parcelable\$Creator", "createFromParcel", "(Landroid/os/Parcel;)Ljava/lang/Object;")
v.castIfNeeded(asmType)
}
}
internal class GenericParcelableParcelSerializer(override val asmType: Type, private val containerClassType: Type) : ParcelSerializer {
override fun writeValue(v: InstructionAdapter) {
// -> parcel, parcelable
v.load(2, Type.INT_TYPE) // -> parcel, parcelable, flags
v.invokevirtual(PARCEL_TYPE.internalName, "writeParcelable", "(Landroid/os/Parcelable;I)V", false)
}
override fun readValue(v: InstructionAdapter) {
// -> parcel
v.aconst(containerClassType) // -> parcel, type
v.invokevirtual("java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false) // -> parcel, classloader
v.invokevirtual(PARCEL_TYPE.internalName, "readParcelable", "(Ljava/lang/ClassLoader;)Landroid/os/Parcelable;", false)
v.castIfNeeded(asmType)
}
}
class NullAwareParcelSerializerWrapper(private val delegate: ParcelSerializer) : ParcelSerializer {
override val asmType: Type
get() = delegate.asmType
override fun writeValue(v: InstructionAdapter) = writeValueNullAware(v) {
delegate.writeValue(v)
}
override fun readValue(v: InstructionAdapter) = readValueNullAware(v) {
delegate.readValue(v)
}
}
internal class PrimitiveArrayParcelSerializer(
override val asmType: Type
) : ParcelSerializer {
private val methodNameBase = when (asmType.elementType) {
Type.INT_TYPE -> "Int"
Type.BOOLEAN_TYPE -> "Boolean"
Type.BYTE_TYPE -> "Byte"
Type.CHAR_TYPE -> "Char"
Type.DOUBLE_TYPE -> "Double"
Type.FLOAT_TYPE -> "Float"
Type.LONG_TYPE -> "Long"
else -> error("Unsupported type ${asmType.elementType.descriptor}")
}
private val writeMethod = Method("write${methodNameBase}Array", "(${asmType.descriptor})V")
private val createArrayMethod = Method("create${methodNameBase}Array", "()${asmType.descriptor}")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, writeMethod.name, writeMethod.signature, false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, createArrayMethod.name, createArrayMethod.signature, false)
}
}
/** write...() and get...() methods in Android should support passing `null` values. */
internal class NullCompliantObjectParcelSerializer(
override val asmType: Type,
writeMethod: Method<String?>,
readMethod: Method<String?>
) : ParcelSerializer {
private val writeMethod = Method(writeMethod.name, writeMethod.signature ?: "(${asmType.descriptor})V")
private val readMethod = Method(readMethod.name, readMethod.signature ?: "()${asmType.descriptor}")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, writeMethod.name, writeMethod.signature, false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, readMethod.name, readMethod.signature, false)
v.castIfNeeded(asmType)
}
}
internal class BoxedPrimitiveTypeParcelSerializer private constructor(private val unboxedType: Type) : ParcelSerializer {
companion object {
private val BOXED_TO_UNBOXED_TYPE_MAPPINGS = mapOf(
"java/lang/Boolean" to Type.BOOLEAN_TYPE,
"java/lang/Character" to Type.CHAR_TYPE,
"java/lang/Byte" to Type.BYTE_TYPE,
"java/lang/Short" to Type.SHORT_TYPE,
"java/lang/Integer" to Type.INT_TYPE,
"java/lang/Float" to Type.FLOAT_TYPE,
"java/lang/Long" to Type.LONG_TYPE,
"java/lang/Double" to Type.DOUBLE_TYPE
)
private val UNBOXED_TO_BOXED_TYPE_MAPPINGS = BOXED_TO_UNBOXED_TYPE_MAPPINGS.map { it.value to it.key }.toMap()
internal val BOXED_VALUE_METHOD_NAMES = mapOf(
"java/lang/Boolean" to "booleanValue",
"java/lang/Character" to "charValue",
"java/lang/Byte" to "byteValue",
"java/lang/Short" to "shortValue",
"java/lang/Integer" to "intValue",
"java/lang/Float" to "floatValue",
"java/lang/Long" to "longValue",
"java/lang/Double" to "doubleValue"
)
private val INSTANCES = BOXED_TO_UNBOXED_TYPE_MAPPINGS.values.map { type ->
type to BoxedPrimitiveTypeParcelSerializer(type)
}.toMap()
fun forUnboxedType(type: Type) = INSTANCES[type] ?: error("Unsupported type $type")
fun forBoxedType(type: Type) = INSTANCES[BOXED_TO_UNBOXED_TYPE_MAPPINGS[type.internalName]] ?: error("Unsupported type $type")
}
override val asmType: Type = Type.getObjectType(UNBOXED_TO_BOXED_TYPE_MAPPINGS[unboxedType] ?: error("Unsupported type $unboxedType"))
private val unboxedSerializer = PrimitiveTypeParcelSerializer.getInstance(unboxedType)
private val typeValueMethodName = BOXED_VALUE_METHOD_NAMES[asmType.internalName] ?: error("Boxed method name not found for $asmType")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(asmType.internalName, typeValueMethodName, "()${unboxedType.descriptor}", false)
unboxedSerializer.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
unboxedSerializer.readValue(v)
v.invokestatic(asmType.internalName, "valueOf", "(${unboxedType.descriptor})${asmType.descriptor}", false)
}
}
internal open class PrimitiveTypeParcelSerializer private constructor(final override val asmType: Type) : ParcelSerializer {
companion object {
private val WRITE_METHOD_NAMES = mapOf(
Type.BOOLEAN_TYPE to Method("writeInt", "(I)V"),
Type.CHAR_TYPE to Method("writeInt", "(I)V"),
Type.BYTE_TYPE to Method("writeByte", "(B)V"),
Type.SHORT_TYPE to Method("writeInt", "(I)V"),
Type.INT_TYPE to Method("writeInt", "(I)V"),
Type.FLOAT_TYPE to Method("writeFloat", "(F)V"),
Type.LONG_TYPE to Method("writeLong", "(J)V"),
Type.DOUBLE_TYPE to Method("writeDouble", "(D)V")
)
private val READ_METHOD_NAMES = mapOf(
Type.BOOLEAN_TYPE to Method("readInt", "()I"),
Type.CHAR_TYPE to Method("readInt", "()I"),
Type.BYTE_TYPE to Method("readByte", "()B"),
Type.SHORT_TYPE to Method("readInt", "()I"),
Type.INT_TYPE to Method("readInt", "()I"),
Type.FLOAT_TYPE to Method("readFloat", "()F"),
Type.LONG_TYPE to Method("readLong", "()J"),
Type.DOUBLE_TYPE to Method("readDouble", "()D")
)
private val INSTANCES = READ_METHOD_NAMES.keys.map {
it to when (it) {
Type.CHAR_TYPE -> CharParcelSerializer
Type.SHORT_TYPE -> ShortParcelSerializer
Type.BOOLEAN_TYPE -> BooleanParcelSerializer
else -> PrimitiveTypeParcelSerializer(it)
}
}.toMap()
fun getInstance(type: Type) = INSTANCES[type] ?: error("Unsupported type ${type.descriptor}")
}
object CharParcelSerializer : PrimitiveTypeParcelSerializer(Type.CHAR_TYPE) {
override fun writeValue(v: InstructionAdapter) {
v.cast(Type.CHAR_TYPE, Type.INT_TYPE)
super.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
super.readValue(v)
v.cast(Type.INT_TYPE, Type.CHAR_TYPE)
}
}
object ShortParcelSerializer : PrimitiveTypeParcelSerializer(Type.SHORT_TYPE) {
override fun writeValue(v: InstructionAdapter) {
v.cast(Type.SHORT_TYPE, Type.INT_TYPE)
super.writeValue(v)
}
override fun readValue(v: InstructionAdapter) {
super.readValue(v)
v.cast(Type.INT_TYPE, Type.SHORT_TYPE)
}
}
object BooleanParcelSerializer : PrimitiveTypeParcelSerializer(Type.BOOLEAN_TYPE) {
override fun readValue(v: InstructionAdapter) {
super.readValue(v)
val falseLabel = Label()
val conditionIsOver = Label()
v.ifeq(falseLabel)
v.iconst(1)
v.goTo(conditionIsOver)
v.visitLabel(falseLabel)
v.iconst(0)
v.visitLabel(conditionIsOver)
}
}
private val writeMethod = WRITE_METHOD_NAMES[asmType] ?: error("Write method not found for $asmType")
private val readMethod = READ_METHOD_NAMES[asmType] ?: error("Read method not found for $asmType")
override fun writeValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, writeMethod.name, writeMethod.signature, false)
}
override fun readValue(v: InstructionAdapter) {
v.invokevirtual(PARCEL_TYPE.internalName, readMethod.name, readMethod.signature, false)
}
}
internal class ParcelSerializerStub(override val asmType: Type, private val kotlinType: KotlinType) : ParcelSerializer {
private fun throwError() {
TODO("Type is only supported in the IR backend: ${DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(kotlinType)}")
}
override fun writeValue(v: InstructionAdapter) {
throwError()
}
override fun readValue(v: InstructionAdapter) {
throwError()
}
}
private fun readValueNullAware(v: InstructionAdapter, block: () -> Unit) {
val labelNull = Label()
val labelReturn = Label()
v.invokevirtual(PARCEL_TYPE.internalName, "readInt", "()I", false)
v.ifeq(labelNull)
v.load(1, PARCEL_TYPE)
block()
v.goTo(labelReturn)
// Just push null on stack if the value is null
v.visitLabel(labelNull)
v.aconst(null)
v.visitLabel(labelReturn)
}
private fun writeValueNullAware(v: InstructionAdapter, block: () -> Unit) {
val labelReturn = Label()
val labelNull = Label()
v.dup()
v.ifnull(labelNull)
// Write 1 if non-null, 0 if null
v.load(1, PARCEL_TYPE)
v.aconst(1)
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false)
block()
v.goTo(labelReturn)
v.visitLabel(labelNull)
v.pop()
v.aconst(0)
v.invokevirtual(PARCEL_TYPE.internalName, "writeInt", "(I)V", false)
v.visitLabel(labelReturn)
}
internal class Method<out T : String?>(val name: String, val signature: T) {
companion object {
operator fun invoke(name: String) = Method(name, null)
}
}
@@ -0,0 +1,60 @@
/*
* 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.serializers
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS
import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL
import org.jetbrains.kotlin.parcelize.isParcelize
import java.io.FileDescriptor
interface ParcelizeExtensionBase {
companion object {
val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
val ALLOWED_CLASS_KINDS = listOf(ClassKind.CLASS, ClassKind.OBJECT, ClassKind.ENUM_CLASS)
}
fun ClassDescriptor.hasCreatorField(): Boolean {
val companionObject = companionObjectDescriptor ?: return false
if (companionObject.name == CREATOR_NAME) {
return true
}
return companionObject.unsubstitutedMemberScope
.getContributedVariables(CREATOR_NAME, NoLookupLocation.FROM_BACKEND)
.isNotEmpty()
}
val ClassDescriptor.isParcelizeClassDescriptor get() = kind in ALLOWED_CLASS_KINDS && isParcelize
fun ClassDescriptor.hasSyntheticDescribeContents() = hasParcelizeSyntheticMethod(DESCRIBE_CONTENTS)
fun ClassDescriptor.hasSyntheticWriteToParcel() = hasParcelizeSyntheticMethod(WRITE_TO_PARCEL)
fun ClassDescriptor.findFunction(componentKind: ParcelizeSyntheticComponent.ComponentKind): SimpleFunctionDescriptor? {
return unsubstitutedMemberScope
.getContributedFunctions(Name.identifier(componentKind.methodName), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS)
.firstOrNull { (it as? ParcelizeSyntheticComponent)?.componentKind == componentKind }
}
private fun ClassDescriptor.hasParcelizeSyntheticMethod(componentKind: ParcelizeSyntheticComponent.ComponentKind): Boolean {
val methodName = Name.identifier(componentKind.methodName)
val writeToParcelMethods = unsubstitutedMemberScope
.getContributedFunctions(methodName, NoLookupLocation.FROM_BACKEND)
.filter { it is ParcelizeSyntheticComponent && it.componentKind == componentKind }
return writeToParcelMethods.size == 1
}
}
@@ -0,0 +1,27 @@
/*
* 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.serializers
import org.jetbrains.kotlin.parcelize.ParcelizeNames
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
typealias TypeParcelerMapping = Pair<KotlinType, KotlinType>
fun KotlinType.isParcelable() = matchesFqNameWithSupertypes(ParcelizeNames.PARCELABLE_FQN.asString())
fun KotlinType.matchesFqNameWithSupertypes(fqName: String): Boolean {
if (this.matchesFqName(fqName)) {
return true
}
return TypeUtils.getAllSupertypes(this).any { it.matchesFqName(fqName) }
}
fun KotlinType.matchesFqName(fqName: String): Boolean {
return this.constructor.declarationDescriptor?.fqNameSafe?.asString() == fqName
}