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

Code before:
        if (currClass.isAbstract) continue

        val currParameters = currClass.allProperties.filter { !it.value.isHidden }

        // avoid generating multiple methods with same no. of parameters
        if (currParameters.size < prevParameterCount) {
          val isOverride = currClass !== pClass || superclass != null && properties.isEmpty()
          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

Review comment (performance):
[nit] Either make `nearestNonAbstractAncestorDeclaresCopyMethodWithSameArity` a lazy value, or cache this so we don't call it on every iteration

Code after:
        allProperties.size
    }

    // besides generating copy method for current class,
    // override copy methods inherited from parent classes
    fun generateCopyMethods(typeBuilder: TypeSpec.Builder) {
      // copy methods don't make sense for abstract classes
      if (pClass.isAbstract) return

      var prevParameterCount = Int.MAX_VALUE
      for (currClass in generateSequence(pClass) { it.superclass }) {
        if (currClass.isAbstract) continue

        val currParameters = currClass.allProperties.filter { !it.value.isHidden }

        // avoid generating multiple methods with same no. of parameters
        if (currParameters.size < prevParameterCount) {
          val isOverride = currClass !== pClass || inheritsCopyMethodWithSameArity()
          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")