Skip to content

Instantly share code, notes, and snippets.

@LordRaydenMK
Created February 2, 2020 22:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save LordRaydenMK/e7f6eddffb4d71de3c6b2534ff240619 to your computer and use it in GitHub Desktop.
Save LordRaydenMK/e7f6eddffb4d71de3c6b2534ff240619 to your computer and use it in GitHub Desktop.
Kotlin compiler plugin built with arrow-meta. Generates equals, hashCode and toString for classes annotated with @valueclass
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class ValueClass
package io.arrowkt.example
import arrow.meta.Meta
import arrow.meta.Plugin
import arrow.meta.invoke
import arrow.meta.phases.analysis.ElementScope
import arrow.meta.quotes.Transform
import arrow.meta.quotes.classDeclaration
import arrow.meta.quotes.classorobject.ClassDeclaration
import arrow.meta.quotes.nameddeclaration.stub.typeparameterlistowner.NamedFunction
import org.jetbrains.kotlin.psi.KtClass
val Meta.valueClass: Plugin
get() =
"valueClass" {
meta(
classDeclaration(::isValueClass) { c: KtClass ->
Transform.replace(
replacing = c,
newDeclaration =
"""|
|$kind $name $`(params)` {
|
| ${overrideHashCode(this)}
|
| ${overrideEquals(this)}
|
| ${overrideToString(this)}
|}""".`class`
)
}
)
}
private fun isValueClass(ktClass: KtClass): Boolean =
!ktClass.isData() &&
ktClass.annotationEntries.any { it.text.matches(Regex("@(arrow\\.)?ValueClass")) } &&
ktClass.primaryConstructorParameters.isNotEmpty() &&
ktClass.primaryConstructorParameters.all { !it.isMutable } &&
ktClass.typeParameters.isEmpty()
private fun ElementScope.overrideHashCode(classScope: ClassDeclaration): NamedFunction {
val hashCodeArs = classScope.`(params)`.value.joinToString { it.name!! }
val hashCode = "java.util.Objects.hash($hashCodeArs)"
return """override fun hashCode(): Int = $hashCode""".function
}
private fun ElementScope.overrideEquals(classScope: ClassDeclaration): NamedFunction {
val fieldEquality = classScope.`(params)`
.value.joinToString(
separator = "&&"
) { "java.util.Objects.equals(this.${it.name}, other.${it.name})" }
return """|
|override fun equals(other: Any?): Boolean {
|if (this === other) return true
|if (javaClass != other?.javaClass) return false
|other as ${classScope.name}
|return $fieldEquality
|}""".trimMargin().function
}
private fun ElementScope.overrideToString(classScope: ClassDeclaration): NamedFunction {
val fields = classScope.`(params)`.value.joinToString { "${it.name} = '$${it.name}'" }
return """override fun toString(): String = "${classScope.name}($fields)"""".function
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment