Skip to content

Commit 4f4a27c

Browse files
authored
Ignore properties in sealed hierarchies where the type changes across children (#3382)
* Fixes #3380 * Add test for #3881
1 parent 88f23e3 commit 4f4a27c

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/info.kt

+6
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ val String.sealedLensConstructorOverridesOnly
2323
| ^
2424
|To generate Lens for a sealed class make sure all children override target properties in constructors.
2525
""".trimMargin()
26+
27+
val String.sealedLensChangedProperties
28+
get() =
29+
"""
30+
|Ignoring $this when generating lenses; the type is not uniform across all children.
31+
""".trimMargin()

arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt

+18-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.google.devtools.ksp.isAbstract
88
import com.google.devtools.ksp.processing.KSPLogger
99
import com.google.devtools.ksp.symbol.KSClassDeclaration
1010
import com.google.devtools.ksp.symbol.KSDeclaration
11+
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
1112
import com.google.devtools.ksp.symbol.KSType
1213
import com.google.devtools.ksp.symbol.KSTypeArgument
1314
import com.google.devtools.ksp.symbol.KSTypeParameter
@@ -119,7 +120,13 @@ internal fun evalAnnotatedClass(
119120
return null
120121
}
121122

122-
val propertyNames = properties
123+
val (goodProperties, badProperties) = properties.partition { it.sameInChildren(subclasses) }
124+
125+
badProperties.forEach {
126+
logger.info(it.qualifiedNameOrSimpleName.sealedLensChangedProperties, element)
127+
}
128+
129+
val propertyNames = goodProperties
123130
.map { it.simpleName.asString() }
124131

125132
val nonConstructorOverrides = subclasses
@@ -133,7 +140,7 @@ internal fun evalAnnotatedClass(
133140
return null
134141
}
135142

136-
properties
143+
goodProperties
137144
.map { it.type.resolve().qualifiedString() }
138145
.zip(propertyNames) { type, name ->
139146
Focus(
@@ -151,6 +158,15 @@ internal fun evalAnnotatedClass(
151158
}
152159
}
153160

161+
fun KSPropertyDeclaration.sameInChildren(subclasses: Sequence<KSClassDeclaration>): Boolean =
162+
subclasses.all { subclass ->
163+
val property = subclass.getAllProperties().singleOrNull { it.simpleName == this.simpleName }
164+
when (property) {
165+
null -> false
166+
else -> property.type.resolve() == this.type.resolve()
167+
}
168+
}
169+
154170
internal fun evalAnnotatedDslElement(element: KSClassDeclaration, logger: KSPLogger): Target =
155171
when {
156172
element.isDataClass ->

arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/LensTests.kt

+48
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,52 @@ class LensTests {
208208
|val r = l != null
209209
""".compilationFails()
210210
}
211+
212+
@Test
213+
fun `Lens for sealed class property, ignoring changed nullability`() {
214+
"""
215+
|$`package`
216+
|$imports
217+
|@optics
218+
|sealed class LensSealed {
219+
| abstract val property1: String?
220+
|
221+
| data class dataChild1(override val property1: String?) : LensSealed()
222+
| data class dataChild2(override val property1: String?, val number: Int) : LensSealed()
223+
| data class dataChild3(override val property1: String, val enabled: Boolean) : LensSealed()
224+
|
225+
| companion object
226+
|}
227+
|
228+
|val l: Lens<LensSealed, String>? = LensSealed.property1
229+
|val r = l != null
230+
""".compilationFails()
231+
}
232+
233+
@Test
234+
fun `Lens for sealed class property, ignoring changed types`() {
235+
"""
236+
|$`package`
237+
|$imports
238+
|@optics
239+
|sealed interface Base<out T> {
240+
| val prop: T
241+
|
242+
| companion object
243+
|}
244+
|
245+
|@optics
246+
|data class Child1(override val prop: String) : Base<String> {
247+
| companion object
248+
|}
249+
|
250+
|@optics
251+
|data class Child2(override val prop: Int) : Base<Int> {
252+
| companion object
253+
|}
254+
|
255+
|val l: Lens<Base<String>, String> = Base.prop()
256+
|val r = l != null
257+
""".compilationFails()
258+
}
211259
}

0 commit comments

Comments
 (0)