improve class casts including filtering

This commit is contained in:
InsanusMokrassar 2023-06-16 13:50:34 +06:00
parent 6f60ecbf2e
commit f153b31464
15 changed files with 3230 additions and 858 deletions

View File

@ -1,6 +1,6 @@
[versions]
kotlin = "1.8.21"
kotlin = "1.8.22"
kotlin-serialization = "1.5.1"
kotlin-coroutines = "1.6.4"
@ -10,7 +10,7 @@ korlibs = "4.0.3"
uuid = "0.7.1"
ktor = "2.3.1"
ksp = "1.8.21-1.0.11"
ksp = "1.8.22-1.0.11"
kotlin-poet = "1.14.2"
microutils = "0.19.2"

View File

@ -8,7 +8,7 @@ import dev.inmo.tgbotapi.types.chat.User
*
* @see FromUser
*/
@ClassCastsIncluded
@ClassCastsIncluded(excludeRegex = ".*Impl")
interface WithUser {
val user: User
}

View File

@ -7,7 +7,7 @@ import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import kotlinx.serialization.Serializable
@Serializable(InlineQueryResultSerializer::class)
@ClassCastsIncluded
@ClassCastsIncluded(excludeRegex = ".*Impl")
interface InlineQueryResult {
val type: String
val id: InlineQueryIdentifier

View File

@ -49,7 +49,7 @@ sealed interface AbleToAddInAttachmentMenuChat : Chat {
}
@Serializable(PreviewChatSerializer::class)
@ClassCastsIncluded(excludeRegex = ".*Impl.kt")
@ClassCastsIncluded(excludeRegex = ".*Impl")
sealed interface Chat {
val id: IdChatIdentifier
}

View File

@ -4,12 +4,14 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializer
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ExtendedChannelChat due")
@ClassCastsExcluded
data class ExtendedChannelChatImpl(
@SerialName(idField)
override val id: ChatId,
@ -36,6 +38,7 @@ data class ExtendedChannelChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ExtendedGroupChat due")
@ClassCastsExcluded
data class ExtendedGroupChatImpl(
@SerialName(idField)
override val id: ChatId,
@ -58,6 +61,7 @@ data class ExtendedGroupChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ExtendedPrivateChat due")
@ClassCastsExcluded
data class ExtendedPrivateChatImpl(
@SerialName(idField)
override val id: UserId,
@ -85,6 +89,7 @@ typealias ExtendedUser = ExtendedPrivateChatImpl
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ExtendedSupergroupChat due")
@ClassCastsExcluded
data class ExtendedSupergroupChatImpl(
@SerialName(idField)
override val id: ChatId,
@ -127,6 +132,7 @@ data class ExtendedSupergroupChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ExtendedForumChat due")
@ClassCastsExcluded
data class ExtendedForumChatImpl(
@SerialName(idField)
override val id: IdChatIdentifier,

View File

@ -5,11 +5,13 @@ import dev.inmo.micro_utils.language_codes.IetfLanguageCodeSerializer
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.abstracts.WithOptionalLanguageCode
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use GroupChat due")
@ClassCastsExcluded
data class GroupChatImpl(
@SerialName(idField)
override val id: ChatId,
@ -19,6 +21,7 @@ data class GroupChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use PrivateChat due")
@ClassCastsExcluded
data class PrivateChatImpl(
@SerialName(idField)
override val id: UserId,
@ -32,6 +35,7 @@ data class PrivateChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use SupergroupChat due")
@ClassCastsExcluded
data class SupergroupChatImpl(
@SerialName(idField)
override val id: ChatId,
@ -43,6 +47,7 @@ data class SupergroupChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ForumChat due")
@ClassCastsExcluded
data class ForumChatImpl(
@SerialName(idField)
override val id: IdChatIdentifier,
@ -54,6 +59,7 @@ data class ForumChatImpl(
@Serializable
@RiskFeature("This class is a subject of changes. It is better to use ChannelChat due")
@ClassCastsExcluded
data class ChannelChatImpl(
@SerialName(idField)
override val id: ChatId,

View File

@ -2,9 +2,11 @@ package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.*
@Serializable
@ClassCastsExcluded
data class AdministratorChatMemberImpl(
@SerialName(userField)
override val user: User,

View File

@ -1,10 +1,12 @@
package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@ClassCastsExcluded
data class ChatAdministratorRightsImpl(
@SerialName(canChangeInfoField)
override val canChangeInfo: Boolean = false,

View File

@ -2,9 +2,11 @@ package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.*
@Serializable
@ClassCastsExcluded
data class LeftChatMemberImpl(@SerialName(userField) override val user: User) : LeftChatMember {
@SerialName(statusField)
@Required

View File

@ -2,9 +2,11 @@ package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import kotlinx.serialization.*
@Serializable
@ClassCastsExcluded
data class MemberChatMemberImpl(@SerialName(userField) override val user: User) : MemberChatMember {
@SerialName(statusField)
@Required

View File

@ -11,7 +11,7 @@ import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@ClassCastsIncluded
@ClassCastsIncluded(excludeRegex = ".*Impl")
interface Message : WithChat {
val messageId: MessageId
val date: DateTime

View File

@ -1,5 +1,13 @@
package dev.inmo.tgbotapi.utils.internal
import dev.inmo.tgbotapi.utils.RiskFeature
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
internal annotation class ClassCastsIncluded(val typesRegex: String = ".*", val excludeRegex: String = "")
@RiskFeature("It is internal API in tgbotapi.core and should not be used outside")
annotation class ClassCastsIncluded(val typesRegex: String = "", val excludeRegex: String = "")
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
@RiskFeature("It is internal API in tgbotapi.core and should not be used outside")
annotation class ClassCastsExcluded()

View File

@ -9,4 +9,5 @@ repositories {
dependencies {
implementation libs.kotlin.poet
implementation libs.ksp
implementation project(":tgbotapi.core")
}

View File

@ -1,15 +1,21 @@
package dev.inmo.tgbotapi.ksp.processor
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.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.ClassCastsExcluded
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
import java.io.File
class TelegramBotAPISymbolProcessor(
@ -18,35 +24,33 @@ class TelegramBotAPISymbolProcessor(
private val outputFile: String = "Output",
private val outputFolder: String? = null
) : SymbolProcessor {
private val classCastsIncludedClassName = ClassName("dev.inmo.tgbotapi.utils.internal", "ClassCastsIncluded")
private val classCastsIncludedClassName = ClassCastsIncluded::class.asClassName()
@OptIn(KspExperimental::class, RiskFeature::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
val classes = resolver.getSymbolsWithAnnotation(classCastsIncludedClassName.canonicalName).filterIsInstance<KSClassDeclaration>()
val classesRegexes: Map<KSClassDeclaration, Pair<Regex, Regex?>> = classes.mapNotNull {
it to (it.annotations.firstNotNullOfOrNull {
runCatching {
if (it.annotationType.resolve().toClassName() == classCastsIncludedClassName) {
val regex = it.arguments.first().value as? String ?: return@runCatching null
val negativeRegex = (it.arguments.first().value as? String) ?.takeIf { it.isNotEmpty() }
Regex(regex) to (negativeRegex ?.let(::Regex))
} else {
null
}
}.getOrNull()
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 classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>()
resolver.getAllFiles().forEach {
it.declarations.forEach { potentialSubtype ->
if (potentialSubtype is KSClassDeclaration) {
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(potentialSubtype.simpleName.toString()) != false
&& regexes ?.second ?.matches(potentialSubtype.simpleName.toString()) != true-> {
currentClass !in allSupertypes
|| regexes ?.first ?.matches(simpleName) == false
|| regexes ?.second ?.matches(simpleName) == true -> continue
else -> {
classesSubtypes.getOrPut(currentClass) { mutableSetOf() }.add(potentialSubtype)
}
}
@ -55,7 +59,16 @@ class TelegramBotAPISymbolProcessor(
}
}
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)
}