[Parcelize] WriteWith Parceler type argument must be subtype of property
K2 checks that the entire Parceler of the WriteWith annotation is a subtype of the expected Parceler for the property, while K1 only checks that the type argument of the WriteWith Parceler is a subtype of the property type. This mismatch leads to inconsistencies between K1 and K2 diagnostic reporting. Switch K2 to K1 behavior so diagnostics are consistent. ^KT-60019 Fixed
This commit is contained in:
@@ -317,3 +317,15 @@ fun FirRegularClassSymbol.getSuperClassSymbolOrAny(session: FirSession): FirRegu
|
|||||||
}
|
}
|
||||||
return session.builtinTypes.anyType.type.toRegularClassSymbol(session) ?: error("Symbol for Any not found")
|
return session.builtinTypes.anyType.type.toRegularClassSymbol(session) ?: error("Symbol for Any not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun FirClassLikeSymbol<*>.getSuperTypes(
|
||||||
|
useSiteSession: FirSession,
|
||||||
|
recursive: Boolean = true,
|
||||||
|
lookupInterfaces: Boolean = true,
|
||||||
|
substituteSuperTypes: Boolean = true,
|
||||||
|
supertypeSupplier: SupertypeSupplier = SupertypeSupplier.Default,
|
||||||
|
): List<ConeClassLikeType> {
|
||||||
|
return SmartList<ConeClassLikeType>().also {
|
||||||
|
collectSuperTypes(it, SmartSet.create(), recursive, lookupInterfaces, substituteSuperTypes, useSiteSession, supertypeSupplier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-11
@@ -18,9 +18,9 @@ import org.jetbrains.kotlin.fir.declarations.toAnnotationClassLikeType
|
|||||||
import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor
|
import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.getSuperTypes
|
||||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
|
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames
|
import org.jetbrains.kotlin.parcelize.ParcelizeNames
|
||||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
|
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
|
||||||
@@ -106,26 +106,27 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker() {
|
|||||||
|
|
||||||
// For `@WriteWith<P>` check that `P` is an object.
|
// For `@WriteWith<P>` check that `P` is an object.
|
||||||
val parcelerType = annotationCall.typeArguments.singleOrNull()?.toConeTypeProjection()?.type ?: return
|
val parcelerType = annotationCall.typeArguments.singleOrNull()?.toConeTypeProjection()?.type ?: return
|
||||||
if (parcelerType.toRegularClassSymbol(context.session)?.classKind != ClassKind.OBJECT) {
|
val parcelerTypeSymbol = parcelerType.toRegularClassSymbol(context.session)
|
||||||
|
if (parcelerTypeSymbol?.classKind != ClassKind.OBJECT) {
|
||||||
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
||||||
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_SHOULD_BE_OBJECT, context)
|
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_SHOULD_BE_OBJECT, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For `@WriteWith<P> T` check that `P` is a subtype of `Parceler<T>`.
|
// For `@WriteWith<P> T` where `P` is a subtype of `Parceler<E>`, check that T is a subtype of E.
|
||||||
//
|
//
|
||||||
// From the perspective of the `WriteWith` annotation call, `T` corresponds to the nearest enclosing annotation container
|
// From the perspective of the `WriteWith` annotation call, `T` corresponds to the nearest enclosing annotation container
|
||||||
// stripped of annotations.
|
// stripped of annotations.
|
||||||
//
|
//
|
||||||
// It's safe to assume that `Parceler` refers to `kotlinx.parcelize.Parceler` rather than `kotlinx.android.parcel.Parceler`,
|
// It's safe to assume that `Parceler` refers to `kotlinx.parcelize.Parceler` rather than `kotlinx.android.parcel.Parceler`,
|
||||||
// since using the deprecated `WriteWith` annotation is an error.
|
// since using the deprecated `WriteWith` annotation is an error.
|
||||||
val targetType = (context.annotationContainers.lastOrNull() as? FirTypeRef)?.coneType?.withAttributes(ConeAttributes.Empty)
|
val targetType = (context.annotationContainers.lastOrNull() as? FirTypeRef)?.coneType
|
||||||
?: return
|
?.withAttributes(ConeAttributes.Empty) ?: return
|
||||||
val expectedType = ConeClassLikeTypeImpl(
|
val parcelerSuperType = parcelerTypeSymbol?.getSuperTypes(context.session)
|
||||||
ParcelizeNames.PARCELER_ID.toLookupTag(),
|
?.firstOrNull { it.classId == ParcelizeNames.PARCELER_ID } ?: return
|
||||||
arrayOf(targetType),
|
val expectedType = parcelerSuperType.typeArguments.singleOrNull()?.type
|
||||||
isNullable = false
|
?.withAttributes(ConeAttributes.Empty) ?: return
|
||||||
)
|
|
||||||
if (!parcelerType.isSubtypeOf(expectedType, context.session)) {
|
if (!targetType.isSubtypeOf(expectedType, context.session)) {
|
||||||
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
||||||
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE, parcelerType, targetType, context)
|
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE, parcelerType, targetType, context)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -30,10 +30,10 @@ object Parceler3 : Parceler<String> {
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
@TypeParceler<String, Parceler2>
|
@TypeParceler<String, Parceler2>
|
||||||
data class Test(
|
data class Test(
|
||||||
val a: String,
|
val a: String,
|
||||||
@<!REDUNDANT_TYPE_PARCELER!>TypeParceler<!><String, Parceler1> val b: String,
|
@<!REDUNDANT_TYPE_PARCELER!>TypeParceler<!><String, Parceler1> val b: String,
|
||||||
@TypeParceler<String, Parceler3> val c: CharSequence,
|
@TypeParceler<String, Parceler3> val c: CharSequence,
|
||||||
val d: @WriteWith<Parceler3> String
|
val d: @WriteWith<Parceler3> String
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
fun box() = parcelTest { parcel ->
|
fun box() = parcelTest { parcel ->
|
||||||
|
|||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
// WITH_STDLIB
|
|
||||||
package test
|
|
||||||
|
|
||||||
import kotlinx.parcelize.*
|
|
||||||
import android.os.*
|
|
||||||
|
|
||||||
class Data<T>
|
|
||||||
typealias StringData = Data<String>
|
|
||||||
typealias NullableStringData = Data<String>?
|
|
||||||
|
|
||||||
object StringParceler : Parceler<String> {
|
|
||||||
override fun create(parcel: Parcel) = TODO()
|
|
||||||
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
object CharSequenceParceler : Parceler<CharSequence> {
|
|
||||||
override fun create(parcel: Parcel) = TODO()
|
|
||||||
override fun CharSequence.write(parcel: Parcel, flags: Int) = TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringClassParceler : Parceler<String> {
|
|
||||||
override fun create(parcel: Parcel) = TODO()
|
|
||||||
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringDataParceler : Parceler<StringData> {
|
|
||||||
override fun create(parcel: Parcel) = TODO()
|
|
||||||
override fun StringData.write(parcel: Parcel, flags: Int) = TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
class NullableStringDataParceler : Parceler<NullableStringData> {
|
|
||||||
override fun create(parcel: Parcel) = TODO()
|
|
||||||
override fun NullableStringData.write(parcel: Parcel, flags: Int) = TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
@<!CLASS_SHOULD_BE_PARCELIZE!>TypeParceler<!><String, StringParceler>
|
|
||||||
class MissingParcelizeAnnotation(val a: @<!CLASS_SHOULD_BE_PARCELIZE!>WriteWith<!><StringParceler> String)
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@TypeParceler<String, StringClassParceler>
|
|
||||||
class ShouldBeClass(val a: @WriteWith<<!PARCELER_SHOULD_BE_OBJECT!>StringClassParceler<!>> String) : Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
class Test(
|
|
||||||
val a: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>StringParceler<!>> Int,
|
|
||||||
val b: @WriteWith<StringParceler> String,
|
|
||||||
val c: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>StringParceler<!>> CharSequence,
|
|
||||||
val d: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>CharSequenceParceler<!>> String,
|
|
||||||
val e: @WriteWith<CharSequenceParceler> CharSequence
|
|
||||||
) : Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@TypeParceler<String, StringParceler>
|
|
||||||
class Test2(@<!REDUNDANT_TYPE_PARCELER!>TypeParceler<!><String, StringParceler> val a: String) : Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@TypeParceler<<!DUPLICATING_TYPE_PARCELERS!>String<!>, StringParceler>
|
|
||||||
@TypeParceler<<!DUPLICATING_TYPE_PARCELERS!>String<!>, CharSequenceParceler>
|
|
||||||
class Test3(val a: String) : Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@TypeParceler<StringData, StringDataParceler>
|
|
||||||
class StringDataParcelerTest(
|
|
||||||
val a: StringData,
|
|
||||||
val b: <!PARCELABLE_TYPE_NOT_SUPPORTED!>StringData?<!>,
|
|
||||||
val c: Data<String>,
|
|
||||||
val d: <!PARCELABLE_TYPE_NOT_SUPPORTED!>Data<String>?<!>,
|
|
||||||
) : Parcelable
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
@TypeParceler<NullableStringData, NullableStringDataParceler>
|
|
||||||
class NullableStringDataParcelerTest(
|
|
||||||
val a: NullableStringData,
|
|
||||||
val b: <!PARCELABLE_TYPE_NOT_SUPPORTED!>StringData<!>,
|
|
||||||
val c: StringData?,
|
|
||||||
val d: <!PARCELABLE_TYPE_NOT_SUPPORTED!>Data<String><!>,
|
|
||||||
val e: Data<String>?,
|
|
||||||
) : Parcelable
|
|
||||||
+6
-1
@@ -1,3 +1,4 @@
|
|||||||
|
// FIR_IDENTICAL
|
||||||
// WITH_STDLIB
|
// WITH_STDLIB
|
||||||
package test
|
package test
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ object CharSequenceParceler : Parceler<CharSequence> {
|
|||||||
override fun CharSequence.write(parcel: Parcel, flags: Int) = TODO()
|
override fun CharSequence.write(parcel: Parcel, flags: Int) = TODO()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias CharSequenceParcelerAlias = CharSequenceParceler
|
||||||
|
|
||||||
class StringClassParceler : Parceler<String> {
|
class StringClassParceler : Parceler<String> {
|
||||||
override fun create(parcel: Parcel) = TODO()
|
override fun create(parcel: Parcel) = TODO()
|
||||||
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
||||||
@@ -46,7 +49,9 @@ class Test(
|
|||||||
val b: @WriteWith<StringParceler> String,
|
val b: @WriteWith<StringParceler> String,
|
||||||
val c: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>StringParceler<!>> CharSequence,
|
val c: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>StringParceler<!>> CharSequence,
|
||||||
val d: @WriteWith<CharSequenceParceler> String,
|
val d: @WriteWith<CharSequenceParceler> String,
|
||||||
val e: @WriteWith<CharSequenceParceler> CharSequence
|
val e: @WriteWith<CharSequenceParceler> CharSequence,
|
||||||
|
val f: @WriteWith<CharSequenceParcelerAlias> String,
|
||||||
|
val g: @WriteWith<CharSequenceParcelerAlias> CharSequence,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|||||||
Reference in New Issue
Block a user