kopykat-kt / kopykat

Little utilities for more pleasant immutable data in Kotlin
Other
283 stars 16 forks source link

nullable is not reflected to mutable data class #62

Closed bellatoris closed 2 years ago

bellatoris commented 2 years ago
// original data class
data class User(
    val id: String,
    val name: String?,
)

// generated data class 
@`User$DslMarker`
public class `User$Mutable`(
  public var id: String,
  public var name: String,
  public val old: User,
)

as you can see the generated mutable data class does not reflect the nullability of fields of original data class.

public fun User.toMutable(): `User$Mutable` = club.staircrusher.stdlib.auth.`User$Mutable`(old =
    this, id = id, name = name)

so that User.toMutable() class can not be even compiled since this discrepancy of mutability.

maxschlosser commented 2 years ago

I have the same issue, on 1.0-rc6 and 1.0-rc7. I did explore the code a little, and from what I can tell it seems like the nullability is swallowed in mutationInfo( ).

https://github.com/kopykat-kt/kopykat/blob/4afdbe3037f9fa0d596335965f9f9dc60ec4157c/kopykat-ksp/src/main/kotlin/at/kopyk/utils/TypeCompileScope.kt#L43-L44

The property and the returned MutationInfo do not have the same nullability.

Sadly I'm not sure whether I would be able to solve this issue. My first idea would be to check for nullability in mutationInfo and ensure that it is preserved, however I'm not sure what the implications for the other extensions would be.

I found this doing the following very basic approach:


// change test to have a nullable property
  @Test
  fun `mutate one property`() {
    """
      |data class Person(val name: String, val age: Int?)
      |
      |val p1 = Person("Alex", 1)
      |val p2 = p1.copy { age = age + 1 }
      |val r = p2.age
      """.evals("r" to 2)
  }

// compare return value of mutationInfo with passed value

  val mutationInfo: Sequence<Pair<KSPropertyDeclaration, MutationInfo<TypeName>>>
    get() = properties.map { prop ->
      prop to mutationInfo(prop.type.resolve()).also {
        println("PROPVSMUTATIONINFO: ${prop.type} ${prop.typeName} ${prop.typeName.isNullable} | ${it.className} ${it.className.isNullable} | ${prop.type.resolve().isMarkedNullable}")
    } }

// compare property.typeName and mutationInfo.className
internal fun FileCompilerScope.addMutableCopy() {
/* (...) */
      primaryConstructor {
        mutationInfo.forEach { (property, mutationInfo) ->
          with(property) {
            println("PROPERTY: ${this.typeName} ${this.typeName.isNullable}")
            println("MUTATIONINFO: ${mutationInfo.className} ${mutationInfo.className.isNullable}")
            addParameter(
              name = baseName,
              type = mutationInfo.className,
              modifiers = parameterModifiers
            )