From b7796d63d861680f2ca8de4e68c121a55aae88de Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 7 Jul 2020 19:30:23 +0900 Subject: [PATCH] Parcelize: Add the Parcelize compiler plugin with sources extracted from Android Extensions plugin (KT-40030) --- .idea/dictionaries/yan.xml | 3 + generators/build.gradle.kts | 1 + .../kotlin/generators/tests/GenerateTests.kt | 22 + .../generators/tests/GenerateTests.kt.as40 | 22 + .../generators/tests/GenerateTests.kt.as41 | 22 + .../parcelize-compiler/build.gradle.kts | 71 ++ ....kotlin.compiler.plugin.ComponentRegistrar | 1 + .../parcelize/ParcelizeAnnotationChecker.kt | 174 ++++ ...eClinitClassBuilderInterceptorExtension.kt | 149 ++++ .../parcelize/ParcelizeCodegenExtension.kt | 439 +++++++++ .../parcelize/ParcelizeComponentRegistrar.kt | 50 ++ .../parcelize/ParcelizeDeclarationChecker.kt | 238 +++++ .../ParcelizeIrGeneratorExtension.kt | 21 + .../parcelize/ParcelizeResolveExtension.kt | 179 ++++ .../DefaultErrorMessagesParcelize.kt | 138 +++ .../parcelize/diagnostic/ErrorsParcelize.java | 61 ++ .../kotlin/parcelize/ir/AndroidIrBuilder.kt | 90 ++ .../kotlin/parcelize/ir/AndroidSymbols.kt | 467 ++++++++++ .../parcelize/ir/IrParcelSerializerFactory.kt | 288 ++++++ .../parcelize/ir/IrParcelSerializers.kt | 536 +++++++++++ .../kotlin/parcelize/ir/IrParcelerScope.kt | 58 ++ .../parcelize/ir/ParcelizeIrTransformer.kt | 322 +++++++ .../jetbrains/kotlin/parcelize/ir/irUtils.kt | 177 ++++ .../parcelize/serializers/ParcelSerializer.kt | 449 ++++++++++ .../serializers/ParcelSerializers.kt | 834 ++++++++++++++++++ .../serializers/ParcelizeExtensionBase.kt | 60 ++ .../testData/box/allPrimitiveTypes.kt | 61 ++ .../testData/box/arraySimple.kt | 39 + .../parcelize-compiler/testData/box/arrays.kt | 77 ++ .../parcelize-compiler/testData/box/binder.kt | 41 + .../testData/box/boxedTypes.kt | 42 + .../parcelize-compiler/testData/box/bundle.kt | 35 + .../testData/box/charSequence.kt | 26 + .../testData/box/customNewArray.kt | 40 + .../testData/box/customParcelable.kt | 38 + .../testData/box/customParcelerScoping.kt | 44 + .../testData/box/customSerializerBoxing.kt | 56 ++ .../testData/box/customSerializerSimple.kt | 49 + .../testData/box/customSerializerWriteWith.kt | 55 ++ .../testData/box/customSimple.kt | 38 + .../testData/box/enumObject.kt | 38 + .../parcelize-compiler/testData/box/enums.kt | 27 + .../testData/box/exceptions.kt | 25 + .../testData/box/functions.kt | 24 + .../testData/box/intArray.kt | 39 + .../testData/box/javaInterop.kt | 33 + .../testData/box/kt19747.kt | 35 + .../testData/box/kt19747_2.kt | 33 + .../testData/box/kt19749.kt | 29 + .../testData/box/kt20002.kt | 40 + .../testData/box/kt20021.kt | 49 + .../testData/box/kt20717.kt | 30 + .../testData/box/kt25839.kt | 29 + .../testData/box/kt26221.kt | 39 + .../testData/box/kt36658.kt | 23 + .../testData/box/kt39981.kt | 21 + .../testData/box/listKinds.kt | 55 ++ .../testData/box/listSimple.kt | 25 + .../parcelize-compiler/testData/box/lists.kt | 42 + .../testData/box/mapKinds.kt | 47 + .../testData/box/mapSimple.kt | 25 + .../parcelize-compiler/testData/box/maps.kt | 41 + .../testData/box/nestedArrays.kt | 26 + .../testData/box/nestedLists.kt | 26 + .../testData/box/nestedMaps.kt | 28 + .../testData/box/nestedParcelable.kt | 34 + .../testData/box/nestedSparseArrays.kt | 30 + .../testData/box/newArray.kt | 29 + .../testData/box/nullableTypes.kt | 38 + .../testData/box/nullableTypesSimple.kt | 29 + .../testData/box/objects.kt | 31 + .../testData/box/openParcelize.kt | 19 + .../testData/box/persistableBundle.kt | 41 + .../testData/box/primitiveTypes.kt | 39 + .../testData/box/sealedClass.kt | 33 + .../parcelize-compiler/testData/box/simple.kt | 25 + .../testData/box/sparseArrays.kt | 71 ++ .../testData/box/sparseBooleanArray.kt | 37 + .../parcelize-compiler/testData/boxLib.kt | 18 + .../testData/codegen/IBinderIInterface.ir.txt | 89 ++ .../testData/codegen/IBinderIInterface.kt | 17 + .../testData/codegen/IBinderIInterface.txt | 126 +++ .../codegen/customDescribeContents.ir.txt | 45 + .../codegen/customDescribeContents.kt | 10 + .../codegen/customDescribeContents.txt | 45 + .../customParcelablesDifferentModule.ir.txt | 69 ++ .../customParcelablesDifferentModule.kt | 11 + .../customParcelablesDifferentModule.txt | 67 ++ .../customParcelablesSameModule.ir.txt | 169 ++++ .../codegen/customParcelablesSameModule.kt | 40 + .../codegen/customParcelablesSameModule.txt | 170 ++++ .../testData/codegen/customSimple.ir.txt | 115 +++ .../testData/codegen/customSimple.kt | 19 + .../testData/codegen/customSimple.txt | 113 +++ .../codegen/customSimpleWithNewArray.ir.txt | 85 ++ .../codegen/customSimpleWithNewArray.kt | 21 + .../codegen/customSimpleWithNewArray.txt | 83 ++ .../describeContentsFromSuperType.ir.txt | 49 + .../codegen/describeContentsFromSuperType.kt | 12 + .../codegen/describeContentsFromSuperType.txt | 49 + .../testData/codegen/duplicatingClinit.ir.txt | 57 ++ .../testData/codegen/duplicatingClinit.kt | 14 + .../testData/codegen/duplicatingClinit.txt | 56 ++ .../codegen/efficientParcelable.ir.txt | 156 ++++ .../testData/codegen/efficientParcelable.kt | 13 + .../testData/codegen/efficientParcelable.txt | 156 ++++ .../testData/codegen/kt25839.ir.txt | 53 ++ .../testData/codegen/kt25839.kt | 10 + .../testData/codegen/kt25839.txt | 71 ++ .../testData/codegen/listInsideList.ir.txt | 70 ++ .../testData/codegen/listInsideList.kt | 8 + .../testData/codegen/listInsideList.txt | 86 ++ .../codegen/nullableNotNullSize.ir.txt | 90 ++ .../testData/codegen/nullableNotNullSize.kt | 12 + .../testData/codegen/nullableNotNullSize.txt | 88 ++ .../testData/codegen/parcelable.ir.txt | 67 ++ .../testData/codegen/parcelable.kt | 37 + .../testData/codegen/parcelable.txt | 66 ++ .../testData/codegen/serializable.ir.txt | 90 ++ .../testData/codegen/serializable.kt | 11 + .../testData/codegen/serializable.txt | 87 ++ .../testData/codegen/serializeValue.ir.txt | 80 ++ .../testData/codegen/serializeValue.kt | 10 + .../testData/codegen/serializeValue.txt | 79 ++ .../testData/codegen/simple.ir.txt | 158 ++++ .../testData/codegen/simple.kt | 9 + .../testData/codegen/simple.txt | 143 +++ .../testData/codegen/simpleList.ir.txt | 38 + .../testData/codegen/simpleList.kt | 8 + .../testData/codegen/simpleList.txt | 38 + .../testData/codegen/size.ir.txt | 205 +++++ .../testData/codegen/size.kt | 13 + .../testData/codegen/size.txt | 201 +++++ .../test/AbstractParcelizeBoxTest.kt | 186 ++++ .../AbstractParcelizeBytecodeListingTest.kt | 28 + .../test/ParcelizeBoxTestGenerated.java | 291 ++++++ ...izeBoxTestWithSerializableLikeExtension.kt | 44 + ...ParcelizeBytecodeListingTestGenerated.java | 121 +++ .../test/ParcelizeIrBoxTestGenerated.java | 291 ++++++ ...eIrBoxTestWithSerializableLikeExtension.kt | 33 + ...rcelizeIrBytecodeListingTestGenerated.java | 121 +++ .../kotlin/parcelize/test/testUtils.kt | 21 + .../parcelize-runtime/build.gradle.kts | 26 + .../src/kotlinx/parcelize/IgnoredOnParcel.kt | 24 + .../src/kotlinx/parcelize/Parceler.kt | 42 + .../src/kotlinx/parcelize/Parcelize.kt | 28 + .../src/kotlinx/parcelize/RawValue.kt | 25 + .../src/kotlinx/parcelize/TypeParceler.kt | 25 + .../src/kotlinx/parcelize/WriteWith.kt | 24 + prepare/compiler/build.gradle.kts | 2 + .../build.gradle.kts | 35 + settings.gradle | 5 + 152 files changed, 11872 insertions(+) create mode 100644 plugins/parcelize/parcelize-compiler/build.gradle.kts create mode 100644 plugins/parcelize/parcelize-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeAnnotationChecker.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeCodegenExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeComponentRegistrar.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeResolveExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/DefaultErrorMessagesParcelize.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/ErrorsParcelize.java create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelerScope.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/ParcelizeIrTransformer.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt create mode 100644 plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelizeExtensionBase.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/arrays.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/binder.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/bundle.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/enums.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/functions.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/intArray.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/lists.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/maps.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/newArray.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/objects.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/simple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/boxLib.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializable.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializable.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simple.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simple.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/size.ir.txt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/size.kt create mode 100644 plugins/parcelize/parcelize-compiler/testData/codegen/size.txt create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBoxTest.kt create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBytecodeListingTest.kt create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestWithSerializableLikeExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBytecodeListingTestGenerated.java create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestWithSerializableLikeExtension.kt create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBytecodeListingTestGenerated.java create mode 100644 plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/testUtils.kt create mode 100644 plugins/parcelize/parcelize-runtime/build.gradle.kts create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/IgnoredOnParcel.kt create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parceler.kt create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parcelize.kt create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/RawValue.kt create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/TypeParceler.kt create mode 100644 plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/WriteWith.kt create mode 100644 prepare/parcelize-compiler-gradle/build.gradle.kts diff --git a/.idea/dictionaries/yan.xml b/.idea/dictionaries/yan.xml index 2b004b50aa3..52bb6ae1c1e 100644 --- a/.idea/dictionaries/yan.xml +++ b/.idea/dictionaries/yan.xml @@ -10,7 +10,10 @@ kapt kotlinc mutators + parcelable parceler + parcelers + parcelize repl testdata uast diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts index b895ec0757c..95dbee9fdc4 100644 --- a/generators/build.gradle.kts +++ b/generators/build.gradle.kts @@ -57,6 +57,7 @@ dependencies { testCompile(projectTests(":plugins:jvm-abi-gen")) testCompile(projectTests(":plugins:android-extensions-compiler")) testCompile(projectTests(":plugins:android-extensions-ide")) + testCompile(projectTests(":plugins:parcelize:parcelize-compiler")) testCompile(projectTests(":kotlin-annotation-processing")) testCompile(projectTests(":kotlin-annotation-processing-cli")) testCompile(projectTests(":kotlin-allopen-compiler-plugin")) diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index cbfaf1e0ba1..d967d435a21 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -174,6 +174,10 @@ import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenc import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBytecodeListingTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBytecodeListingTest import org.jetbrains.kotlin.psi.patternMatching.AbstractPsiUnifierTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverScriptTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverTest @@ -1508,6 +1512,24 @@ fun main(args: Array) { } } + testGroup("plugins/parcelize/parcelize-compiler/tests", "plugins/parcelize/parcelize-compiler/testData") { + testClass { + model("box", targetBackend = TargetBackend.JVM) + } + + testClass { + model("box", targetBackend = TargetBackend.JVM_IR) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM_IR) + } + } + testGroup("plugins/jvm-abi-gen/test", "plugins/jvm-abi-gen/testData") { testClass { model("compare", recursive = false, extension = null) diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as40 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as40 index be49ff0303d..84a7ec4885c 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as40 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as40 @@ -148,6 +148,10 @@ import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenc import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBytecodeListingTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBytecodeListingTest import org.jetbrains.kotlin.psi.patternMatching.AbstractPsiUnifierTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverScriptTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverTest @@ -1127,6 +1131,24 @@ fun main(args: Array) { } } + testGroup("plugins/parcelize/parcelize-compiler/tests", "plugins/parcelize/parcelize-compiler/testData") { + testClass { + model("box", targetBackend = TargetBackend.JVM) + } + + testClass { + model("box", targetBackend = TargetBackend.JVM_IR) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM_IR) + } + } + testGroup("plugins/kapt3/kapt3-compiler/test", "plugins/kapt3/kapt3-compiler/testData") { testClass { model("converter") diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as41 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as41 index be49ff0303d..84a7ec4885c 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as41 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as41 @@ -148,6 +148,10 @@ import org.jetbrains.kotlin.nj2k.inference.mutability.AbstractMutabilityInferenc import org.jetbrains.kotlin.nj2k.inference.nullability.AbstractNullabilityInferenceTest import org.jetbrains.kotlin.noarg.AbstractBlackBoxCodegenTestForNoArg import org.jetbrains.kotlin.noarg.AbstractBytecodeListingTestForNoArg +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeBytecodeListingTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBoxTest +import org.jetbrains.kotlin.parcelize.test.AbstractParcelizeIrBytecodeListingTest import org.jetbrains.kotlin.psi.patternMatching.AbstractPsiUnifierTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverScriptTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverTest @@ -1127,6 +1131,24 @@ fun main(args: Array) { } } + testGroup("plugins/parcelize/parcelize-compiler/tests", "plugins/parcelize/parcelize-compiler/testData") { + testClass { + model("box", targetBackend = TargetBackend.JVM) + } + + testClass { + model("box", targetBackend = TargetBackend.JVM_IR) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM) + } + + testClass { + model("codegen", targetBackend = TargetBackend.JVM_IR) + } + } + testGroup("plugins/kapt3/kapt3-compiler/test", "plugins/kapt3/kapt3-compiler/testData") { testClass { model("converter") diff --git a/plugins/parcelize/parcelize-compiler/build.gradle.kts b/plugins/parcelize/parcelize-compiler/build.gradle.kts new file mode 100644 index 00000000000..94e04195844 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/build.gradle.kts @@ -0,0 +1,71 @@ +description = "Parcelize compiler plugin" + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +val robolectricClasspath by configurations.creating +val parcelizeRuntimeForTests by configurations.creating + +dependencies { + testCompile(intellijCoreDep()) { includeJars("intellij-core") } + + compileOnly(project(":compiler:util")) + compileOnly(project(":compiler:plugin-api")) + compileOnly(project(":compiler:frontend")) + compileOnly(project(":compiler:frontend.java")) + compileOnly(project(":compiler:backend")) + compileOnly(project(":compiler:ir.backend.common")) + compileOnly(project(":compiler:backend.jvm")) + compileOnly(project(":compiler:ir.tree.impl")) + compileOnly(project(":plugins:parcelize:parcelize-runtime")) + compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) } + + testCompile(project(":compiler:util")) + testCompile(project(":compiler:backend")) + testCompile(project(":compiler:ir.backend.common")) + testCompile(project(":compiler:backend.jvm")) + testCompile(project(":compiler:cli")) + testCompile(project(":plugins:parcelize:parcelize-runtime")) + testCompile(projectTests(":compiler:tests-common")) + testCompile(project(":kotlin-test:kotlin-test-jvm")) + testCompile(commonDep("junit:junit")) + + testRuntime(intellijPluginDep("junit")) + + robolectricClasspath(commonDep("org.robolectric", "robolectric")) + robolectricClasspath("org.robolectric:android-all:4.4_r1-robolectric-1") + robolectricClasspath(project(":plugins:parcelize:parcelize-runtime")) { isTransitive = false } + + embedded(project(":plugins:parcelize:parcelize-runtime")) { isTransitive = false } + + parcelizeRuntimeForTests(project(":plugins:parcelize:parcelize-runtime")) { isTransitive = false } +} + +sourceSets { + "main" { projectDefault() } + "test" { projectDefault() } +} + +runtimeJar() +javadocJar() +sourcesJar() + +testsJar() + +projectTest { + dependsOn(parcelizeRuntimeForTests) + dependsOn(":dist") + workingDir = rootDir + useAndroidJar() + doFirst { + systemProperty("parcelizeRuntime.classpath", parcelizeRuntimeForTests.asPath) + val androidPluginPath = File(intellijRootDir(), "plugins/android/lib").canonicalPath + systemProperty("ideaSdk.androidPlugin.path", androidPluginPath) + systemProperty("robolectric.classpath", robolectricClasspath.asPath) + } +} + +apply(from = "$rootDir/gradle/kotlinPluginPublication.gradle.kts") diff --git a/plugins/parcelize/parcelize-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/plugins/parcelize/parcelize-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar new file mode 100644 index 00000000000..da4c2aa3e36 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar @@ -0,0 +1 @@ +org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeAnnotationChecker.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeAnnotationChecker.kt new file mode 100644 index 00000000000..a48a9ce8152 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeAnnotationChecker.kt @@ -0,0 +1,174 @@ +/* + * 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 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 +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.name.FqName +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 + +class ParcelizeAnnotationChecker : CallChecker { + 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) + } + + 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() ?: return + val annotationOwner = annotationEntry.getStrictParentOfType() ?: return + + if (annotationClass.fqNameSafe == TYPE_PARCELER_FQNAME) { + checkTypeParcelerUsage(resolvedCall, annotationEntry, context, annotationOwner) + } + + if (annotationClass.fqNameSafe == WRITE_WITH_FQNAME) { + checkWriteWithUsage(resolvedCall, annotationEntry, context, annotationOwner) + } + + if (annotationClass.fqNameSafe == IGNORED_ON_PARCEL_FQNAME) { + checkIgnoredOnParcelUsage(annotationEntry, context, annotationOwner) + } + } + + private fun checkIgnoredOnParcelUsage(annotationEntry: KtAnnotationEntry, context: CallCheckerContext, element: KtModifierListOwner) { + if (element is KtParameter && PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) is KtPrimaryConstructor) { + context.trace.report(ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY.on(annotationEntry)) + } else if (element !is KtProperty || PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) !is KtClass) { + 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 == TYPE_PARCELER_FQNAME } + .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() 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() + 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_FQNAME } ?: 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 PsiElement.getStrictParentOfType(): T? { + return PsiTreeUtil.getParentOfType(this, T::class.java, true) +} + +internal inline fun PsiElement.getNonStrictParentOfType(): T? { + return PsiTreeUtil.getParentOfType(this, T::class.java, false) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt new file mode 100644 index 00000000000..da91f94c9c6 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt @@ -0,0 +1,149 @@ +/* + * 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 + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.codegen.ClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilderFactory +import org.jetbrains.kotlin.codegen.DelegatingClassBuilder +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +class ParcelizeClinitClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { + override fun interceptClassBuilderFactory( + interceptedFactory: ClassBuilderFactory, + bindingContext: BindingContext, + diagnostics: DiagnosticSink + ): ClassBuilderFactory { + return ParcelableClinitClassBuilderFactory(interceptedFactory, bindingContext) + } + + private inner class ParcelableClinitClassBuilderFactory( + private val delegateFactory: ClassBuilderFactory, + val bindingContext: BindingContext + ) : ClassBuilderFactory { + + override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder { + return AndroidOnDestroyCollectorClassBuilder(origin, delegateFactory.newClassBuilder(origin), bindingContext) + } + + override fun getClassBuilderMode() = delegateFactory.classBuilderMode + + override fun asText(builder: ClassBuilder?): String? { + return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun asBytes(builder: ClassBuilder?): ByteArray? { + return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun close() { + delegateFactory.close() + } + } + + private class AndroidOnDestroyCollectorClassBuilder( + val declarationOrigin: JvmDeclarationOrigin, + val delegateClassBuilder: ClassBuilder, + val bindingContext: BindingContext + ) : DelegatingClassBuilder() { + private var currentClass: KtClassOrObject? = null + private var currentClassName: String? = null + private var isClinitGenerated = false + + override fun getDelegate() = delegateClassBuilder + + override fun defineClass( + origin: PsiElement?, + version: Int, + access: Int, + name: String, + signature: String?, + superName: String, + interfaces: Array + ) { + currentClass = origin as? KtClassOrObject + currentClassName = name + isClinitGenerated = false + + super.defineClass(origin, version, access, name, signature, superName, interfaces) + } + + override fun done() { + if (!isClinitGenerated && currentClass != null && currentClassName != null) { + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + val baseVisitor = super.newMethod(JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_STATIC, "", "()V", null, null) + val visitor = ClinitAwareMethodVisitor(currentClassName!!, baseVisitor) + + visitor.visitCode() + visitor.visitInsn(Opcodes.RETURN) + visitor.visitEnd() + } + } + + super.done() + } + + override fun newMethod( + origin: JvmDeclarationOrigin, + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + if (name == "" && currentClass != null && currentClassName != null) { + isClinitGenerated = true + + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + return ClinitAwareMethodVisitor( + currentClassName!!, + super.newMethod(origin, access, name, desc, signature, exceptions) + ) + } + } + + return super.newMethod(origin, access, name, desc, signature, exceptions) + } + } + + private class ClinitAwareMethodVisitor(val parcelableName: String, mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) { + override fun visitInsn(opcode: Int) { + if (opcode == Opcodes.RETURN) { + val iv = InstructionAdapter(this) + val creatorName = "$parcelableName\$Creator" + val creatorType = Type.getObjectType(creatorName) + + iv.anew(creatorType) + iv.dup() + iv.invokespecial(creatorName, "", "()V", false) + iv.putstatic(parcelableName, "CREATOR", "Landroid/os/Parcelable\$Creator;") + } + + super.visitInsn(opcode) + } + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeCodegenExtension.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeCodegenExtension.kt new file mode 100644 index 00000000000..0176567e4a5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeCodegenExtension.kt @@ -0,0 +1,439 @@ +/* + * Copyright 2010-2019 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 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 +import org.jetbrains.kotlin.codegen.OwnerKind +import org.jetbrains.kotlin.codegen.context.ClassContext +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension +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.ParcelizeResolveExtension.Companion.createMethod +import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.* +import org.jetbrains.kotlin.parcelize.serializers.* +import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase.Companion.FILE_DESCRIPTOR_FQNAME +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorFactory +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.org.objectweb.asm.Type + +open class ParcelizeCodegenExtension : ParcelizeExtensionBase, ExpressionCodegenExtension { + open fun isAvailable(element: PsiElement): Boolean { + return true + } + + override val shouldGenerateClassSyntheticPartsInLightClassesMode: Boolean + get() = true + + override fun generateClassSyntheticParts(codegen: ImplementationBodyCodegen) { + val declaration = codegen.myClass as? PsiElement ?: return + if (!isAvailable(declaration)) { + return + } + + val parcelableClass = codegen.descriptor + if (!parcelableClass.isParcelizeClassDescriptor) { + return + } + + val propertiesToSerialize = getPropertiesToSerialize(codegen, parcelableClass) + + val parcelerObject = parcelableClass.companionObjectDescriptor?.takeIf { + TypeUtils.getAllSupertypes(it.defaultType).any { supertype -> supertype.isParceler } + } + + with(parcelableClass) { + if (hasSyntheticDescribeContents()) { + writeDescribeContentsFunction(codegen, propertiesToSerialize) + } + + if (hasSyntheticWriteToParcel()) { + writeWriteToParcel(codegen, propertiesToSerialize, PARCEL_TYPE, parcelerObject) + } + + if (!hasCreatorField()) { + writeCreatorAccessField(codegen, parcelableClass) + } + } + + if (codegen.state.classBuilderMode != ClassBuilderMode.LIGHT_CLASSES) { + val parcelClassType = ParcelizeResolveExtension.resolveParcelClassType(parcelableClass.module) + ?: error("Can't resolve 'android.os.Parcel' class") + + val parcelableCreatorClassType = ParcelizeResolveExtension.resolveParcelableCreatorClassType(parcelableClass.module) + ?: error("Can't resolve 'android.os.Parcelable.Creator' class") + + writeCreatorClass( + codegen, + parcelableClass, parcelClassType, parcelableCreatorClassType, + PARCEL_TYPE, parcelerObject, propertiesToSerialize + ) + } + } + + private fun getCompanionClassType(containerAsmType: Type, parcelerObject: ClassDescriptor): Pair { + val shortName = parcelerObject.name + return Pair(Type.getObjectType(containerAsmType.internalName + "\$$shortName"), shortName.asString()) + } + + private fun ClassDescriptor.writeWriteToParcel( + codegen: ImplementationBodyCodegen, + properties: List, + parcelAsmType: Type, + parcelerObject: ClassDescriptor? + ): Unit? { + val containerAsmType = codegen.typeMapper.mapType(this.defaultType) + + return findFunction(WRITE_TO_PARCEL)?.write(codegen) { + if (parcelerObject != null) { + val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject) + + v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor) + v.load(0, containerAsmType) + v.load(1, PARCEL_TYPE) + v.load(2, Type.INT_TYPE) + v.invokevirtual( + companionAsmType.internalName, "write", + "(${containerAsmType.descriptor}${PARCEL_TYPE.descriptor}I)V", false + ) + } else { + val frameMap = FrameMap().apply { + enterTemp(containerAsmType) + enterTemp(PARCEL_TYPE) + enterTemp(Type.INT_TYPE) + } + + val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList(), frameMap) + + if (properties.isEmpty()) { + val entityType = this@writeWriteToParcel.defaultType + val asmType = codegen.state.typeMapper.mapType(entityType) + val serializer = if (this@writeWriteToParcel.kind == ClassKind.CLASS) { + NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType)) + } else { + ParcelSerializer.get(entityType, asmType, globalContext, strict = true) + } + + v.load(1, parcelAsmType) + v.load(0, containerAsmType) + serializer.writeValue(v) + } else { + for ((fieldName, type, parcelers) in properties) { + val asmType = codegen.typeMapper.mapType(type) + + v.load(1, parcelAsmType) + v.load(0, containerAsmType) + v.getfield(containerAsmType.internalName, fieldName, asmType.descriptor) + + val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers)) + serializer.writeValue(v) + } + + } + } + + v.areturn(Type.VOID_TYPE) + } + } + + private fun ClassDescriptor.writeDescribeContentsFunction( + codegen: ImplementationBodyCodegen, + propertiesToSerialize: List + ): Unit? { + val hasFileDescriptorAnywhere = propertiesToSerialize.any { it.type.containsFileDescriptor() } + + return findFunction(DESCRIBE_CONTENTS)?.write(codegen) { + v.aconst(if (hasFileDescriptorAnywhere) 1 /* CONTENTS_FILE_DESCRIPTOR */ else 0) + v.areturn(Type.INT_TYPE) + } + } + + private fun KotlinType.containsFileDescriptor(): Boolean { + val declarationDescriptor = this.constructor.declarationDescriptor + if (declarationDescriptor != null) { + if (declarationDescriptor.fqNameSafe == FILE_DESCRIPTOR_FQNAME) { + return true + } + } + + return this.arguments.any { it.type.containsFileDescriptor() } + } + + data class PropertyToSerialize(val name: String, val type: KotlinType, val parcelers: List) + + private fun getPropertiesToSerialize( + codegen: ImplementationBodyCodegen, + parcelableClass: ClassDescriptor + ): List { + if (parcelableClass.kind != ClassKind.CLASS) { + return emptyList() + } + + val constructor = parcelableClass.constructors.firstOrNull { it.isPrimary } ?: return emptyList() + + val propertiesToSerialize = constructor.valueParameters.mapNotNull { param -> + codegen.bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, param] + } + + val classParcelers = getTypeParcelers(parcelableClass.annotations) + + return propertiesToSerialize.map { + PropertyToSerialize(it.name.asString(), it.type, classParcelers + getTypeParcelers(it.annotations)) + } + } + + private fun writeCreateFromParcel( + codegen: ImplementationBodyCodegen, + parcelableClass: ClassDescriptor, + parcelableCreatorClassType: KotlinType, + creatorClass: ClassDescriptorImpl, + parcelClassType: KotlinType, + parcelAsmType: Type, + parcelerObject: ClassDescriptor?, + properties: List + ) { + val containerAsmType = codegen.typeMapper.mapType(parcelableClass) + val creatorAsmType = codegen.typeMapper.mapType(creatorClass) + + val overriddenFunction = parcelableCreatorClassType.findFunction(CREATE_FROM_PARCEL) + ?: error("Can't resolve 'android.os.Parcelable.Creator.${CREATE_FROM_PARCEL.methodName}' method") + + createMethod( + creatorClass, CREATE_FROM_PARCEL, Modality.FINAL, + parcelableClass.defaultType, "in" to parcelClassType + ).write(codegen, overriddenFunction) { + if (parcelerObject != null) { + val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject) + + v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor) + v.load(1, PARCEL_TYPE) + v.invokevirtual(companionAsmType.internalName, "create", "(${PARCEL_TYPE.descriptor})$containerAsmType", false) + } else { + v.anew(containerAsmType) + v.dup() + + val asmConstructorParameters = StringBuilder() + val frameMap = FrameMap().apply { + enterTemp(creatorAsmType) + enterTemp(PARCEL_TYPE) + } + + val globalContext = ParcelSerializer.ParcelSerializerContext(codegen.typeMapper, containerAsmType, emptyList(), frameMap) + + if (properties.isEmpty()) { + val entityType = parcelableClass.defaultType + val asmType = codegen.state.typeMapper.mapType(entityType) + val serializer = if (parcelableClass.kind == ClassKind.CLASS) { + NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType)) + } else { + ParcelSerializer.get(entityType, asmType, globalContext, strict = true) + } + v.load(1, parcelAsmType) + serializer.readValue(v) + } else { + for ((_, type, parcelers) in properties) { + val asmType = codegen.typeMapper.mapType(type) + asmConstructorParameters.append(asmType.descriptor) + + val serializer = ParcelSerializer.get(type, asmType, globalContext.copy(typeParcelers = parcelers)) + v.load(1, parcelAsmType) + serializer.readValue(v) + } + + v.invokespecial(containerAsmType.internalName, "", "($asmConstructorParameters)V", false) + } + } + + v.areturn(containerAsmType) + } + } + + private fun writeCreatorAccessField(codegen: ImplementationBodyCodegen, parcelableClass: ClassDescriptor) { + val creatorType = Type.getObjectType("android/os/Parcelable\$Creator") + val parcelableType = codegen.typeMapper.mapType(parcelableClass) + val fieldSignature = "L${creatorType.internalName}<${parcelableType.descriptor}>;" + + codegen.v.newField( + JvmDeclarationOrigin.NO_ORIGIN, + ACC_STATIC or ACC_PUBLIC or ACC_FINAL, + "CREATOR", + creatorType.descriptor, + fieldSignature, + null + ) + } + + private fun writeCreatorClass( + codegen: ImplementationBodyCodegen, + parcelableClass: ClassDescriptor, + parcelClassType: KotlinType, + parcelableCreatorClassType: KotlinType, + parcelAsmType: Type, + parcelerObject: ClassDescriptor?, + properties: List + ) { + val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType) + val creatorAsmType = Type.getObjectType(containerAsmType.internalName + "\$Creator") + + val creatorClass = ClassDescriptorImpl( + parcelableClass, Name.identifier("Creator"), Modality.FINAL, ClassKind.CLASS, listOf(parcelableCreatorClassType), + parcelableClass.source, false, LockBasedStorageManager.NO_LOCKS + ) + + creatorClass.initialize( + MemberScope.Empty, emptySet(), + DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source) + ) + + val classBuilderForCreator = codegen.state.factory.newVisitor( + JvmDeclarationOrigin(JvmDeclarationOriginKind.OTHER, null, creatorClass), + Type.getObjectType(creatorAsmType.internalName), + codegen.myClass.containingKtFile + ) + + val classContextForCreator = ClassContext( + codegen.typeMapper, creatorClass, OwnerKind.IMPLEMENTATION, codegen.context.parentContext, null + ) + val codegenForCreator = ImplementationBodyCodegen( + codegen.myClass, classContextForCreator, classBuilderForCreator, codegen.state, codegen.parentCodegen, false + ) + + val classSignature = "Ljava/lang/Object;Landroid/os/Parcelable\$Creator<${containerAsmType.descriptor}>;" + classBuilderForCreator.defineClass( + null, V1_6, ACC_PUBLIC or ACC_FINAL or ACC_SUPER, + creatorAsmType.internalName, classSignature, "java/lang/Object", + arrayOf("android/os/Parcelable\$Creator") + ) + + codegen.v.visitInnerClass(creatorAsmType.internalName, containerAsmType.internalName, "Creator", ACC_PUBLIC or ACC_STATIC) + codegenForCreator.v.visitInnerClass(creatorAsmType.internalName, containerAsmType.internalName, "Creator", ACC_PUBLIC or ACC_STATIC) + + writeSyntheticClassMetadata(classBuilderForCreator, codegen.state) + + writeCreatorConstructor(codegenForCreator, creatorClass, creatorAsmType) + writeNewArrayMethod(codegenForCreator, parcelableClass, parcelableCreatorClassType, creatorClass, parcelerObject) + + writeCreateFromParcel( + codegenForCreator, + parcelableClass, parcelableCreatorClassType, creatorClass, parcelClassType, + parcelAsmType, parcelerObject, properties + ) + + classBuilderForCreator.done() + } + + private fun writeCreatorConstructor(codegen: ImplementationBodyCodegen, creatorClass: ClassDescriptor, creatorAsmType: Type) { + DescriptorFactory.createPrimaryConstructorForObject(creatorClass, creatorClass.source) + .apply { + returnType = creatorClass.defaultType + }.write(codegen) { + v.load(0, creatorAsmType) + v.invokespecial("java/lang/Object", "", "()V", false) + v.areturn(Type.VOID_TYPE) + } + } + + private fun writeNewArrayMethod( + codegen: ImplementationBodyCodegen, + parcelableClass: ClassDescriptor, + parcelableCreatorClassType: KotlinType, + creatorClass: ClassDescriptorImpl, + parcelerObject: ClassDescriptor? + ) { + val builtIns = parcelableClass.builtIns + val parcelableAsmType = codegen.typeMapper.mapType(parcelableClass) + + val overriddenFunction = parcelableCreatorClassType.findFunction(NEW_ARRAY) + ?: error("Can't resolve 'android.os.Parcelable.Creator.${NEW_ARRAY.methodName}' method") + + createMethod( + creatorClass, NEW_ARRAY, Modality.FINAL, + builtIns.getArrayType(Variance.INVARIANT, parcelableClass.defaultType), + "size" to builtIns.intType + ).write(codegen, overriddenFunction) { + if (parcelerObject != null) { + val newArrayMethod = parcelerObject.unsubstitutedMemberScope + .getContributedFunctions(Name.identifier("newArray"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) + .firstOrNull { + it.typeParameters.isEmpty() + && it.kind == CallableMemberDescriptor.Kind.DECLARATION + && (it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type)) + && !((it.containingDeclaration as? ClassDescriptor)?.defaultType?.isParceler ?: true) + } + + if (newArrayMethod != null) { + val containerAsmType = codegen.typeMapper.mapType(parcelableClass.defaultType) + val (companionAsmType, companionFieldName) = getCompanionClassType(containerAsmType, parcelerObject) + + v.getstatic(containerAsmType.internalName, companionFieldName, companionAsmType.descriptor) + v.load(1, Type.INT_TYPE) + v.invokevirtual(companionAsmType.internalName, "newArray", "(I)[$parcelableAsmType", false) + v.areturn(Type.getType("[$parcelableAsmType")) + + return@write + } + } + + v.load(1, Type.INT_TYPE) + v.newarray(parcelableAsmType) + v.areturn(Type.getType("[$parcelableAsmType")) + } + } + + private fun FunctionDescriptor.write( + codegen: ImplementationBodyCodegen, + overriddenDescriptor: FunctionDescriptor? = null, + code: ExpressionCodegen.() -> Unit + ) { + val declarationOrigin = JvmDeclarationOrigin(JvmDeclarationOriginKind.OTHER, null, this) + if (overriddenDescriptor != null) { + this.overriddenDescriptors = listOf(overriddenDescriptor) + } + + codegen.functionCodegen.generateMethod(declarationOrigin, this, object : CodegenBased(codegen.state) { + override fun doGenerateBody(e: ExpressionCodegen, signature: JvmMethodSignature) = with(e) { + e.code() + } + }) + } + + private fun KotlinType.findFunction(componentKind: ParcelizeSyntheticComponent.ComponentKind): SimpleFunctionDescriptor? { + return memberScope + .getContributedFunctions(Name.identifier(componentKind.methodName), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) + .firstOrNull() + } +} + +internal fun getTypeParcelers(annotations: Annotations): List { + val typeParcelerFqName = FqName(TypeParceler::class.java.name) + val serializers = mutableListOf() + + for (annotation in annotations.filter { it.fqName == typeParcelerFqName }) { + val (mappedType, parcelerType) = annotation.type.arguments.takeIf { it.size == 2 } ?: continue + serializers += TypeParcelerMapping(mappedType.type, parcelerType.type) + } + + return serializers +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeComponentRegistrar.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeComponentRegistrar.kt new file mode 100644 index 00000000000..7a0a8eb018c --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeComponentRegistrar.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2020 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 com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension + +class ParcelizeComponentRegistrar : ComponentRegistrar { + companion object { + fun registerParcelizeComponents(project: Project) { + ExpressionCodegenExtension.registerExtension(project, ParcelizeCodegenExtension()) + IrGenerationExtension.registerExtension(project, ParcelizeIrGeneratorExtension()) + SyntheticResolveExtension.registerExtension(project, ParcelizeResolveExtension()) + ClassBuilderInterceptorExtension.registerExtension(project, ParcelizeClinitClassBuilderInterceptorExtension()) + StorageComponentContainerContributor.registerExtension(project, ParcelizeDeclarationCheckerComponentContainerContributor()) + } + } + + override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { + registerParcelizeComponents(project) + } +} + +class ParcelizeDeclarationCheckerComponentContainerContributor : StorageComponentContainerContributor { + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: TargetPlatform, + moduleDescriptor: ModuleDescriptor, + ) { + if (platform.isJvm()) { + container.useInstance(ParcelizeDeclarationChecker()) + container.useInstance(ParcelizeAnnotationChecker()) + } + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt new file mode 100644 index 00000000000..3189f197468 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeDeclarationChecker.kt @@ -0,0 +1,238 @@ +/* + * 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 kotlinx.parcelize.IgnoredOnParcel +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.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +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 + +val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable") +val ANDROID_PARCELABLE_CREATOR_CLASS_FQNAME = FqName("android.os.Parcelable.Creator") +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) + } + + 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) + } + 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 == IGNORED_ON_PARCEL_FQNAME } + + return property.annotations.hasIgnoredOnParcel() || (property.getter?.annotations?.hasIgnoredOnParcel() ?: false) + } + + if (containingClass.isParcelize + && (declaration.hasDelegate() || bindingContext[BindingContext.BACKING_FIELD_REQUIRED, property] == true) + && !hasIgnoredOnParcel() + ) { + 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 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())) { + 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 sealedOrAbstract = + declaration.modifierList?.let { it.getModifier(KtTokens.ABSTRACT_KEYWORD) ?: it.getModifier(KtTokens.SEALED_KEYWORD) } + if (sealedOrAbstract != null) { + diagnosticHolder.report(ErrorsParcelize.PARCELABLE_SHOULD_BE_INSTANTIABLE.on(sealedOrAbstract)) + } + + 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 == ANDROID_PARCELABLE_CLASS_FQNAME }) { + 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)) + } + } + + 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 + ) + + for (parameter in primaryConstructor?.valueParameters.orEmpty()) { + 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 + val type = descriptor.type + + if (!type.isError && !containerClass.hasCustomParceler()) { + val asmType = typeMapper.mapType(type) + + try { + val parcelers = getTypeParcelers(descriptor.annotations) + getTypeParcelers(containerClass.annotations) + val context = ParcelSerializer.ParcelSerializerContext( + typeMapper, + typeMapper.mapType(containerClass.defaultType), + 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 } + } +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt new file mode 100644 index 00000000000..2fc74f6306f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeIrGeneratorExtension.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2020 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.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.parcelize.ir.AndroidSymbols +import org.jetbrains.kotlin.parcelize.ir.ParcelizeIrTransformer + +class ParcelizeIrGeneratorExtension : IrGenerationExtension { + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + val arrayOfNulls = pluginContext.symbols.arrayOfNulls + val charSequence = pluginContext.symbols.charSequence + val androidSymbols = AndroidSymbols(pluginContext.irBuiltIns, arrayOfNulls, charSequence, moduleFragment) + ParcelizeIrTransformer(pluginContext, androidSymbols).transform(moduleFragment) + } +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeResolveExtension.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeResolveExtension.kt new file mode 100644 index 00000000000..d46f4d05511 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeResolveExtension.kt @@ -0,0 +1,179 @@ +/* + * 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 kotlinx.parcelize.Parceler +import kotlinx.parcelize.Parcelize +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +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.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS +import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension +import org.jetbrains.kotlin.resolve.source.PsiSourceElement +import org.jetbrains.kotlin.types.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(ClassId.topLevel(FqName("android.os.Parcel")))?.defaultType + } + + fun resolveParcelableCreatorClassType(module: ModuleDescriptor): SimpleType? { + val creatorClassId = ClassId(FqName("android.os"), FqName("Parcelable.Creator"), false) + return module.findClassAcrossModuleDependencies(creatorClassId)?.defaultType + } + + fun createMethod( + classDescriptor: ClassDescriptor, + componentKind: ParcelizeSyntheticComponent.ComponentKind, + modality: Modality, + returnType: KotlinType, + vararg parameters: Pair + ): 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(), 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 = + 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 { + return if (thisDescriptor.isParcelize) parcelizeMethodNames else emptyList() + } + + override fun generateSyntheticMethods( + thisDescriptor: ClassDescriptor, + name: Name, + bindingContext: BindingContext, + fromSupertypes: List, + result: MutableCollection + ) { + fun isParcelizePluginEnabled(): Boolean { + val sourceElement = (thisDescriptor.source as? PsiSourceElement)?.psi ?: return false + return isAvailable(sourceElement) + } + + if (name.asString() == DESCRIBE_CONTENTS.methodName + && thisDescriptor.isParcelize + && 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 + && isParcelizePluginEnabled() + && result.none { it.isWriteToParcel() } + ) { + val builtIns = thisDescriptor.builtIns + val parcelClassType = resolveParcelClassType(thisDescriptor.module) ?: ErrorUtils.createErrorType("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("writeToParcel"), + DESCRIBE_CONTENTS("describeContents"), + NEW_ARRAY("newArray"), + CREATE_FROM_PARCEL("createFromParcel") + } +} + +val PARCELIZE_CLASS_FQNAME: FqName = FqName(Parcelize::class.java.canonicalName) +internal val PARCELER_FQNAME: FqName = FqName(Parceler::class.java.canonicalName) + +val ClassDescriptor.isParcelize: Boolean + get() = this.annotations.hasAnnotation(PARCELIZE_CLASS_FQNAME) + +val KotlinType.isParceler + get() = constructor.declarationDescriptor?.fqNameSafe == PARCELER_FQNAME \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/DefaultErrorMessagesParcelize.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/DefaultErrorMessagesParcelize.kt new file mode 100644 index 00000000000..3ec3054a203 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/DefaultErrorMessagesParcelize.kt @@ -0,0 +1,138 @@ +/* + * 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 a 'sealed' or '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 declared in the primary constructor" + ) + } +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/ErrorsParcelize.java b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/ErrorsParcelize.java new file mode 100644 index 00000000000..87c178223d7 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/diagnostic/ErrorsParcelize.java @@ -0,0 +1,61 @@ +/* + * 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 PARCELABLE_SHOULD_BE_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_DELEGATE_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_SHOULD_NOT_BE_ENUM_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_SHOULD_BE_INSTANTIABLE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_CANT_BE_INNER_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_CANT_BE_LOCAL_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 NO_PARCELABLE_SUPERTYPE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 OVERRIDING_WRITE_TO_PARCEL_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 CREATOR_DEFINITION_IS_NOT_ALLOWED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PARCELER_SHOULD_BE_OBJECT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory2 PARCELER_TYPE_INCOMPATIBLE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory0 DUPLICATING_TYPE_PARCELERS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 REDUNDANT_TYPE_PARCELER = DiagnosticFactory1.create(WARNING); + DiagnosticFactory1 CLASS_SHOULD_BE_PARCELIZE = DiagnosticFactory1.create(ERROR); + + DiagnosticFactory0 INAPPLICABLE_IGNORED_ON_PARCEL = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY = DiagnosticFactory0.create(WARNING); + + @SuppressWarnings("UnusedDeclaration") + Object _initializer = new Object() { + { + Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages(ErrorsParcelize.class, DefaultErrorMessagesParcelize.INSTANCE); + } + }; + +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt new file mode 100644 index 00000000000..8fb0177c7ac --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidIrBuilder.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2020 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.ir + +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrSymbol + +// An IR builder with access to AndroidSymbols and convenience methods to build calls to some of these methods. +class AndroidIrBuilder internal constructor( + val androidSymbols: AndroidSymbols, + symbol: IrSymbol, + startOffset: Int, + endOffset: Int +) : IrBuilderWithScope(IrGeneratorContextBase(androidSymbols.irBuiltIns), Scope(symbol), startOffset, endOffset) { + fun parcelReadInt(receiver: IrExpression): IrExpression { + return irCall(androidSymbols.parcelReadInt).apply { + dispatchReceiver = receiver + } + } + + fun parcelReadParcelable(receiver: IrExpression, loader: IrExpression): IrExpression { + return irCall(androidSymbols.parcelReadParcelable).apply { + dispatchReceiver = receiver + putValueArgument(0, loader) + } + } + + fun parcelReadString(receiver: IrExpression): IrExpression { + return irCall(androidSymbols.parcelReadString).apply { + dispatchReceiver = receiver + } + } + + fun parcelReadValue(receiver: IrExpression, loader: IrExpression): IrExpression { + return irCall(androidSymbols.parcelReadValue).apply { + dispatchReceiver = receiver + putValueArgument(0, loader) + } + } + + fun parcelWriteInt(receiver: IrExpression, value: IrExpression): IrExpression { + return irCall(androidSymbols.parcelWriteInt).apply { + dispatchReceiver = receiver + putValueArgument(0, value) + } + } + + fun parcelWriteParcelable(receiver: IrExpression, p: IrExpression, parcelableFlags: IrExpression): IrExpression { + return irCall(androidSymbols.parcelWriteParcelable).apply { + dispatchReceiver = receiver + putValueArgument(0, p) + putValueArgument(1, parcelableFlags) + } + } + + fun parcelWriteString(receiver: IrExpression, value: IrExpression): IrExpression { + return irCall(androidSymbols.parcelWriteString).apply { + dispatchReceiver = receiver + putValueArgument(0, value) + } + } + + fun parcelWriteValue(receiver: IrExpression, v: IrExpression): IrExpression { + return irCall(androidSymbols.parcelWriteValue).apply { + dispatchReceiver = receiver + putValueArgument(0, v) + } + } + + fun textUtilsWriteToParcel(cs: IrExpression, p: IrExpression, parcelableFlags: IrExpression): IrExpression { + return irCall(androidSymbols.textUtilsWriteToParcel).apply { + putValueArgument(0, cs) + putValueArgument(1, p) + putValueArgument(2, parcelableFlags) + } + } + + fun classGetClassLoader(receiver: IrExpression): IrExpression { + return irCall(androidSymbols.classGetClassLoader).apply { + dispatchReceiver = receiver + } + } + + fun getTextUtilsCharSequenceCreator(): IrExpression { + return irGetField(null, androidSymbols.textUtilsCharSequenceCreator.owner) + } +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt new file mode 100644 index 00000000000..878192c6e58 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/AndroidSymbols.kt @@ -0,0 +1,467 @@ +/* + * Copyright 2010-2020 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. + */ +// This file was autogenerated based on android.jar, do not edit it directly. +package org.jetbrains.kotlin.parcelize.ir + +import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.declarations.IrFactory +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.IrPackageFragment +import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.makeNullable +import org.jetbrains.kotlin.ir.types.starProjectedType +import org.jetbrains.kotlin.ir.types.typeWith +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +// All of the IR declarations needed by the parcelize plugin. Note that the declarations are generated based on JVM descriptors and +// hence contain just enough information to produce correct JVM bytecode for *calls*. In particular, we omit generic types and +// supertypes, which are not needed to produce correct bytecode. +class AndroidSymbols( + val irBuiltIns: IrBuiltIns, + val arrayOfNulls: IrSimpleFunctionSymbol, + private val charSequence: IrClassSymbol, + private val moduleFragment: IrModuleFragment +) { + private val irFactory: IrFactory = IrFactoryImpl + + private val javaIo: IrPackageFragment = createPackage("java.io") + private val javaLang: IrPackageFragment = createPackage("java.lang") + private val javaUtil: IrPackageFragment = createPackage("java.util") + + private val kotlinJvm: IrPackageFragment = createPackage("kotlin.jvm") + + private val androidOs: IrPackageFragment = createPackage("android.os") + private val androidUtil: IrPackageFragment = createPackage("android.util") + private val androidText: IrPackageFragment = createPackage("android.text") + + private val androidOsBundle: IrClassSymbol = + createClass(androidOs, "Bundle", ClassKind.CLASS, Modality.FINAL) + + private val androidOsIBinder: IrClassSymbol = + createClass(androidOs, "IBinder", ClassKind.INTERFACE, Modality.ABSTRACT) + + val androidOsParcel: IrClassSymbol = + createClass(androidOs, "Parcel", ClassKind.CLASS, Modality.FINAL) + + private val androidOsParcelFileDescriptor: IrClassSymbol = + createClass(androidOs, "ParcelFileDescriptor", ClassKind.CLASS, Modality.OPEN) + + private val androidOsParcelable: IrClassSymbol = + createClass(androidOs, "Parcelable", ClassKind.INTERFACE, Modality.ABSTRACT) + + private val androidOsPersistableBundle: IrClassSymbol = + createClass(androidOs, "PersistableBundle", ClassKind.CLASS, Modality.FINAL) + + private val androidTextTextUtils: IrClassSymbol = + createClass(androidText, "TextUtils", ClassKind.CLASS, Modality.OPEN) + + private val androidUtilSize: IrClassSymbol = + createClass(androidUtil, "Size", ClassKind.CLASS, Modality.FINAL) + + private val androidUtilSizeF: IrClassSymbol = + createClass(androidUtil, "SizeF", ClassKind.CLASS, Modality.FINAL) + + private val androidUtilSparseBooleanArray: IrClassSymbol = + createClass(androidUtil, "SparseBooleanArray", ClassKind.CLASS, Modality.OPEN) + + private val javaIoFileDescriptor: IrClassSymbol = + createClass(javaIo, "FileDescriptor", ClassKind.CLASS, Modality.FINAL) + + private val javaIoSerializable: IrClassSymbol = + createClass(javaIo, "Serializable", ClassKind.INTERFACE, Modality.ABSTRACT) + + val javaLangClass: IrClassSymbol = + createClass(javaLang, "Class", ClassKind.CLASS, Modality.FINAL) + + private val javaLangClassLoader: IrClassSymbol = + createClass(javaLang, "ClassLoader", ClassKind.CLASS, Modality.ABSTRACT) + + private val javaUtilArrayList: IrClassSymbol = + createClass(javaUtil, "ArrayList", ClassKind.CLASS, Modality.OPEN) + + private val javaUtilLinkedHashMap: IrClassSymbol = + createClass(javaUtil, "LinkedHashMap", ClassKind.CLASS, Modality.OPEN) + + private val javaUtilLinkedHashSet: IrClassSymbol = + createClass(javaUtil, "LinkedHashSet", ClassKind.CLASS, Modality.OPEN) + + private val javaUtilList: IrClassSymbol = + createClass(javaUtil, "List", ClassKind.INTERFACE, Modality.ABSTRACT) + + private val javaUtilTreeMap: IrClassSymbol = + createClass(javaUtil, "TreeMap", ClassKind.CLASS, Modality.OPEN) + + private val javaUtilTreeSet: IrClassSymbol = + createClass(javaUtil, "TreeSet", ClassKind.CLASS, Modality.OPEN) + + val androidOsParcelableCreator: IrClassSymbol = irFactory.buildClass { + name = Name.identifier("Creator") + kind = ClassKind.INTERFACE + modality = Modality.ABSTRACT + }.apply { + createImplicitParameterDeclarationWithWrappedDescriptor() + val t = addTypeParameter("T", irBuiltIns.anyNType) + parent = androidOsParcelable.owner + + addFunction("createFromParcel", t.defaultType, Modality.ABSTRACT).apply { + addValueParameter("source", androidOsParcel.defaultType) + } + + addFunction( + "newArray", irBuiltIns.arrayClass.typeWith(t.defaultType.makeNullable()), + Modality.ABSTRACT + ).apply { + addValueParameter("size", irBuiltIns.intType) + } + }.symbol + + val kotlinKClassJava: IrPropertySymbol = irFactory.buildProperty { + name = Name.identifier("java") + }.apply { + parent = kotlinJvm + addGetter().apply { + addExtensionReceiver(irBuiltIns.kClassClass.starProjectedType) + returnType = javaLangClass.defaultType + } + }.symbol + + val parcelCreateBinderArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("createBinderArray", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType)).symbol + + val parcelCreateBinderArrayList: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("createBinderArrayList", javaUtilArrayList.defaultType).symbol + + val parcelCreateBooleanArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createBooleanArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType + ).symbol + + val parcelCreateByteArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createByteArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType + ).symbol + + val parcelCreateCharArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createCharArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType + ).symbol + + val parcelCreateDoubleArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createDoubleArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType + ).symbol + + val parcelCreateFloatArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createFloatArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType + ).symbol + + val parcelCreateIntArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createIntArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType + ).symbol + + val parcelCreateLongArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction( + "createLongArray", + irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType + ).symbol + + val parcelCreateStringArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("createStringArray", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType)).symbol + + val parcelCreateStringArrayList: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("createStringArrayList", javaUtilArrayList.defaultType).symbol + + val parcelReadBundle: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readBundle", androidOsBundle.defaultType).symbol + + val parcelReadByte: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readByte", irBuiltIns.byteType).symbol + + val parcelReadDouble: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readDouble", irBuiltIns.doubleType).symbol + + val parcelReadFileDescriptor: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readFileDescriptor", androidOsParcelFileDescriptor.defaultType).symbol + + val parcelReadFloat: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readFloat", irBuiltIns.floatType).symbol + + val parcelReadInt: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readInt", irBuiltIns.intType).symbol + + val parcelReadLong: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readLong", irBuiltIns.longType).symbol + + val parcelReadParcelable: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readParcelable", androidOsParcelable.defaultType).apply { + addValueParameter("loader", javaLangClassLoader.defaultType) + }.symbol + + val parcelReadPersistableBundle: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readPersistableBundle", androidOsPersistableBundle.defaultType).symbol + + val parcelReadSerializable: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readSerializable", javaIoSerializable.defaultType).symbol + + val parcelReadSize: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readSize", androidUtilSize.defaultType).symbol + + val parcelReadSizeF: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readSizeF", androidUtilSizeF.defaultType).symbol + + val parcelReadSparseBooleanArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readSparseBooleanArray", androidUtilSparseBooleanArray.defaultType).symbol + + val parcelReadString: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readString", irBuiltIns.stringType).symbol + + val parcelReadStrongBinder: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readStrongBinder", androidOsIBinder.defaultType).symbol + + val parcelReadValue: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("readValue", irBuiltIns.anyNType).apply { + addValueParameter("loader", javaLangClassLoader.defaultType) + }.symbol + + val parcelWriteBinderArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeBinderArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType)) + }.symbol + + val parcelWriteBinderList: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeBinderList", irBuiltIns.unitType).apply { + addValueParameter("val", javaUtilList.defaultType) + }.symbol + + val parcelWriteBooleanArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeBooleanArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType) + }.symbol + + val parcelWriteBundle: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeBundle", irBuiltIns.unitType).apply { + addValueParameter("val", androidOsBundle.defaultType) + }.symbol + + val parcelWriteByte: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeByte", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.byteType) + }.symbol + + val parcelWriteByteArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeByteArray", irBuiltIns.unitType).apply { + addValueParameter("b", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType) + }.symbol + + val parcelWriteCharArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeCharArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType) + }.symbol + + val parcelWriteDouble: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeDouble", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.doubleType) + }.symbol + + val parcelWriteDoubleArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeDoubleArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType) + }.symbol + + val parcelWriteFileDescriptor: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeFileDescriptor", irBuiltIns.unitType).apply { + addValueParameter("val", javaIoFileDescriptor.defaultType) + }.symbol + + val parcelWriteFloat: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeFloat", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.floatType) + }.symbol + + val parcelWriteFloatArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeFloatArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType) + }.symbol + + val parcelWriteInt: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeInt", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.intType) + }.symbol + + val parcelWriteIntArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeIntArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType) + }.symbol + + val parcelWriteLong: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeLong", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.longType) + }.symbol + + val parcelWriteLongArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeLongArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType) + }.symbol + + val parcelWriteParcelable: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeParcelable", irBuiltIns.unitType).apply { + addValueParameter("p", androidOsParcelable.defaultType) + addValueParameter("parcelableFlags", irBuiltIns.intType) + }.symbol + + val parcelWritePersistableBundle: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writePersistableBundle", irBuiltIns.unitType).apply { + addValueParameter("val", androidOsPersistableBundle.defaultType) + }.symbol + + val parcelWriteSerializable: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeSerializable", irBuiltIns.unitType).apply { + addValueParameter("s", javaIoSerializable.defaultType) + }.symbol + + val parcelWriteSize: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeSize", irBuiltIns.unitType).apply { + addValueParameter("val", androidUtilSize.defaultType) + }.symbol + + val parcelWriteSizeF: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeSizeF", irBuiltIns.unitType).apply { + addValueParameter("val", androidUtilSizeF.defaultType) + }.symbol + + val parcelWriteSparseBooleanArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeSparseBooleanArray", irBuiltIns.unitType).apply { + addValueParameter("val", androidUtilSparseBooleanArray.defaultType) + }.symbol + + val parcelWriteString: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeString", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.stringType) + }.symbol + + val parcelWriteStringArray: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeStringArray", irBuiltIns.unitType).apply { + addValueParameter("val", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType)) + }.symbol + + val parcelWriteStringList: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeStringList", irBuiltIns.unitType).apply { + addValueParameter("val", javaUtilList.defaultType) + }.symbol + + val parcelWriteStrongBinder: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeStrongBinder", irBuiltIns.unitType).apply { + addValueParameter("val", androidOsIBinder.defaultType) + }.symbol + + val parcelWriteValue: IrSimpleFunctionSymbol = + androidOsParcel.owner.addFunction("writeValue", irBuiltIns.unitType).apply { + addValueParameter("v", irBuiltIns.anyNType) + }.symbol + + val textUtilsWriteToParcel: IrSimpleFunctionSymbol = + androidTextTextUtils.owner.addFunction("writeToParcel", irBuiltIns.unitType, isStatic = true).apply { + addValueParameter("cs", charSequence.defaultType) + addValueParameter("p", androidOsParcel.defaultType) + addValueParameter("parcelableFlags", irBuiltIns.intType) + }.symbol + + val classGetClassLoader: IrSimpleFunctionSymbol = + javaLangClass.owner.addFunction("getClassLoader", javaLangClassLoader.defaultType).symbol + + val arrayListConstructor: IrConstructorSymbol = javaUtilArrayList.owner.addConstructor().apply { + addValueParameter("p_0", irBuiltIns.intType) + }.symbol + + val arrayListAdd: IrSimpleFunctionSymbol = + javaUtilArrayList.owner.addFunction("add", irBuiltIns.booleanType).apply { + addValueParameter("p_0", irBuiltIns.anyNType) + }.symbol + + val linkedHashMapConstructor: IrConstructorSymbol = + javaUtilLinkedHashMap.owner.addConstructor().apply { + addValueParameter("p_0", irBuiltIns.intType) + }.symbol + + val linkedHashMapPut: IrSimpleFunctionSymbol = + javaUtilLinkedHashMap.owner.addFunction("put", irBuiltIns.anyNType).apply { + addValueParameter("p_0", irBuiltIns.anyNType) + addValueParameter("p_1", irBuiltIns.anyNType) + }.symbol + + val linkedHashSetConstructor: IrConstructorSymbol = + javaUtilLinkedHashSet.owner.addConstructor().apply { + addValueParameter("p_0", irBuiltIns.intType) + }.symbol + + val linkedHashSetAdd: IrSimpleFunctionSymbol = + javaUtilLinkedHashSet.owner.addFunction("add", irBuiltIns.booleanType).apply { + addValueParameter("p_0", irBuiltIns.anyNType) + }.symbol + + val treeMapConstructor: IrConstructorSymbol = javaUtilTreeMap.owner.addConstructor().symbol + + val treeMapPut: IrSimpleFunctionSymbol = + javaUtilTreeMap.owner.addFunction("put", irBuiltIns.anyNType).apply { + addValueParameter("p_0", irBuiltIns.anyNType) + addValueParameter("p_1", irBuiltIns.anyNType) + }.symbol + + val treeSetConstructor: IrConstructorSymbol = javaUtilTreeSet.owner.addConstructor().symbol + + val treeSetAdd: IrSimpleFunctionSymbol = + javaUtilTreeSet.owner.addFunction("add", irBuiltIns.booleanType).apply { + addValueParameter("p_0", irBuiltIns.anyNType) + }.symbol + + val textUtilsCharSequenceCreator: IrFieldSymbol = androidTextTextUtils.owner.addField { + name = Name.identifier("CHAR_SEQUENCE_CREATOR") + type = androidOsParcelableCreator.defaultType + isStatic = true + }.symbol + + private fun createPackage(packageName: String): IrPackageFragment = + IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment( + moduleFragment.descriptor, + FqName(packageName) + ) + + private fun createClass( + irPackage: IrPackageFragment, + shortName: String, + classKind: ClassKind, + classModality: Modality + ): IrClassSymbol = irFactory.buildClass { + name = Name.identifier(shortName) + kind = classKind + modality = classModality + }.apply { + parent = irPackage + createImplicitParameterDeclarationWithWrappedDescriptor() + }.symbol + + fun createBuilder( + symbol: IrSymbol, + startOffset: Int = UNDEFINED_OFFSET, + endOffset: Int = UNDEFINED_OFFSET + ) = AndroidIrBuilder(this, symbol, startOffset, endOffset) +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt new file mode 100644 index 00000000000..027f835ef12 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializerFactory.kt @@ -0,0 +1,288 @@ +/* + * Copyright 2010-2020 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.ir + +import org.jetbrains.kotlin.backend.jvm.codegen.psiElement +import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +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 + +class IrParcelSerializerFactory(symbols: AndroidSymbols) { + /** + * Resolve the given [irType] to a corresponding [IrParcelSerializer]. This depends on the TypeParcelers which + * are currently in [scope], as well as the type of the enclosing Parceleable class [parcelizeType], which is needed + * to get a class loader for reflection based serialization. Beyond this, we need to know whether to allow + * using read/writeValue for serialization (if [strict] is false). Beyond this, we need to know whether we are + * producing parcelers for properties of a Parcelable (if [toplevel] is true), or for a complete Parcelable. + */ + fun get( + irType: IrType, + scope: IrParcelerScope?, + parcelizeType: IrType, + strict: Boolean = false, + toplevel: Boolean = false + ): IrParcelSerializer { + fun strict() = strict && !irType.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME) + + scope.getCustomSerializer(irType)?.let { parceler -> + return IrCustomParcelSerializer(parceler) + } + + // TODO inline classes + val classifier = irType.erasedUpperBound + val classifierFqName = classifier.fqNameWhenAvailable?.asString() + when (classifierFqName) { + // Built-in parcel serializers + "kotlin.String", "java.lang.String" -> + return stringSerializer + "kotlin.CharSequence", "java.lang.CharSequence" -> + return charSequenceSerializer + "android.os.Bundle" -> + return bundleSerializer + "android.os.PersistableBundle" -> + return persistableBundleSerializer + + // Non-nullable built-in serializers + "kotlin.Byte", "java.lang.Byte" -> + return wrapNullableSerializerIfNeeded(irType, byteSerializer) + "kotlin.Boolean", "java.lang.Boolean" -> + return wrapNullableSerializerIfNeeded(irType, booleanSerializer) + "kotlin.Char", "java.lang.Character" -> + return wrapNullableSerializerIfNeeded(irType, charSerializer) + "kotlin.Short", "java.lang.Short" -> + return wrapNullableSerializerIfNeeded(irType, shortSerializer) + "kotlin.Int", "java.lang.Integer" -> + return wrapNullableSerializerIfNeeded(irType, intSerializer) + "kotlin.Long", "java.lang.Long" -> + return wrapNullableSerializerIfNeeded(irType, longSerializer) + "kotlin.Float", "java.lang.Float" -> + return wrapNullableSerializerIfNeeded(irType, floatSerializer) + "kotlin.Double", "java.lang.Double" -> + return wrapNullableSerializerIfNeeded(irType, doubleSerializer) + "java.io.FileDescriptor" -> + return wrapNullableSerializerIfNeeded(irType, fileDescriptorSerializer) + "android.util.Size" -> + return wrapNullableSerializerIfNeeded(irType, sizeSerializer) + "android.util.SizeF" -> + return wrapNullableSerializerIfNeeded(irType, sizeFSerializer) + + // Built-in non-parameterized container types. + "kotlin.IntArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.intType)) + return intArraySerializer + "kotlin.BooleanArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.booleanType)) + return booleanArraySerializer + "kotlin.ByteArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.byteType)) + return byteArraySerializer + "kotlin.CharArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.charType)) + return charArraySerializer + "kotlin.FloatArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.floatType)) + return floatArraySerializer + "kotlin.DoubleArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.doubleType)) + return doubleArraySerializer + "kotlin.LongArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.longType)) + return longArraySerializer + "android.util.SparseBooleanArray" -> + if (!scope.hasCustomSerializer(irBuiltIns.booleanType)) + return sparseBooleanArraySerializer + } + + // Generic container types + when (classifierFqName) { + // Apart from kotlin.Array and kotlin.ShortArray, these will only be hit if we have a + // special parceler for the element type. + "kotlin.Array", "kotlin.ShortArray", "kotlin.IntArray", + "kotlin.BooleanArray", "kotlin.ByteArray", "kotlin.CharArray", + "kotlin.FloatArray", "kotlin.DoubleArray", "kotlin.LongArray" -> { + val elementType = irType.getArrayElementType(irBuiltIns) + + if (!scope.hasCustomSerializer(elementType)) { + when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) { + "java.lang.String", "kotlin.String" -> + return stringArraySerializer + "android.os.IBinder" -> + return iBinderArraySerializer + } + } + + val arrayType = + if (classifier.defaultType.isPrimitiveArray()) classifier.defaultType else irBuiltIns.arrayClass.typeWith(elementType) + return IrArrayParcelSerializer(arrayType, elementType, get(elementType, scope, parcelizeType, strict())) + } + + // This will only be hit if we have a custom serializer for booleans + "android.util.SparseBooleanArray" -> + return IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.booleanType, + get(irBuiltIns.booleanType, scope, parcelizeType, strict()) + ) + "android.util.SparseIntArray" -> + return IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.intType, + get(irBuiltIns.intType, scope, parcelizeType, strict()) + ) + "android.util.SparseLongArray" -> + return IrSparseArrayParcelSerializer( + classifier, + irBuiltIns.longType, + get(irBuiltIns.longType, scope, parcelizeType, strict()) + ) + "android.util.SparseArray" -> { + val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns) + return IrSparseArrayParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) + } + + // TODO: More java collections? + // TODO: Add tests for all of these types, not just some common ones... + // FIXME: Is the support for ArrayDeque missing in the old BE? + "kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List", + "kotlin.collections.ArrayList", "java.util.ArrayList", + "kotlin.collections.ArrayDeque", "java.util.ArrayDeque", + "kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set", + "kotlin.collections.HashSet", "java.util.HashSet", + "kotlin.collections.LinkedHashSet", "java.util.LinkedHashSet", + "java.util.NavigableSet", "java.util.SortedSet" -> { + val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns) + if (!scope.hasCustomSerializer(elementType) && classifierFqName in setOf( + "kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList", + "java.util.List", "java.util.ArrayList" + ) + ) { + when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) { + "android.os.IBinder" -> + return iBinderListSerializer + "kotlin.String", "java.lang.String" -> + return stringListSerializer + } + } + return wrapNullableSerializerIfNeeded( + irType, + IrListParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict())) + ) + } + + "kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map", + "kotlin.collections.HashMap", "java.util.HashMap", + "kotlin.collections.LinkedHashMap", "java.util.LinkedHashMap", + "java.util.SortedMap", "java.util.NavigableMap", "java.util.TreeMap", + "java.util.concurrent.ConcurrentHashMap" -> { + val keyType = (irType as IrSimpleType).arguments[0].upperBound(irBuiltIns) + val valueType = irType.arguments[1].upperBound(irBuiltIns) + val parceler = + IrMapParcelSerializer( + classifier, + keyType, + valueType, + get(keyType, scope, parcelizeType, strict()), + get(valueType, scope, parcelizeType, strict()) + ) + return wrapNullableSerializerIfNeeded(irType, parceler) + } + } + + // Generic parcelable types + when { + classifier.isSubclassOfFqName("android.os.Parcelable") + // Avoid infinite loops when deriving parcelers for enum or object classes. + && !(toplevel && (classifier.isObject || classifier.isEnumClass)) -> { + // We try to use writeToParcel/createFromParcel directly whenever possible, but there are some caveats. + // + // According to the JLS, changing a class from final to non-final is a binary compatible change, hence we + // cannot use the writeToParcel/createFromParcel methods directly when serializing classes from external + // dependencies. This issue was originally reported in KT-20029. + // + // Conversely, we can and should apply this optimization to all classes in the current module which + // implement Parcelable (KT-20030). There are two cases to consider. If the class is annotated with + // @Parcelize, we will create the corresponding methods/fields ourselves, before we generate the code + // for writeToParcel/createFromParcel. For Java classes (or compiled Kotlin classes annotated with + // @Parcelize), we'll have a field in the class itself. Finally, with Parcelable instances which were + // manually implemented in Kotlin, we'll instead have an @JvmField property getter in the companion object. + return if (classifier.modality == Modality.FINAL && classifier.psiElement != null + && (classifier.isParcelize || classifier.hasCreatorField) + ) { + wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier)) + } else { + // In all other cases, we have to use the generic methods in Parcel, which use reflection internally. + IrGenericParcelableParcelSerializer(parcelizeType) + } + } + + classifier.isSubclassOfFqName("android.os.IBinder") -> + return iBinderSerializer + + classifier.isObject -> + return IrObjectParcelSerializer(classifier) + + classifier.isEnumClass -> + return wrapNullableSerializerIfNeeded(irType, IrEnumParcelSerializer(classifier)) + + classifier.isSubclassOfFqName("java.io.Serializable") + // Functions and Continuations are always serializable. + || irType.isFunctionTypeOrSubtype() || irType.isSuspendFunctionTypeOrSubtype() -> + return serializableSerializer + + strict() -> + throw IllegalArgumentException("Illegal type, could not find a specific serializer for ${irType.render()}") + + else -> + return IrGenericValueParcelSerializer(parcelizeType) + } + } + + private fun wrapNullableSerializerIfNeeded(irType: IrType, serializer: IrParcelSerializer) = + if (irType.isNullable()) IrNullAwareParcelSerializer(serializer) else serializer + + private val irBuiltIns: IrBuiltIns = symbols.irBuiltIns + + private val stringArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArray, symbols.parcelWriteStringArray) + private val stringListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArrayList, symbols.parcelWriteStringList) + private val iBinderSerializer = IrSimpleParcelSerializer(symbols.parcelReadStrongBinder, symbols.parcelWriteStrongBinder) + private val iBinderArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArray, symbols.parcelWriteBinderArray) + private val iBinderListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArrayList, symbols.parcelWriteBinderList) + private val serializableSerializer = IrSimpleParcelSerializer(symbols.parcelReadSerializable, symbols.parcelWriteSerializable) + private val stringSerializer = IrSimpleParcelSerializer(symbols.parcelReadString, symbols.parcelWriteString) + private val byteSerializer = IrSimpleParcelSerializer(symbols.parcelReadByte, symbols.parcelWriteByte) + private val intSerializer = IrSimpleParcelSerializer(symbols.parcelReadInt, symbols.parcelWriteInt) + private val longSerializer = IrSimpleParcelSerializer(symbols.parcelReadLong, symbols.parcelWriteLong) + private val floatSerializer = IrSimpleParcelSerializer(symbols.parcelReadFloat, symbols.parcelWriteFloat) + private val doubleSerializer = IrSimpleParcelSerializer(symbols.parcelReadDouble, symbols.parcelWriteDouble) + private val intArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateIntArray, symbols.parcelWriteIntArray) + private val booleanArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBooleanArray, symbols.parcelWriteBooleanArray) + private val byteArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateByteArray, symbols.parcelWriteByteArray) + private val charArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateCharArray, symbols.parcelWriteCharArray) + private val doubleArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateDoubleArray, symbols.parcelWriteDoubleArray) + private val floatArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateFloatArray, symbols.parcelWriteFloatArray) + private val longArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateLongArray, symbols.parcelWriteLongArray) + + // Primitive types without dedicated read/write methods need an additional cast. + private val booleanSerializer = IrWrappedIntParcelSerializer(irBuiltIns.booleanType) + private val shortSerializer = IrWrappedIntParcelSerializer(irBuiltIns.shortType) + private val charSerializer = IrWrappedIntParcelSerializer(irBuiltIns.charType) + + private val charSequenceSerializer = IrCharSequenceParcelSerializer() + + // TODO The old backend uses the hidden "read/writeRawFileDescriptor" methods. + private val fileDescriptorSerializer = IrSimpleParcelSerializer(symbols.parcelReadFileDescriptor, symbols.parcelWriteFileDescriptor) + + private val sizeSerializer = IrSimpleParcelSerializer(symbols.parcelReadSize, symbols.parcelWriteSize) + private val sizeFSerializer = IrSimpleParcelSerializer(symbols.parcelReadSizeF, symbols.parcelWriteSizeF) + private val bundleSerializer = IrSimpleParcelSerializer(symbols.parcelReadBundle, symbols.parcelWriteBundle) + private val persistableBundleSerializer = + IrSimpleParcelSerializer(symbols.parcelReadPersistableBundle, symbols.parcelWritePersistableBundle) + private val sparseBooleanArraySerializer = + IrSimpleParcelSerializer(symbols.parcelReadSparseBooleanArray, symbols.parcelWriteSparseBooleanArray) +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt new file mode 100644 index 00000000000..472628c0584 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelSerializers.kt @@ -0,0 +1,536 @@ +/* + * Copyright 2010-2020 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.ir + +import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface +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.IrValueDeclaration +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase.Companion.CREATOR_NAME + +interface IrParcelSerializer { + fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression + fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression +} + +fun AndroidIrBuilder.readParcelWith(serializer: IrParcelSerializer, parcel: IrValueDeclaration): IrExpression { + return with(serializer) { readParcel(parcel) } +} + +fun AndroidIrBuilder.writeParcelWith( + serializer: IrParcelSerializer, + parcel: IrValueDeclaration, + flags: IrValueDeclaration, + value: IrExpression +): IrExpression { + return with(serializer) { writeParcel(parcel, flags, value) } +} + +// Creates a serializer from a pair of parcel methods of the form reader()T and writer(T)V. +class IrSimpleParcelSerializer(private val reader: IrSimpleFunctionSymbol, private val writer: IrSimpleFunctionSymbol) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irCall(reader).apply { dispatchReceiver = irGet(parcel) } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return irCall(writer).apply { + dispatchReceiver = irGet(parcel) + putValueArgument(0, value) + } + } +} + +// Serialize a value of the primitive [parcelType] by coercion to int. +class IrWrappedIntParcelSerializer(private val parcelType: IrType) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return if (parcelType.isBoolean()) { + irNotEquals(parcelReadInt(irGet(parcel)), irInt(0)) + } else { + val conversion = context.irBuiltIns.intClass.functions.first { function -> + function.owner.name.asString() == "to${parcelType.getClass()!!.name}" + } + irCall(conversion).apply { + dispatchReceiver = parcelReadInt(irGet(parcel)) + } + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression = + parcelWriteInt( + irGet(parcel), + if (parcelType.isBoolean()) { + irIfThenElse(context.irBuiltIns.intType, value, irInt(1), irInt(0)) + } else { + val conversion = parcelType.classOrNull!!.functions.first { function -> + function.owner.name.asString() == "toInt" + } + irCall(conversion).apply { dispatchReceiver = value } + } + ) +} + +// Wraps a non-null aware parceler to handle nullable types. +class IrNullAwareParcelSerializer(private val serializer: IrParcelSerializer) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + val nonNullResult = readParcelWith(serializer, parcel) + return irIfThenElse( + nonNullResult.type.makeNullable(), + irEquals(parcelReadInt(irGet(parcel)), irInt(0)), + irNull(), + nonNullResult + ) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return irLetS(value) { irValueSymbol -> + irIfNull( + context.irBuiltIns.unitType, + irGet(irValueSymbol.owner), + parcelWriteInt(irGet(parcel), irInt(0)), + irBlock { + +parcelWriteInt(irGet(parcel), irInt(1)) + +writeParcelWith(serializer, parcel, flags, irGet(irValueSymbol.owner)) + } + ) + } + } +} + +// Parcel serializer for object classes. We avoid empty parcels by writing a dummy value. Not null-safe. +class IrObjectParcelSerializer(private val objectClass: IrClass) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression + // Avoid empty parcels + { + return irBlock { + +parcelReadInt(irGet(parcel)) + +irGetObject(objectClass.symbol) + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelWriteInt(irGet(parcel), irInt(1)) + } +} + +// Parcel serializer for classes with a default constructor. We avoid empty parcels by writing a dummy value. Not null-safe. +class IrNoParameterClassParcelSerializer(private val irClass: IrClass) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + val defaultConstructor = irClass.primaryConstructor!! + return irBlock { + +parcelReadInt(irGet(parcel)) + +irCall(defaultConstructor) + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelWriteInt(irGet(parcel), irInt(1)) + } +} + +// Parcel serializer for enum classes. Not null-safe. +class IrEnumParcelSerializer(enumClass: IrClass) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irCall(enumValueOf).apply { + putValueArgument(0, parcelReadString(irGet(parcel))) + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelWriteString(irGet(parcel), irCall(enumName).apply { + dispatchReceiver = value + }) + } + + private val enumValueOf: IrFunctionSymbol = + enumClass.functions.single { function -> + function.name.asString() == "valueOf" && function.dispatchReceiverParameter == null + && function.extensionReceiverParameter == null && function.valueParameters.size == 1 + && function.valueParameters.single().type.isString() + }.symbol + + private val enumName: IrFunctionSymbol = enumClass.getPropertyGetter("name")!! +} + +// Parcel serializer for the java CharSequence interface. +class IrCharSequenceParcelSerializer : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return parcelableCreatorCreateFromParcel(getTextUtilsCharSequenceCreator(), irGet(parcel)) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return textUtilsWriteToParcel(value, irGet(parcel), irGet(flags)) + } +} + +// Parcel serializer for Parcelables in the same module, which accesses the writeToParcel/createFromParcel methods without reflection. +class IrEfficientParcelableParcelSerializer(private val irClass: IrClass) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + val creator: IrExpression = irClass.fields.find { it.name == CREATOR_NAME }?.let { creatorField -> + irGetField(null, creatorField) + } ?: irCall(irClass.creatorGetter!!).apply { + dispatchReceiver = irGetObject(irClass.companionObject()!!.symbol) + } + + return parcelableCreatorCreateFromParcel(creator, irGet(parcel)) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelableWriteToParcel(irClass, value, irGet(parcel), irGet(flags)) + } +} + +// Parcel serializer for Parcelables using reflection. +// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027. +class IrGenericParcelableParcelSerializer(private val parcelizeType: IrType) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return parcelReadParcelable(irGet(parcel), classGetClassLoader(javaClassReference(parcelizeType))) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelWriteParcelable(irGet(parcel), value, irGet(flags)) + } +} + +// Fallback parcel serializer for unknown types using Parcel.readValue/writeValue. +// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027. +class IrGenericValueParcelSerializer(private val parcelizeType: IrType) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return parcelReadValue(irGet(parcel), classGetClassLoader(javaClassReference(parcelizeType))) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelWriteValue(irGet(parcel), value) + } +} + +// Parcel serializer using a custom Parceler object. +class IrCustomParcelSerializer(private val parcelerObject: IrClass) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return parcelerCreate(parcelerObject, parcel) + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return parcelerWrite(parcelerObject, parcel, flags, value) + } +} + +// Parcel serializer for array types. This handles both primitive array types (for ShortArray and for primitive arrays using custom element +// parcelers) as well as boxed arrays. +// TODO: Unsigned array types +class IrArrayParcelSerializer( + private val arrayType: IrType, + private val elementType: IrType, + private val elementSerializer: IrParcelSerializer +) : IrParcelSerializer { + private fun AndroidIrBuilder.newArray(size: IrExpression): IrExpression { + val arrayConstructor: IrFunctionSymbol = if (arrayType.isBoxedArray) { + androidSymbols.arrayOfNulls + } else { + arrayType.classOrNull!!.constructors.single { it.owner.valueParameters.size == 1 } + } + + return irCall(arrayConstructor, arrayType).apply { + if (typeArgumentsCount != 0) + putTypeArgument(0, elementType) + putValueArgument(0, size) + } + } + + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irBlock { + val arraySize = irTemporary(parcelReadInt(irGet(parcel))) + val arrayTemporary = irTemporary(newArray(irGet(arraySize))) + forUntil(irGet(arraySize)) { index -> + val setter = arrayType.classOrNull!!.getSimpleFunction("set")!! + +irCall(setter).apply { + dispatchReceiver = irGet(arrayTemporary) + putValueArgument(0, irGet(index)) + putValueArgument(1, readParcelWith(elementSerializer, parcel)) + } + } + +irGet(arrayTemporary) + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return irBlock { + val arrayTemporary = irTemporary(value) + val arraySizeSymbol = arrayType.classOrNull!!.getPropertyGetter("size")!! + val arraySize = irTemporary(irCall(arraySizeSymbol).apply { + dispatchReceiver = irGet(arrayTemporary) + }) + + +parcelWriteInt(irGet(parcel), irGet(arraySize)) + + forUntil(irGet(arraySize)) { index -> + val getter = context.irBuiltIns.arrayClass.getSimpleFunction("get")!! + val element = irCall(getter, elementType).apply { + dispatchReceiver = irGet(arrayTemporary) + putValueArgument(0, irGet(index)) + } + +writeParcelWith(elementSerializer, parcel, flags, element) + } + } + } +} + +// Parcel serializer for android SparseArrays. Note that this also needs to handle BooleanSparseArray, in case of a custom element parceler. +class IrSparseArrayParcelSerializer( + private val sparseArrayClass: IrClass, + private val elementType: IrType, + private val elementSerializer: IrParcelSerializer +) : IrParcelSerializer { + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irBlock { + val remainingSizeTemporary = irTemporaryVar(parcelReadInt(irGet(parcel))) + + val sparseArrayConstructor = sparseArrayClass.constructors.first { irConstructor -> + irConstructor.valueParameters.size == 1 && irConstructor.valueParameters.single().type.isInt() + } + + val constructorCall = if (sparseArrayClass.typeParameters.isEmpty()) + irCall(sparseArrayConstructor) + else + irCallConstructor(sparseArrayConstructor.symbol, listOf(elementType)) + + val arrayTemporary = irTemporary(constructorCall.apply { + putValueArgument(0, irGet(remainingSizeTemporary)) + }) + + +irWhile().apply { + condition = irNotEquals(irGet(remainingSizeTemporary), irInt(0)) + body = irBlock { + val sparseArrayPut = sparseArrayClass.functions.first { function -> + function.name.asString() == "put" && function.valueParameters.size == 2 + } + +irCall(sparseArrayPut).apply { + dispatchReceiver = irGet(arrayTemporary) + putValueArgument(0, parcelReadInt(irGet(parcel))) + putValueArgument(1, readParcelWith(elementSerializer, parcel)) + } + + val dec = context.irBuiltIns.intClass.getSimpleFunction("dec")!! + +irSetVar(remainingSizeTemporary.symbol, irCall(dec).apply { + dispatchReceiver = irGet(remainingSizeTemporary) + }) + } + } + + +irGet(arrayTemporary) + } + } + + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + return irBlock { + val sizeFunction = sparseArrayClass.functions.first { function -> + function.name.asString() == "size" && function.valueParameters.isEmpty() + } + val keyAtFunction = sparseArrayClass.functions.first { function -> + function.name.asString() == "keyAt" && function.valueParameters.size == 1 + } + val valueAtFunction = sparseArrayClass.functions.first { function -> + function.name.asString() == "valueAt" && function.valueParameters.size == 1 + } + + val arrayTemporary = irTemporary(value) + val sizeTemporary = irTemporary(irCall(sizeFunction).apply { + dispatchReceiver = irGet(arrayTemporary) + }) + + +parcelWriteInt(irGet(parcel), irGet(sizeTemporary)) + + forUntil(irGet(sizeTemporary)) { index -> + +parcelWriteInt(irGet(parcel), irCall(keyAtFunction).apply { + dispatchReceiver = irGet(arrayTemporary) + putValueArgument(0, irGet(index)) + }) + + +writeParcelWith(elementSerializer, parcel, flags, irCall(valueAtFunction.symbol, elementType).apply { + dispatchReceiver = irGet(arrayTemporary) + putValueArgument(0, irGet(index)) + }) + } + } + } +} + +// Parcel serializer for all lists supported by Parcelize. List interfaces use hard-coded default implementations for deserialization. +// List maps to ArrayList, Set maps to LinkedHashSet, NavigableSet and SortedSet map to TreeSet. +class IrListParcelSerializer( + private val irClass: IrClass, + private val elementType: IrType, + private val elementSerializer: IrParcelSerializer +) : IrParcelSerializer { + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + val sizeFunction = irClass.getPropertyGetter("size")!! + val iteratorFunction = irClass.getMethodWithoutArguments("iterator") + val iteratorClass = iteratorFunction.returnType.erasedUpperBound + val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext") + val iteratorNext = iteratorClass.getMethodWithoutArguments("next") + + return irBlock { + val list = irTemporary(value) + +parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply { + dispatchReceiver = irGet(list) + }) + val iterator = irTemporary(irCall(iteratorFunction).apply { + dispatchReceiver = irGet(list) + }) + +irWhile().apply { + condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) } + body = writeParcelWith(elementSerializer, parcel, flags, irCall(iteratorNext.symbol, elementType).apply { + dispatchReceiver = irGet(iterator) + }) + } + } + } + + private fun listSymbols(symbols: AndroidSymbols): Pair { + // If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back + // the the default constructor if none exist. + if (!irClass.isJvmInterface) { + val constructor = irClass.constructors.find { constructor -> + constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt() + } ?: irClass.constructors.first { constructor -> constructor.valueParameters.isEmpty() } + + val add = irClass.functions.first { function -> + function.name.asString() == "add" && function.valueParameters.size == 1 + } + + return constructor.symbol to add.symbol + } + + return when (irClass.fqNameWhenAvailable?.asString()) { + "kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List" -> + symbols.arrayListConstructor to symbols.arrayListAdd + "kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set" -> + symbols.linkedHashSetConstructor to symbols.linkedHashSetAdd + "java.util.NavigableSet", "java.util.SortedSet" -> + symbols.treeSetConstructor to symbols.treeSetAdd + else -> error("Unknown list interface type: ${irClass.render()}") + } + } + + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irBlock { + val (constructorSymbol, addSymbol) = listSymbols(androidSymbols) + val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel))) + val list = irTemporary(irCall(constructorSymbol).apply { + if (constructorSymbol.owner.valueParameters.isNotEmpty()) + putValueArgument(0, irGet(sizeTemporary)) + }) + forUntil(irGet(sizeTemporary)) { + +irCall(addSymbol).apply { + dispatchReceiver = irGet(list) + putValueArgument(0, readParcelWith(elementSerializer, parcel)) + } + } + +irGet(list) + } + } +} + +// Parcel serializer for all maps supported by Parcelize. Map interfaces use hard-coded default implementations for deserialization. +// Map uses LinkedHashMap, while NavigableMap and SortedMap use to TreeMap. +class IrMapParcelSerializer( + private val irClass: IrClass, + private val keyType: IrType, + private val valueType: IrType, + private val keySerializer: IrParcelSerializer, + private val valueSerializer: IrParcelSerializer +) : IrParcelSerializer { + override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression { + val sizeFunction = irClass.getPropertyGetter("size")!! + val entriesFunction = irClass.getPropertyGetter("entries")!! + val entrySetClass = entriesFunction.owner.returnType.erasedUpperBound + val iteratorFunction = entrySetClass.getMethodWithoutArguments("iterator") + val iteratorClass = iteratorFunction.returnType.erasedUpperBound + val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext") + val iteratorNext = iteratorClass.getMethodWithoutArguments("next") + val elementClass = + (entriesFunction.owner.returnType as IrSimpleType).arguments.single().upperBound(context.irBuiltIns).erasedUpperBound + val elementKey = elementClass.getPropertyGetter("key")!! + val elementValue = elementClass.getPropertyGetter("value")!! + + return irBlock { + val list = irTemporary(value) + +parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply { + dispatchReceiver = irGet(list) + }) + val iterator = irTemporary(irCall(iteratorFunction).apply { + dispatchReceiver = irCall(entriesFunction).apply { + dispatchReceiver = irGet(list) + } + }) + +irWhile().apply { + condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) } + body = irBlock { + val element = irTemporary(irCall(iteratorNext).apply { + dispatchReceiver = irGet(iterator) + }) + +writeParcelWith(keySerializer, parcel, flags, irCall(elementKey, keyType).apply { + dispatchReceiver = irGet(element) + }) + +writeParcelWith(valueSerializer, parcel, flags, irCall(elementValue, valueType).apply { + dispatchReceiver = irGet(element) + }) + } + } + } + } + + private fun mapSymbols(symbols: AndroidSymbols): Pair { + // If the IrClass refers to a concrete type, try to find a constructor with capacity or fall back + // the the default constructor if none exist. + if (!irClass.isJvmInterface) { + val constructor = irClass.constructors.find { constructor -> + constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt() + } ?: irClass.constructors.find { constructor -> + constructor.valueParameters.isEmpty() + }!! + + val put = irClass.functions.first { function -> + function.name.asString() == "put" && function.valueParameters.size == 2 + } + + return constructor.symbol to put.symbol + } + + return when (irClass.fqNameWhenAvailable?.asString()) { + "kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map" -> + symbols.linkedHashMapConstructor to symbols.linkedHashMapPut + "java.util.SortedMap", "java.util.NavigableMap" -> + symbols.treeMapConstructor to symbols.treeMapPut + else -> error("Unknown map interface type: ${irClass.render()}") + } + } + + override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression { + return irBlock { + val (constructorSymbol, putSymbol) = mapSymbols(androidSymbols) + val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel))) + val map = irTemporary(irCall(constructorSymbol).apply { + if (constructorSymbol.owner.valueParameters.isNotEmpty()) + putValueArgument(0, irGet(sizeTemporary)) + }) + forUntil(irGet(sizeTemporary)) { + +irCall(putSymbol).apply { + dispatchReceiver = irGet(map) + putValueArgument(0, readParcelWith(keySerializer, parcel)) + putValueArgument(1, readParcelWith(valueSerializer, parcel)) + } + } + +irGet(map) + } + } +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelerScope.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelerScope.kt new file mode 100644 index 00000000000..6574f1cd3a6 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/IrParcelerScope.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2020 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.ir + +import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +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. +// Note that custom parcelers are resolved in *reverse* lexical order. +class IrParcelerScope(private val parent: IrParcelerScope? = null) { + private val typeParcelers = mutableMapOf() + + fun add(type: IrType, parceler: IrClass) { + typeParcelers.putIfAbsent(type, parceler) + } + + fun get(type: IrType): IrClass? = + parent?.get(type) ?: typeParcelers[type] +} + +fun IrParcelerScope?.getCustomSerializer(irType: IrType): IrClass? { + return irType.getAnnotation(ParcelizeAnnotationChecker.WRITE_WITH_FQNAME)?.let { writeWith -> + (writeWith.type as IrSimpleType).arguments.single().typeOrNull!!.getClass()!! + } ?: this?.get(irType) +} + +fun IrParcelerScope?.hasCustomSerializer(irType: IrType): Boolean { + return getCustomSerializer(irType) != null +} + +fun IrAnnotationContainer.getParcelerScope(parent: IrParcelerScope? = null): IrParcelerScope? { + val typeParcelerAnnotations = annotations.filterTo(mutableListOf()) { + it.symbol.owner.constructedClass.fqNameWhenAvailable == ParcelizeAnnotationChecker.TYPE_PARCELER_FQNAME + } + + if (typeParcelerAnnotations.isEmpty()) + return parent + + val scope = IrParcelerScope(parent) + + for (annotation in typeParcelerAnnotations) { + val (mappedType, parcelerType) = (annotation.type as IrSimpleType).arguments.map { it.typeOrNull!! } + scope.add(mappedType, parcelerType.getClass()!!) + } + + return scope +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/ParcelizeIrTransformer.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/ParcelizeIrTransformer.kt new file mode 100644 index 00000000000..6d04529add1 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/ParcelizeIrTransformer.kt @@ -0,0 +1,322 @@ +/* + * Copyright 2010-2020 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.ir + +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder +import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionReference +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.parcelize.ANDROID_PARCELABLE_CLASS_FQNAME +import org.jetbrains.kotlin.parcelize.PARCELER_FQNAME +import org.jetbrains.kotlin.parcelize.ParcelizeSyntheticComponent +import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +@OptIn(ObsoleteDescriptorBasedAPI::class) +class ParcelizeIrTransformer(private val context: IrPluginContext, private val androidSymbols: AndroidSymbols) : + ParcelizeExtensionBase, IrElementVisitorVoid { + private val serializerFactory = IrParcelSerializerFactory(androidSymbols) + + private val deferredOperations = mutableListOf<() -> Unit>() + private fun defer(block: () -> Unit) = deferredOperations.add(block) + + private fun IrPluginContext.createIrBuilder(symbol: IrSymbol) = + DeclarationIrBuilder(this, symbol, symbol.owner.startOffset, symbol.owner.endOffset) + + private val symbolMap = mutableMapOf() + + private val irFactory: IrFactory = IrFactoryImpl + + fun transform(moduleFragment: IrModuleFragment) { + moduleFragment.accept(this, null) + deferredOperations.forEach { it() } + + // Remap broken stubs, which psi2ir generates for the synthetic descriptors coming from the ParcelizeResolveExtension. + moduleFragment.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitCall(expression: IrCall): IrExpression { + val remappedSymbol = symbolMap[expression.symbol] + ?: return super.visitCall(expression) + return IrCallImpl( + expression.startOffset, expression.endOffset, expression.type, remappedSymbol, + expression.typeArgumentsCount, expression.valueArgumentsCount, expression.origin, + expression.superQualifierSymbol + ).apply { + copyTypeAndValueArgumentsFrom(expression) + } + } + + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + val remappedSymbol = symbolMap[expression.symbol] + val remappedReflectionTarget = expression.reflectionTarget?.let { symbolMap[it] } + if (remappedSymbol == null && remappedReflectionTarget == null) + return super.visitFunctionReference(expression) + + return IrFunctionReferenceImpl( + expression.startOffset, expression.endOffset, expression.type, remappedSymbol ?: expression.symbol, + expression.typeArgumentsCount, expression.valueArgumentsCount, remappedReflectionTarget, + expression.origin + ).apply { + copyTypeAndValueArgumentsFrom(expression) + } + } + + override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement { + // Remap overridden symbols, otherwise the code might break in BridgeLowering + declaration.overriddenSymbols = declaration.overriddenSymbols.map { symbol -> + symbolMap[symbol] ?: symbol + } + return super.visitSimpleFunction(declaration) + } + }) + } + + override fun visitElement(element: IrElement) = element.acceptChildren(this, null) + + override fun visitClass(declaration: IrClass) { + declaration.acceptChildren(this, null) + if (!declaration.isParcelize) + return + + val parcelableProperties = declaration.parcelableProperties + + // If the companion extends Parceler, it can override parts of the generated implementation. + val parcelerObject = declaration.companionObject()?.takeIf { + it.isSubclassOfFqName(PARCELER_FQNAME.asString()) + } + + if (declaration.descriptor.hasSyntheticDescribeContents()) { + val describeContents = declaration.addOverride( + ANDROID_PARCELABLE_CLASS_FQNAME, + "describeContents", + context.irBuiltIns.intType, + modality = Modality.OPEN + ).apply { + val flags = if (parcelableProperties.any { it.field.type.containsFileDescriptors }) 1 else 0 + body = context.createIrBuilder(symbol).run { + irExprBody(irInt(flags)) + } + + metadata = DescriptorMetadataSource.Function( + declaration.descriptor.findFunction(ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS)!! + ) + } + + declaration.functions.find { + it.descriptor.safeAs()?.componentKind == ParcelizeSyntheticComponent.ComponentKind.DESCRIBE_CONTENTS + }?.let { stub -> + symbolMap[stub.symbol] = describeContents.symbol + declaration.declarations.remove(stub) + } + } + + if (declaration.descriptor.hasSyntheticWriteToParcel()) { + val writeToParcel = declaration.addOverride( + ANDROID_PARCELABLE_CLASS_FQNAME, + "writeToParcel", + context.irBuiltIns.unitType, + modality = Modality.OPEN + ).apply { + val receiverParameter = dispatchReceiverParameter!! + val parcelParameter = addValueParameter("out", androidSymbols.androidOsParcel.defaultType) + val flagsParameter = addValueParameter("flags", context.irBuiltIns.intType) + + // We need to defer the construction of the writer, since it may refer to the [writeToParcel] methods in other + // @Parcelize classes in the current module, which might not be constructed yet at this point. + defer { + body = androidSymbols.createBuilder(symbol).run { + irBlockBody { + when { + parcelerObject != null -> + +parcelerWrite(parcelerObject, parcelParameter, flagsParameter, irGet(receiverParameter)) + + parcelableProperties.isNotEmpty() -> + for (property in parcelableProperties) { + +writeParcelWith( + property.parceler, + parcelParameter, + flagsParameter, + irGetField(irGet(receiverParameter), property.field) + ) + } + + else -> + +writeParcelWith( + declaration.classParceler, + parcelParameter, + flagsParameter, + irGet(receiverParameter) + ) + } + } + } + } + + metadata = DescriptorMetadataSource.Function( + declaration.descriptor.findFunction(ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL)!! + ) + } + + declaration.functions.find { + it.descriptor.safeAs()?.componentKind == ParcelizeSyntheticComponent.ComponentKind.WRITE_TO_PARCEL + }?.let { stub -> + symbolMap[stub.symbol] = writeToParcel.symbol + declaration.declarations.remove(stub) + } + } + + val creatorType = androidSymbols.androidOsParcelableCreator.typeWith(declaration.defaultType) + + if (!declaration.descriptor.hasCreatorField()) { + declaration.addField { + name = ParcelizeExtensionBase.CREATOR_NAME + type = creatorType + isStatic = true + isFinal = true + }.apply { + val irField = this + val creatorClass = irFactory.buildClass { + name = Name.identifier("Creator") + visibility = DescriptorVisibilities.LOCAL + }.apply { + parent = irField + superTypes = listOf(creatorType) + createImplicitParameterDeclarationWithWrappedDescriptor() + + addConstructor { + isPrimary = true + }.apply { + body = context.createIrBuilder(symbol).irBlockBody { + +irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single()) + } + } + + val arrayType = context.irBuiltIns.arrayClass.typeWith(declaration.defaultType.makeNullable()) + addFunction("newArray", arrayType).apply { + overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!) + val sizeParameter = addValueParameter("size", context.irBuiltIns.intType) + body = context.createIrBuilder(symbol).run { + irExprBody( + parcelerNewArray(parcelerObject, sizeParameter) + ?: irCall(androidSymbols.arrayOfNulls, arrayType).apply { + putTypeArgument(0, arrayType) + putValueArgument(0, irGet(sizeParameter)) + } + ) + } + } + + addFunction("createFromParcel", declaration.defaultType).apply { + overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!) + val parcelParameter = addValueParameter("parcel", androidSymbols.androidOsParcel.defaultType) + + // We need to defer the construction of the create method, since it may refer to the [Parcelable.Creator] + // instances in other @Parcelize classes in the current module, which may not exist yet. + defer { + body = androidSymbols.createBuilder(symbol).run { + irExprBody( + when { + parcelerObject != null -> + parcelerCreate(parcelerObject, parcelParameter) + + parcelableProperties.isNotEmpty() -> + irCall(declaration.primaryConstructor!!).apply { + for ((index, property) in parcelableProperties.withIndex()) { + putValueArgument(index, readParcelWith(property.parceler, parcelParameter)) + } + } + + else -> + readParcelWith(declaration.classParceler, parcelParameter) + } + ) + } + } + } + } + + initializer = context.createIrBuilder(symbol).run { + irExprBody(irBlock { + +creatorClass + +irCall(creatorClass.primaryConstructor!!) + }) + } + } + } + } + + private fun IrClass.addOverride( + baseFqName: FqName, + name: String, + returnType: IrType, + modality: Modality = Modality.FINAL + ): IrSimpleFunction = addFunction(name, returnType, modality).apply { + overriddenSymbols = superTypes.mapNotNull { superType -> + superType.classOrNull?.owner?.takeIf { superClass -> superClass.isSubclassOfFqName(baseFqName.asString()) } + }.flatMap { superClass -> + superClass.functions.filter { function -> + function.name.asString() == name && function.overridesFunctionIn(baseFqName) + }.map { it.symbol }.toList() + } + } + + private class ParcelableProperty(val field: IrField, parcelerThunk: () -> IrParcelSerializer) { + val parceler by lazy(parcelerThunk) + } + + private val IrClass.classParceler: IrParcelSerializer + get() = if (kind == ClassKind.CLASS) { + IrNoParameterClassParcelSerializer(this) + } else { + serializerFactory.get(defaultType, parcelizeType = defaultType, strict = true, toplevel = true, scope = getParcelerScope()) + } + + private val IrClass.parcelableProperties: List + get() { + if (kind != ClassKind.CLASS) return emptyList() + + val constructor = primaryConstructor ?: return emptyList() + val topLevelScope = getParcelerScope() + + return constructor.valueParameters.map { parameter -> + val property = properties.first { it.name == parameter.name } + val localScope = property.getParcelerScope(topLevelScope) + ParcelableProperty(property.backingField!!) { + serializerFactory.get(parameter.type, parcelizeType = defaultType, scope = localScope) + } + } + } + + // *Heuristic* to determine if a Parcelable contains file descriptors. + private val IrType.containsFileDescriptors: Boolean + get() = erasedUpperBound.fqNameWhenAvailable == ParcelizeExtensionBase.FILE_DESCRIPTOR_FQNAME || + (this as? IrSimpleType)?.arguments?.any { argument -> + argument.typeOrNull?.containsFileDescriptors == true + } == true +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt new file mode 100644 index 00000000000..50403ecbcb2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ir/irUtils.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2010-2020 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.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.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +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.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) + +// 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? + get() = companionObject()?.getPropertyGetter(CREATOR_NAME.asString())?.takeIf { + it.owner.correspondingPropertySymbol?.owner?.backingField?.hasAnnotation(FqName("kotlin.jvm.JvmField")) == true + } + +// true if the class has a static CREATOR field +val IrClass.hasCreatorField: Boolean + get() = fields.any { field -> field.name == CREATOR_NAME } || creatorGetter != null + +// object P : Parceler { fun T.write(parcel: Parcel, flags: Int) ...} +fun IrBuilderWithScope.parcelerWrite( + parceler: IrClass, parcel: IrValueDeclaration, + flags: IrValueDeclaration, value: IrExpression +): IrCall { + return irCall(parceler.parcelerSymbolByName("write")!!).apply { + dispatchReceiver = irGetObject(parceler.symbol) + extensionReceiver = value + putValueArgument(0, irGet(parcel)) + putValueArgument(1, irGet(flags)) + } +} + +// object P : Parceler { fun create(parcel: Parcel): T } +fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression { + return irCall(parceler.parcelerSymbolByName("create")!!).apply { + dispatchReceiver = irGetObject(parceler.symbol) + putValueArgument(0, irGet(parcel)) + } +} + +// object P: Parceler { fun newArray(size: Int): Array } +fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? { + return parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol -> + irCall(newArraySymbol).apply { + dispatchReceiver = irGetObject(parceler.symbol) + putValueArgument(0, irGet(size)) + } + } +} + +// class Parcelable { fun writeToParcel(parcel: Parcel, flags: Int) ...} +fun IrBuilderWithScope.parcelableWriteToParcel( + parcelableClass: IrClass, + parcelable: IrExpression, + parcel: IrExpression, + flags: IrExpression +): IrExpression { + val writeToParcel = parcelableClass.functions.first { function -> + function.name.asString() == "writeToParcel" && function.overridesFunctionIn(ANDROID_PARCELABLE_CLASS_FQNAME) + } + + return irCall(writeToParcel).apply { + dispatchReceiver = parcelable + putValueArgument(0, parcel) + putValueArgument(1, flags) + } +} + +// class C : Parcelable.Creator { fun createFromParcel(parcel: Parcel): T ...} +fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression, parcel: IrExpression): IrExpression { + val createFromParcel = creator.type.getClass()!!.functions.first { function -> + function.name.asString() == "createFromParcel" && function.overridesFunctionIn(ANDROID_PARCELABLE_CREATOR_CLASS_FQNAME) + } + + return irCall(createFromParcel).apply { + dispatchReceiver = creator + putValueArgument(0, parcel) + } +} + +// Find a named function declaration which overrides the corresponding function in [Parceler]. +// This is more reliable than trying to match the functions signature ourselves, since the frontend +// has already done the work. +private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? { + return functions.firstOrNull { function -> + !function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME) + }?.symbol +} + +fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean { + return parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName } +} + +private fun IrBuilderWithScope.kClassReference(classType: IrType): IrClassReferenceImpl { + return IrClassReferenceImpl( + startOffset, endOffset, context.irBuiltIns.kClassClass.starProjectedType, context.irBuiltIns.kClassClass, classType + ) +} + +private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression): IrCall { + return irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply { + extensionReceiver = kClassReference + } +} + +// Produce a static reference to the java class of the given type. +fun AndroidIrBuilder.javaClassReference(classType: IrType): IrCall = kClassToJavaClass(kClassReference(classType)) + +fun IrClass.isSubclassOfFqName(fqName: String): Boolean { + return fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) } +} + +inline fun IrBlockBuilder.forUntil(upperBound: IrExpression, loopBody: IrBlockBuilder.(IrValueDeclaration) -> Unit) { + val indexTemporary = irTemporaryVar(irInt(0)) + +irWhile().apply { + condition = irNotEquals(irGet(indexTemporary), upperBound) + body = irBlock { + loopBody(indexTemporary) + val inc = context.irBuiltIns.intClass.getSimpleFunction("inc")!! + +irSetVar(indexTemporary.symbol, irCall(inc).apply { + dispatchReceiver = irGet(indexTemporary) + }) + } + } +} + +fun IrTypeArgument.upperBound(builtIns: IrBuiltIns): IrType = + when (this) { + is IrStarProjection -> builtIns.anyNType + is IrTypeProjection -> { + if (variance == Variance.OUT_VARIANCE || variance == Variance.INVARIANT) + type + else + builtIns.anyNType + } + else -> error("Unknown type argument: ${render()}") + } + +private fun IrClass.getSimpleFunction(name: String): IrSimpleFunctionSymbol? = + findDeclaration { it.name.asString() == name }?.symbol + +// This is a version of getPropertyGetter which does not throw when applied to broken lazy classes, such as java.util.HashMap, +// which contains two "size" properties with different visibilities. +fun IrClass.getPropertyGetter(name: String): IrSimpleFunctionSymbol? = + declarations.filterIsInstance().firstOrNull { it.name.asString() == name && it.getter != null }?.getter?.symbol + ?: getSimpleFunction("") + +fun IrClass.getMethodWithoutArguments(name: String): IrSimpleFunction = + functions.first { function -> + function.name.asString() == name && function.dispatchReceiverParameter != null + && function.extensionReceiverParameter == null && function.valueParameters.isEmpty() + } diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt new file mode 100644 index 00000000000..3960d14d68f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializer.kt @@ -0,0 +1,449 @@ +/* + * 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.serializers + +import kotlinx.parcelize.WriteWith +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.isParcelize +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +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.TypeUtils +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 + +val RAW_VALUE_ANNOTATION_FQNAME = FqName("kotlinx.parcelize.RawValue") + +internal typealias TypeParcelerMapping = Pair + +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, + val frameMap: FrameMap + ) { + fun findParcelerClass(type: KotlinType): KotlinType? { + return typeParcelers.firstOrNull { it.first == type }?.second + } + } + + companion object { + private val WRITE_WITH_FQNAME = FqName(WriteWith::class.java.name) + + 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.annotations.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME) + + fun findCustomParcelerType(type: KotlinType): KotlinType? { + type.annotations.findAnnotation(WRITE_WITH_FQNAME)?.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 { + asmType.descriptor == "[I" + || asmType.descriptor == "[Z" + || asmType.descriptor == "[B" + || asmType.descriptor == "[C" + || asmType.descriptor == "[S" + || 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.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.annotations.hasAnnotation(RAW_VALUE_ANNOTATION_FQNAME)) + 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.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 + } + } +} + +internal fun KotlinType.isParcelable() = matchesFqNameWithSupertypes("android.os.Parcelable") + +private fun KotlinType.matchesFqName(fqName: String): Boolean { + return this.constructor.declarationDescriptor?.fqNameSafe?.asString() == fqName +} + +private fun KotlinType.matchesFqNameWithSupertypes(fqName: String): Boolean { + if (this.matchesFqName(fqName)) { + return true + } + + return TypeUtils.getAllSupertypes(this).any { it.matchesFqName(fqName) } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt new file mode 100644 index 00000000000..082b9f7e57f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelSerializers.kt @@ -0,0 +1,834 @@ +/* + * 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.serializers + +import kotlinx.parcelize.Parceler +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.types.KotlinType +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +internal val PARCEL_TYPE = Type.getObjectType("android/os/Parcel") +internal val PARCELER_TYPE = Type.getObjectType(Parceler::class.java.name.replace(".", "/")) + +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, + + 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: + */ + protected abstract fun doWriteValue(v: InstructionAdapter) + + /** + * Stack before: collection, parcel + * Stack after: + */ + 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, "", "(I)V", false) // -> list + } else { + v.invokespecial(collectionType.internalName, "", "()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, + + 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, "", "(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) + } +} + +internal 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, "", "()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) + } +} + +internal 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, + readMethod: Method +) : 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) + } +} + +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(val name: String, val signature: T) { + companion object { + operator fun invoke(name: String) = Method(name, null) + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelizeExtensionBase.kt b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelizeExtensionBase.kt new file mode 100644 index 00000000000..8407fd2b19b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/serializers/ParcelizeExtensionBase.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2019 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.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 CREATOR_NAME = Name.identifier("CREATOR") + 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 + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt b/plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt new file mode 100644 index 00000000000..01faab7f0ec --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt @@ -0,0 +1,61 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class PrimitiveTypes( + val boo: Boolean, val c: Char, val byt: Byte, val s: Short, + val i: Int, val f: Float, val l: Long, val d: Double, + + val nboo: Boolean?, val nc: Char?, val nbyt: Byte?, val ns: Short?, + val ni: Int?, val nf: Float?, val nl: Long?, val nd: Double?, + + val jboo: java.lang.Boolean, val jc: java.lang.Character, val jbyt: java.lang.Byte, val js: java.lang.Short, + val ji: java.lang.Integer, val jf: java.lang.Float, val jl: java.lang.Long, val jd: java.lang.Double, + + val njboo: java.lang.Boolean?, val njc: java.lang.Character?, val njbyt: java.lang.Byte?, val njs: java.lang.Short?, + val nji: java.lang.Integer?, val njf: java.lang.Float?, val njl: java.lang.Long?, val njd: java.lang.Double? +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = PrimitiveTypes( + true, '#', 3.toByte(), 10.toShort(), -300, -5.0f, Long.MAX_VALUE, 3.14, + true, '#', 3.toByte(), 10.toShort(), -300, -5.0f, Long.MAX_VALUE, 3.14, + true as java.lang.Boolean, '#' as java.lang.Character, + 3.toByte() as java.lang.Byte, 10.toShort() as java.lang.Short, + -300 as java.lang.Integer, -5.0f as java.lang.Float, + 10L as java.lang.Long, 3.14 as java.lang.Double, + true as java.lang.Boolean, '#' as java.lang.Character, + 3.toByte() as java.lang.Byte, 10.toShort() as java.lang.Short, + -300 as java.lang.Integer, -5.0f as java.lang.Float, + 10L as java.lang.Long, 3.14 as java.lang.Double + ) + val second = PrimitiveTypes( + false, '\n', Byte.MIN_VALUE, Short.MIN_VALUE, + Int.MIN_VALUE, Float.POSITIVE_INFINITY, Long.MAX_VALUE, Double.NEGATIVE_INFINITY, + null, null, null, null, null, null, null, null, + false as java.lang.Boolean, '\n' as java.lang.Character, + Byte.MIN_VALUE as java.lang.Byte, Short.MIN_VALUE as java.lang.Short, + Int.MIN_VALUE as java.lang.Integer, Float.POSITIVE_INFINITY as java.lang.Float, + java.lang.Long(Long.MAX_VALUE), java.lang.Double(Double.NEGATIVE_INFINITY), + null, null, null, null, null, null, null, null + ) + + first.writeToParcel(parcel, 0) + second.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + val second2 = readFromParcel(parcel) + + assert(first == first2) + assert(second == second2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt new file mode 100644 index 00000000000..819c9b48e9c --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt @@ -0,0 +1,39 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +data class Test(val a: Array) : Parcelable { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + + other as Test + + if (!Arrays.equals(a, other.a)) return false + + return true + } + + override fun hashCode() = Arrays.hashCode(a) +} + +fun box() = parcelTest { parcel -> + val first = Test(arrayOf("A", "B")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/arrays.kt b/plugins/parcelize/parcelize-compiler/testData/box/arrays.kt new file mode 100644 index 00000000000..d0a52705cae --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/arrays.kt @@ -0,0 +1,77 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +data class Test( + val a: Array, + val b: Array, + val c: IntArray, + val d: CharArray?, + val e: Array, + val f: Array>, + val g: List>, + val h: Array? +) : Parcelable { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + + other as Test + + if (!Arrays.equals(a, other.a)) return false + if (!Arrays.equals(b, other.b)) return false + if (!Arrays.equals(c, other.c)) return false + if (!Arrays.equals(d, other.d)) return false + if (!Arrays.deepEquals(e, other.e)) return false + if (!Arrays.equals(f, other.f)) return false + + if (g.size != other.g.size) return false + if (!g.zip(other.g).all { (f, s) -> Arrays.equals(f, s) }) return false + + if (!Arrays.equals(h, other.h)) return false + + return true + } + + override fun hashCode(): Int { + var result = Arrays.hashCode(a) + result = 31 * result + Arrays.hashCode(b) + result = 31 * result + Arrays.hashCode(c) + result = 31 * result + (d?.let { Arrays.hashCode(it) } ?: 0) + result = 31 * result + Arrays.hashCode(e) + result = 31 * result + Arrays.hashCode(f) + result = 31 * result + g.hashCode() + result = 31 * result + (h?.let { Arrays.hashCode(it) } ?: 0) + return result + } +} + +fun box() = parcelTest { parcel -> + val first = Test( + a = arrayOf("A", "B", "C"), + b = arrayOf("A", null, "B"), + c = intArrayOf(1, 2, 3), + d = null, + e = arrayOf(intArrayOf(2, 4, 1), intArrayOf(10, 20)), + f = arrayOf(listOf("A"), listOf("B", "C")), + g = listOf(arrayOf("Z", "X"), arrayOf()), + h = arrayOf("") + ) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/binder.kt b/plugins/parcelize/parcelize-compiler/testData/box/binder.kt new file mode 100644 index 00000000000..880f18603e4 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/binder.kt @@ -0,0 +1,41 @@ +// IGNORE_BACKEND: JVM +// See KT-38103 +// There is no such thing as a readStrongInterface method to deserialize arbitrary IIinterface implementations +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Binder +import android.os.IBinder +import android.os.IInterface +import android.os.Parcel +import android.os.Parcelable +import java.io.Serializable + +class MockBinder : Binder(), Serializable + +@Parcelize +class MockIInterface : IInterface, Parcelable { + override fun asBinder(): IBinder = MockBinder() +} + +@Parcelize +class ServiceContainer( + val binder: MockBinder, + val iinterface: MockIInterface, + val binderArray: Array, + val binderList: List +) : Parcelable + +fun box() = parcelTest { parcel -> + val test = ServiceContainer(MockBinder(), MockIInterface(), arrayOf(MockBinder()), listOf(MockBinder())) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt b/plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt new file mode 100644 index 00000000000..359d93f5835 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt @@ -0,0 +1,42 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class BoxedTypes( + val boo: java.lang.Boolean, + val c: java.lang.Character, + val byt: java.lang.Byte, + val s: java.lang.Short, + val i: java.lang.Integer, + val f: java.lang.Float, + val l: java.lang.Long, + val d: java.lang.Double +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = BoxedTypes( + true as java.lang.Boolean, + '#' as java.lang.Character, + 3.toByte() as java.lang.Byte, + 10.toShort() as java.lang.Short, + -300 as java.lang.Integer, + -5.0f as java.lang.Float, + Long.MAX_VALUE as java.lang.Long, + 3.14 as java.lang.Double) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/bundle.kt b/plugins/parcelize/parcelize-compiler/testData/box/bundle.kt new file mode 100644 index 00000000000..c1b83d9f8e5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/bundle.kt @@ -0,0 +1,35 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.os.Bundle + +@Parcelize +data class User(val a: Bundle) : Parcelable + +fun box() = parcelTest { parcel -> + val test = User(Bundle().apply { putChar("A", 'c'); putByte("B", 40.toByte()); putString("C", "ABC") }) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(compareBundles(test.a, test2.a)) +} + +private fun compareBundles(first: Bundle, second: Bundle): Boolean { + if (first.size() != second.size()) return false + + for (key in first.keySet()) { + if (first.get(key) != second.get(key)) return false + } + + return true +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt b/plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt new file mode 100644 index 00000000000..79456cbc108 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt @@ -0,0 +1,26 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.text.SpannableString + +@Parcelize +data class Test(val simple: CharSequence, val spanned: CharSequence) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test("John", SpannableString("Smith")) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test.simple.toString() == test2.simple.toString()) + assert(test.spanned.toString() == test2.spanned.toString()) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt b/plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt new file mode 100644 index 00000000000..a7639a1b7c5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt @@ -0,0 +1,40 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable { + companion object : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(firstName) + parcel.writeString(secondName) + } + + override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), 0) + + override fun newArray(size: Int) = arrayOfNulls(size) as Array + } +} + +fun box() = parcelTest { parcel -> + val user = User("John", "Smith", 20) + val user2 = User("Joe", "Bloggs", 30) + val array = arrayOf(user, user2) + parcel.writeTypedArray(array, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator + val result = parcel.createTypedArray(creator) + + assert(result.size == 2) + assert(result[0].firstName == user.firstName) + assert(result[1].firstName == user2.firstName) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt b/plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt new file mode 100644 index 00000000000..b73a8609681 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt @@ -0,0 +1,38 @@ +// IGNORE_BACKEND: JVM +// See KT-38105 +// Throws IllegalAccessError, since the code tries to access the private companion field directly from the generated User$Creator class. +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +data class User(val name: String, val age: Int) + +@Parcelize +data class UserParcelable(val user: User) : Parcelable { + private companion object : Parceler { + override fun UserParcelable.write(parcel: Parcel, flags: Int) { + parcel.writeString(user.name) + } + + override fun create(parcel: Parcel) = UserParcelable(User(parcel.readString(), 0)) + } +} + +fun box() = parcelTest { parcel -> + val userParcelable = UserParcelable(User("John", 20)) + userParcelable.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val userParcelable2 = readFromParcel(parcel) + + assert(userParcelable.user.name == userParcelable2.user.name) + assert(userParcelable2.user.age == 0) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt b/plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt new file mode 100644 index 00000000000..7fb8fe35caf --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt @@ -0,0 +1,44 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseBooleanArray + +object Parceler1 : Parceler { + override fun create(parcel: Parcel) = -parcel.readInt() + + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeInt(this) + } +} + +object Parceler2 : Parceler { + override fun create(parcel: Parcel) = parcel.readString().length + + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeString("Abc") + } +} + +@Parcelize +@TypeParceler +data class Ints(val a: Int, @TypeParceler val b: Int, val c: @WriteWith Int) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Ints(1, 1, 1) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test2.a == -test.a) + assert(test2.b == -test.b) + assert(test2.c == 3) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt new file mode 100644 index 00000000000..c690d172450 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt @@ -0,0 +1,56 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +object Parceler1 : Parceler { + override fun create(parcel: Parcel) = -parcel.readInt() + + override fun Int.write(parcel: Parcel, flags: Int) { + parcel.writeInt(this) + } +} + +object Parceler2 : Parceler { + override fun create(parcel: Parcel) = parcel.readString().length.toLong() + + override fun Long.write(parcel: Parcel, flags: Int) { + parcel.writeString("Abc") + } +} + +@Parcelize +data class Test( + val a: Int, + @TypeParceler val b: Int, + @TypeParceler val c: Long, + @TypeParceler val d: List, + @TypeParceler val e: LongArray +) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test(5, 5, 50L, listOf(1, 2, 3), longArrayOf(3, 2, 1)) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + println(test.toString()) + println(test2.toString()) + + with (test) { + assert(a == 5 && b == 5 && c == 50L && d == listOf(1, 2, 3) && Arrays.equals(e, longArrayOf(3, 2, 1))) + } + + with (test2) { + assert(a == 5 && b == -5 && c == 3L && d == listOf(-1, -2, -3) && Arrays.equals(e, longArrayOf(3, 3, 3))) + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt new file mode 100644 index 00000000000..0935cc90112 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt @@ -0,0 +1,49 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +object Parceler1 : Parceler { + override fun create(parcel: Parcel) = parcel.readInt().toString() + + override fun String.write(parcel: Parcel, flags: Int) { + parcel.writeInt(length) + } +} + +typealias Parceler2 = Parceler1 + +object Parceler3 : Parceler { + override fun create(parcel: Parcel) = parcel.readString().toUpperCase() + + override fun String.write(parcel: Parcel, flags: Int) { + parcel.writeString(this) + } +} + +@Parcelize +@TypeParceler +data class Test( + val a: String, + @TypeParceler val b: String, + @TypeParceler val c: CharSequence, + val d: @WriteWith String +) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test("Abc", "Abc", "Abc", "Abc") + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test.a == "Abc" && test.b == "Abc" && test.c == "Abc" && test.d == "Abc") + assert(test2.a == "3" && test2.b == "3" && test2.c == "Abc" && test2.d == "ABC") +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt new file mode 100644 index 00000000000..bc8f95b7a3b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt @@ -0,0 +1,55 @@ +// IGNORE_BACKEND: JVM +// See KT-38107 +// The JVM backend is missing support for custom parcelers in List +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +object Parceler1 : Parceler { + override fun create(parcel: Parcel) = parcel.readInt().toString() + + override fun String.write(parcel: Parcel, flags: Int) { + parcel.writeInt(length) + } +} + +object Parceler2 : Parceler> { + override fun create(parcel: Parcel) = listOf(parcel.readString()) + + override fun List.write(parcel: Parcel, flags: Int) { + parcel.writeString(this.joinToString(",")) + } +} + +@Parcelize +data class Test( + val a: String, + val b: @WriteWith String, + val c: List<@WriteWith String>, + val d: @WriteWith List, + val e: @WriteWith List<@WriteWith String> +) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test("Abc", "Abc", listOf("A", "bc"), listOf("A", "bc"), listOf("A", "bc")) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + with (test) { + assert(a == "Abc" && b == "Abc" && c == listOf("A", "bc") && d == listOf("A", "bc") && e == listOf("A", "bc")) + } + + with (test2) { + assert(a == "Abc" && b == "3" && c == listOf("1", "2") && d == listOf("A,bc") && e == listOf("A,bc")) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt new file mode 100644 index 00000000000..2ba8456562b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt @@ -0,0 +1,38 @@ +// IGNORE_BACKEND: JVM +// See KT-38105 +// Throws IllegalAccessError, since the code tries to access the private companion field directly from the generated User$Creator class. +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable { + private companion object : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(firstName) + parcel.writeString(secondName) + } + + override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), 0) + } +} + +fun box() = parcelTest { parcel -> + val user = User("John", "Smith", 20) + user.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val user2 = readFromParcel(parcel) + + assert(user.firstName == user2.firstName) + assert(user.secondName == user2.secondName) + assert(user2.age == 0) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt b/plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt new file mode 100644 index 00000000000..b9d74fcd1b3 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt @@ -0,0 +1,38 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +enum class Color : Parcelable { BLACK, WHITE } + +@Parcelize +object Obj : Parcelable + +fun box() = parcelTest { parcel -> + val black = Color.BLACK + val obj = Obj + + black.writeToParcel(parcel, 0) + obj.writeToParcel(parcel, 0) + + println(black) + println(obj) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val black2 = readFromParcel(parcel) + val obj2 = readFromParcel(parcel) + + println(black2) + println(obj2) + + assert(black2 == black) + assert(obj2 != null) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/enums.kt b/plugins/parcelize/parcelize-compiler/testData/box/enums.kt new file mode 100644 index 00000000000..41217b43a3b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/enums.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +enum class Color { + BLACK, WHITE +} + +@Parcelize +data class Test(val name: String, val color: Color) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test("John", Color.WHITE) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + assert(test == test2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt b/plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt new file mode 100644 index 00000000000..8571e465627 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt @@ -0,0 +1,25 @@ +// IGNORE_BACKEND: JVM +// Parcel.readException throws the exception it reads and only supports a small number of exception types. +// If we have to parcel an exception we should instead use read/writeSerializable (compare KT-31830) +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +class ExceptionContainer(val exn: Exception) : Parcelable + +fun box() = parcelTest { parcel -> + val test = ExceptionContainer(java.lang.RuntimeException("Don't throw me.")) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/functions.kt b/plugins/parcelize/parcelize-compiler/testData/box/functions.kt new file mode 100644 index 00000000000..e99bce084a5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/functions.kt @@ -0,0 +1,24 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test(val callback: () -> Int = { 0 }, val suspendCallback: suspend () -> Int = { 0 }) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test({ 1 }, { 1 }) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test.callback() == 1) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/intArray.kt b/plugins/parcelize/parcelize-compiler/testData/box/intArray.kt new file mode 100644 index 00000000000..7d0e0d1a9d8 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/intArray.kt @@ -0,0 +1,39 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +data class Film(val genres: Array) : Parcelable { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Film + + if (!Arrays.equals(genres, other.genres)) return false + + return true + } + + override fun hashCode(): Int { + return Arrays.hashCode(genres) + } +} + +fun box() = parcelTest { parcel -> + val film = Film(arrayOf(3, 5, 7)) + film.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val film2 = readFromParcel(parcel) + assert(film == film2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt b/plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt new file mode 100644 index 00000000000..2f8f4da2add --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt @@ -0,0 +1,33 @@ +// This issue affects AIDL generated files, as reported in KT-25807 +// WITH_RUNTIME +// FILE: J.java +import android.os.Parcel; +import test.K; + +public class J { + public static K readParcel(Parcel parcel) { + return K.CREATOR.createFromParcel(parcel); + } +} + +// FILE: test.kt +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class K(val x: Int) : Parcelable + +fun box() = parcelTest { parcel -> + val first = K(0) + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = J.readParcel(parcel) + assert(first == second) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt new file mode 100644 index 00000000000..62896cd193a --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt @@ -0,0 +1,35 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.io.Serializable + +class JHelp(var j1: String) { + val j2 = 9 +} + +@Parcelize +class J(val j: @RawValue JHelp) : Parcelable + +fun box() = parcelTest { parcel -> + val test = J(JHelp("A")) + + var exceptionCaught = false + try { + test.writeToParcel(parcel, 0) + } catch (e: RuntimeException) { + if (e.message!!.contains("Parcel: unable to marshal value test.JHelp")) { + exceptionCaught = true + } else { + throw e + } + } + + if (!exceptionCaught) { + error("Exception should be thrown") + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt new file mode 100644 index 00000000000..448443dffe9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt @@ -0,0 +1,33 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.io.Serializable + +interface IJHelp { + val j1: String +} + +class JHelp(override var j1: String): IJHelp, Serializable { + val j2 = 9 +} + +@Parcelize +class J(val j: @RawValue JHelp) : Parcelable + +fun box() = parcelTest { parcel -> + val test = J(JHelp("A")) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test.j.j1 == test2.j.j1) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt new file mode 100644 index 00000000000..7cbcfd92ad4 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt @@ -0,0 +1,29 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.io.Serializable + +class MHelp(var m1: String): Serializable { + val m2 = 9 +} + +@Parcelize +class M(val m: @RawValue MHelp) : Parcelable + +fun box() = parcelTest { parcel -> + val test = M(MHelp("A")) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(test.m.m1 == test2.m.m1) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt new file mode 100644 index 00000000000..037ae43a490 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt @@ -0,0 +1,40 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +data class Test(val a: LongArray, val b: List) : Parcelable { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other?.javaClass != javaClass) return false + + other as Test + + if (!Arrays.equals(a, other.a)) return false + if (b != other.b) return false + + return true + } + + override fun hashCode() = Arrays.hashCode(a) +} + +fun box() = parcelTest { parcel -> + val first = Test(longArrayOf(1, 2, 3, 4, 5), listOf(1, 2, 3, 4)) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt new file mode 100644 index 00000000000..b24e291ac5d --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt @@ -0,0 +1,49 @@ +// IGNORE_BACKEND: JVM +// See KT-38106 +// This feature regressed with the fix for KT-22576 +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +enum class ParcelableEnum : Parcelable { + ONE, TWO, THREE; + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(ordinal) + } + + override fun describeContents() = 0 + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = ParcelableEnum.ONE + override fun newArray(size: Int) = arrayOfNulls(size) + } + } +} + +@Parcelize +class Test(val parcelableEnum: ParcelableEnum): Parcelable + +fun box() = parcelTest { parcel -> + val first = Test(ParcelableEnum.THREE) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first.parcelableEnum == ParcelableEnum.THREE) + assert(first2.parcelableEnum == ParcelableEnum.ONE) + assert(first != first2) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt new file mode 100644 index 00000000000..13f583c220c --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt @@ -0,0 +1,30 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +fun box() = doTest { creator -> + assert(creator.newArray(5) != null) +} + +fun doTest(work: (Parcelable.Creator) -> Unit): String { + val dummy = DummyParcelable(42) + + val clazz = dummy.javaClass + val field = clazz.getDeclaredField("CREATOR") + val creator = field.get(dummy) as Parcelable.Creator + + val parcel = Parcel.obtain() + dummy.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + + work(creator) + return "OK" +} + +@Parcelize +data class DummyParcelable(val int: Int): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt new file mode 100644 index 00000000000..bd55fd5ea5c --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt @@ -0,0 +1,29 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +class User : Parcelable + +@Parcelize +class User2() : Parcelable + +fun box() = parcelTest { parcel -> + val user = User() + val user2 = User2() + + user.writeToParcel(parcel, 0) + user2.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + readFromParcel(parcel) + readFromParcel(parcel) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt new file mode 100644 index 00000000000..8852244e767 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt @@ -0,0 +1,39 @@ +// IGNORE_BACKEND: JVM +// Fails with a VerifyError in Foo.writeToParcel +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseArray + +@Parcelize +data class PInt(val x: Int) : Parcelable + +@Parcelize +data class Foo(val values: SparseArray>) : Parcelable + +fun box() = parcelTest { parcel -> + val pint = PInt(0) + val sarray = SparseArray() + sarray.put(0, pint) + val sarray2 = SparseArray>() + sarray2.put(1, sarray) + val foo = Foo(sarray2) + + foo.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val foo2 = readFromParcel(parcel) + assert(foo2.values.size() == 1) + assert(foo2.values.get(1) != null) // SparseArray.contains was only added in Android R + assert(foo2.values.get(1).size() == 1) + assert(foo2.values.get(1).get(0) != null) + assert(foo2.values.get(1).get(0) == pint) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt new file mode 100644 index 00000000000..0113db9fc81 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt @@ -0,0 +1,23 @@ +// IGNORE_BACKEND: JVM +// StackOverflowError caused by infinite loop in MyObject.writeToParcel +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +private object MyObject : Parcelable + +fun box() = parcelTest { parcel -> + MyObject.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + readFromParcel(parcel) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt b/plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt new file mode 100644 index 00000000000..801a049cf44 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt @@ -0,0 +1,21 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize class TestParcel : Parcelable { + companion object : Parceler { + override fun create(parcel: Parcel): TestParcel { + return TestParcel() + } + override fun TestParcel.write(parcel: Parcel, flags: Int) {} + } +} + +fun box() = parcelTest { parcel -> + TestParcel() +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt b/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt new file mode 100644 index 00000000000..8883420c0f0 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt @@ -0,0 +1,55 @@ +// WITH_RUNTIME +// FULL_JDK + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.* + +@Parcelize +data class Test( + val a: List, + val b: MutableList, + val c: ArrayList, + val d: LinkedList, + val e: Set, + val f: MutableSet, + val g: TreeSet, + val h: HashSet, + val i: LinkedHashSet, + val j: NavigableSet, + val k: SortedSet +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test( + a = listOf("A"), + b = mutableListOf("B"), + c = ArrayList().apply { this += "C" }, + d = LinkedList().apply { this += "D" }, + e = setOf("E"), + f = mutableSetOf("F"), + g = TreeSet().apply { this += "G" }, + h = HashSet().apply { this += "H" }, + i = LinkedHashSet().apply { this += "I" }, + j = TreeSet().apply { this += "J" }, + k = TreeSet().apply { this += "K" } + ) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) + assert((first.d as LinkedList<*>).size == 1) + assert((first2.h as HashSet<*>).size == 1) + assert(first2.j is NavigableSet<*>) + assert(first2.k is SortedSet<*>) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt new file mode 100644 index 00000000000..5029aa8ec54 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt @@ -0,0 +1,25 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test(val a: List) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test(listOf("A", "B")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/lists.kt b/plugins/parcelize/parcelize-compiler/testData/box/lists.kt new file mode 100644 index 00000000000..3ddbd31982a --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/lists.kt @@ -0,0 +1,42 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test( + val a: List, + val b: List, + val c: List, + val d: List, + val e: List?>, + val f: List>> +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test( + a = listOf("A", "B"), + b = listOf("A", null, "C"), + c = listOf(1, 2, 3), + d = listOf(1, null, 5), + e = listOf(listOf("A", "B"), listOf(), null), + f = listOf(listOf(listOf(1, 2), listOf(3)), listOf(listOf(5, 3))) + ) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) + + assert(first2.a == listOf("A", "B")) + assert(first2.b.size == 3) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt b/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt new file mode 100644 index 00000000000..d714822ef81 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt @@ -0,0 +1,47 @@ +// WITH_RUNTIME +// FULL_JDK + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.* + +@Parcelize +data class Test( + val a: Map, + val b: MutableMap, + val c: HashMap, + val d: LinkedHashMap, + val e: TreeMap, + val f: SortedMap, + val g: NavigableMap +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test( + a = mapOf("A" to "B"), + b = mutableMapOf("A" to "B"), + c = HashMap().apply { put("A", "B") }, + d = LinkedHashMap().apply { put("A", "B") }, + e = TreeMap().apply { put("A", "B") }, + f = TreeMap().apply { put("A", "B") }, + g = TreeMap().apply { put("A", "B") } + ) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) + assert((first.c as HashMap<*, *>).size == 1) + assert((first2.e as TreeMap<*, *>).size == 1) + assert(first2.f is SortedMap<*, *>) + assert(first2.g is NavigableMap<*, *>) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt new file mode 100644 index 00000000000..a6ca24bb230 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt @@ -0,0 +1,25 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test(val a: Map) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test(mapOf("A" to "B", "C" to "D")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/maps.kt b/plugins/parcelize/parcelize-compiler/testData/box/maps.kt new file mode 100644 index 00000000000..b758d112ab4 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/maps.kt @@ -0,0 +1,41 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test( + val a: Map, + val b: Map, + val c: Map, + val d: Map>, + val e: Map>, + val f: Map, + val g: Map>> +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test( + a = mapOf("A" to "B", "C" to "D"), + b = mapOf("A" to "B", null to "D", "E" to "F"), + c = mapOf("A" to null, "C" to "D"), + d = mapOf("A" to mapOf(1 to "", 2 to "x")), + e = mapOf(1 to listOf("", ""), null to listOf()), + f = mapOf(true to false, false to true), + g = mapOf("A" to mapOf("B" to mapOf("C" to "D", "E" to "F"))) + ) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + + assert(first == first2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt b/plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt new file mode 100644 index 00000000000..8add9ddd3c5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt @@ -0,0 +1,26 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: Array>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(arrayOf(arrayOf(0, 1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + assert(Arrays.equals(second.data[0], arrayOf(0, 1))) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt b/plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt new file mode 100644 index 00000000000..def43a46c90 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt @@ -0,0 +1,26 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: List>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(listOf(arrayOf(0, 1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + assert(Arrays.equals(second.data[0], arrayOf(0, 1))) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt b/plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt new file mode 100644 index 00000000000..fa2bfe03d93 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt @@ -0,0 +1,28 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import java.util.Arrays + +@Parcelize +class Data(val data: Map, Array>) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Data(mapOf(arrayOf(0) to arrayOf(1))) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size == 1) + val entry = second.data.entries.single() + assert(Arrays.equals(entry.key, arrayOf(0))) + assert(Arrays.equals(entry.value, arrayOf(1))) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt b/plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt new file mode 100644 index 00000000000..75e476f46a0 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt @@ -0,0 +1,34 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +// Starts with A, should be loaded before other classes +abstract class AParcelable : Parcelable + +@Parcelize +data class P1(val a: String) : AParcelable() + +sealed class Sealed : AParcelable() + +@Parcelize +data class Sealed1(val a: Int) : Sealed() + +@Parcelize +data class Test(val a: P1, val b: AParcelable, val c: Sealed, val d: Sealed1) : Parcelable + +fun box() = parcelTest { parcel -> + val test = Test(P1(""), P1("My"), Sealed1(1), Sealed1(5)) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + assert(test == test2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt b/plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt new file mode 100644 index 00000000000..c6bb3c71b7e --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt @@ -0,0 +1,30 @@ +// WITH_RUNTIME +// IGNORE_BACKEND: JVM + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseArray +import java.util.Arrays + +@Parcelize +class Data(val data: SparseArray>) : Parcelable + +fun box() = parcelTest { parcel -> + var array = SparseArray>() + array.append(0, arrayOf(0, 1)) + val first = Data(array) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + assert(second.data.size() == 1) + assert(Arrays.equals(second.data.get(0), arrayOf(0, 1))) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/newArray.kt b/plugins/parcelize/parcelize-compiler/testData/box/newArray.kt new file mode 100644 index 00000000000..4ed9cc868ff --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/newArray.kt @@ -0,0 +1,29 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable + +fun box() = parcelTest { parcel -> + val user = User("John", "Smith", 20) + val user2 = User("Joe", "Bloggs", 30) + val array = arrayOf(user, user2) + parcel.writeTypedArray(array, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val creator = User::class.java.getDeclaredField("CREATOR").get(null) as Parcelable.Creator + val result = parcel.createTypedArray(creator) + + assert(result.size == 2) + assert(result[0].firstName == user.firstName) + assert(result[1].firstName == user2.firstName) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt b/plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt new file mode 100644 index 00000000000..e165b3f8ab2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt @@ -0,0 +1,38 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Foo(val a: String) : Parcelable + +@Parcelize +data class Test( + val str1: String, + val str2: String?, + val int1: Int, + val int2: Int?, + val foo: Foo? +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test("John", "Smith", 20, 30, Foo("a")) + val second = Test("A", null, 20, null, null) + + first.writeToParcel(parcel, 0) + second.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + val second2 = readFromParcel(parcel) + + assert(first == first2) + assert(second == second2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt b/plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt new file mode 100644 index 00000000000..7b777e7ccda --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt @@ -0,0 +1,29 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class Test(val a: String?) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Test("John") + val second = Test(null) + + first.writeToParcel(parcel, 0) + second.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + val second2 = readFromParcel(parcel) + + assert(first == first2) + assert(second == second2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/objects.kt b/plugins/parcelize/parcelize-compiler/testData/box/objects.kt new file mode 100644 index 00000000000..9cd0643c5f5 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/objects.kt @@ -0,0 +1,31 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +object Obj1 { + object Obj2 +} + +@Parcelize +data class Test(val o1: Obj1, val o2: Obj1.Obj2, val com: Com) : Parcelable { + companion object Com { + + } +} + +fun box() = parcelTest { parcel -> + val test = Test(Obj1, Obj1.Obj2, Test.Com) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + assert(test == test2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt b/plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt new file mode 100644 index 00000000000..e07e26245c2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +open class Base(val a: String) : Parcelable + +@Parcelize +class Inh(var b: Int) : Base(""), Parcelable + +fun box(): String { + Inh(0) + return "OK" +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt b/plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt new file mode 100644 index 00000000000..a7bba12cb56 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt @@ -0,0 +1,41 @@ +// IGNORE_BACKEND: JVM +// See KT-38104 +// The support for PersistableBundles is broken on JVM. +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.os.BaseBundle +import android.os.PersistableBundle + +@Parcelize +data class User(val a: PersistableBundle) : Parcelable + +fun box() = parcelTest { parcel -> + val test = User( + PersistableBundle().apply { putLong("A", 1L) } + ) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(compareBundles(test.a, test2.a)) +} + +private fun compareBundles(first: BaseBundle, second: BaseBundle): Boolean { + if (first.size() != second.size()) return false + + for (key in first.keySet()) { + if (first.get(key) != second.get(key)) return false + } + + return true +} diff --git a/plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt b/plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt new file mode 100644 index 00000000000..9bcc086d4e7 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt @@ -0,0 +1,39 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +data class PrimitiveTypes( + val boo: Boolean, + val c: Char, + val byt: Byte, + val s: Short, + val i: Int, + val f: Float, + val l: Long, + val d: Double +) : Parcelable + +fun box() = parcelTest { parcel -> + val first = PrimitiveTypes(true, '#', 3.toByte(), 10.toShort(), -300, -5.0f, Long.MAX_VALUE, 3.14) + val second = PrimitiveTypes(false, '\n', Byte.MIN_VALUE, Short.MIN_VALUE, Int.MIN_VALUE, Float.POSITIVE_INFINITY, + Long.MAX_VALUE, Double.NEGATIVE_INFINITY) + + first.writeToParcel(parcel, 0) + second.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val first2 = readFromParcel(parcel) + val second2 = readFromParcel(parcel) + + assert(first == first2) + assert(second == second2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt b/plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt new file mode 100644 index 00000000000..eae381fa5c9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt @@ -0,0 +1,33 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +sealed class Foo : Parcelable { + @Parcelize + data class A(val x: Int) : Foo() + + @Parcelize + data class B (val x: String) : Foo() +} + +@Parcelize +data class Bar(val a: Foo) : Parcelable + +fun box() = parcelTest { parcel -> + val first = Bar(Foo.B("OK")) + + first.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val second = readFromParcel(parcel) + + assert(first == second) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/simple.kt b/plugins/parcelize/parcelize-compiler/testData/box/simple.kt new file mode 100644 index 00000000000..4462e898ad0 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/simple.kt @@ -0,0 +1,25 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +annotation class SerializableLike + +@Parcelize @SerializableLike +data class User(val firstName: String, val secondName: String, val age: Int) : Parcelable + +fun box() = parcelTest { parcel -> + val user = User("John", "Smith", 20) + user.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val user2 = readFromParcel(parcel) + assert(user == user2) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt b/plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt new file mode 100644 index 00000000000..4b2673129a1 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt @@ -0,0 +1,71 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.* + +@Parcelize +data class Data(val a: String, val b: String) : Parcelable + +@Parcelize +data class User(val a: SparseIntArray, val b: SparseLongArray, val c: SparseArray) : Parcelable + +fun box() = parcelTest { parcel -> + val user = User( + a = SparseIntArray().apply { put(1, 5); put(100, -1); put(1000, 0) }, + b = SparseLongArray().apply { put(3, 2); put(2, 3); put(10, 10) }, + c = SparseArray().apply { put(1, Data("A", "B")); put(10, Data("C", "D")); put(105, Data("E", "")) } + ) + + user.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val user2 = readFromParcel(parcel) + + assert(compareSparseIntArrays(user.a, user2.a)) + assert(compareSparseLongArrays(user.b, user2.b)) + assert(compareSparseArrays(user.c, user2.c)) +} + +private fun compareSparseIntArrays(first: SparseIntArray, second: SparseIntArray): Boolean { + if (first === second) return true + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} + +private fun compareSparseLongArrays(first: SparseLongArray, second: SparseLongArray): Boolean { + if (first === second) return true + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} + +private fun compareSparseArrays(first: SparseArray<*>, second: SparseArray<*>): Boolean { + if (first === second) return true + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt b/plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt new file mode 100644 index 00000000000..c97e04aa8dc --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt @@ -0,0 +1,37 @@ +// WITH_RUNTIME + +@file:JvmName("TestKt") +package test + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseBooleanArray + +@Parcelize +data class User(val a: SparseBooleanArray) : Parcelable + +fun box() = parcelTest { parcel -> + val test = User(SparseBooleanArray().apply { put(1, false); put(5, true); put(1000, false) }) + test.writeToParcel(parcel, 0) + + val bytes = parcel.marshall() + parcel.unmarshall(bytes, 0, bytes.size) + parcel.setDataPosition(0) + + val test2 = readFromParcel(parcel) + + assert(compareSparseBooleanArrays(test.a, test2.a)) +} + +private fun compareSparseBooleanArrays(first: SparseBooleanArray, second: SparseBooleanArray): Boolean { + if (first === second) return true + if (first.size() != second.size()) return false + + for (i in 0 until first.size()) { + if (first.keyAt(i) != second.keyAt(i)) return false + if (first.valueAt(i) != second.valueAt(i)) return false + } + + return true +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/boxLib.kt b/plugins/parcelize/parcelize-compiler/testData/boxLib.kt new file mode 100644 index 00000000000..b24f2513a9b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/boxLib.kt @@ -0,0 +1,18 @@ +package test + +import android.os.Parcel + +fun parcelTest(block: (Parcel) -> Unit): String { + val parcel = Parcel.obtain() + try { + block(parcel) + return "OK" + } finally { + parcel.recycle() + } +} + +inline fun readFromParcel(parcel: Parcel): T { + val creator = T::class.java.getDeclaredField("CREATOR").get(null) + return creator::class.java.getDeclaredMethod("createFromParcel", Parcel::class.java).invoke(creator, parcel) as T +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.ir.txt new file mode 100644 index 00000000000..6c8d7c9c817 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.ir.txt @@ -0,0 +1,89 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readStrongBinder, ()Landroid/os/IBinder;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createBinderArray, ()[Landroid/os/IBinder;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createBinderArrayList, ()Ljava/util/ArrayList;) + CHECKCAST + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createBinderArrayList, ()Ljava/util/ArrayList;) + INVOKESPECIAL (User, , (Landroid/os/IBinder;[Landroid/os/IBinder;Ljava/util/List;Ljava/util/ArrayList;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.os.IBinder binder + + private final android.os.IBinder[] binderArray + + private final java.util.ArrayList binderArrayList + + private final java.util.List binderList + + static void () + + public void (android.os.IBinder binder, android.os.IBinder[] binderArray, java.util.List binderList, java.util.ArrayList binderArrayList) + + public int describeContents() + + public final android.os.IBinder getBinder() + + public final android.os.IBinder[] getBinderArray() + + public final java.util.ArrayList getBinderArrayList() + + public final java.util.List getBinderList() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binder, Landroid/os/IBinder;) + INVOKEVIRTUAL (android/os/Parcel, writeStrongBinder, (Landroid/os/IBinder;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderArray, [Landroid/os/IBinder;) + INVOKEVIRTUAL (android/os/Parcel, writeBinderArray, ([Landroid/os/IBinder;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderList, Ljava/util/List;) + INVOKEVIRTUAL (android/os/Parcel, writeBinderList, (Ljava/util/List;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderArrayList, Ljava/util/ArrayList;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeBinderList, (Ljava/util/List;)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt new file mode 100644 index 00000000000..013980147b3 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt @@ -0,0 +1,17 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable +import android.os.IBinder +import android.os.IInterface + +@Parcelize +class User( + val binder: IBinder, + val binderArray: Array, + val binderList: List, + val binderArrayList: ArrayList // should be serialized using our strategy, not using Parcel.writeBinderList() + // There is no readStrongInterface method in Parcel. + // val intf: IInterface? +) : Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.txt new file mode 100644 index 00000000000..48a3345f324 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.txt @@ -0,0 +1,126 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readStrongBinder, ()Landroid/os/IBinder;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createBinderArray, ()[Landroid/os/IBinder;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, createBinderArrayList, ()Ljava/util/ArrayList;) + CHECKCAST + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + ISTORE (2) + NEW + DUP + ILOAD (2) + INVOKESPECIAL (java/util/ArrayList, , (I)V) + ILOAD (2) + LABEL (L2) + DUP_X1 + IFEQ (L3) + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readStrongBinder, ()Landroid/os/IBinder;) + INVOKEVIRTUAL (java/util/ArrayList, add, (Ljava/lang/Object;)Z) + POP + SWAP + LDC (-1) + IADD + GOTO (L2) + LABEL (L3) + SWAP + POP + INVOKESPECIAL (User, , (Landroid/os/IBinder;[Landroid/os/IBinder;Ljava/util/List;Ljava/util/ArrayList;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + } + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.os.IBinder binder + + private final android.os.IBinder[] binderArray + + private final java.util.ArrayList binderArrayList + + private final java.util.List binderList + + static void () + + public void (android.os.IBinder binder, android.os.IBinder[] binderArray, java.util.List binderList, java.util.ArrayList binderArrayList) + + public int describeContents() + + public final android.os.IBinder getBinder() + + public final android.os.IBinder[] getBinderArray() + + public final java.util.ArrayList getBinderArrayList() + + public final java.util.List getBinderList() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binder, Landroid/os/IBinder;) + INVOKEVIRTUAL (android/os/Parcel, writeStrongBinder, (Landroid/os/IBinder;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderArray, [Landroid/os/IBinder;) + INVOKEVIRTUAL (android/os/Parcel, writeBinderArray, ([Landroid/os/IBinder;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderList, Ljava/util/List;) + INVOKEVIRTUAL (android/os/Parcel, writeBinderList, (Ljava/util/List;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (binderArrayList, Ljava/util/ArrayList;) + DUP_X1 + INVOKEINTERFACE (java/util/Collection, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;) + LABEL (L1) + DUP + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L2) + DUP + ALOAD (1) + SWAP + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeStrongBinder, (Landroid/os/IBinder;)V) + GOTO (L1) + LABEL (L2) + POP + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.ir.txt new file mode 100644 index 00000000000..73d3d5804bc --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.ir.txt @@ -0,0 +1,45 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) + + public int describeContents() { + LABEL (L0) + LINENUMBER (9) + BIPUSH (100) + IRETURN + LABEL (L1) + } + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public final boolean isProUser() + + public void writeToParcel(android.os.Parcel out, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt new file mode 100644 index 00000000000..97d047ff626 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt @@ -0,0 +1,10 @@ +// CURIOUS_ABOUT describeContents +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class User(val firstName: String, val lastName: String, val age: Int, val isProUser: Boolean) : Parcelable { + override fun describeContents() = 100 +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.txt new file mode 100644 index 00000000000..c573057e5c2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.txt @@ -0,0 +1,45 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) + + public int describeContents() { + LABEL (L0) + LINENUMBER (9) + BIPUSH (100) + IRETURN + LABEL (L1) + } + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public final boolean isProUser() + + public void writeToParcel(android.os.Parcel parcel, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.ir.txt new file mode 100644 index 00000000000..b3bb6281748 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.ir.txt @@ -0,0 +1,69 @@ +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (Ltest/Foo;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readParcelable, (Ljava/lang/ClassLoader;)Landroid/os/Parcelable;) + CHECKCAST + INVOKESPECIAL (test/Foo, , (Landroid/accounts/Account;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + LABEL (L1) + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.accounts.Account kp + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (android.accounts.Account kp) + + public int describeContents() + + public final android.accounts.Account getKp() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (kp, Landroid/accounts/Account;) + CHECKCAST + ILOAD (2) + INVOKEVIRTUAL (android/os/Parcel, writeParcelable, (Landroid/os/Parcelable;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt new file mode 100644 index 00000000000..a6a985a2d6e --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt @@ -0,0 +1,11 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, +// WITH_RUNTIME + +package test + +import kotlinx.parcelize.* +import android.os.* +import android.accounts.Account + +@Parcelize +class Foo(val kp: Account): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.txt new file mode 100644 index 00000000000..cc7848632cd --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.txt @@ -0,0 +1,67 @@ +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (Ltest/Foo;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readParcelable, (Ljava/lang/ClassLoader;)Landroid/os/Parcelable;) + CHECKCAST + INVOKESPECIAL (test/Foo, , (Landroid/accounts/Account;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (11) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.accounts.Account kp + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (android.accounts.Account kp) + + public int describeContents() + + public final android.accounts.Account getKp() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (kp, Landroid/accounts/Account;) + ILOAD (2) + INVOKEVIRTUAL (android/os/Parcel, writeParcelable, (Landroid/os/Parcelable;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.ir.txt new file mode 100644 index 00000000000..414b0ada937 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.ir.txt @@ -0,0 +1,169 @@ +public final class k/KotlinParcelable$Companion : java/lang/Object { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) +} + +public final class k/KotlinParcelable$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public k.KotlinParcelable createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (1) + LDC (source) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + LINENUMBER (23) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + ISTORE (2) + LABEL (L2) + LINENUMBER (24) + NEW + DUP + ILOAD (2) + INVOKESPECIAL (k/KotlinParcelable, , (I)V) + ARETURN + LABEL (L3) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (k/KotlinParcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Lk/KotlinParcelable;) + ARETURN + LABEL (L1) + } + + public k.KotlinParcelable[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class k/KotlinParcelable : java/lang/Object, android/os/Parcelable { + public final static k.KotlinParcelable$Creator CREATOR + + public final static k.KotlinParcelable$Companion Companion + + private int data + + static void () { + NEW + DUP + ACONST_NULL + INVOKESPECIAL (k/KotlinParcelable$Companion, , (Lkotlin/jvm/internal/DefaultConstructorMarker;)V) + PUTSTATIC (Companion, Lk/KotlinParcelable$Companion;) + LABEL (L0) + LINENUMBER (18) + NEW + DUP + INVOKESPECIAL (k/KotlinParcelable$Creator, , ()V) + PUTSTATIC (CREATOR, Lk/KotlinParcelable$Creator;) + RETURN + } + + public void (int data) + + public final int component1() + + public final k.KotlinParcelable copy(int data) + + public static k.KotlinParcelable copy$default(k.KotlinParcelable p0, int p1, int p2, java.lang.Object p3) + + public int describeContents() + + public boolean equals(java.lang.Object other) + + public final int getData() + + public int hashCode() + + public final void setData(int ) + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel dest, int flags) { + LABEL (L0) + ALOAD (1) + LDC (dest) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + LINENUMBER (13) + ALOAD (1) + ALOAD (0) + GETFIELD (data, I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + LINENUMBER (14) + RETURN + LABEL (L3) + } +} + +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + GETSTATIC (CREATOR, Lk/KotlinParcelable$Creator;) + ALOAD (1) + INVOKEVIRTUAL (k/KotlinParcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Lk/KotlinParcelable;) + INVOKESPECIAL (test/Foo, , (Lk/KotlinParcelable;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + LABEL (L1) + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final k.KotlinParcelable kp + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (k.KotlinParcelable kp) + + public int describeContents() + + public final k.KotlinParcelable getKp() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (0) + GETFIELD (kp, Lk/KotlinParcelable;) + ALOAD (1) + ILOAD (2) + INVOKEVIRTUAL (k/KotlinParcelable, writeToParcel, (Landroid/os/Parcel;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt new file mode 100644 index 00000000000..93e532b726b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt @@ -0,0 +1,40 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, +// WITH_RUNTIME + +// FILE: KotlinParcelable.kt +package k +import android.os.* + +data class KotlinParcelable(var data: Int): Parcelable { + + override fun describeContents() = 1 + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeInt(data) + } + + companion object { + @JvmField + val CREATOR = Creator() + } + + class Creator : Parcelable.Creator { + override fun createFromParcel(source: Parcel): KotlinParcelable { + val data = source.readInt() + return KotlinParcelable(data) + } + + override fun newArray(size: Int) = arrayOfNulls(size) + } +} + + +// FILE: test.kt +package test + +import kotlinx.parcelize.* +import android.os.* +import k.* + +@Parcelize +class Foo(val kp: KotlinParcelable): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.txt new file mode 100644 index 00000000000..570272720c9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.txt @@ -0,0 +1,170 @@ +public final class k/KotlinParcelable$Companion : java/lang/Object { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) +} + +public final class k/KotlinParcelable$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public k.KotlinParcelable createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (1) + LDC (source) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + LINENUMBER (23) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + ISTORE (2) + LABEL (L2) + LINENUMBER (24) + NEW + DUP + ILOAD (2) + INVOKESPECIAL (k/KotlinParcelable, , (I)V) + ARETURN + LABEL (L3) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (21) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (k/KotlinParcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Lk/KotlinParcelable;) + ARETURN + } + + public k.KotlinParcelable[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class k/KotlinParcelable : java/lang/Object, android/os/Parcelable { + public final static k.KotlinParcelable$Creator CREATOR + + public final static k.KotlinParcelable$Companion Companion + + private int data + + static void () { + NEW + DUP + ACONST_NULL + INVOKESPECIAL (k/KotlinParcelable$Companion, , (Lkotlin/jvm/internal/DefaultConstructorMarker;)V) + PUTSTATIC (Companion, Lk/KotlinParcelable$Companion;) + LABEL (L0) + LINENUMBER (18) + NEW + DUP + INVOKESPECIAL (k/KotlinParcelable$Creator, , ()V) + PUTSTATIC (CREATOR, Lk/KotlinParcelable$Creator;) + RETURN + } + + public void (int data) + + public final int component1() + + public final k.KotlinParcelable copy(int data) + + public static k.KotlinParcelable copy$default(k.KotlinParcelable p0, int p1, int p2, java.lang.Object p3) + + public int describeContents() + + public boolean equals(java.lang.Object p0) + + public final int getData() + + public int hashCode() + + public final void setData(int ) + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel dest, int flags) { + LABEL (L0) + ALOAD (1) + LDC (dest) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + LINENUMBER (13) + ALOAD (1) + ALOAD (0) + GETFIELD (data, I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + LINENUMBER (14) + RETURN + LABEL (L3) + } +} + +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (Ltest/Foo;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readParcelable, (Ljava/lang/ClassLoader;)Landroid/os/Parcelable;) + CHECKCAST + INVOKESPECIAL (test/Foo, , (Lk/KotlinParcelable;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (9) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final k.KotlinParcelable kp + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (k.KotlinParcelable kp) + + public int describeContents() + + public final k.KotlinParcelable getKp() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (kp, Lk/KotlinParcelable;) + ILOAD (2) + INVOKEVIRTUAL (android/os/Parcel, writeParcelable, (Landroid/os/Parcelable;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.ir.txt new file mode 100644 index 00000000000..bd2d01ec82d --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.ir.txt @@ -0,0 +1,115 @@ +final class User$Companion : java/lang/Object, kotlinx/parcelize/Parceler { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + public User create(android.os.Parcel parcel) + + public java.lang.Object create(android.os.Parcel parcel) + + public User[] newArray(int size) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ILOAD (1) + INVOKESTATIC (kotlinx/parcelize/Parceler$DefaultImpls, newArray, (Lkotlinx/parcelize/Parceler;I)[Ljava/lang/Object;) + CHECKCAST + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int size) { + LABEL (L0) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } + + public void write(User $this$write, android.os.Parcel parcel, int flags) + + public void write(java.lang.Object $this$write, android.os.Parcel parcel, int flags) +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + INVOKESTATIC (User, access$getCompanion$p$s2645995, ()LUser$Companion;) + ALOAD (1) + INVOKEVIRTUAL (User$Companion, create, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public final User[] newArray(int size) { + LABEL (L0) + ILOAD (1) + ANEWARRAY + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int size) { + LABEL (L0) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Creator, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final static User$Companion Companion + + private final int age + + private final java.lang.String firstName + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age) + + public final static User$Companion access$getCompanion$p$s2645995() + + public int describeContents() + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + GETSTATIC (Companion, LUser$Companion;) + ALOAD (0) + ALOAD (1) + ILOAD (2) + INVOKEVIRTUAL (User$Companion, write, (LUser;Landroid/os/Parcel;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt new file mode 100644 index 00000000000..7769e4afd49 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt @@ -0,0 +1,19 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, newArray +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +class User(val firstName: String, val lastName: String, val age: Int) : Parcelable { + private companion object : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(firstName) + parcel.writeString(lastName) + parcel.writeInt(age) + } + + override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), parcel.readInt()) + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.txt new file mode 100644 index 00000000000..dfd305ef74b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.txt @@ -0,0 +1,113 @@ +final class User$Companion : java/lang/Object, kotlinx/parcelize/Parceler { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + public User create(android.os.Parcel parcel) + + public java.lang.Object create(android.os.Parcel p0) + + public User[] newArray(int size) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ILOAD (1) + INVOKESTATIC (kotlinx/parcelize/Parceler$DefaultImpls, newArray, (Lkotlinx/parcelize/Parceler;I)[Ljava/lang/Object;) + CHECKCAST + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + } + + public void write(User $this$write, android.os.Parcel parcel, int flags) + + public void write(java.lang.Object p0, android.os.Parcel p1, int p2) +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + GETSTATIC (Companion, LUser$Companion;) + ALOAD (1) + INVOKEVIRTUAL (User$Companion, create, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (9) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + } + + public final User[] newArray(int size) { + LABEL (L0) + ILOAD (1) + ANEWARRAY + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int p0) { + LABEL (L0) + LINENUMBER (9) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Creator, newArray, (I)[LUser;) + ARETURN + } +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final static User$Companion Companion + + private final int age + + private final java.lang.String firstName + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age) + + public int describeContents() + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + GETSTATIC (Companion, LUser$Companion;) + ALOAD (0) + ALOAD (1) + ILOAD (2) + INVOKEVIRTUAL (User$Companion, write, (LUser;Landroid/os/Parcel;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.ir.txt new file mode 100644 index 00000000000..928f93b96c9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.ir.txt @@ -0,0 +1,85 @@ +final class User$Companion : java/lang/Object, kotlinx/parcelize/Parceler { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + public User create(android.os.Parcel parcel) + + public java.lang.Object create(android.os.Parcel parcel) + + public User[] newArray(int size) { + LABEL (L0) + LINENUMBER (19) + ILOAD (1) + ANEWARRAY + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int size) { + LABEL (L0) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } + + public void write(User $this$write, android.os.Parcel parcel, int flags) + + public void write(java.lang.Object $this$write, android.os.Parcel parcel, int flags) +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final User[] newArray(int size) { + LABEL (L0) + INVOKESTATIC (User, access$getCompanion$p$s2645995, ()LUser$Companion;) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int size) { + LABEL (L0) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Creator, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final static User$Companion Companion + + private final int age + + private final java.lang.String firstName + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age) + + public final static User$Companion access$getCompanion$p$s2645995() + + public int describeContents() + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public void writeToParcel(android.os.Parcel out, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt new file mode 100644 index 00000000000..c00505fc486 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt @@ -0,0 +1,21 @@ +// CURIOUS_ABOUT newArray +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcel +import android.os.Parcelable + +@Parcelize +class User(val firstName: String, val lastName: String, val age: Int) : Parcelable { + private companion object : Parceler { + override fun User.write(parcel: Parcel, flags: Int) { + parcel.writeString(firstName) + parcel.writeString(lastName) + parcel.writeInt(age) + } + + override fun create(parcel: Parcel) = User(parcel.readString(), parcel.readString(), parcel.readInt()) + + override fun newArray(size: Int) = arrayOfNulls(size) as Array + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.txt new file mode 100644 index 00000000000..8e6acc70948 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.txt @@ -0,0 +1,83 @@ +final class User$Companion : java/lang/Object, kotlinx/parcelize/Parceler { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + public User create(android.os.Parcel parcel) + + public java.lang.Object create(android.os.Parcel p0) + + public User[] newArray(int size) { + LABEL (L0) + LINENUMBER (19) + ILOAD (1) + ANEWARRAY + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + } + + public void write(User $this$write, android.os.Parcel parcel, int flags) + + public void write(java.lang.Object p0, android.os.Parcel p1, int p2) +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final User[] newArray(int size) { + LABEL (L0) + GETSTATIC (Companion, LUser$Companion;) + ILOAD (1) + INVOKEVIRTUAL (User$Companion, newArray, (I)[LUser;) + ARETURN + LABEL (L1) + } + + public java.lang.Object[] newArray(int p0) { + LABEL (L0) + LINENUMBER (9) + ALOAD (0) + ILOAD (1) + INVOKEVIRTUAL (User$Creator, newArray, (I)[LUser;) + ARETURN + } +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final static User$Companion Companion + + private final int age + + private final java.lang.String firstName + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age) + + public int describeContents() + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public void writeToParcel(android.os.Parcel parcel, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.ir.txt new file mode 100644 index 00000000000..ec98d0d7a0a --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.ir.txt @@ -0,0 +1,49 @@ +public abstract class AbstractUser : java/lang/Object, android/os/Parcelable { + public void () + + public int describeContents() { + LABEL (L0) + LINENUMBER (8) + BIPUSH (100) + IRETURN + LABEL (L1) + } +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class User : AbstractUser { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public final boolean isProUser() + + public void writeToParcel(android.os.Parcel out, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt new file mode 100644 index 00000000000..4aad0269f52 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt @@ -0,0 +1,12 @@ +// CURIOUS_ABOUT describeContents +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable + +abstract class AbstractUser : Parcelable { + override fun describeContents() = 100 +} + +@Parcelize +class User(val firstName: String, val lastName: String, val age: Int, val isProUser: Boolean) : AbstractUser() \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.txt new file mode 100644 index 00000000000..0e5151bcce1 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.txt @@ -0,0 +1,49 @@ +public abstract class AbstractUser : java/lang/Object, android/os/Parcelable { + public void () + + public int describeContents() { + LABEL (L0) + LINENUMBER (8) + BIPUSH (100) + IRETURN + LABEL (L1) + } +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class User : AbstractUser { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) + + public final int getAge() + + public final java.lang.String getFirstName() + + public final java.lang.String getLastName() + + public final boolean isProUser() + + public void writeToParcel(android.os.Parcel parcel, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.ir.txt new file mode 100644 index 00000000000..bf76f8386b0 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.ir.txt @@ -0,0 +1,57 @@ +public final class User$Companion : java/lang/Object { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + private static void getTest$annotations() +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + public final static User$Companion Companion + + private final java.lang.String firstName + + private final static java.lang.StringBuilder test + + static void () { + NEW + DUP + ACONST_NULL + INVOKESPECIAL (User$Companion, , (Lkotlin/jvm/internal/DefaultConstructorMarker;)V) + PUTSTATIC (Companion, LUser$Companion;) + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + LABEL (L0) + LINENUMBER (12) + NEW + DUP + INVOKESPECIAL (java/lang/StringBuilder, , ()V) + PUTSTATIC (test, Ljava/lang/StringBuilder;) + RETURN + } + + public void (java.lang.String firstName) + + public int describeContents() + + public final java.lang.String getFirstName() + + public void writeToParcel(android.os.Parcel out, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt new file mode 100644 index 00000000000..20b0e4f6d7b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt @@ -0,0 +1,14 @@ +// CURIOUS_ABOUT +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable +import kotlin.jvm.JvmStatic + +@Parcelize +class User(val firstName: String) : Parcelable { + companion object { + @JvmStatic + private val test = StringBuilder() + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.txt new file mode 100644 index 00000000000..b354c9441d9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.txt @@ -0,0 +1,56 @@ +public final class User$Companion : java/lang/Object { + private void () + + public void (kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) + + private static void getTest$annotations() +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + public final static User$Companion Companion + + private final java.lang.String firstName + + private final static java.lang.StringBuilder test + + static void () { + NEW + DUP + ACONST_NULL + INVOKESPECIAL (User$Companion, , (Lkotlin/jvm/internal/DefaultConstructorMarker;)V) + PUTSTATIC (Companion, LUser$Companion;) + LABEL (L0) + LINENUMBER (12) + NEW + DUP + INVOKESPECIAL (java/lang/StringBuilder, , ()V) + PUTSTATIC (test, Ljava/lang/StringBuilder;) + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (java.lang.String firstName) + + public int describeContents() + + public final java.lang.String getFirstName() + + public void writeToParcel(android.os.Parcel parcel, int flags) +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.ir.txt new file mode 100644 index 00000000000..7c0c7cf3eb2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.ir.txt @@ -0,0 +1,156 @@ +public final class test/Bar$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Bar createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFNE (L2) + ACONST_NULL + GOTO (L3) + LABEL (L2) + GETSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + ALOAD (1) + INVOKEINTERFACE (android/os/Parcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Ljava/lang/Object;) + LABEL (L3) + CHECKCAST + INVOKESPECIAL (test/Bar, , (Ltest/Foo;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Bar$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Bar;) + ARETURN + LABEL (L1) + } + + public final test.Bar[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/Bar : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final test.Foo foo + + static void () { + NEW + DUP + INVOKESPECIAL (test/Bar$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (test.Foo foo) + + public int describeContents() + + public final test.Foo getFoo() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (0) + GETFIELD (foo, Ltest/Foo;) + ASTORE (3) + ALOAD (3) + IFNONNULL (L1) + ALOAD (1) + ICONST_0 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + GOTO (L2) + LABEL (L1) + ALOAD (1) + ICONST_1 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (3) + ALOAD (1) + ILOAD (2) + INVOKEVIRTUAL (test/Foo, writeToParcel, (Landroid/os/Parcel;I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} + +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + GETSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + ALOAD (1) + INVOKEINTERFACE (android/os/Parcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Ljava/lang/Object;) + CHECKCAST + INVOKESPECIAL (test/Foo, , (Ltest/Bar;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + LABEL (L1) + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final test.Bar bar + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (test.Bar bar) + + public int describeContents() + + public final test.Bar getBar() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (0) + GETFIELD (bar, Ltest/Bar;) + ALOAD (1) + ILOAD (2) + INVOKEVIRTUAL (test/Bar, writeToParcel, (Landroid/os/Parcel;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt new file mode 100644 index 00000000000..7bac4738fa9 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt @@ -0,0 +1,13 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, +// WITH_RUNTIME +//FILE: test.kt +package test + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class Foo(val bar: Bar): Parcelable + +@Parcelize +class Bar(val foo: Foo?) : Parcelable diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.txt new file mode 100644 index 00000000000..df3235c5e31 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.txt @@ -0,0 +1,156 @@ +public final class test/Bar$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Bar createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + ALOAD (1) + GETSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + SWAP + INVOKEINTERFACE (android/os/Parcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Ljava/lang/Object;) + CHECKCAST + GOTO (L3) + LABEL (L2) + ACONST_NULL + LABEL (L3) + INVOKESPECIAL (test/Bar, , (Ltest/Foo;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (13) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Bar$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Bar;) + ARETURN + } + + public final test.Bar[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/Bar : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final test.Foo foo + + static void () { + NEW + DUP + INVOKESPECIAL (test/Bar$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (test.Foo foo) + + public int describeContents() + + public final test.Foo getFoo() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (foo, Ltest/Foo;) + DUP + IFNULL (L1) + ALOAD (1) + LDC (1) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + SWAP + LDC (0) + INVOKEINTERFACE (android/os/Parcelable, writeToParcel, (Landroid/os/Parcel;I)V) + GOTO (L2) + LABEL (L1) + POP + LDC (0) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} + +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + GETSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + SWAP + INVOKEINTERFACE (android/os/Parcelable$Creator, createFromParcel, (Landroid/os/Parcel;)Ljava/lang/Object;) + CHECKCAST + INVOKESPECIAL (test/Foo, , (Ltest/Bar;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final test.Bar bar + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (test.Bar bar) + + public int describeContents() + + public final test.Bar getBar() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (bar, Ltest/Bar;) + SWAP + LDC (0) + INVOKEINTERFACE (android/os/Parcelable, writeToParcel, (Landroid/os/Parcel;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.ir.txt new file mode 100644 index 00000000000..5daa1c2f1e7 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.ir.txt @@ -0,0 +1,53 @@ +public final class test/SomeClass$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.SomeClass createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + POP + NEW + DUP + INVOKESPECIAL (test/SomeClass, , ()V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/SomeClass$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/SomeClass;) + ARETURN + LABEL (L1) + } + + public final test.SomeClass[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/SomeClass : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + static void () + + public void () + + public int describeContents() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ICONST_1 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt new file mode 100644 index 00000000000..6e632542061 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt @@ -0,0 +1,10 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel +// WITH_RUNTIME + +package test + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class SomeClass : Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.txt new file mode 100644 index 00000000000..7b083f5e410 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.txt @@ -0,0 +1,71 @@ +public final class test/SomeClass$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.SomeClass createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + NEW + DUP + INVOKESPECIAL (test/SomeClass, , ()V) + GOTO (L3) + LABEL (L2) + ACONST_NULL + LABEL (L3) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/SomeClass$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/SomeClass;) + ARETURN + } + + public final test.SomeClass[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/SomeClass : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + static void () + + public void () + + public int describeContents() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + DUP + IFNULL (L1) + ALOAD (1) + LDC (1) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + POP2 + GOTO (L2) + LABEL (L1) + POP + LDC (0) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.ir.txt new file mode 100644 index 00000000000..4541abfd5f6 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.ir.txt @@ -0,0 +1,70 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final java.util.List names + + static void () + + public void (java.util.List names) + + public int describeContents() + + public final java.util.List getNames() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (0) + GETFIELD (names, Ljava/util/List;) + ASTORE (3) + ALOAD (1) + ALOAD (3) + INVOKEINTERFACE (java/util/List, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (3) + INVOKEINTERFACE (java/util/List, iterator, ()Ljava/util/Iterator;) + ASTORE (4) + LABEL (L1) + ALOAD (4) + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L2) + ALOAD (4) + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + ASTORE (5) + ALOAD (1) + ALOAD (5) + INVOKEINTERFACE (java/util/List, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (5) + INVOKEINTERFACE (java/util/List, iterator, ()Ljava/util/Iterator;) + ASTORE (6) + LABEL (L3) + ALOAD (6) + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L1) + ALOAD (1) + ALOAD (6) + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeStringList, (Ljava/util/List;)V) + GOTO (L3) + LABEL (L2) + RETURN + LABEL (L4) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt new file mode 100644 index 00000000000..248b4b23a4b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt @@ -0,0 +1,8 @@ +// CURIOUS_ABOUT writeToParcel +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class Test(val names: List>>): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.txt new file mode 100644 index 00000000000..dbd89b6639d --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.txt @@ -0,0 +1,86 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final java.util.List names + + static void () + + public void (java.util.List names) + + public int describeContents() + + public final java.util.List getNames() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (names, Ljava/util/List;) + DUP_X1 + INVOKEINTERFACE (java/util/Collection, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;) + LABEL (L1) + DUP + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L2) + DUP + ALOAD (1) + SWAP + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + DUP_X1 + INVOKEINTERFACE (java/util/Collection, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;) + LABEL (L3) + DUP + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L4) + DUP + ALOAD (1) + SWAP + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + DUP_X1 + INVOKEINTERFACE (java/util/Collection, size, ()I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEINTERFACE (java/util/Collection, iterator, ()Ljava/util/Iterator;) + LABEL (L5) + DUP + INVOKEINTERFACE (java/util/Iterator, hasNext, ()Z) + IFEQ (L6) + DUP + ALOAD (1) + SWAP + INVOKEINTERFACE (java/util/Iterator, next, ()Ljava/lang/Object;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V) + GOTO (L5) + LABEL (L6) + POP + GOTO (L3) + LABEL (L4) + POP + GOTO (L1) + LABEL (L2) + POP + RETURN + LABEL (L7) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.ir.txt new file mode 100644 index 00000000000..0b1b0b8eab2 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.ir.txt @@ -0,0 +1,90 @@ +public final class TestNotNull$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestNotNull createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final TestNotNull[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class TestNotNull : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size a + + static void () + + public void (android.util.Size a) + + public int describeContents() + + public final android.util.Size getA() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (a, Landroid/util/Size;) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + RETURN + LABEL (L1) + } +} + +public final class TestNullable$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestNullable createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final TestNullable[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class TestNullable : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size a + + static void () + + public void (android.util.Size a) + + public int describeContents() + + public final android.util.Size getA() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (0) + GETFIELD (a, Landroid/util/Size;) + ASTORE (3) + ALOAD (3) + IFNONNULL (L1) + ALOAD (1) + ICONST_0 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + GOTO (L2) + LABEL (L1) + ALOAD (1) + ICONST_1 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (1) + ALOAD (3) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt new file mode 100644 index 00000000000..6f5419ee8eb --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt @@ -0,0 +1,12 @@ +// CURIOUS_ABOUT writeToParcel +// WITH_RUNTIME + +import android.util.Size +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class TestNullable(val a: Size?): Parcelable + +@Parcelize +class TestNotNull(val a: Size): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.txt new file mode 100644 index 00000000000..cca4a02f99f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.txt @@ -0,0 +1,88 @@ +public final class TestNotNull$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestNotNull createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final TestNotNull[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class TestNotNull : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size a + + static void () + + public void (android.util.Size a) + + public int describeContents() + + public final android.util.Size getA() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (a, Landroid/util/Size;) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + RETURN + LABEL (L1) + } +} + +public final class TestNullable$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestNullable createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final TestNullable[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class TestNullable : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size a + + static void () + + public void (android.util.Size a) + + public int describeContents() + + public final android.util.Size getA() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (a, Landroid/util/Size;) + DUP + IFNULL (L1) + ALOAD (1) + LDC (1) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + GOTO (L2) + LABEL (L1) + POP + LDC (0) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.ir.txt new file mode 100644 index 00000000000..ce68ef87551 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.ir.txt @@ -0,0 +1,67 @@ +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (Ltest/Foo;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readParcelable, (Ljava/lang/ClassLoader;)Landroid/os/Parcelable;) + INVOKESPECIAL (test/Foo, , (Landroid/os/Parcelable;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + LABEL (L1) + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.os.Parcelable parcelable + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (android.os.Parcelable parcelable) + + public int describeContents() + + public final android.os.Parcelable getParcelable() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (parcelable, Landroid/os/Parcelable;) + ILOAD (2) + INVOKEVIRTUAL (android/os/Parcel, writeParcelable, (Landroid/os/Parcelable;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt new file mode 100644 index 00000000000..cefc54ca79f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt @@ -0,0 +1,37 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, +// WITH_RUNTIME + +//FILE: test/JavaClass.java +package test; + +class JavaClass { + void test() { + // Here we test access to CREATOR + Object o = Foo.CREATOR; + } +} + +//FILE: android/os/Parcel.java +package android.os; + +public class Parcel {} + +//FILE: android/os/Parcelable.java +package android.os; + +public interface Parcelable { + public static interface Creator { + T createFromParcel(Parcel source); + T[] newArray(int size); + } +} + + +//FILE: test.kt +package test + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class Foo(val parcelable: Parcelable): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.txt new file mode 100644 index 00000000000..abdb27c3c04 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.txt @@ -0,0 +1,66 @@ +public final class test/Foo$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final test.Foo createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (Ltest/Foo;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readParcelable, (Ljava/lang/ClassLoader;)Landroid/os/Parcelable;) + INVOKESPECIAL (test/Foo, , (Landroid/os/Parcelable;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (8) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (test/Foo$Creator, createFromParcel, (Landroid/os/Parcel;)Ltest/Foo;) + ARETURN + } + + public final test.Foo[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class test/Foo : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.os.Parcelable parcelable + + static void () { + NEW + DUP + INVOKESPECIAL (test/Foo$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (android.os.Parcelable parcelable) + + public int describeContents() + + public final android.os.Parcelable getParcelable() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (parcelable, Landroid/os/Parcelable;) + ILOAD (2) + INVOKEVIRTUAL (android/os/Parcel, writeParcelable, (Landroid/os/Parcelable;I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.ir.txt new file mode 100644 index 00000000000..8377570df67 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.ir.txt @@ -0,0 +1,90 @@ +public final class SerializableSimple : java/lang/Object, java/io/Serializable { + private final java.lang.String a + + private final java.lang.String b + + public void (java.lang.String a, java.lang.String b) + + public final java.lang.String getA() + + public final java.lang.String getB() +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()Ljava/io/Serializable;) + CHECKCAST + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()Ljava/io/Serializable;) + CHECKCAST + INVOKESPECIAL (User, , (LSerializableSimple;LSerializableSimple;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final SerializableSimple notNull + + private final SerializableSimple nullable + + static void () { + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (SerializableSimple notNull, SerializableSimple nullable) + + public int describeContents() + + public final SerializableSimple getNotNull() + + public final SerializableSimple getNullable() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (notNull, LSerializableSimple;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (Ljava/io/Serializable;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (nullable, LSerializableSimple;) + CHECKCAST + INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (Ljava/io/Serializable;)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt new file mode 100644 index 00000000000..225ec06eaaf --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt @@ -0,0 +1,11 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable +import java.io.Serializable + +class SerializableSimple(val a: String, val b: String) : Serializable + +@Parcelize +class User(val notNull: SerializableSimple, val nullable: SerializableSimple) : Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.txt new file mode 100644 index 00000000000..45e882dcb87 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializable.txt @@ -0,0 +1,87 @@ +public final class SerializableSimple : java/lang/Object, java/io/Serializable { + private final java.lang.String a + + private final java.lang.String b + + public void (java.lang.String a, java.lang.String b) + + public final java.lang.String getA() + + public final java.lang.String getB() +} + +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final User createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()Ljava/io/Serializable;) + CHECKCAST + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSerializable, ()Ljava/io/Serializable;) + CHECKCAST + INVOKESPECIAL (User, , (LSerializableSimple;LSerializableSimple;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (11) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + } + + public final User[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final SerializableSimple notNull + + private final SerializableSimple nullable + + static void () { + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (SerializableSimple notNull, SerializableSimple nullable) + + public int describeContents() + + public final SerializableSimple getNotNull() + + public final SerializableSimple getNullable() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (notNull, LSerializableSimple;) + INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (Ljava/io/Serializable;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (nullable, LSerializableSimple;) + INVOKEVIRTUAL (android/os/Parcel, writeSerializable, (Ljava/io/Serializable;)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.ir.txt new file mode 100644 index 00000000000..0fc65dc1950 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.ir.txt @@ -0,0 +1,80 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (LTest;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readValue, (Ljava/lang/ClassLoader;)Ljava/lang/Object;) + CHECKCAST + INVOKESPECIAL (Test, , (LValue;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + LABEL (L1) + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final Value value + + static void () { + NEW + DUP + INVOKESPECIAL (Test$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (Value value) + + public int describeContents() { + LABEL (L0) + ICONST_0 + IRETURN + LABEL (L1) + } + + public final Value getValue() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (value, LValue;) + INVOKEVIRTUAL (android/os/Parcel, writeValue, (Ljava/lang/Object;)V) + RETURN + LABEL (L1) + } +} + +public final class Value : java/lang/Object { + private final int x + + public void (int x) + + public final int getX() +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt new file mode 100644 index 00000000000..607b5514ee4 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt @@ -0,0 +1,10 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, , describeContents +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable + +class Value(val x: Int) + +@Parcelize +class Test(val value: @RawValue Value) : Parcelable diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.txt new file mode 100644 index 00000000000..a7cda06e23b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.txt @@ -0,0 +1,79 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + NEW + DUP + ALOAD (1) + LDC (LValue;) + INVOKEVIRTUAL (java/lang/Class, getClassLoader, ()Ljava/lang/ClassLoader;) + INVOKEVIRTUAL (android/os/Parcel, readValue, (Ljava/lang/ClassLoader;)Ljava/lang/Object;) + CHECKCAST + INVOKESPECIAL (Test, , (LValue;)V) + ARETURN + LABEL (L1) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final Value value + + static void () { + NEW + DUP + INVOKESPECIAL (Test$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (Value value) + + public int describeContents() { + LABEL (L0) + LDC (0) + IRETURN + LABEL (L1) + } + + public final Value getValue() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (value, LValue;) + INVOKEVIRTUAL (android/os/Parcel, writeValue, (Ljava/lang/Object;)V) + RETURN + LABEL (L1) + } +} + +public final class Value : java/lang/Object { + private final int x + + public void (int x) + + public final int getX() +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simple.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.ir.txt new file mode 100644 index 00000000000..54fb592573c --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.ir.txt @@ -0,0 +1,158 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () { + Local variables: + 0 this: LUser$Creator; + } + + public final User createFromParcel(android.os.Parcel parcel) { + Local variables: + 0 this: LUser$Creator; + 1 parcel: Landroid/os/Parcel; + + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + ICONST_1 + GOTO (L3) + LABEL (L2) + ICONST_0 + LABEL (L3) + INVOKESPECIAL (User, , (Ljava/lang/String;Ljava/lang/String;IZ)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + Local variables: + 0 this: LUser$Creator; + 1 source: Landroid/os/Parcel; + + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + LABEL (L1) + } + + public final User[] newArray(int size) { + Local variables: + 0 this: LUser$Creator; + 1 size: I + } + + public java.lang.Object[] newArray(int size) { + Local variables: + 0 this: LUser$Creator; + 1 size: I + } +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () { + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + CHECKCAST + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) { + Local variables: + 0 this: LUser; + 1 firstName: Ljava/lang/String; + 2 lastName: Ljava/lang/String; + 3 age: I + 4 isProUser: Z + } + + public int describeContents() { + Local variables: + 0 this: LUser; + + LABEL (L0) + ICONST_0 + IRETURN + LABEL (L1) + } + + public final int getAge() { + Local variables: + 0 this: LUser; + } + + public final java.lang.String getFirstName() { + Local variables: + 0 this: LUser; + } + + public final java.lang.String getLastName() { + Local variables: + 0 this: LUser; + } + + public final boolean isProUser() { + Local variables: + 0 this: LUser; + } + + public void writeToParcel(android.os.Parcel out, int flags) { + Local variables: + 0 this: LUser; + 1 out: Landroid/os/Parcel; + 2 flags: I + + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (firstName, Ljava/lang/String;) + INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (lastName, Ljava/lang/String;) + INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (age, I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (1) + ALOAD (0) + GETFIELD (isProUser, Z) + IFEQ (L1) + ICONST_1 + GOTO (L2) + LABEL (L1) + ICONST_0 + LABEL (L2) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt new file mode 100644 index 00000000000..60ae12b82d4 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt @@ -0,0 +1,9 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel, , describeContents +// WITH_RUNTIME +// LOCAL_VARIABLE_TABLE + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class User(val firstName: String, val lastName: String, val age: Int, val isProUser: Boolean) : Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simple.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.txt new file mode 100644 index 00000000000..04621e76733 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simple.txt @@ -0,0 +1,143 @@ +public final class User$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () { + Local variables: + 0 this: LUser$Creator; + } + + public final User createFromParcel(android.os.Parcel in) { + Local variables: + 0 this: LUser$Creator; + 1 in: Landroid/os/Parcel; + + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readString, ()Ljava/lang/String;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + ICONST_1 + GOTO (L3) + LABEL (L2) + ICONST_0 + LABEL (L3) + INVOKESPECIAL (User, , (Ljava/lang/String;Ljava/lang/String;IZ)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (9) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (User$Creator, createFromParcel, (Landroid/os/Parcel;)LUser;) + ARETURN + } + + public final User[] newArray(int size) { + Local variables: + 0 this: LUser$Creator; + 1 size: I + } + + public java.lang.Object[] newArray(int p0) +} + +public final class User : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final int age + + private final java.lang.String firstName + + private final boolean isProUser + + private final java.lang.String lastName + + static void () { + NEW + DUP + INVOKESPECIAL (User$Creator, , ()V) + PUTSTATIC (CREATOR, Landroid/os/Parcelable$Creator;) + RETURN + } + + public void (java.lang.String firstName, java.lang.String lastName, int age, boolean isProUser) { + Local variables: + 0 this: LUser; + 1 firstName: Ljava/lang/String; + 2 lastName: Ljava/lang/String; + 3 age: I + 4 isProUser: Z + } + + public int describeContents() { + Local variables: + 0 this: LUser; + + LABEL (L0) + LDC (0) + IRETURN + LABEL (L1) + } + + public final int getAge() { + Local variables: + 0 this: LUser; + } + + public final java.lang.String getFirstName() { + Local variables: + 0 this: LUser; + } + + public final java.lang.String getLastName() { + Local variables: + 0 this: LUser; + } + + public final boolean isProUser() { + Local variables: + 0 this: LUser; + } + + public void writeToParcel(android.os.Parcel parcel, int flags) { + Local variables: + 0 this: LUser; + 1 parcel: Landroid/os/Parcel; + 2 flags: I + + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (firstName, Ljava/lang/String;) + INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (lastName, Ljava/lang/String;) + INVOKEVIRTUAL (android/os/Parcel, writeString, (Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (age, I) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (1) + ALOAD (0) + GETFIELD (isProUser, Z) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.ir.txt new file mode 100644 index 00000000000..ac7e0b89047 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.ir.txt @@ -0,0 +1,38 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel parcel) + + public java.lang.Object createFromParcel(android.os.Parcel source) + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final java.util.List names + + static void () + + public void (java.util.List names) + + public int describeContents() + + public final java.util.List getNames() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (names, Ljava/util/List;) + INVOKEVIRTUAL (android/os/Parcel, writeStringList, (Ljava/util/List;)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt new file mode 100644 index 00000000000..a14c8e270fc --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt @@ -0,0 +1,8 @@ +// CURIOUS_ABOUT writeToParcel +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable + +@Parcelize +class Test(val names: List): Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.txt new file mode 100644 index 00000000000..610ee2ebf81 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.txt @@ -0,0 +1,38 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel in) + + public java.lang.Object createFromParcel(android.os.Parcel p0) + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final java.util.List names + + static void () + + public void (java.util.List names) + + public int describeContents() + + public final java.util.List getNames() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (names, Ljava/util/List;) + INVOKEVIRTUAL (android/os/Parcel, writeStringList, (Ljava/util/List;)V) + RETURN + LABEL (L1) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/size.ir.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/size.ir.txt new file mode 100644 index 00000000000..b33e3e9622e --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/size.ir.txt @@ -0,0 +1,205 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFNE (L2) + ACONST_NULL + GOTO (L3) + LABEL (L2) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;) + LABEL (L3) + INVOKESPECIAL (Test, , (Landroid/util/Size;Landroid/util/Size;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + LABEL (L1) + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size nullable + + private final android.util.Size size + + static void () + + public void (android.util.Size size, android.util.Size nullable) + + public final android.util.Size component1() + + public final android.util.Size component2() + + public final Test copy(android.util.Size size, android.util.Size nullable) + + public static Test copy$default(Test p0, android.util.Size p1, android.util.Size p2, int p3, java.lang.Object p4) + + public int describeContents() + + public boolean equals(java.lang.Object other) + + public final android.util.Size getNullable() + + public final android.util.Size getSize() + + public int hashCode() + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (size, Landroid/util/Size;) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + ALOAD (0) + GETFIELD (nullable, Landroid/util/Size;) + ASTORE (3) + ALOAD (3) + IFNONNULL (L1) + ALOAD (1) + ICONST_0 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + GOTO (L2) + LABEL (L1) + ALOAD (1) + ICONST_1 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (1) + ALOAD (3) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} + +public final class TestF$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestF createFromParcel(android.os.Parcel parcel) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFNE (L2) + ACONST_NULL + GOTO (L3) + LABEL (L2) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;) + LABEL (L3) + INVOKESPECIAL (TestF, , (Landroid/util/SizeF;Landroid/util/SizeF;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel source) { + LABEL (L0) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (TestF$Creator, createFromParcel, (Landroid/os/Parcel;)LTestF;) + ARETURN + LABEL (L1) + } + + public final TestF[] newArray(int size) + + public java.lang.Object[] newArray(int size) +} + +public final class TestF : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.SizeF nullable + + private final android.util.SizeF size + + static void () + + public void (android.util.SizeF size, android.util.SizeF nullable) + + public final android.util.SizeF component1() + + public final android.util.SizeF component2() + + public final TestF copy(android.util.SizeF size, android.util.SizeF nullable) + + public static TestF copy$default(TestF p0, android.util.SizeF p1, android.util.SizeF p2, int p3, java.lang.Object p4) + + public int describeContents() + + public boolean equals(java.lang.Object other) + + public final android.util.SizeF getNullable() + + public final android.util.SizeF getSize() + + public int hashCode() + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel out, int flags) { + LABEL (L0) + ALOAD (1) + LDC (out) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (size, Landroid/util/SizeF;) + INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V) + ALOAD (0) + GETFIELD (nullable, Landroid/util/SizeF;) + ASTORE (3) + ALOAD (3) + IFNONNULL (L1) + ALOAD (1) + ICONST_0 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + GOTO (L2) + LABEL (L1) + ALOAD (1) + ICONST_1 + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + ALOAD (1) + ALOAD (3) + INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/size.kt b/plugins/parcelize/parcelize-compiler/testData/codegen/size.kt new file mode 100644 index 00000000000..ededf3ce94b --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/size.kt @@ -0,0 +1,13 @@ +// CURIOUS_ABOUT writeToParcel, createFromParcel +// WITH_RUNTIME + +import kotlinx.parcelize.* +import android.os.Parcelable +import android.util.Size +import android.util.SizeF + +@Parcelize +data class Test(val size: Size, val nullable: Size?) : Parcelable + +@Parcelize +data class TestF(val size: SizeF, val nullable: SizeF?) : Parcelable \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/testData/codegen/size.txt b/plugins/parcelize/parcelize-compiler/testData/codegen/size.txt new file mode 100644 index 00000000000..46e54a909f7 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/testData/codegen/size.txt @@ -0,0 +1,201 @@ +public final class Test$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final Test createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSize, ()Landroid/util/Size;) + GOTO (L3) + LABEL (L2) + ACONST_NULL + LABEL (L3) + INVOKESPECIAL (Test, , (Landroid/util/Size;Landroid/util/Size;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (10) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (Test$Creator, createFromParcel, (Landroid/os/Parcel;)LTest;) + ARETURN + } + + public final Test[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class Test : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.Size nullable + + private final android.util.Size size + + static void () + + public void (android.util.Size size, android.util.Size nullable) + + public final android.util.Size component1() + + public final android.util.Size component2() + + public final Test copy(android.util.Size size, android.util.Size nullable) + + public static Test copy$default(Test p0, android.util.Size p1, android.util.Size p2, int p3, java.lang.Object p4) + + public int describeContents() + + public boolean equals(java.lang.Object p0) + + public final android.util.Size getNullable() + + public final android.util.Size getSize() + + public int hashCode() + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (size, Landroid/util/Size;) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (nullable, Landroid/util/Size;) + DUP + IFNULL (L1) + ALOAD (1) + LDC (1) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEVIRTUAL (android/os/Parcel, writeSize, (Landroid/util/Size;)V) + GOTO (L2) + LABEL (L1) + POP + LDC (0) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} + +public final class TestF$Creator : java/lang/Object, android/os/Parcelable$Creator { + public void () + + public final TestF createFromParcel(android.os.Parcel in) { + LABEL (L0) + ALOAD (1) + LDC (in) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + LABEL (L1) + NEW + DUP + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readInt, ()I) + IFEQ (L2) + ALOAD (1) + INVOKEVIRTUAL (android/os/Parcel, readSizeF, ()Landroid/util/SizeF;) + GOTO (L3) + LABEL (L2) + ACONST_NULL + LABEL (L3) + INVOKESPECIAL (TestF, , (Landroid/util/SizeF;Landroid/util/SizeF;)V) + ARETURN + LABEL (L4) + } + + public java.lang.Object createFromParcel(android.os.Parcel p0) { + LABEL (L0) + LINENUMBER (13) + ALOAD (0) + ALOAD (1) + INVOKEVIRTUAL (TestF$Creator, createFromParcel, (Landroid/os/Parcel;)LTestF;) + ARETURN + } + + public final TestF[] newArray(int size) + + public java.lang.Object[] newArray(int p0) +} + +public final class TestF : java/lang/Object, android/os/Parcelable { + public final static android.os.Parcelable$Creator CREATOR + + private final android.util.SizeF nullable + + private final android.util.SizeF size + + static void () + + public void (android.util.SizeF size, android.util.SizeF nullable) + + public final android.util.SizeF component1() + + public final android.util.SizeF component2() + + public final TestF copy(android.util.SizeF size, android.util.SizeF nullable) + + public static TestF copy$default(TestF p0, android.util.SizeF p1, android.util.SizeF p2, int p3, java.lang.Object p4) + + public int describeContents() + + public boolean equals(java.lang.Object p0) + + public final android.util.SizeF getNullable() + + public final android.util.SizeF getSize() + + public int hashCode() + + public java.lang.String toString() + + public void writeToParcel(android.os.Parcel parcel, int flags) { + LABEL (L0) + ALOAD (1) + LDC (parcel) + INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkNotNullParameter, (Ljava/lang/Object;Ljava/lang/String;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (size, Landroid/util/SizeF;) + INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V) + ALOAD (1) + ALOAD (0) + GETFIELD (nullable, Landroid/util/SizeF;) + DUP + IFNULL (L1) + ALOAD (1) + LDC (1) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + INVOKEVIRTUAL (android/os/Parcel, writeSizeF, (Landroid/util/SizeF;)V) + GOTO (L2) + LABEL (L1) + POP + LDC (0) + INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V) + LABEL (L2) + RETURN + LABEL (L3) + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBoxTest.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBoxTest.kt new file mode 100644 index 00000000000..b14b28f295a --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBoxTest.kt @@ -0,0 +1,186 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.test + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot +import org.jetbrains.kotlin.codegen.CodegenTestCase +import org.jetbrains.kotlin.codegen.getClassFiles +import org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.utils.PathUtil +import org.jetbrains.org.objectweb.asm.ClassWriter +import org.jetbrains.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES +import org.jetbrains.org.objectweb.asm.ClassWriter.COMPUTE_MAXS +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.junit.runner.JUnitCore +import java.io.File +import java.nio.file.Files +import java.util.concurrent.TimeUnit + +abstract class AbstractParcelizeIrBoxTest : AbstractParcelizeBoxTest() { + override val backend = TargetBackend.JVM_IR +} + +abstract class AbstractParcelizeBoxTest : CodegenTestCase() { + companion object { + val LIBRARY_KT = File("plugins/parcelize/parcelize-compiler/testData/boxLib.kt") + + private val androidPluginPath: String by lazy { + System.getProperty("ideaSdk.androidPlugin.path")?.takeIf { File(it).isDirectory } + ?: throw RuntimeException("Unable to get a valid path from 'ideaSdk.androidPlugin.path' property, please point it to the Idea android plugin location") + } + + private fun getLayoutLibFile(pattern: String): File { + val nameRegex = "^$pattern-[0-9\\.]+\\.jar$".toRegex() + return File(androidPluginPath).listFiles().orEmpty().singleOrNull { it.name.matches(nameRegex) } + ?: error("Can't find file for pattern $nameRegex in $androidPluginPath. " + + "Available files: \n${File(androidPluginPath).list().orEmpty().asList()}") + } + + val layoutlibJar: File by lazy { getLayoutLibFile("layoutlib(-jre[0-9]+)?") } + val layoutlibApiJar: File by lazy { getLayoutLibFile("layoutlib-api") } + + private val JUNIT_GENERATED_TEST_CLASS_BYTES by lazy { constructSyntheticTestClass() } + private const val JUNIT_GENERATED_TEST_CLASS_FQNAME = "test.JunitTest" + + private fun constructSyntheticTestClass(): ByteArray { + return with(ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES)) { + visit(49, ACC_PUBLIC, JUNIT_GENERATED_TEST_CLASS_FQNAME.replace('.', '/'), null, "java/lang/Object", emptyArray()) + visitSource(null, null) + + with(visitAnnotation("Lorg/junit/runner/RunWith;", true)) { + visit("value", Type.getType("Lorg/robolectric/RobolectricTestRunner;")) + visitEnd() + } + + with(visitAnnotation("Lorg/robolectric/annotation/Config;", true)) { + visit("sdk", intArrayOf(21)) + visit("manifest", "--none") + visitEnd() + } + + with(visitMethod(ACC_PUBLIC, "", "()V", null, null)) { + visitVarInsn(ALOAD, 0) + visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) + + visitInsn(RETURN) + visitMaxs(-1, -1) + visitEnd() + } + + with(visitMethod(ACC_PUBLIC, "test", "()V", null, null)) { + visitAnnotation("Lorg/junit/Test;", true).visitEnd() + + val v = InstructionAdapter(this) + + val assertionOk = Label() + + v.invokestatic("test/TestKt", "box", "()Ljava/lang/String;", false) // -> ret + v.dup() // -> ret, ret + v.aconst("OK") // -> ret, ret, ok + v.invokevirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z", false) // -> ret, eq + v.ifne(assertionOk) // -> ret + + val assertionErrorType = Type.getObjectType("java/lang/AssertionError") + + v.anew(assertionErrorType) // -> ret, ae + v.dupX1() // -> ae, ret, ae + v.swap() // -> ae, ae, ret + v.invokespecial(assertionErrorType.internalName, "", "(Ljava/lang/Object;)V", false) // -> ae + v.athrow() + + v.visitLabel(assertionOk) + v.pop() // -> [empty] + v.areturn(Type.VOID_TYPE) + + visitMaxs(-1, -1) + visitEnd() + } + + visitEnd() + toByteArray() + } + } + } + + private fun getClasspathForTest(): List { + val kotlinRuntimeJar = PathUtil.kotlinPathsForIdeaPlugin.stdlibPath + + val robolectricClasspath = System.getProperty("robolectric.classpath") + ?: throw RuntimeException("Unable to get a valid classpath from 'robolectric.classpath' property, please set it accordingly") + val robolectricJars = robolectricClasspath.split(File.pathSeparator) + .map { File(it) } + .sortedBy { it.nameWithoutExtension } + + val junitCoreResourceName = JUnitCore::class.java.name.replace('.', '/') + ".class" + val junitJar = File( + JUnitCore::class.java.classLoader.getResource(junitCoreResourceName)!!.file + .substringAfter("file:") + .substringBeforeLast('!') + ) + + val parcelizeRuntimeJars = System.getProperty("parcelizeRuntime.classpath")?.split(File.pathSeparator)?.map(::File) + ?: error("Unable to get a valid classpath from 'parcelizeRuntime.classpath' property") + + return listOf(kotlinRuntimeJar, layoutlibJar, layoutlibApiJar) + robolectricJars + junitJar + parcelizeRuntimeJars + } + + override fun doMultiFileTest(wholeFile: File, files: List) { + compile(files + TestFile(LIBRARY_KT.name, LIBRARY_KT.readText())) + + val javaBin = File(System.getProperty("java.home").takeIf { it.isNotEmpty() } ?: error("JAVA_HOME is not set"), "bin") + val javaExe = File(javaBin, "java.exe").takeIf { it.exists() } ?: File(javaBin, "java") + assert(javaExe.exists()) { "Can't find 'java' executable in $javaBin" } + + val libraryClasspath = getClasspathForTest() + val dirForTestClasses = Files.createTempDirectory("parcel").toFile() + + fun writeClass(fqNameOrPath: String, bytes: ByteArray) { + val path = if (fqNameOrPath.endsWith(".class")) fqNameOrPath else (fqNameOrPath.replace('.', '/') + ".class") + File(dirForTestClasses, path).also { it.parentFile.mkdirs() }.writeBytes(bytes) + } + + try { + writeClass(JUNIT_GENERATED_TEST_CLASS_FQNAME, JUNIT_GENERATED_TEST_CLASS_BYTES) + classFileFactory.getClassFiles().forEach { writeClass(it.relativePath, it.asByteArray()) } + javaClassesOutputDirectory?.listFiles()?.forEach { writeClass(it.name, it.readBytes()) } + + val process = ProcessBuilder( + javaExe.absolutePath, + "-ea", + "-classpath", + (libraryClasspath + dirForTestClasses).joinToString(File.pathSeparator), + JUnitCore::class.java.name, + JUNIT_GENERATED_TEST_CLASS_FQNAME + ).start() + + process.waitFor(3, TimeUnit.MINUTES) + println(process.inputStream.bufferedReader().lineSequence().joinToString("\n")) + if (process.exitValue() != 0) { + throw AssertionError("Process exited with exit code ${process.exitValue()} \n" + classFileFactory.createText()) + } + } finally { + if (!dirForTestClasses.deleteRecursively()) { + throw AssertionError("Unable to delete $dirForTestClasses") + } + } + } + + override fun setupEnvironment(environment: KotlinCoreEnvironment) { + ParcelizeComponentRegistrar.registerParcelizeComponents(environment.project) + addParcelizeRuntimeLibrary(environment) + environment.updateClasspath(listOf(JvmClasspathRoot(KotlinTestUtils.findAndroidApiJar()))) + } + + override fun updateJavaClasspath(javaClasspath: MutableList) { + javaClasspath += KotlinTestUtils.findAndroidApiJar().absolutePath + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBytecodeListingTest.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBytecodeListingTest.kt new file mode 100644 index 00000000000..fe93dcb66a7 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/AbstractParcelizeBytecodeListingTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.test + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.AbstractAsmLikeInstructionListingTest +import org.jetbrains.kotlin.parcelize.ParcelizeComponentRegistrar +import org.jetbrains.kotlin.test.TargetBackend +import java.io.File + +abstract class AbstractParcelizeBytecodeListingTest : AbstractAsmLikeInstructionListingTest() { + override fun setupEnvironment(environment: KotlinCoreEnvironment) { + ParcelizeComponentRegistrar.registerParcelizeComponents(environment.project) + addParcelizeRuntimeLibrary(environment) + addAndroidJarLibrary(environment) + } +} + +abstract class AbstractParcelizeIrBytecodeListingTest : AbstractParcelizeBytecodeListingTest() { + override val backend = TargetBackend.JVM_IR + + override fun getExpectedTextFileName(wholeFile: File): String { + return wholeFile.nameWithoutExtension + ".ir.txt" + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java new file mode 100644 index 00000000000..96323a1dc4f --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestGenerated.java @@ -0,0 +1,291 @@ +/* + * Copyright 2010-2020 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/parcelize/parcelize-compiler/testData/box") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInBox() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/parcelize/parcelize-compiler/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("allPrimitiveTypes.kt") + public void testAllPrimitiveTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt"); + } + + @TestMetadata("arraySimple.kt") + public void testArraySimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt"); + } + + @TestMetadata("arrays.kt") + public void testArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/arrays.kt"); + } + + @TestMetadata("binder.kt") + public void testBinder() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/binder.kt"); + } + + @TestMetadata("boxedTypes.kt") + public void testBoxedTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt"); + } + + @TestMetadata("bundle.kt") + public void testBundle() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/bundle.kt"); + } + + @TestMetadata("charSequence.kt") + public void testCharSequence() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt"); + } + + @TestMetadata("customNewArray.kt") + public void testCustomNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt"); + } + + @TestMetadata("customParcelable.kt") + public void testCustomParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt"); + } + + @TestMetadata("customParcelerScoping.kt") + public void testCustomParcelerScoping() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt"); + } + + @TestMetadata("customSerializerBoxing.kt") + public void testCustomSerializerBoxing() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt"); + } + + @TestMetadata("customSerializerSimple.kt") + public void testCustomSerializerSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt"); + } + + @TestMetadata("customSerializerWriteWith.kt") + public void testCustomSerializerWriteWith() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt"); + } + + @TestMetadata("customSimple.kt") + public void testCustomSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt"); + } + + @TestMetadata("enumObject.kt") + public void testEnumObject() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt"); + } + + @TestMetadata("enums.kt") + public void testEnums() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/enums.kt"); + } + + @TestMetadata("exceptions.kt") + public void testExceptions() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt"); + } + + @TestMetadata("functions.kt") + public void testFunctions() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/functions.kt"); + } + + @TestMetadata("intArray.kt") + public void testIntArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/intArray.kt"); + } + + @TestMetadata("javaInterop.kt") + public void testJavaInterop() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt"); + } + + @TestMetadata("kt19747.kt") + public void testKt19747() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt"); + } + + @TestMetadata("kt19747_2.kt") + public void testKt19747_2() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt"); + } + + @TestMetadata("kt19749.kt") + public void testKt19749() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt"); + } + + @TestMetadata("kt20002.kt") + public void testKt20002() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt"); + } + + @TestMetadata("kt20021.kt") + public void testKt20021() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt"); + } + + @TestMetadata("kt20717.kt") + public void testKt20717() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt"); + } + + @TestMetadata("kt25839.kt") + public void testKt25839() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt"); + } + + @TestMetadata("kt26221.kt") + public void testKt26221() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt"); + } + + @TestMetadata("kt36658.kt") + public void testKt36658() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt"); + } + + @TestMetadata("kt39981.kt") + public void testKt39981() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt"); + } + + @TestMetadata("listKinds.kt") + public void testListKinds() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt"); + } + + @TestMetadata("listSimple.kt") + public void testListSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt"); + } + + @TestMetadata("lists.kt") + public void testLists() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/lists.kt"); + } + + @TestMetadata("mapKinds.kt") + public void testMapKinds() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt"); + } + + @TestMetadata("mapSimple.kt") + public void testMapSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt"); + } + + @TestMetadata("maps.kt") + public void testMaps() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/maps.kt"); + } + + @TestMetadata("nestedArrays.kt") + public void testNestedArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt"); + } + + @TestMetadata("nestedLists.kt") + public void testNestedLists() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt"); + } + + @TestMetadata("nestedMaps.kt") + public void testNestedMaps() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt"); + } + + @TestMetadata("nestedParcelable.kt") + public void testNestedParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt"); + } + + @TestMetadata("nestedSparseArrays.kt") + public void testNestedSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt"); + } + + @TestMetadata("newArray.kt") + public void testNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/newArray.kt"); + } + + @TestMetadata("nullableTypes.kt") + public void testNullableTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt"); + } + + @TestMetadata("nullableTypesSimple.kt") + public void testNullableTypesSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt"); + } + + @TestMetadata("objects.kt") + public void testObjects() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/objects.kt"); + } + + @TestMetadata("openParcelize.kt") + public void testOpenParcelize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt"); + } + + @TestMetadata("persistableBundle.kt") + public void testPersistableBundle() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt"); + } + + @TestMetadata("primitiveTypes.kt") + public void testPrimitiveTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt"); + } + + @TestMetadata("sealedClass.kt") + public void testSealedClass() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/simple.kt"); + } + + @TestMetadata("sparseArrays.kt") + public void testSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt"); + } + + @TestMetadata("sparseBooleanArray.kt") + public void testSparseBooleanArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt"); + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestWithSerializableLikeExtension.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestWithSerializableLikeExtension.kt new file mode 100644 index 00000000000..c3c94318b9a --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBoxTestWithSerializableLikeExtension.kt @@ -0,0 +1,44 @@ +/* + * 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.test + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension + +class ParcelizeBoxTestWithSerializableLikeExtension : AbstractParcelizeBoxTest() { + fun testSimple() = doTest("plugins/parcelize/parcelize-compiler/testData/box/simple.kt") + + override fun setupEnvironment(environment: KotlinCoreEnvironment) { + super.setupEnvironment(environment) + SyntheticResolveExtension.registerExtension(environment.project, SerializableLike()) + } + + private class SerializableLike : SyntheticResolveExtension { + override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? { + fun ClassDescriptor.isSerializableLike() = annotations.hasAnnotation(FqName("test.SerializableLike")) + + return when { + thisDescriptor.kind == ClassKind.CLASS && thisDescriptor.isSerializableLike() -> Name.identifier("Companion") + else -> return null + } + } + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBytecodeListingTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBytecodeListingTestGenerated.java new file mode 100644 index 00000000000..89fd28d75b6 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeBytecodeListingTestGenerated.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2020 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/parcelize/parcelize-compiler/testData/codegen") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ParcelizeBytecodeListingTestGenerated extends AbstractParcelizeBytecodeListingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInCodegen() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/parcelize/parcelize-compiler/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @TestMetadata("customDescribeContents.kt") + public void testCustomDescribeContents() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt"); + } + + @TestMetadata("customParcelablesDifferentModule.kt") + public void testCustomParcelablesDifferentModule() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt"); + } + + @TestMetadata("customParcelablesSameModule.kt") + public void testCustomParcelablesSameModule() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt"); + } + + @TestMetadata("customSimple.kt") + public void testCustomSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt"); + } + + @TestMetadata("customSimpleWithNewArray.kt") + public void testCustomSimpleWithNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt"); + } + + @TestMetadata("describeContentsFromSuperType.kt") + public void testDescribeContentsFromSuperType() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt"); + } + + @TestMetadata("duplicatingClinit.kt") + public void testDuplicatingClinit() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt"); + } + + @TestMetadata("efficientParcelable.kt") + public void testEfficientParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt"); + } + + @TestMetadata("IBinderIInterface.kt") + public void testIBinderIInterface() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt"); + } + + @TestMetadata("kt25839.kt") + public void testKt25839() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt"); + } + + @TestMetadata("listInsideList.kt") + public void testListInsideList() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt"); + } + + @TestMetadata("nullableNotNullSize.kt") + public void testNullableNotNullSize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt"); + } + + @TestMetadata("parcelable.kt") + public void testParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt"); + } + + @TestMetadata("serializable.kt") + public void testSerializable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt"); + } + + @TestMetadata("serializeValue.kt") + public void testSerializeValue() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt"); + } + + @TestMetadata("simpleList.kt") + public void testSimpleList() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt"); + } + + @TestMetadata("size.kt") + public void testSize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/size.kt"); + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java new file mode 100644 index 00000000000..b950e63b92d --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestGenerated.java @@ -0,0 +1,291 @@ +/* + * Copyright 2010-2020 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/parcelize/parcelize-compiler/testData/box") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInBox() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/parcelize/parcelize-compiler/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("allPrimitiveTypes.kt") + public void testAllPrimitiveTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/allPrimitiveTypes.kt"); + } + + @TestMetadata("arraySimple.kt") + public void testArraySimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/arraySimple.kt"); + } + + @TestMetadata("arrays.kt") + public void testArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/arrays.kt"); + } + + @TestMetadata("binder.kt") + public void testBinder() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/binder.kt"); + } + + @TestMetadata("boxedTypes.kt") + public void testBoxedTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/boxedTypes.kt"); + } + + @TestMetadata("bundle.kt") + public void testBundle() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/bundle.kt"); + } + + @TestMetadata("charSequence.kt") + public void testCharSequence() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/charSequence.kt"); + } + + @TestMetadata("customNewArray.kt") + public void testCustomNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customNewArray.kt"); + } + + @TestMetadata("customParcelable.kt") + public void testCustomParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customParcelable.kt"); + } + + @TestMetadata("customParcelerScoping.kt") + public void testCustomParcelerScoping() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customParcelerScoping.kt"); + } + + @TestMetadata("customSerializerBoxing.kt") + public void testCustomSerializerBoxing() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerBoxing.kt"); + } + + @TestMetadata("customSerializerSimple.kt") + public void testCustomSerializerSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerSimple.kt"); + } + + @TestMetadata("customSerializerWriteWith.kt") + public void testCustomSerializerWriteWith() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSerializerWriteWith.kt"); + } + + @TestMetadata("customSimple.kt") + public void testCustomSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/customSimple.kt"); + } + + @TestMetadata("enumObject.kt") + public void testEnumObject() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/enumObject.kt"); + } + + @TestMetadata("enums.kt") + public void testEnums() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/enums.kt"); + } + + @TestMetadata("exceptions.kt") + public void testExceptions() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/exceptions.kt"); + } + + @TestMetadata("functions.kt") + public void testFunctions() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/functions.kt"); + } + + @TestMetadata("intArray.kt") + public void testIntArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/intArray.kt"); + } + + @TestMetadata("javaInterop.kt") + public void testJavaInterop() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/javaInterop.kt"); + } + + @TestMetadata("kt19747.kt") + public void testKt19747() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19747.kt"); + } + + @TestMetadata("kt19747_2.kt") + public void testKt19747_2() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19747_2.kt"); + } + + @TestMetadata("kt19749.kt") + public void testKt19749() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt19749.kt"); + } + + @TestMetadata("kt20002.kt") + public void testKt20002() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20002.kt"); + } + + @TestMetadata("kt20021.kt") + public void testKt20021() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20021.kt"); + } + + @TestMetadata("kt20717.kt") + public void testKt20717() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt20717.kt"); + } + + @TestMetadata("kt25839.kt") + public void testKt25839() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt25839.kt"); + } + + @TestMetadata("kt26221.kt") + public void testKt26221() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt26221.kt"); + } + + @TestMetadata("kt36658.kt") + public void testKt36658() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt36658.kt"); + } + + @TestMetadata("kt39981.kt") + public void testKt39981() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/kt39981.kt"); + } + + @TestMetadata("listKinds.kt") + public void testListKinds() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listKinds.kt"); + } + + @TestMetadata("listSimple.kt") + public void testListSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/listSimple.kt"); + } + + @TestMetadata("lists.kt") + public void testLists() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/lists.kt"); + } + + @TestMetadata("mapKinds.kt") + public void testMapKinds() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapKinds.kt"); + } + + @TestMetadata("mapSimple.kt") + public void testMapSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/mapSimple.kt"); + } + + @TestMetadata("maps.kt") + public void testMaps() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/maps.kt"); + } + + @TestMetadata("nestedArrays.kt") + public void testNestedArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedArrays.kt"); + } + + @TestMetadata("nestedLists.kt") + public void testNestedLists() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedLists.kt"); + } + + @TestMetadata("nestedMaps.kt") + public void testNestedMaps() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedMaps.kt"); + } + + @TestMetadata("nestedParcelable.kt") + public void testNestedParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedParcelable.kt"); + } + + @TestMetadata("nestedSparseArrays.kt") + public void testNestedSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nestedSparseArrays.kt"); + } + + @TestMetadata("newArray.kt") + public void testNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/newArray.kt"); + } + + @TestMetadata("nullableTypes.kt") + public void testNullableTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypes.kt"); + } + + @TestMetadata("nullableTypesSimple.kt") + public void testNullableTypesSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/nullableTypesSimple.kt"); + } + + @TestMetadata("objects.kt") + public void testObjects() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/objects.kt"); + } + + @TestMetadata("openParcelize.kt") + public void testOpenParcelize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/openParcelize.kt"); + } + + @TestMetadata("persistableBundle.kt") + public void testPersistableBundle() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/persistableBundle.kt"); + } + + @TestMetadata("primitiveTypes.kt") + public void testPrimitiveTypes() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/primitiveTypes.kt"); + } + + @TestMetadata("sealedClass.kt") + public void testSealedClass() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sealedClass.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/simple.kt"); + } + + @TestMetadata("sparseArrays.kt") + public void testSparseArrays() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sparseArrays.kt"); + } + + @TestMetadata("sparseBooleanArray.kt") + public void testSparseBooleanArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/box/sparseBooleanArray.kt"); + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestWithSerializableLikeExtension.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestWithSerializableLikeExtension.kt new file mode 100644 index 00000000000..9fe3959d8ad --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBoxTestWithSerializableLikeExtension.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2020 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.test + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension + +class ParcelizeIrBoxTestWithSerializableLikeExtension : AbstractParcelizeIrBoxTest() { + fun testSimple() = doTest("plugins/parcelize/parcelize-compiler/testData/box/simple.kt") + + override fun setupEnvironment(environment: KotlinCoreEnvironment) { + super.setupEnvironment(environment) + SyntheticResolveExtension.registerExtension(environment.project, SerializableLike()) + } + + private class SerializableLike : SyntheticResolveExtension { + override fun getSyntheticCompanionObjectNameIfNeeded(thisDescriptor: ClassDescriptor): Name? { + fun ClassDescriptor.isSerializableLike() = annotations.hasAnnotation(FqName("test.SerializableLike")) + + return when { + thisDescriptor.kind == ClassKind.CLASS && thisDescriptor.isSerializableLike() -> Name.identifier("Companion") + else -> return null + } + } + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBytecodeListingTestGenerated.java b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBytecodeListingTestGenerated.java new file mode 100644 index 00000000000..ff96ef25d2d --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/ParcelizeIrBytecodeListingTestGenerated.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2020 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.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/parcelize/parcelize-compiler/testData/codegen") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ParcelizeIrBytecodeListingTestGenerated extends AbstractParcelizeIrBytecodeListingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInCodegen() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/parcelize/parcelize-compiler/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("customDescribeContents.kt") + public void testCustomDescribeContents() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customDescribeContents.kt"); + } + + @TestMetadata("customParcelablesDifferentModule.kt") + public void testCustomParcelablesDifferentModule() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesDifferentModule.kt"); + } + + @TestMetadata("customParcelablesSameModule.kt") + public void testCustomParcelablesSameModule() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customParcelablesSameModule.kt"); + } + + @TestMetadata("customSimple.kt") + public void testCustomSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customSimple.kt"); + } + + @TestMetadata("customSimpleWithNewArray.kt") + public void testCustomSimpleWithNewArray() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/customSimpleWithNewArray.kt"); + } + + @TestMetadata("describeContentsFromSuperType.kt") + public void testDescribeContentsFromSuperType() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/describeContentsFromSuperType.kt"); + } + + @TestMetadata("duplicatingClinit.kt") + public void testDuplicatingClinit() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/duplicatingClinit.kt"); + } + + @TestMetadata("efficientParcelable.kt") + public void testEfficientParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/efficientParcelable.kt"); + } + + @TestMetadata("IBinderIInterface.kt") + public void testIBinderIInterface() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/IBinderIInterface.kt"); + } + + @TestMetadata("kt25839.kt") + public void testKt25839() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/kt25839.kt"); + } + + @TestMetadata("listInsideList.kt") + public void testListInsideList() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/listInsideList.kt"); + } + + @TestMetadata("nullableNotNullSize.kt") + public void testNullableNotNullSize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/nullableNotNullSize.kt"); + } + + @TestMetadata("parcelable.kt") + public void testParcelable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/parcelable.kt"); + } + + @TestMetadata("serializable.kt") + public void testSerializable() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/serializable.kt"); + } + + @TestMetadata("serializeValue.kt") + public void testSerializeValue() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/serializeValue.kt"); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simple.kt"); + } + + @TestMetadata("simpleList.kt") + public void testSimpleList() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/simpleList.kt"); + } + + @TestMetadata("size.kt") + public void testSize() throws Exception { + runTest("plugins/parcelize/parcelize-compiler/testData/codegen/size.kt"); + } +} diff --git a/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/testUtils.kt b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/testUtils.kt new file mode 100644 index 00000000000..3c85c3e8a98 --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/tests/org/jetbrains/kotlin/parcelize/test/testUtils.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2020 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.test + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File + +fun addParcelizeRuntimeLibrary(environment: KotlinCoreEnvironment) { + val runtimeLibrary = File(PathUtil.kotlinPathsForCompiler.libPath, "parcelize-runtime.jar") + environment.updateClasspath(listOf(JvmClasspathRoot(runtimeLibrary))) +} + +fun addAndroidJarLibrary(environment: KotlinCoreEnvironment) { + environment.updateClasspath(listOf(JvmClasspathRoot(KotlinTestUtils.findAndroidApiJar()))) +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/build.gradle.kts b/plugins/parcelize/parcelize-runtime/build.gradle.kts new file mode 100644 index 00000000000..95c548c287f --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/build.gradle.kts @@ -0,0 +1,26 @@ +description = "Runtime library for the Parcelize compiler plugin" + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +jvmTarget = "1.6" + +dependencies { + compile(kotlinStdlib()) + compileOnly(commonDep("com.google.android", "android")) +} + +sourceSets { + "main" { projectDefault() } + "test" {} +} + +publish { + artifactId = "kotlin-parcelize-runtime" +} + +runtimeJar() +sourcesJar() +javadocJar() \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/IgnoredOnParcel.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/IgnoredOnParcel.kt new file mode 100644 index 00000000000..7a4af8cdfa1 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/IgnoredOnParcel.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlinx.parcelize + +/** + * The property annotated with [IgnoredOnParcel] will not be stored into parcel. + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.SOURCE) +annotation class IgnoredOnParcel \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parceler.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parceler.kt new file mode 100644 index 00000000000..566ea7c2497 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parceler.kt @@ -0,0 +1,42 @@ +/* + * 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. + */ + +@file:Suppress("unused") +package kotlinx.parcelize + +import android.os.Parcel + +/** + * The base interface for custom [Parcelize] serializers. + */ +interface Parceler { + /** + * Writes the [T] instance state to the [parcel]. + */ + fun T.write(parcel: Parcel, flags: Int) + + /** + * Reads the [T] instance state from the [parcel], constructs the new [T] instance and returns it. + */ + fun create(parcel: Parcel): T + + /** + * Returns a new [Array] with the given array [size]. + */ + fun newArray(size: Int): Array { + throw NotImplementedError("Generated by Android Extensions automatically") + } +} \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parcelize.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parcelize.kt new file mode 100644 index 00000000000..5ac1d0393f9 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/Parcelize.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlinx.parcelize + +/** + * Instructs the Kotlin compiler to generate `writeToParcel()`, `describeContents()` [android.os.Parcelable] methods, + * as well as a `CREATOR` factory class automatically. + * + * The annotation is applicable only to classes that implements [android.os.Parcelable] (directly or indirectly). + * Note that only the primary constructor properties will be serialized. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class Parcelize \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/RawValue.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/RawValue.kt new file mode 100644 index 00000000000..811fbb43738 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/RawValue.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlinx.parcelize + +/** + * Write the corresponding property value with [android.os.Parcel.writeValue]. + * Serialization may fail at runtime, depending on actual property value type. + */ +@Target(AnnotationTarget.TYPE) +@Retention(AnnotationRetention.BINARY) +annotation class RawValue \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/TypeParceler.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/TypeParceler.kt new file mode 100644 index 00000000000..3659709a613 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/TypeParceler.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlinx.parcelize + +/** + * Specifies what [Parceler] should be used for a particular type [T]. + */ +@Retention(AnnotationRetention.SOURCE) +@Repeatable +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +annotation class TypeParceler> \ No newline at end of file diff --git a/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/WriteWith.kt b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/WriteWith.kt new file mode 100644 index 00000000000..b45d55b5d64 --- /dev/null +++ b/plugins/parcelize/parcelize-runtime/src/kotlinx/parcelize/WriteWith.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlinx.parcelize + +/** + * Specifies what [Parceler] should be used for the annotated type. + */ +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.TYPE) +annotation class WriteWith

> \ No newline at end of file diff --git a/prepare/compiler/build.gradle.kts b/prepare/compiler/build.gradle.kts index bc98b4f8dee..a120c2d507b 100644 --- a/prepare/compiler/build.gradle.kts +++ b/prepare/compiler/build.gradle.kts @@ -112,6 +112,8 @@ val distLibraryProjects = listOfNotNull( val distCompilerPluginProjects = listOf( ":kotlin-allopen-compiler-plugin", ":kotlin-android-extensions-runtime", + ":plugins:parcelize:parcelize-compiler", + ":plugins:parcelize:parcelize-runtime", ":kotlin-noarg-compiler-plugin", ":kotlin-sam-with-receiver-compiler-plugin", ":kotlinx-serialization-compiler-plugin" diff --git a/prepare/parcelize-compiler-gradle/build.gradle.kts b/prepare/parcelize-compiler-gradle/build.gradle.kts new file mode 100644 index 00000000000..6f78dff311a --- /dev/null +++ b/prepare/parcelize-compiler-gradle/build.gradle.kts @@ -0,0 +1,35 @@ +import org.gradle.jvm.tasks.Jar + +description = "Parcelize compiler plugin" + +plugins { + kotlin("jvm") +} + +dependencies { + compileOnly(project(":compiler:util")) + compileOnly(project(":compiler:plugin-api")) + compileOnly(project(":compiler:frontend")) + compileOnly(project(":compiler:frontend.java")) + compileOnly(project(":compiler:backend")) + compileOnly(project(":plugins:parcelize:parcelize-runtime")) + runtimeOnly(projectRuntimeJar(":kotlin-compiler-embeddable")) + compileOnly(commonDep("com.google.android", "android")) + compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + + embedded(project(":plugins:parcelize:parcelize-compiler")) { isTransitive = false } + embedded(project(":plugins:parcelize:parcelize-runtime")) { isTransitive = false } +} + +sourceSets { + "main" { projectDefault() } + "test" {} +} + +publish() + +runtimeJar(rewriteDefaultJarDepsToShadedCompiler()) + +sourcesJar() + +javadocJar() \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index fe69d3e36fa..90ff89d80e5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -323,6 +323,10 @@ include ":compiler:fir:cones", include ":idea:idea-frontend-fir:idea-fir-low-level-api" +include ":plugins:parcelize:parcelize-compiler", + ":plugins:parcelize:parcelize-runtime", + ":kotlin-parcelize-compiler" + include ":prepare:ide-plugin-dependencies:android-extensions-compiler-plugin-for-ide", ":prepare:ide-plugin-dependencies:allopen-compiler-plugin-for-ide", ":prepare:ide-plugin-dependencies:allopen-compiler-plugin-tests-for-ide", @@ -479,6 +483,7 @@ project(':kotlin-jps-plugin').projectDir = "$rootDir/prepare/jps-plugin" as File project(':idea:idea-android-output-parser').projectDir = "$rootDir/idea/idea-android/idea-android-output-parser" as File project(':plugins:android-extensions-compiler').projectDir = "$rootDir/plugins/android-extensions/android-extensions-compiler" as File project(':kotlin-android-extensions').projectDir = "$rootDir/prepare/android-extensions-compiler-gradle" as File +project(':kotlin-parcelize-compiler').projectDir = "$rootDir/prepare/parcelize-compiler-gradle" as File project(':kotlin-android-extensions-runtime').projectDir = "$rootDir/plugins/android-extensions/android-extensions-runtime" as File project(':plugins:android-extensions-ide').projectDir = "$rootDir/plugins/android-extensions/android-extensions-idea" as File project(':kotlin-allopen-compiler-plugin').projectDir = "$rootDir/plugins/allopen/allopen-cli" as File