Repository: apple/pkl
File: pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/KotlinCodeGenerator.kt (Kotlin)

Code before:
          typeBuilder.addFunction(generateCopyMethod(currParameters, isOverride))
          prevParameterCount = currParameters.size
        }
      }
    }

    fun generateEqualsMethod(): FunSpec {
      val builder =
        FunSpec.builder("equals")
          .addModifiers(KModifier.OVERRIDE)
          .addParameter("other", ANY_NULL)
          .returns(BOOLEAN)
          .addStatement("if (this === other) return true")
          // generating this.javaClass instead of class literal avoids a SpotBugs warning
          .addStatement("if (this.javaClass != other?.javaClass) return false")
          .addStatement("other as %T", kotlinPoetClassName)

      for ((propertyName, property) in allProperties) {
        val accessor = if (property.isRegex()) "%N.pattern" else "%N"
        builder.addStatement(
          "if (this.$accessor != other.$accessor) return false",
          propertyName,
          propertyName
        )
      }

      builder.addStatement("return true")
      return builder.build()
    }

    fun generateHashCodeMethod(): FunSpec {
      val builder =
        FunSpec.builder("hashCode")
          .addModifiers(KModifier.OVERRIDE)
          .returns(INT)
          .addStatement("var result = 1")

      for (propertyName in allProperties.keys) {
        // use Objects.hashCode() because Kotlin's Any?.hashCode()
        // doesn't work for platform types (will get NPE if null)
        builder.addStatement(
          "result = 31 * result + %T.hashCode(this.%N)",
          Objects::class,
          propertyName
        )
      }

      builder.addStatement("return result")
      return builder.build()
    }


Review comment (suggestion):
This needs to handle possibly quoted identifiers. Played around with it and this seems to do the trick:

```suggestion
              val propertyNameQuoted = CodeBlock.of("%N", propertyName).toString()
              if (propertyName == propertyNameQuoted) {
                append(propertyName)
              } else {
                append("{")
                append(propertyNameQuoted)
                append("}")
              }
```

Code after:

      builder.addStatement("return result")
      return builder.build()
    }

    // produce same output as default toString() method of data classes
    fun generateToStringMethod(): FunSpec {
      return FunSpec.builder("toString")
        .addModifiers(KModifier.OVERRIDE)
        .returns(STRING)
        .addStatement(
          "return %P",
          CodeBlock.builder()
            .apply {
              add("%L", pClass.toKotlinPoetName().simpleName)
              add("(")
              for ((index, propertyName) in allProperties.keys.withIndex()) {
                add(if (index == 0) "%L" else ", %L", propertyName)
                add("=$")
                add("%N", propertyName)
              }
              add(")")
            }
            .build()
        )
        .build()
    }

    fun generateDeprecation(
      annotations: Collection<PObject>,
      addAnnotation: (AnnotationSpec) -> Unit
    ) {
      annotations
        .firstOrNull { it.classInfo == PClassInfo.Deprecated }
        ?.let { deprecation ->
          val builder = AnnotationSpec.builder(Deprecated::class)
          (deprecation["message"] as String?)?.let { builder.addMember("message = %S", it) }
          addAnnotation(builder.build())
        }
    }

    fun generateProperty(propertyName: String, property: PClass.Property): PropertySpec {
      val typeName = property.type.toKotlinPoetName()
      val builder = PropertySpec.builder(propertyName, typeName).initializer("%L", propertyName)

      generateDeprecation(property.annotations) { builder.addAnnotation(it) }

      val docComment = property.docComment
      if (docComment != null && options.generateKdoc) {
        builder.addKdoc(renderAsKdoc(docComment))
      }