From 9196e4c3674db75f2fd872accb7d62d362b9a02d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 30 Jun 2024 20:40:12 +0600 Subject: [PATCH 1/3] start 0.21.2 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d24e17eb4df..02034c24319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.21.2 + ## 0.21.1 * `KSP`: diff --git a/gradle.properties b/gradle.properties index b4d958bca7c..00f40b785d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.21.1 -android_code_version=260 +version=0.21.2 +android_code_version=261 From 2b9bb4f1416303728a6d785eea4f044090e5ef7a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 30 Jun 2024 20:56:54 +0600 Subject: [PATCH 2/3] add ClassCasts generator --- CHANGELOG.md | 4 + ksp/classcasts/build.gradle | 7 ++ ksp/classcasts/generator/build.gradle | 21 ++++ .../src/main/kotlin/ClassCastsFiller.kt | 106 ++++++++++++++++ .../generator/src/main/kotlin/Processor.kt | 116 ++++++++++++++++++ .../generator/src/main/kotlin/Provider.kt | 11 ++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + ksp/classcasts/generator/test/build.gradle | 27 ++++ .../test/src/commonMain/kotlin/Test.kt | 12 ++ .../src/commonMain/kotlin/TestClassCasts.kt | 32 +++++ .../commonMain/kotlin/ClassCastsExcluded.kt | 5 + .../commonMain/kotlin/ClassCastsIncluded.kt | 5 + settings.gradle | 4 + 13 files changed, 351 insertions(+) create mode 100644 ksp/classcasts/build.gradle create mode 100644 ksp/classcasts/generator/build.gradle create mode 100644 ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt create mode 100644 ksp/classcasts/generator/src/main/kotlin/Processor.kt create mode 100644 ksp/classcasts/generator/src/main/kotlin/Provider.kt create mode 100644 ksp/classcasts/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 ksp/classcasts/generator/test/build.gradle create mode 100644 ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt create mode 100644 ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt create mode 100644 ksp/classcasts/src/commonMain/kotlin/ClassCastsExcluded.kt create mode 100644 ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 02034c24319..e0085933516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.21.2 +* `KSP`: + * `ClassCasts`: + * Module has been initialized + ## 0.21.1 * `KSP`: diff --git a/ksp/classcasts/build.gradle b/ksp/classcasts/build.gradle new file mode 100644 index 00000000000..d425197852e --- /dev/null +++ b/ksp/classcasts/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" diff --git a/ksp/classcasts/generator/build.gradle b/ksp/classcasts/generator/build.gradle new file mode 100644 index 00000000000..8fcda0f1491 --- /dev/null +++ b/ksp/classcasts/generator/build.gradle @@ -0,0 +1,21 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +apply from: "$publishJvmOnlyPath" + +repositories { + mavenCentral() +} + +dependencies { + api project(":micro_utils.ksp.generator") + api project(":micro_utils.ksp.classcasts") + api libs.kotlin.poet + api libs.ksp +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} diff --git a/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt b/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt new file mode 100644 index 00000000000..d70734b16f3 --- /dev/null +++ b/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt @@ -0,0 +1,106 @@ +package dev.inmo.micro_utils.ksp.classcasts.generator + +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName + + +private fun FileSpec.Builder.addTopLevelImport(className: ClassName) { + className.topLevelClassName().let { + addImport(it.packageName, it.simpleNames) + } +} + +private fun FileSpec.Builder.createTypeDefinition(ksClassDeclaration: KSClassDeclaration): TypeName { + val className = ksClassDeclaration.toClassName() + return if (ksClassDeclaration.typeParameters.isNotEmpty()) { + className.parameterizedBy( + ksClassDeclaration.typeParameters.map { + it.bounds.first().resolve().also { + val typeClassName = it.toClassName() + addTopLevelImport(typeClassName) + }.toTypeName() + } + ) + } else { + className + } +} + +internal fun FileSpec.Builder.fill( + sourceKSClassDeclaration: KSClassDeclaration, + subtypesMap: Map>, + targetClassDeclaration: KSClassDeclaration = sourceKSClassDeclaration +) { + if (sourceKSClassDeclaration == targetClassDeclaration) { + subtypesMap[sourceKSClassDeclaration] ?.forEach { + fill(sourceKSClassDeclaration, subtypesMap, it) + } + } else { + val sourceClassName = sourceKSClassDeclaration.toClassName() + val targetClassClassName = targetClassDeclaration.toClassName() + val targetClassTypeDefinition = createTypeDefinition(targetClassDeclaration) + val simpleName = targetClassDeclaration.simpleName.asString() + val withFirstLowerCase = simpleName.replaceFirstChar { it.lowercase() } + val castedOrNullName = "${withFirstLowerCase}OrNull" + + addTopLevelImport(targetClassClassName) + addFunction( + FunSpec.builder(castedOrNullName).apply { + receiver(sourceClassName) + addCode( + "return this as? %L", + targetClassTypeDefinition + ) + returns(targetClassTypeDefinition.copy(nullable = true)) + addModifiers(KModifier.INLINE) + }.build() + ) + addFunction( + FunSpec.builder("${withFirstLowerCase}OrThrow").apply { + receiver(sourceClassName) + addCode( + "return this as %L", + targetClassTypeDefinition + ) + returns(targetClassTypeDefinition) + addModifiers(KModifier.INLINE) + }.build() + ) + addFunction( + FunSpec.builder("if$simpleName").apply { + val genericType = TypeVariableName("T", null) + addTypeVariable(genericType) + receiver(sourceClassName) + addParameter( + "block", + LambdaTypeName.get( + null, + targetClassTypeDefinition, + returnType = genericType + ) + ) + addCode( + "return ${castedOrNullName}() ?.let(block)", + targetClassTypeDefinition + ) + returns(genericType.copy(nullable = true)) + 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) + } + } +} diff --git a/ksp/classcasts/generator/src/main/kotlin/Processor.kt b/ksp/classcasts/generator/src/main/kotlin/Processor.kt new file mode 100644 index 00000000000..d996ba7bd43 --- /dev/null +++ b/ksp/classcasts/generator/src/main/kotlin/Processor.kt @@ -0,0 +1,116 @@ +package dev.inmo.micro_utils.ksp.classcasts.generator + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAllSuperTypes +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.isAnnotationPresent +import com.google.devtools.ksp.processing.CodeGenerator +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 dev.inmo.micro_ksp.generator.writeFile +import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded +import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded + +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() + val classesRegexes: Map> = 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 classesSubtypes = mutableMapOf>() + + 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 fillWithSealeds(source: KSClassDeclaration, current: KSClassDeclaration = source) { + val regexes = classesRegexes[source] + current.getSealedSubclasses().forEach { + val simpleName = it.simpleName.getShortName() + if ( + regexes ?.first ?.matches(simpleName) == false + || regexes ?.second ?.matches(simpleName) == true + || it.isAnnotationPresent(ClassCastsExcluded::class) + ) { + return@forEach + } + classesSubtypes.getOrPut(source) { mutableSetOf() }.add(it) + fillWithSealeds(source, it) + } + } + classes.forEach { fillWithSealeds(it) } + + addAnnotation( + AnnotationSpec.builder(Suppress::class).apply { + addMember("\"unused\"") + addMember("\"RemoveRedundantQualifierName\"") + addMember("\"RedundantVisibilityModifier\"") + addMember("\"NOTHING_TO_INLINE\"") + addMember("\"UNCHECKED_CAST\"") + addMember("\"OPT_IN_USAGE\"") + useSiteTarget(AnnotationSpec.UseSiteTarget.FILE) + }.build() + ) + classes.forEach { + fill( + it, + classesSubtypes.toMap() + ) + } + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + (resolver.getSymbolsWithAnnotation(ClassCastsIncluded::class.qualifiedName!!)).filterIsInstance().forEach { + val prefix = it.getAnnotationsByType(ClassCastsIncluded::class).first().outputFilePrefix + it.writeFile(prefix = prefix, suffix = "ClassCasts") { + FileSpec.builder( + it.packageName.asString(), + "${it.simpleName.getShortName()}ClassCasts" + ).apply { + addFileComment( + """ + THIS CODE HAVE BEEN GENERATED AUTOMATICALLY + TO REGENERATE IT JUST DELETE FILE + ORIGINAL FILE: ${it.containingFile ?.fileName} + """.trimIndent() + ) + generateClassCasts(it, resolver) + }.build() + } + } + + return emptyList() + } +} diff --git a/ksp/classcasts/generator/src/main/kotlin/Provider.kt b/ksp/classcasts/generator/src/main/kotlin/Provider.kt new file mode 100644 index 00000000000..11b2c94a8f0 --- /dev/null +++ b/ksp/classcasts/generator/src/main/kotlin/Provider.kt @@ -0,0 +1,11 @@ +package dev.inmo.micro_utils.ksp.classcasts.generator + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class Provider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor( + environment.codeGenerator + ) +} diff --git a/ksp/classcasts/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/ksp/classcasts/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000000..88b6e894479 --- /dev/null +++ b/ksp/classcasts/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +dev.inmo.micro_utils.ksp.classcasts.generator.Provider diff --git a/ksp/classcasts/generator/test/build.gradle b/ksp/classcasts/generator/test/build.gradle new file mode 100644 index 00000000000..ee86dd185d9 --- /dev/null +++ b/ksp/classcasts/generator/test/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" + id "com.google.devtools.ksp" +} + +apply from: "$mppProjectWithSerializationPresetPath" + + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.ksp.classcasts") + } + } + } +} + + +dependencies { + add("kspCommonMainMetadata", project(":micro_utils.ksp.classcasts.generator")) +} + +ksp { +} diff --git a/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt b/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt new file mode 100644 index 00000000000..ddcd317a427 --- /dev/null +++ b/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt @@ -0,0 +1,12 @@ +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 +sealed interface Test { + object A : Test + @ClassCastsExcluded + object B : Test + object C : Test +} diff --git a/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt b/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt new file mode 100644 index 00000000000..f1a9e967c91 --- /dev/null +++ b/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt @@ -0,0 +1,32 @@ +// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY +// TO REGENERATE IT JUST DELETE FILE +// ORIGINAL FILE: Test.kt +@file:Suppress( + "unused", + "RemoveRedundantQualifierName", + "RedundantVisibilityModifier", + "NOTHING_TO_INLINE", + "UNCHECKED_CAST", + "OPT_IN_USAGE", +) + +package dev.inmo.micro_utils.ksp.classcasts.generator.test + +import dev.inmo.micro_utils.ksp.classcasts.generator.test.Test +import kotlin.Suppress + +public inline fun Test.aOrNull(): Test.A? = this as? + dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A + +public inline fun Test.aOrThrow(): Test.A = this as + dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.A + +public inline fun Test.ifA(block: (Test.A) -> T): T? = aOrNull() ?.let(block) + +public inline fun Test.cOrNull(): Test.C? = this as? + dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C + +public inline fun Test.cOrThrow(): Test.C = this as + dev.inmo.micro_utils.ksp.classcasts.generator.test.Test.C + +public inline fun Test.ifC(block: (Test.C) -> T): T? = cOrNull() ?.let(block) diff --git a/ksp/classcasts/src/commonMain/kotlin/ClassCastsExcluded.kt b/ksp/classcasts/src/commonMain/kotlin/ClassCastsExcluded.kt new file mode 100644 index 00000000000..1d9ad4d17d8 --- /dev/null +++ b/ksp/classcasts/src/commonMain/kotlin/ClassCastsExcluded.kt @@ -0,0 +1,5 @@ +package dev.inmo.micro_utils.ksp.classcasts + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class ClassCastsExcluded diff --git a/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt b/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt new file mode 100644 index 00000000000..3099a916092 --- /dev/null +++ b/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt @@ -0,0 +1,5 @@ +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 = "") diff --git a/settings.gradle b/settings.gradle index b4c5e9abf8f..b31bd466a2e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -55,6 +55,10 @@ String[] includes = [ ":ksp:sealed:generator", ":ksp:sealed:generator:test", + ":ksp:classcasts", + ":ksp:classcasts:generator", + ":ksp:classcasts:generator:test", + ":dokka" ] From f9795d53a0e964b5e5e5ffd1961dc4b8602730ad Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 30 Jun 2024 22:55:28 +0600 Subject: [PATCH 3/3] improve classcasts --- .../src/main/kotlin/ClassCastsFiller.kt | 20 +---- .../generator/src/main/kotlin/Processor.kt | 85 ++++++++++--------- .../test/src/commonMain/kotlin/Test.kt | 7 +- .../src/commonMain/kotlin/TestClassCasts.kt | 8 ++ .../commonMain/kotlin/ClassCastsIncluded.kt | 7 +- 5 files changed, 65 insertions(+), 62 deletions(-) diff --git a/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt b/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt index d70734b16f3..660545c1151 100644 --- a/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt +++ b/ksp/classcasts/generator/src/main/kotlin/ClassCastsFiller.kt @@ -32,14 +32,9 @@ private fun FileSpec.Builder.createTypeDefinition(ksClassDeclaration: KSClassDec internal fun FileSpec.Builder.fill( sourceKSClassDeclaration: KSClassDeclaration, - subtypesMap: Map>, - targetClassDeclaration: KSClassDeclaration = sourceKSClassDeclaration + subtypes: Set ) { - 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) - } } } diff --git a/ksp/classcasts/generator/src/main/kotlin/Processor.kt b/ksp/classcasts/generator/src/main/kotlin/Processor.kt index d996ba7bd43..bd387d233b8 100644 --- a/ksp/classcasts/generator/src/main/kotlin/Processor.kt +++ b/ksp/classcasts/generator/src/main/kotlin/Processor.kt @@ -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() - val classesRegexes: Map> = 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>() - 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) diff --git a/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt b/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt index ddcd317a427..51b71a25bf2 100644 --- a/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt +++ b/ksp/classcasts/generator/test/src/commonMain/kotlin/Test.kt @@ -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 + } } diff --git a/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt b/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt index f1a9e967c91..88bf768af4e 100644 --- a/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt +++ b/ksp/classcasts/generator/test/src/commonMain/kotlin/TestClassCasts.kt @@ -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 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 Test.ifD(block: (Test.D) -> T): T? = dOrNull() ?.let(block) diff --git a/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt b/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt index 3099a916092..f965aef624f 100644 --- a/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt +++ b/ksp/classcasts/src/commonMain/kotlin/ClassCastsIncluded.kt @@ -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 +)