mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-11-22 16:23:50 +00:00
improve classcasts
This commit is contained in:
parent
2b9bb4f141
commit
f9795d53a0
@ -32,14 +32,9 @@ private fun FileSpec.Builder.createTypeDefinition(ksClassDeclaration: KSClassDec
|
||||
|
||||
internal fun FileSpec.Builder.fill(
|
||||
sourceKSClassDeclaration: KSClassDeclaration,
|
||||
subtypesMap: Map<KSClassDeclaration, Set<KSClassDeclaration>>,
|
||||
targetClassDeclaration: KSClassDeclaration = sourceKSClassDeclaration
|
||||
subtypes: Set<KSClassDeclaration>
|
||||
) {
|
||||
if (sourceKSClassDeclaration == targetClassDeclaration) {
|
||||
subtypesMap[sourceKSClassDeclaration] ?.forEach {
|
||||
fill(sourceKSClassDeclaration, subtypesMap, it)
|
||||
}
|
||||
} else {
|
||||
subtypes.forEach { targetClassDeclaration ->
|
||||
val sourceClassName = sourceKSClassDeclaration.toClassName()
|
||||
val targetClassClassName = targetClassDeclaration.toClassName()
|
||||
val targetClassTypeDefinition = createTypeDefinition(targetClassDeclaration)
|
||||
@ -91,16 +86,5 @@ internal fun FileSpec.Builder.fill(
|
||||
addModifiers(KModifier.INLINE)
|
||||
}.build()
|
||||
)
|
||||
|
||||
subtypesMap[targetClassDeclaration] ?.let {
|
||||
if (it.count { it.classKind == ClassKind.CLASS } > 1) {
|
||||
it
|
||||
} else {
|
||||
it.filter { it.classKind != ClassKind.CLASS }
|
||||
}
|
||||
} ?.forEach {
|
||||
fill(sourceKSClassDeclaration, subtypesMap, it)
|
||||
fill(targetClassDeclaration, subtypesMap, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,67 +9,72 @@ import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.symbol.*
|
||||
import com.squareup.kotlinpoet.*
|
||||
import com.squareup.kotlinpoet.ksp.toClassName
|
||||
import dev.inmo.micro_ksp.generator.writeFile
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
import java.io.File
|
||||
|
||||
class Processor(
|
||||
private val codeGenerator: CodeGenerator
|
||||
) : SymbolProcessor {
|
||||
private val classCastsIncludedClassName = ClassCastsIncluded::class.asClassName()
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
private fun FileSpec.Builder.generateClassCasts(
|
||||
ksClassDeclaration: KSClassDeclaration,
|
||||
resolver: Resolver
|
||||
) {
|
||||
val classes = resolver.getSymbolsWithAnnotation(classCastsIncludedClassName.canonicalName).filterIsInstance<KSClassDeclaration>()
|
||||
val classesRegexes: Map<KSClassDeclaration, Pair<Regex?, Regex?>> = classes.mapNotNull {
|
||||
it to (it.getAnnotationsByType(ClassCastsIncluded::class).firstNotNullOfOrNull {
|
||||
it.typesRegex.takeIf { it.isNotEmpty() } ?.let(::Regex) to it.excludeRegex.takeIf { it.isNotEmpty() } ?.let(::Regex)
|
||||
} ?: return@mapNotNull null)
|
||||
}.toMap()
|
||||
val rootAnnotation = ksClassDeclaration.getAnnotationsByType(ClassCastsIncluded::class).first()
|
||||
val (includeRegex: Regex?, excludeRegex: Regex?) = rootAnnotation.let {
|
||||
it.typesRegex.takeIf { it.isNotEmpty() } ?.let(::Regex) to it.excludeRegex.takeIf { it.isNotEmpty() } ?.let(::Regex)
|
||||
}
|
||||
val classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>()
|
||||
|
||||
resolver.getAllFiles().forEach {
|
||||
it.declarations.forEach { potentialSubtype ->
|
||||
if (
|
||||
potentialSubtype is KSClassDeclaration
|
||||
&& potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class).not()
|
||||
) {
|
||||
val allSupertypes = potentialSubtype.getAllSuperTypes().map { it.declaration }
|
||||
|
||||
for (currentClass in classes) {
|
||||
val regexes = classesRegexes[currentClass]
|
||||
val simpleName = potentialSubtype.simpleName.getShortName()
|
||||
when {
|
||||
currentClass !in allSupertypes
|
||||
|| regexes ?.first ?.matches(simpleName) == false
|
||||
|| regexes ?.second ?.matches(simpleName) == true -> continue
|
||||
else -> {
|
||||
classesSubtypes.getOrPut(currentClass) { mutableSetOf() }.add(potentialSubtype)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun KSClassDeclaration.checkSupertypeLevel(levelsAllowed: Int?): Boolean {
|
||||
val supertypes by lazy {
|
||||
superTypes.map { it.resolve().declaration }
|
||||
}
|
||||
return when {
|
||||
levelsAllowed == null -> true
|
||||
levelsAllowed <= 0 -> false
|
||||
supertypes.any { it == ksClassDeclaration } -> true
|
||||
else -> supertypes.any {
|
||||
(it as? KSClassDeclaration) ?.checkSupertypeLevel(levelsAllowed - 1) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
fun fillWithSealeds(source: KSClassDeclaration, current: KSClassDeclaration = source) {
|
||||
val regexes = classesRegexes[source]
|
||||
|
||||
fun handleDeclaration(ksDeclarationContainer: KSDeclarationContainer) {
|
||||
ksDeclarationContainer.declarations.forEach { potentialSubtype ->
|
||||
val simpleName = potentialSubtype.simpleName.getShortName()
|
||||
when {
|
||||
potentialSubtype === ksClassDeclaration -> {}
|
||||
potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class) -> return@forEach
|
||||
potentialSubtype !is KSClassDeclaration || !potentialSubtype.checkSupertypeLevel(rootAnnotation.levelsToInclude.takeIf { it >= 0 }) -> return@forEach
|
||||
excludeRegex ?.matches(simpleName) == true -> return@forEach
|
||||
includeRegex ?.matches(simpleName) == false -> {}
|
||||
else -> classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(potentialSubtype)
|
||||
}
|
||||
handleDeclaration(potentialSubtype as? KSDeclarationContainer ?: return@forEach)
|
||||
}
|
||||
}
|
||||
resolver.getAllFiles().forEach {
|
||||
handleDeclaration(it)
|
||||
}
|
||||
fun fillWithSealeds(current: KSClassDeclaration) {
|
||||
current.getSealedSubclasses().forEach {
|
||||
val simpleName = it.simpleName.getShortName()
|
||||
if (
|
||||
regexes ?.first ?.matches(simpleName) == false
|
||||
|| regexes ?.second ?.matches(simpleName) == true
|
||||
includeRegex ?.matches(simpleName) == false
|
||||
|| excludeRegex ?.matches(simpleName) == true
|
||||
|| it.isAnnotationPresent(ClassCastsExcluded::class)
|
||||
) {
|
||||
return@forEach
|
||||
}
|
||||
classesSubtypes.getOrPut(source) { mutableSetOf() }.add(it)
|
||||
fillWithSealeds(source, it)
|
||||
classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(it)
|
||||
fillWithSealeds(it)
|
||||
}
|
||||
}
|
||||
classes.forEach { fillWithSealeds(it) }
|
||||
fillWithSealeds(ksClassDeclaration)
|
||||
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Suppress::class).apply {
|
||||
@ -82,12 +87,10 @@ class Processor(
|
||||
useSiteTarget(AnnotationSpec.UseSiteTarget.FILE)
|
||||
}.build()
|
||||
)
|
||||
classes.forEach {
|
||||
fill(
|
||||
it,
|
||||
classesSubtypes.toMap()
|
||||
)
|
||||
}
|
||||
fill(
|
||||
ksClassDeclaration,
|
||||
classesSubtypes.values.flatten().toSet()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
|
@ -3,10 +3,13 @@ package dev.inmo.micro_utils.ksp.classcasts.generator.test
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
|
||||
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
|
||||
|
||||
@ClassCastsIncluded
|
||||
@ClassCastsIncluded(levelsToInclude = 1)
|
||||
sealed interface Test {
|
||||
object A : Test
|
||||
@ClassCastsExcluded
|
||||
object B : Test
|
||||
object B : Test // Will not be included in class casts due to annotation ClassCastsExcluded
|
||||
object C : Test
|
||||
interface D : Test {
|
||||
object DD : D // Will not be included in class casts due to levelsToInclude
|
||||
}
|
||||
}
|
||||
|
@ -30,3 +30,11 @@ public inline fun Test.cOrThrow(): Test.C = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C
|
||||
|
||||
public inline fun <T> Test.ifC(block: (Test.C) -> T): T? = cOrNull() ?.let(block)
|
||||
|
||||
public inline fun Test.dOrNull(): Test.D? = this as?
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun Test.dOrThrow(): Test.D = this as
|
||||
dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.D
|
||||
|
||||
public inline fun <T> Test.ifD(block: (Test.D) -> T): T? = dOrNull() ?.let(block)
|
||||
|
@ -2,4 +2,9 @@ package dev.inmo.micro_utils.ksp.classcasts
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class ClassCastsIncluded(val typesRegex: String = "", val excludeRegex: String = "", val outputFilePrefix: String = "")
|
||||
annotation class ClassCastsIncluded(
|
||||
val typesRegex: String = "",
|
||||
val excludeRegex: String = "",
|
||||
val outputFilePrefix: String = "",
|
||||
val levelsToInclude: Int = -1
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user