Skip to content

Commit dfd06ea

Browse files
authored
Fix isData check with value classes (#731)
1 parent 80d71fd commit dfd06ea

File tree

2 files changed

+69
-5
lines changed

2 files changed

+69
-5
lines changed

mvrx-common/src/main/java/com/airbnb/mvrx/MavericksMutabilityHelper.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,27 @@ fun assertMavericksDataClassImmutability(
6666
*/
6767
internal val Class<*>.isData: Boolean
6868
get() {
69-
if (!declaredMethods.any { it.name == "copy\$default" && it.isSynthetic }) {
70-
return false
69+
// When a value class is present in the constructor, Kotlin mangles the copy method name
70+
// to avoid signature clashes (e.g., "copy-KtkBMb8$default" instead of "copy$default").
71+
// We check for either the exact name "copy$default" or the pattern "copy-*$default".
72+
val hasCopyDefault = declaredMethods.any { method ->
73+
method.isSynthetic && method.name.let { name ->
74+
name == "copy\$default" || (name.startsWith("copy-") && name.endsWith("\$default"))
75+
}
7176
}
77+
if (!hasCopyDefault) return false
7278

73-
// if the data class property is internal then kotlin appends '$module_name_debug' to the
74-
// expected function name.
75-
declaredMethods.firstOrNull { it.name.startsWith("component1") } ?: return false
79+
// Similarly, component1 can be mangled when it's a value class type.
80+
// It can also have module names appended for internal properties.
81+
// Patterns: "component1", "component1$module", "component1-<hash>"
82+
val hasComponent1 = declaredMethods.any { method ->
83+
method.name.let { name ->
84+
name == "component1" ||
85+
name.startsWith("component1\$") ||
86+
name.startsWith("component1-")
87+
}
88+
}
89+
if (!hasComponent1) return false
7690

7791
declaredMethods.firstOrNull { it.name == "equals" } ?: return false
7892
declaredMethods.firstOrNull { it.name == "hashCode" } ?: return false

mvrx-common/src/test/kotlin/com/airbnb/mvrx/MavericksMutabilityHelperKtTest.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,57 @@ class MavericksMutabilityHelperKtTest {
1212
assertFalse(String::class.java.isData)
1313
}
1414

15+
@Test
16+
fun isDataWithValueClass() {
17+
assertTrue(TestDataClassWithValueClass::class.java.isData)
18+
}
19+
20+
@Test
21+
fun isDataWithValueClassAsFirstParameter() {
22+
// When a value class is the first parameter, component1 is mangled
23+
assertTrue(TestDataClassWithValueClassFirst::class.java.isData)
24+
}
25+
26+
@Test
27+
fun assertImmutabilityWithValueClass() {
28+
// Test that assertMavericksDataClassImmutability works with value classes
29+
assertMavericksDataClassImmutability(TestDataClassWithValueClass::class)
30+
assertMavericksDataClassImmutability(TestDataClassWithValueClassFirst::class)
31+
}
32+
33+
@Test
34+
fun isDataWithOnlyValueClass() {
35+
assertTrue(TestDataClassWithOnlyValueClass::class.java.isData)
36+
}
37+
38+
@Test
39+
fun isDataWithMultipleValueClasses() {
40+
assertTrue(TestDataClassWithMultipleValueClasses::class.java.isData)
41+
}
42+
43+
@JvmInline
44+
value class TestValueClass(val value: Int)
45+
1546
data class TestDataClass(
1647
internal val foo: Int
1748
)
49+
50+
data class TestDataClassWithValueClass(
51+
val foo: Int,
52+
val valueClass: TestValueClass
53+
)
54+
55+
data class TestDataClassWithValueClassFirst(
56+
val valueClass: TestValueClass,
57+
val foo: Int
58+
)
59+
60+
data class TestDataClassWithOnlyValueClass(
61+
val valueClass: TestValueClass
62+
)
63+
64+
data class TestDataClassWithMultipleValueClasses(
65+
val valueClass1: TestValueClass,
66+
val valueClass2: TestValueClass
67+
)
1868
}

0 commit comments

Comments
 (0)