diff --git a/compose-stability-analyzer-idea/CHANGELOG.md b/compose-stability-analyzer-idea/CHANGELOG.md index 99604df..2a0b108 100644 --- a/compose-stability-analyzer-idea/CHANGELOG.md +++ b/compose-stability-analyzer-idea/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the IntelliJ IDEA plugin will be documented in this file. ### Fixed - Fixed stability analysis for Compose shape types (RoundedCornerShape, CircleShape, etc.) to correctly show as STABLE instead of RUNTIME - Fixed StackOverflowError when analyzing recursive or self-referential types (Issue #11) +- Fixed false positive warnings for @Parcelize data classes (Issue #3) - Improved consistency between IDEA plugin and compiler plugin stability inference - Added Compose Foundation shapes to known stable types list - Added cycle detection to prevent infinite recursion in type stability analysis @@ -14,6 +15,7 @@ All notable changes to the IntelliJ IDEA plugin will be documented in this file. ### Improved - Enhanced accuracy of stability analysis to match compiler plugin behavior - Better handling of complex function type aliases and deeply nested generic types +- @Parcelize-annotated classes with stable properties are now correctly identified as STABLE --- diff --git a/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt b/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt index 1254102..642d346 100644 --- a/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt +++ b/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/k2/KtStabilityInferencer.kt @@ -48,10 +48,11 @@ import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability * 14. Standard collections (RUNTIME) * 15. Value classes * 16. Enums - * 17. Interfaces (RUNTIME) - * 18. Abstract classes (RUNTIME) - * 19. Regular classes - property analysis (returns STABLE/UNSTABLE if definitive) - * 20. @StabilityInferred (RUNTIME - only for uncertain cases) + * 17. @Parcelize - check properties + * 18. Interfaces (RUNTIME) + * 19. Abstract classes (RUNTIME) + * 20. Regular classes - property analysis (returns STABLE/UNSTABLE if definitive) + * 21. @StabilityInferred (RUNTIME - only for uncertain cases) */ internal class KtStabilityInferencer { @@ -301,7 +302,38 @@ internal class KtStabilityInferencer { return KtStability.Certain(stable = true, reason = StabilityConstants.Messages.ENUM_STABLE) } - // 17. Interfaces - cannot determine (RUNTIME) + // 17. @Parcelize data classes - check only properties, ignore Parcelable interface + val hasParcelize = classSymbol.annotations.any { annotation -> + annotation.classId?.asSingleFqName()?.asString() == "kotlinx.parcelize.Parcelize" + } + if (hasParcelize) { + val properties = classSymbol.declaredMemberScope.callables + .filterIsInstance() + .toList() + + // Check for var properties + if (properties.any { !it.isVal }) { + return KtStability.Certain( + stable = false, + reason = "Has mutable (var) properties", + ) + } + + // Check property type stability + val allPropertiesStable = properties.all { property -> + val propertyStability = ktStabilityOf(property.returnType) + propertyStability.isStable() + } + + if (allPropertiesStable) { + return KtStability.Certain( + stable = true, + reason = "@Parcelize with all stable properties", + ) + } + } + + // 18. Interfaces - cannot determine (RUNTIME) if (classSymbol.classKind == KaClassKind.INTERFACE) { return KtStability.Runtime( className = fqName ?: simpleName, @@ -309,7 +341,7 @@ internal class KtStabilityInferencer { ) } - // 18. Abstract classes - cannot determine (RUNTIME) + // 19. Abstract classes - cannot determine (RUNTIME) if (classSymbol.modality == KaSymbolModality.ABSTRACT) { return KtStability.Runtime( className = fqName ?: simpleName, @@ -317,7 +349,7 @@ internal class KtStabilityInferencer { ) } - // 19. Regular classes - analyze properties first before checking @StabilityInferred + // 20. Regular classes - analyze properties first before checking @StabilityInferred val propertyStability = analyzeClassProperties(classSymbol, currentlyAnalyzing) return when { diff --git a/stability-compiler/src/main/kotlin/com/skydoves/compose/stability/compiler/lower/StabilityAnalyzerTransformer.kt b/stability-compiler/src/main/kotlin/com/skydoves/compose/stability/compiler/lower/StabilityAnalyzerTransformer.kt index 064801e..3f68802 100644 --- a/stability-compiler/src/main/kotlin/com/skydoves/compose/stability/compiler/lower/StabilityAnalyzerTransformer.kt +++ b/stability-compiler/src/main/kotlin/com/skydoves/compose/stability/compiler/lower/StabilityAnalyzerTransformer.kt @@ -234,7 +234,7 @@ public class StabilityAnalyzerTransformer( * 11. Standard collections - RUNTIME * 12. Value classes (inline classes) * 13. Enums - STABLE - * 14. @StabilityInferred - RUNTIME + * 14. @Parcelize - check properties * 15. Interfaces - RUNTIME * 16. Abstract classes - RUNTIME * 17. Regular classes (with property analysis) @@ -339,17 +339,46 @@ public class StabilityAnalyzerTransformer( return ParameterStability.STABLE } - // 14. Interfaces - cannot determine (RUNTIME) + // 14. @Parcelize data classes - check only properties, ignore Parcelable interface + if (clazz.hasAnnotation(FqName("kotlinx.parcelize.Parcelize"))) { + val properties = clazz.declarations + .filterIsInstance() + + if (properties.isEmpty()) { + return ParameterStability.STABLE + } + + // Check for var properties + if (properties.any { it.isVar }) { + return ParameterStability.UNSTABLE + } + + // Check property type stability + val propertyStabilities = properties.mapNotNull { property -> + property.getter?.returnType?.let { analyzeTypeStability(it) } + } + + if (propertyStabilities.any { it == ParameterStability.UNSTABLE }) { + return ParameterStability.UNSTABLE + } + + if (propertyStabilities.all { it == ParameterStability.STABLE }) { + return ParameterStability.STABLE + } + // If properties have mixed stability, fall through to interface check + } + + // 15. Interfaces - cannot determine (RUNTIME) if (clazz.isInterfaceIr()) { return ParameterStability.RUNTIME } - // 15. Abstract classes - cannot determine (RUNTIME) + // 16. Abstract classes - cannot determine (RUNTIME) if (clazz.modality == org.jetbrains.kotlin.descriptors.Modality.ABSTRACT) { return ParameterStability.RUNTIME } - // 16. Regular classes - analyze properties first before checking @StabilityInferred + // 17. Regular classes - analyze properties first before checking @StabilityInferred val propertyStability = analyzeClassProperties(clazz) when (propertyStability) {