/
HigherKindsFileGenerator.kt
87 lines (74 loc) · 3.89 KB
/
HigherKindsFileGenerator.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package arrow.higherkinds
import arrow.common.Package
import arrow.common.utils.knownError
import arrow.common.utils.typeConstraints
import me.eugeniomarletti.kotlin.metadata.shadow.metadata.ProtoBuf
import java.io.File
import javax.lang.model.element.Name
val KindPostFix = "Of"
val KindedJPostFix = "KindedJ"
val HKMarkerPreFix = "For"
data class HigherKind(
val `package`: Package,
val target: AnnotatedHigherKind
) {
val tparams: List<ProtoBuf.TypeParameter> = target.classOrPackageProto.typeParameters
val kindName: Name = target.classElement.simpleName
val alias: String = if (tparams.size == 1) "arrow.Kind" else "arrow.Kind${tparams.size}"
val aliasJ: String = if (tparams.size == 1) "io.kindedj.Hk" else "io.kindedj.HkJ${tparams.size}"
val typeArgs: List<String> = target.classOrPackageProto.typeParameters.map { target.classOrPackageProto.nameResolver.getString(it.name) }
val expandedTypeArgs: String = target.classOrPackageProto.typeParameters.joinToString(
separator = ", ", transform = { target.classOrPackageProto.nameResolver.getString(it.name) })
val typeConstraints = target.classOrPackageProto.typeConstraints()
val name: String = "${kindName}$KindPostFix"
val nameJ: String = "${kindName}$KindedJPostFix"
val markerName = "$HKMarkerPreFix${kindName}"
}
class HigherKindsFileGenerator(
private val generatedDir: File,
annotatedList: List<AnnotatedHigherKind>
) {
private val higherKinds: List<HigherKind> = annotatedList.map { HigherKind(it.classOrPackageProto.`package`, it) }
/**
* Main entry point for higher kinds extension generation
*/
fun generate() {
higherKinds.forEachIndexed { _, hk ->
val elementsToGenerate = listOf(genKindMarker(hk), genKindTypeAliases(hk), genKindedJTypeAliases(hk), genEv(hk))
val source: String = elementsToGenerate.joinToString(prefix = "package ${hk.`package`}\n\n", separator = "\n", postfix = "\n")
val file = File(generatedDir, higherKindsAnnotationClass.simpleName + ".${hk.target.classElement.qualifiedName}.kt")
file.writeText(source)
}
}
private fun genKindTypeAliases(hk: HigherKind): String = when {
hk.tparams.isEmpty() -> knownError("Class must have at least one type param to derive HigherKinds")
hk.tparams.size <= 22 -> {
val kindAlias = "typealias ${hk.name}<${hk.expandedTypeArgs}> = ${hk.alias}<${hk.markerName}, ${hk.expandedTypeArgs}>"
val acc = if (hk.tparams.size == 1) kindAlias else kindAlias + "\n" + genPartiallyAppliedKinds(hk)
acc
}
else -> knownError("HigherKinds are currently only supported up to a max of 22 type args")
}
private fun genPartiallyAppliedKinds(hk: HigherKind): String {
val appliedTypeArgs = hk.typeArgs.dropLast(1)
val expandedAppliedTypeArgs = appliedTypeArgs.joinToString(", ")
val hkimpl = if (appliedTypeArgs.size == 1) "arrow.Kind" else "arrow.Kind${appliedTypeArgs.size}"
return "typealias ${hk.name.replace("Of$".toRegex()) { "PartialOf" }}<$expandedAppliedTypeArgs> = $hkimpl<${hk.markerName}, $expandedAppliedTypeArgs>"
}
private fun genKindedJTypeAliases(hk: HigherKind): String =
if (hk.tparams.size <= 5 && allInvariantParams(hk.tparams)) {
"typealias ${hk.nameJ}<${hk.expandedTypeArgs}> = ${hk.aliasJ}<${hk.markerName}, ${hk.expandedTypeArgs}>"
} else {
""
}
private fun allInvariantParams(tparams: List<ProtoBuf.TypeParameter>): Boolean =
tparams.all { it.variance == ProtoBuf.TypeParameter.Variance.INV }
private fun genEv(hk: HigherKind): String =
"""
|@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
|inline fun <${hk.expandedTypeArgs}> ${hk.name}<${hk.expandedTypeArgs}>.fix(): ${hk.kindName}<${hk.expandedTypeArgs}>${hk.typeConstraints} =
| this as ${hk.kindName}<${hk.expandedTypeArgs}>
""".trimMargin()
private fun genKindMarker(hk: HigherKind): String =
"class ${hk.markerName} private constructor() { companion object }"
}