Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compose-stability-analyzer-idea/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ 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

### 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

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -301,23 +302,54 @@ 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<KaPropertySymbol>()
.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,
reason = "Interface type - actual implementation could be mutable",
)
}

// 18. Abstract classes - cannot determine (RUNTIME)
// 19. Abstract classes - cannot determine (RUNTIME)
if (classSymbol.modality == KaSymbolModality.ABSTRACT) {
return KtStability.Runtime(
className = fqName ?: simpleName,
reason = "Abstract class - actual implementation could be mutable",
)
}

// 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<org.jetbrains.kotlin.ir.declarations.IrProperty>()

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) {
Expand Down