Parcelize: Add missing FIR diagnostics
This commit is contained in:
+86
-13
@@ -5,16 +5,24 @@
|
||||
|
||||
package org.jetbrains.kotlin.parcelize.fir.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnnotationCallChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.findClosestClassOrObject
|
||||
import org.jetbrains.kotlin.fir.correspondingProperty
|
||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
||||
import org.jetbrains.kotlin.fir.expressions.classId
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.DEPRECATED_RUNTIME_PACKAGE
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELIZE_CLASS_CLASS_IDS
|
||||
@@ -28,12 +36,14 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker() {
|
||||
val resolvedAnnotationSymbol = annotationType.lookupTag.toFirRegularClassSymbol(context.session) ?: return
|
||||
when (val annotationClassId = resolvedAnnotationSymbol.classId) {
|
||||
in TYPE_PARCELER_CLASS_IDS -> {
|
||||
checkTypeParcelerUsage(expression, context, reporter)
|
||||
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)
|
||||
if (checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)) {
|
||||
checkTypeParcelerUsage(expression, context, reporter)
|
||||
}
|
||||
}
|
||||
in WRITE_WITH_CLASS_IDS -> {
|
||||
checkWriteWithUsage(expression, context, reporter)
|
||||
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)
|
||||
if (checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = true)) {
|
||||
checkWriteWithUsage(expression, context, reporter)
|
||||
}
|
||||
}
|
||||
in IGNORED_ON_PARCEL_CLASS_IDS -> {
|
||||
checkDeprecatedAnnotations(expression, annotationClassId, context, reporter, isForbidden = false)
|
||||
@@ -50,20 +60,83 @@ object FirParcelizeAnnotationChecker : FirAnnotationCallChecker() {
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter,
|
||||
isForbidden: Boolean
|
||||
) {
|
||||
): Boolean {
|
||||
if (annotationClassId.packageFqName == DEPRECATED_RUNTIME_PACKAGE) {
|
||||
val factory = if (isForbidden) KtErrorsParcelize.FORBIDDEN_DEPRECATED_ANNOTATION else KtErrorsParcelize.DEPRECATED_ANNOTATION
|
||||
reporter.reportOn(annotationCall.source, factory, context)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkTypeParcelerUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val thisMappedType = annotationCall.typeArguments.takeIf { it.size == 2 }?.first()?.toConeTypeProjection()?.type
|
||||
?: return
|
||||
|
||||
val annotationContainer = context.annotationContainers.lastOrNull()
|
||||
val duplicatingAnnotationCount = annotationContainer
|
||||
?.annotations
|
||||
?.filter { it.classId in TYPE_PARCELER_CLASS_IDS }
|
||||
?.mapNotNull { it.typeArguments.takeIf { it.size == 2 }?.first()?.toConeTypeProjection()?.type }
|
||||
?.count { it == thisMappedType }
|
||||
|
||||
if (duplicatingAnnotationCount != null && duplicatingAnnotationCount > 1) {
|
||||
val reportElement = annotationCall.typeArguments.firstOrNull()?.source ?: annotationCall.source
|
||||
reporter.reportOn(reportElement, KtErrorsParcelize.DUPLICATING_TYPE_PARCELERS, context)
|
||||
return
|
||||
}
|
||||
|
||||
checkIfTheContainingClassIsParcelize(annotationCall, context, reporter)
|
||||
|
||||
// If we are looking at a parameter of the primary constructor of a class, check that the
|
||||
// enclosing class doesn't have the same TypeParceler annotation.
|
||||
if (annotationContainer is FirValueParameter && annotationContainer.correspondingProperty?.fromPrimaryConstructor == true) {
|
||||
val enclosingClass = context.findClosestClassOrObject() ?: return
|
||||
|
||||
val annotationType = annotationCall.typeRef.coneType
|
||||
if (enclosingClass.annotations.any { it.typeRef.coneType == annotationType }) {
|
||||
val reportElement = annotationCall.calleeReference.source ?: annotationCall.source
|
||||
reporter.reportOn(reportElement, KtErrorsParcelize.REDUNDANT_TYPE_PARCELER, enclosingClass.symbol, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun checkTypeParcelerUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// TODO: this check checks type arguments of annotation which are not supported in FIR
|
||||
private fun checkWriteWithUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
checkIfTheContainingClassIsParcelize(annotationCall, context, reporter)
|
||||
|
||||
// For `@WriteWith<P>` check that `P` is an object.
|
||||
val parcelerType = annotationCall.typeArguments.singleOrNull()?.toConeTypeProjection()?.type ?: return
|
||||
if (parcelerType.toRegularClassSymbol(context.session)?.classKind != ClassKind.OBJECT) {
|
||||
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
||||
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_SHOULD_BE_OBJECT, context)
|
||||
}
|
||||
|
||||
// For `@WriteWith<P> T` check that `P` is a subtype of `Parceler<T>`.
|
||||
//
|
||||
// From the perspective of the `WriteWith` annotation call, `T` corresponds to the nearest enclosing annotation container
|
||||
// stripped of annotations.
|
||||
//
|
||||
// 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.
|
||||
val targetType = (context.annotationContainers.lastOrNull() as? FirTypeRef)?.coneType?.withAttributes(ConeAttributes.Empty)
|
||||
?: return
|
||||
val expectedType = ConeClassLikeTypeImpl(
|
||||
ConeClassLikeLookupTagImpl(ParcelizeNames.PARCELER_ID),
|
||||
arrayOf(targetType),
|
||||
isNullable = false
|
||||
)
|
||||
if (!parcelerType.isSubtypeOf(expectedType, context.session)) {
|
||||
val reportElement = annotationCall.typeArguments.singleOrNull()?.source ?: annotationCall.source
|
||||
reporter.reportOn(reportElement, KtErrorsParcelize.PARCELER_TYPE_INCOMPATIBLE, parcelerType, targetType, context)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun checkWriteWithUsage(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// TODO: this check checks type arguments of annotation which are not supported in FIR
|
||||
private fun checkIfTheContainingClassIsParcelize(annotationCall: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val enclosingClass = context.findClosestClassOrObject() ?: return
|
||||
|
||||
if (!enclosingClass.symbol.isParcelize(context.session)) {
|
||||
val reportElement = annotationCall.calleeReference.source ?: annotationCall.source
|
||||
reporter.reportOn(reportElement, KtErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE, enclosingClass.symbol, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-11
@@ -5,7 +5,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.parcelize.fir.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
||||
import org.jetbrains.kotlin.descriptors.isEnumClass
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
@@ -18,12 +20,13 @@ import org.jetbrains.kotlin.fir.declarations.utils.fromPrimaryConstructor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
|
||||
import org.jetbrains.kotlin.fir.resolve.fqName
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeErrorType
|
||||
import org.jetbrains.kotlin.fir.types.classId
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.parcelize.BuiltinParcelableTypes
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.CREATOR_NAME
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.IGNORED_ON_PARCEL_CLASS_IDS
|
||||
import org.jetbrains.kotlin.parcelize.ParcelizeNames.PARCELER_ID
|
||||
@@ -55,7 +58,6 @@ object FirParcelizePropertyChecker : FirPropertyChecker() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun checkParcelableClassProperty(
|
||||
property: FirProperty,
|
||||
containingClassSymbol: FirRegularClassSymbol,
|
||||
@@ -63,18 +65,92 @@ object FirParcelizePropertyChecker : FirPropertyChecker() {
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
val type = property.returnTypeRef.coneType
|
||||
if (type is ConeErrorType || containingClassSymbol.hasCustomParceler(context.session)) return
|
||||
/*
|
||||
* TODO: abstract code from ParcelSerializer or IrParcelSerializerFactory to avoid duplication
|
||||
* of allowed types checking
|
||||
*/
|
||||
if (type is ConeErrorType || containingClassSymbol.hasCustomParceler(context.session) || property.hasIgnoredOnParcel()) {
|
||||
return
|
||||
}
|
||||
|
||||
val session = context.session
|
||||
val customParcelerTypes = getCustomParcelerTypes(property.annotations + containingClassSymbol.annotations, session)
|
||||
if (!checkParcelableType(type, customParcelerTypes, session)) {
|
||||
reporter.reportOn(property.returnTypeRef.source, KtErrorsParcelize.PARCELABLE_TYPE_NOT_SUPPORTED, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCustomParcelerTypes(annotations: List<FirAnnotation>, session: FirSession): Set<ConeKotlinType> =
|
||||
annotations.mapNotNullTo(mutableSetOf()) { annotation ->
|
||||
if (annotation.fqName(session) in ParcelizeNames.TYPE_PARCELER_FQ_NAMES && annotation.typeArguments.size == 2) {
|
||||
annotation.typeArguments[0].toConeTypeProjection().type
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParcelableType(type: ConeKotlinType, customParcelerTypes: Set<ConeKotlinType>, session: FirSession): Boolean {
|
||||
if (type.hasParcelerAnnotation(session) || type in customParcelerTypes) {
|
||||
return true
|
||||
}
|
||||
|
||||
val upperBound = type.getErasedUpperBound(session)
|
||||
val symbol = upperBound?.toRegularClassSymbol(session)
|
||||
?: return false
|
||||
|
||||
if (symbol.classKind.isSingleton || symbol.classKind.isEnumClass) {
|
||||
return true
|
||||
}
|
||||
|
||||
val fqName = symbol.classId.asFqNameString()
|
||||
if (fqName in BuiltinParcelableTypes.PARCELABLE_BASE_TYPE_FQNAMES) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (fqName in BuiltinParcelableTypes.PARCELABLE_CONTAINER_FQNAMES) {
|
||||
return upperBound.typeArguments.all { projection ->
|
||||
projection.type?.let { checkParcelableType(it, customParcelerTypes, session) } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
return with(session.typeContext) {
|
||||
type.anySuperTypeConstructor {
|
||||
it is ConeKotlinType &&
|
||||
(it.classId?.asFqNameString() in BuiltinParcelableTypes.PARCELABLE_SUPERTYPE_FQNAMES ||
|
||||
it.isBuiltinFunctionalType(session))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConeKotlinType.getErasedUpperBound(session: FirSession): ConeClassLikeType? =
|
||||
when (this) {
|
||||
is ConeClassLikeType ->
|
||||
fullyExpandedType(session)
|
||||
|
||||
is ConeTypeParameterType -> {
|
||||
val bounds = lookupTag.typeParameterSymbol.resolvedBounds
|
||||
val representativeBound = bounds.firstOrNull {
|
||||
val kind = it.coneType.toRegularClassSymbol(session)?.classKind
|
||||
?: return@firstOrNull false
|
||||
kind != ClassKind.INTERFACE && kind != ClassKind.ANNOTATION_CLASS
|
||||
} ?: bounds.first()
|
||||
representativeBound.coneType.getErasedUpperBound(session)
|
||||
}
|
||||
|
||||
else ->
|
||||
null
|
||||
}
|
||||
|
||||
private fun ConeKotlinType.hasParcelerAnnotation(session: FirSession): Boolean {
|
||||
for (annotation in customAnnotations) {
|
||||
val fqName = annotation.fqName(session)
|
||||
if (fqName in ParcelizeNames.RAW_VALUE_ANNOTATION_FQ_NAMES || fqName in ParcelizeNames.WRITE_WITH_FQ_NAMES) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun FirProperty.hasIgnoredOnParcel(): Boolean {
|
||||
return annotations.hasIgnoredOnParcel() || (getter?.annotations?.hasIgnoredOnParcel() ?: false)
|
||||
}
|
||||
|
||||
|
||||
private fun List<FirAnnotation>.hasIgnoredOnParcel(): Boolean {
|
||||
return this.any {
|
||||
if (it.annotationTypeRef.coneType.classId !in IGNORED_ON_PARCEL_CLASS_IDS) return@any false
|
||||
|
||||
+1
-1
@@ -19,8 +19,8 @@ package org.jetbrains.kotlin.parcelize.fir.diagnostics
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderer
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.RENDER_CLASS_OR_OBJECT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_TYPE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT
|
||||
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.CLASS_SHOULD_BE_PARCELIZE
|
||||
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.CREATOR_DEFINITION_IS_NOT_ALLOWED
|
||||
import org.jetbrains.kotlin.parcelize.fir.diagnostics.KtErrorsParcelize.DEPRECATED_ANNOTATION
|
||||
|
||||
+4
-2
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.DELEG
|
||||
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.INNER_MODIFIER
|
||||
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.NAME_IDENTIFIER
|
||||
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.OVERRIDE_MODIFIER
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
|
||||
@@ -43,8 +45,8 @@ object KtErrorsParcelize {
|
||||
val PARCELER_SHOULD_BE_OBJECT by error0<PsiElement>()
|
||||
val PARCELER_TYPE_INCOMPATIBLE by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||
val DUPLICATING_TYPE_PARCELERS by error0<PsiElement>()
|
||||
val REDUNDANT_TYPE_PARCELER by warning1<PsiElement, KtClassOrObject>()
|
||||
val CLASS_SHOULD_BE_PARCELIZE by error1<PsiElement, KtClassOrObject>()
|
||||
val REDUNDANT_TYPE_PARCELER by warning1<PsiElement, FirClassSymbol<*>>()
|
||||
val CLASS_SHOULD_BE_PARCELIZE by error1<PsiElement, FirClassSymbol<*>>()
|
||||
val FORBIDDEN_DEPRECATED_ANNOTATION by error0<PsiElement>()
|
||||
val DEPRECATED_ANNOTATION by warning0<PsiElement>()
|
||||
val DEPRECATED_PARCELER by error0<PsiElement>()
|
||||
|
||||
+9
-9
@@ -19,27 +19,27 @@ class StringClassParceler : Parceler<String> {
|
||||
override fun String.write(parcel: Parcel, flags: Int) = TODO()
|
||||
}
|
||||
|
||||
@TypeParceler<String, StringParceler>
|
||||
class MissingParcelizeAnnotation(val a: @WriteWith<StringParceler> String)
|
||||
@<!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<StringClassParceler> String) : Parcelable
|
||||
class ShouldBeClass(val a: @WriteWith<<!PARCELER_SHOULD_BE_OBJECT!>StringClassParceler<!>> String) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
class Test(
|
||||
val a: @WriteWith<StringParceler> Int,
|
||||
val a: @WriteWith<<!PARCELER_TYPE_INCOMPATIBLE!>StringParceler<!>> Int,
|
||||
val b: @WriteWith<StringParceler> String,
|
||||
val c: @WriteWith<StringParceler> CharSequence,
|
||||
val d: @WriteWith<CharSequenceParceler> 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(@TypeParceler<String, StringParceler> val a: String) : Parcelable
|
||||
class Test2(@<!REDUNDANT_TYPE_PARCELER!>TypeParceler<!><String, StringParceler> val a: String) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
@TypeParceler<String, StringParceler>
|
||||
@TypeParceler<String, CharSequenceParceler>
|
||||
@TypeParceler<<!DUPLICATING_TYPE_PARCELERS!>String<!>, StringParceler>
|
||||
@TypeParceler<<!DUPLICATING_TYPE_PARCELERS!>String<!>, CharSequenceParceler>
|
||||
class Test3(val a: String) : Parcelable
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package test
|
||||
|
||||
import kotlinx.parcelize.*
|
||||
import android.os.*
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
@Parcelize
|
||||
class Foo(val box: Box): Parcelable {
|
||||
companion object : Parceler<Foo> {
|
||||
override fun create(parcel: Parcel) = Foo(Box(parcel.readString()))
|
||||
|
||||
override fun Foo.write(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(box.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Foo2(val box: Box): Parcelable
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package test
|
||||
|
||||
import kotlinx.parcelize.*
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package test
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
import android.os.Parcelable
|
||||
|
||||
@Parcelize
|
||||
class User(
|
||||
val a: String,
|
||||
val b: Any,
|
||||
val c: Any?,
|
||||
val d: Map<Any, String>,
|
||||
val e: @RawValue Any?,
|
||||
val f: @RawValue Map<String, Any>,
|
||||
val g: Map<String, @RawValue Any>,
|
||||
val h: Map<@RawValue Any, List<@RawValue Any>>
|
||||
) : Parcelable
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
package test
|
||||
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
Reference in New Issue
Block a user