google / ksp

Kotlin Symbol Processing API
https://github.com/google/ksp
Apache License 2.0
2.89k stars 274 forks source link

KSDeclaration.isOpen and isAbstract had better be independant as in kotlin reflcetion. #1166

Open ShawxingKwok opened 2 years ago

neetopia commented 2 years ago

can you elaborate on the request?

ShawxingKwok commented 2 years ago

For example

abstract class Bar{
    abstract fun foo()
}

Class Bar and function Bar::foo are both open in Ksp, but not in Kotlin reflection. I think Ksp had better keep the same with Kotlin reflection library on this feature.

ShawxingKwok commented 2 years ago

sealed class / interface should be also abstract.

ShawxingKwok commented 2 years ago

Here is my modification.

private val cache = ImmutableKSDeclarationInfoCache<Modifier>(false, false)

public val KSDeclaration.status: Modifier get() = cache.getOrPut {
    when {
        // consider that bug first: modifiers in the java @interface declaration contain ABSTRACT
        this is KSClassDeclaration && classKind == ClassKind.ANNOTATION_CLASS -> Modifier.FINAL

        // return directly if its modifiers contain any key modifier
        Modifier.FINAL in modifiers || Modifier.JAVA_STATIC in modifiers -> Modifier.FINAL
        Modifier.OPEN in modifiers || Modifier.JAVA_DEFAULT in modifiers -> Modifier.OPEN
        Modifier.SEALED in modifiers || Modifier.ABSTRACT in modifiers -> Modifier.ABSTRACT

        // situations below are property or function with no FINAL, OPEN or ABSTRACT
        this is KSClassDeclaration ->
            when (classKind) {
                ClassKind.INTERFACE -> Modifier.ABSTRACT

                ClassKind.CLASS ->
                    // default kotlin class
                    if (origin == Origin.KOTLIN || origin == Origin.KOTLIN_LIB)
                        Modifier.FINAL
                    else // default java class
                        Modifier.OPEN

                // object, annotation, enum
                else -> Modifier.FINAL
            }

        // this is property or function
        else -> {
            val parentKlass = parentDeclaration as? KSClassDeclaration

            when (parentKlass?.classKind) {
                // on top level
                null -> Modifier.FINAL

                ClassKind.INTERFACE -> when (this) {
                    // constant in java interface was considered above
                    // and here is from only kotlin
                    is KSPropertyDeclaration ->
                        if (Modifier.ABSTRACT in getter!!.modifiers)
                            Modifier.ABSTRACT
                        else
                            Modifier.OPEN

                    is KSFunctionDeclaration ->
                        // 'isAbstract' is realized in KSFunctionDeclarationImpl, but that part calls
                        // an internal function ktFunction.hasBody and can't be taken here.
                        if (isAbstract) Modifier.ABSTRACT
                        else Modifier.OPEN

                    else -> unexpectedError()
                }

                ClassKind.CLASS -> when {
                    parentKlass.status == Modifier.FINAL -> Modifier.FINAL

                    // situations below are in open or abstract class
                    // default kotlin property or function
                    origin == Origin.KOTLIN_LIB || origin == Origin.KOTLIN ->
                        if (Modifier.OVERRIDE in modifiers)
                            Modifier.OPEN
                        else
                            Modifier.FINAL

                    // java property
                    this is KSPropertyDeclaration -> Modifier.FINAL

                    // default java function
                    this is KSFunctionDeclaration -> Modifier.OPEN

                    else -> unexpectedError()
                }

                // in an object, annotation, or enum class
                else -> Modifier.FINAL
            }
        }
    }
}

private fun KSDeclaration._isMyOpen() = status == Modifier.OPEN
private fun KSDeclaration._isMyAbstract() = status == Modifier.ABSTRACT
private fun KSDeclaration._isFinal() = status == Modifier.FINAL

public fun KSClassDeclaration.isMyOpen(): Boolean = _isMyOpen()
public fun KSClassDeclaration.isMyAbstract(): Boolean = _isMyAbstract()
public fun KSClassDeclaration.isFinal(): Boolean = _isFinal()

public fun KSPropertyDeclaration.isMyOpen(): Boolean = _isMyOpen()
public fun KSPropertyDeclaration.isMyAbstract(): Boolean = _isMyAbstract()
public fun KSPropertyDeclaration.isFinal(): Boolean = _isFinal()

public fun KSFunctionDeclaration.isMyOpen(): Boolean = _isMyOpen()
public fun KSFunctionDeclaration.isMyAbstract(): Boolean = _isMyAbstract()
public fun KSFunctionDeclaration.isFinal(): Boolean = _isFinal()
ShawxingKwok commented 1 year ago

And you can refer to my cache class.

/**
 * Caches immutable KSDeclaration info of type [T] which is same in the same site. Local declarations 
 * themselves would be used as keys and cleared every round, but qualified names of others would be 
 * used as keys, which is, therefore, much more efficient.
 * @param concurrent if enabled, the cache work would be synchronized, which is generally for
 * complicated work.
 * @param nullable if the result is nullable
 */
public class ImmutableKSDeclarationInfoCache<T>(
    private val concurrent: Boolean,
    private val nullable: Boolean
) {
    private val unregisteredCache: MutableMap<Any, T> = mutableMapOf()
    // saves local info and is cleared every round.
    private val registeredCache: MutableMap<Any, T> = mutableMapOf<Any, T>().alsoRegister()

    private val lock = object {}

    context (KSDeclaration)
    @Suppress("UNCHECKED_CAST")
    public fun getOrPut(defaultValue: ()->T): T {
        val (cache, key) =
            when(val qualifiedName = qualifiedName?.asString()){
                null -> registeredCache to this
                else -> unregisteredCache to qualifiedName
            }

        fun getValue(): T = when{
            !nullable -> cache.getOrPut(key, defaultValue)
            key in cache -> cache[key] as T
            else -> defaultValue().also{ cache[key] = it }
        }

        if (concurrent)
            return synchronized(lock, ::getValue)
        else
            return getValue()
    }
}