Parcelize: Support @IgnoreOnParcel primary constructor parameters...
...with default values. See https://issuetracker.google.com/177850560
This commit is contained in:
committed by
Alexander Udalov
parent
00289d3514
commit
58c687e42d
+3
-1
@@ -86,7 +86,9 @@ open class ParcelizeAnnotationChecker : CallChecker {
|
||||
|
||||
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))
|
||||
if (!element.hasDefaultValue()) {
|
||||
context.trace.report(ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY.on(annotationEntry))
|
||||
}
|
||||
} else if (element !is KtProperty || PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) !is KtClassOrObject) {
|
||||
context.trace.report(ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL.on(annotationEntry))
|
||||
}
|
||||
|
||||
+6
@@ -226,6 +226,12 @@ open class ParcelizeDeclarationChecker : DeclarationChecker {
|
||||
}
|
||||
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter] ?: return
|
||||
|
||||
// Don't check parameters which won't be serialized
|
||||
if (descriptor.annotations.any { it.fqName in IGNORED_ON_PARCEL_FQ_NAMES }) {
|
||||
return
|
||||
}
|
||||
|
||||
val type = descriptor.type
|
||||
|
||||
if (!type.isError && !containerClass.hasCustomParceler()) {
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ object DefaultErrorMessagesParcelize : DefaultErrorMessages.Extension {
|
||||
|
||||
MAP.put(
|
||||
ErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
|
||||
"'@IgnoredOnParcel' is inapplicable to properties declared in the primary constructor"
|
||||
"'@IgnoredOnParcel' is inapplicable to properties without default value declared in the primary constructor"
|
||||
)
|
||||
|
||||
MAP.put(
|
||||
|
||||
+19
-2
@@ -13,7 +13,8 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirConstructorChecker
|
||||
import org.jetbrains.kotlin.fir.declarations.FirConstructor
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR
|
||||
import org.jetbrains.kotlin.fir.expressions.classId
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames
|
||||
|
||||
object FirParcelizeConstructorChecker : FirConstructorChecker() {
|
||||
override fun check(declaration: FirConstructor, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
@@ -29,7 +30,23 @@ object FirParcelizeConstructorChecker : FirConstructorChecker() {
|
||||
} else {
|
||||
for (valueParameter in declaration.valueParameters) {
|
||||
if (valueParameter.source?.hasValOrVar() != true) {
|
||||
reporter.reportOn(valueParameter.source, PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR, context)
|
||||
reporter.reportOn(
|
||||
valueParameter.source,
|
||||
KtErrorsParcelize.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR,
|
||||
context
|
||||
)
|
||||
}
|
||||
if (valueParameter.defaultValue == null) {
|
||||
val illegalAnnotation = valueParameter.annotations.firstOrNull {
|
||||
it.classId in ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
|
||||
}
|
||||
if (illegalAnnotation != null) {
|
||||
reporter.reportOn(
|
||||
illegalAnnotation.source,
|
||||
KtErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -44,7 +44,6 @@ object FirParcelizePropertyChecker : FirPropertyChecker() {
|
||||
}
|
||||
if (fromPrimaryConstructor) {
|
||||
checkParcelableClassProperty(declaration, containingClassSymbol, context, reporter)
|
||||
checkIgnoredOnParcelUsage(declaration, context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +55,6 @@ object FirParcelizePropertyChecker : FirPropertyChecker() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIgnoredOnParcelUsage(property: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val illegalAnnotation = property.annotations.firstOrNull { it.classId in IGNORED_ON_PARCEL_CLASS_IDS } ?: return
|
||||
reporter.reportOn(illegalAnnotation.source, KtErrorsParcelize.INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY, context)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun checkParcelableClassProperty(
|
||||
property: FirProperty,
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ object KtDefaultErrorMessagesParcelize {
|
||||
|
||||
map.put(
|
||||
INAPPLICABLE_IGNORED_ON_PARCEL_CONSTRUCTOR_PROPERTY,
|
||||
"'@IgnoredOnParcel' is inapplicable to properties declared in the primary constructor"
|
||||
"'@IgnoredOnParcel' is inapplicable to properties without default value declared in the primary constructor"
|
||||
)
|
||||
|
||||
map.put(
|
||||
|
||||
+24
-15
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATE_FROM_PARCEL_NAME
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_FQ_NAMES
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.NEW_ARRAY_NAME
|
||||
import org.jetbrains.kotlin.parcelize.serializers.ParcelizeExtensionBase
|
||||
|
||||
@@ -34,8 +35,8 @@ abstract class ParcelizeIrTransformerBase(
|
||||
protected val deferredOperations = mutableListOf<() -> Unit>()
|
||||
protected fun defer(block: () -> Unit) = deferredOperations.add(block)
|
||||
|
||||
protected fun IrSimpleFunction.generateDescribeContentsBody(parcelableProperties: List<ParcelableProperty>) {
|
||||
val flags = if (parcelableProperties.any { it.field.type.containsFileDescriptors }) 1 else 0
|
||||
protected fun IrSimpleFunction.generateDescribeContentsBody(parcelableProperties: List<ParcelableProperty?>) {
|
||||
val flags = if (parcelableProperties.any { it != null && it.field.type.containsFileDescriptors }) 1 else 0
|
||||
body = context.createIrBuilder(symbol).run {
|
||||
irExprBody(irInt(flags))
|
||||
}
|
||||
@@ -44,7 +45,7 @@ abstract class ParcelizeIrTransformerBase(
|
||||
protected fun IrSimpleFunction.generateWriteToParcelBody(
|
||||
irClass: IrClass,
|
||||
parcelerObject: IrClass?,
|
||||
parcelableProperties: List<ParcelableProperty>,
|
||||
parcelableProperties: List<ParcelableProperty?>,
|
||||
receiverParameter: IrValueParameter,
|
||||
parcelParameter: IrValueParameter,
|
||||
flagsParameter: IrValueParameter
|
||||
@@ -57,12 +58,14 @@ abstract class ParcelizeIrTransformerBase(
|
||||
|
||||
parcelableProperties.isNotEmpty() ->
|
||||
for (property in parcelableProperties) {
|
||||
+writeParcelWith(
|
||||
property.parceler,
|
||||
parcelParameter,
|
||||
flagsParameter,
|
||||
irGetField(irGet(receiverParameter), property.field)
|
||||
)
|
||||
if (property != null) {
|
||||
+writeParcelWith(
|
||||
property.parceler,
|
||||
parcelParameter,
|
||||
flagsParameter,
|
||||
irGetField(irGet(receiverParameter), property.field)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else ->
|
||||
@@ -77,7 +80,7 @@ abstract class ParcelizeIrTransformerBase(
|
||||
}
|
||||
}
|
||||
|
||||
protected fun generateCreator(declaration: IrClass, parcelerObject: IrClass?, parcelableProperties: List<ParcelableProperty>) {
|
||||
protected fun generateCreator(declaration: IrClass, parcelerObject: IrClass?, parcelableProperties: List<ParcelableProperty?>) {
|
||||
// Since the `CREATOR` object cannot refer to the type parameters of the parcelable class we use a star projected type
|
||||
val declarationType = declaration.symbol.starProjectedType
|
||||
val creatorType = androidSymbols.androidOsParcelableCreator.typeWith(declarationType)
|
||||
@@ -136,7 +139,9 @@ abstract class ParcelizeIrTransformerBase(
|
||||
parcelableProperties.isNotEmpty() ->
|
||||
irCall(declaration.primaryConstructor!!).apply {
|
||||
for ((index, property) in parcelableProperties.withIndex()) {
|
||||
putValueArgument(index, readParcelWith(property.parceler, parcelParameter))
|
||||
if (property != null) {
|
||||
putValueArgument(index, readParcelWith(property.parceler, parcelParameter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +176,7 @@ abstract class ParcelizeIrTransformerBase(
|
||||
|
||||
private val serializerFactory = IrParcelSerializerFactory(androidSymbols)
|
||||
|
||||
protected val IrClass.parcelableProperties: List<ParcelableProperty>
|
||||
protected val IrClass.parcelableProperties: List<ParcelableProperty?>
|
||||
get() {
|
||||
if (kind != ClassKind.CLASS) return emptyList()
|
||||
|
||||
@@ -180,9 +185,13 @@ abstract class ParcelizeIrTransformerBase(
|
||||
|
||||
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)
|
||||
if (property.hasAnyAnnotation(IGNORED_ON_PARCEL_FQ_NAMES)) {
|
||||
null
|
||||
} else {
|
||||
val localScope = property.getParcelerScope(topLevelScope)
|
||||
ParcelableProperty(property.backingField!!) {
|
||||
serializerFactory.get(parameter.type, parcelizeType = defaultType, scope = localScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// WITH_STDLIB
|
||||
// IGNORE_BACKEND: JVM
|
||||
|
||||
@file:JvmName("TestKt")
|
||||
package test
|
||||
|
||||
import kotlinx.parcelize.*
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class T(val value: Int)
|
||||
|
||||
@Parcelize
|
||||
class A(
|
||||
val x: String,
|
||||
@IgnoredOnParcel
|
||||
val i1: String = "default",
|
||||
@IgnoredOnParcel
|
||||
val i2: T = T(10),
|
||||
val d: String = "default",
|
||||
) : Parcelable {
|
||||
@IgnoredOnParcel
|
||||
val a: String = x
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object B : Parcelable {
|
||||
@IgnoredOnParcel
|
||||
val x: T = T(2)
|
||||
}
|
||||
|
||||
fun box() = parcelTest { parcel ->
|
||||
val a1 = A("X", "i1", T(0), "d")
|
||||
|
||||
a1.writeToParcel(parcel, 0)
|
||||
B.writeToParcel(parcel, 0)
|
||||
|
||||
val bytes = parcel.marshall()
|
||||
parcel.unmarshall(bytes, 0, bytes.size)
|
||||
parcel.setDataPosition(0)
|
||||
|
||||
val a2 = parcelableCreator<A>().createFromParcel(parcel)
|
||||
val b = parcelableCreator<B>().createFromParcel(parcel)
|
||||
|
||||
assert(a1.x == a2.x)
|
||||
assert(a2.i1 == "default")
|
||||
assert(a2.i2.value == 10)
|
||||
assert(a1.d == a2.d)
|
||||
assert(a1.a == a2.a)
|
||||
assert(b == B)
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// FIR_IDENTICAL
|
||||
package test
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
|
||||
@Parcelize
|
||||
class A(@IgnoredOnParcel val x: String = "OK") : Parcelable
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// FIR_IDENTICAL
|
||||
package test
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
|
||||
class T(val x: Int)
|
||||
|
||||
@Parcelize
|
||||
class A(
|
||||
// T is not parcelable, but we don't need it to be since it is not being serialized.
|
||||
@IgnoredOnParcel val x: T = T(0)
|
||||
) : Parcelable {
|
||||
@IgnoredOnParcel val y: T = x
|
||||
}
|
||||
+12
@@ -66,6 +66,18 @@ public class FirParcelizeDiagnosticTestGenerated extends AbstractFirParcelizeDia
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/emptyPrimaryConstructor.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcelDefaultValues.kt")
|
||||
public void testIgnoredOnParcelDefaultValues() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/ignoredOnParcelDefaultValues.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcelUnsupportedType.kt")
|
||||
public void testIgnoredOnParcelUnsupportedType() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/ignoredOnParcelUnsupportedType.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt20062.kt")
|
||||
public void testKt20062() throws Exception {
|
||||
|
||||
+6
@@ -151,6 +151,12 @@ public class ParcelizeBoxTestGenerated extends AbstractParcelizeBoxTest {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/generics.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcel.kt")
|
||||
public void testIgnoredOnParcel() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/ignoredOnParcel.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("intArray.kt")
|
||||
public void testIntArray() throws Exception {
|
||||
|
||||
+12
@@ -66,6 +66,18 @@ public class ParcelizeDiagnosticTestGenerated extends AbstractParcelizeDiagnosti
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/emptyPrimaryConstructor.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcelDefaultValues.kt")
|
||||
public void testIgnoredOnParcelDefaultValues() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/ignoredOnParcelDefaultValues.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcelUnsupportedType.kt")
|
||||
public void testIgnoredOnParcelUnsupportedType() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/diagnostics/ignoredOnParcelUnsupportedType.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt20062.kt")
|
||||
public void testKt20062() throws Exception {
|
||||
|
||||
+6
@@ -151,6 +151,12 @@ public class ParcelizeFirBoxTestGenerated extends AbstractParcelizeFirBoxTest {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/generics.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcel.kt")
|
||||
public void testIgnoredOnParcel() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/ignoredOnParcel.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("intArray.kt")
|
||||
public void testIntArray() throws Exception {
|
||||
|
||||
+6
@@ -151,6 +151,12 @@ public class ParcelizeIrBoxTestGenerated extends AbstractParcelizeIrBoxTest {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/generics.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("ignoredOnParcel.kt")
|
||||
public void testIgnoredOnParcel() throws Exception {
|
||||
runTest("plugins/parcelize/parcelize-compiler/testData/box/ignoredOnParcel.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("intArray.kt")
|
||||
public void testIntArray() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user