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