KSAsMemberOf.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.room.compiler.processing.ksp

import org.jetbrains.kotlin.ksp.closestClassDeclaration
import org.jetbrains.kotlin.ksp.getAllSuperTypes
import org.jetbrains.kotlin.ksp.processing.Resolver
import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
import org.jetbrains.kotlin.ksp.symbol.KSDeclaration
import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
import org.jetbrains.kotlin.ksp.symbol.KSName
import org.jetbrains.kotlin.ksp.symbol.KSPropertyDeclaration
import org.jetbrains.kotlin.ksp.symbol.KSType
import org.jetbrains.kotlin.ksp.symbol.KSTypeArgument
import org.jetbrains.kotlin.ksp.symbol.KSTypeParameter
import org.jetbrains.kotlin.ksp.symbol.KSVariableParameter
import org.jetbrains.kotlin.ksp.symbol.Nullability

/**
 * Returns the type of a property as if it is member of the given [ksType].
 *
 * This is a temporary / inefficient implementation until KSP provides the API. It also does not
 * handle inner classes properly.
 * TODO: remove once https://github.com/android/kotlin/issues/26 is implemented
 */
internal fun KSPropertyDeclaration.typeAsMemberOf(resolver: Resolver, ksType: KSType): KSType {
    val myType: KSType = checkNotNull(type?.requireType()) {
        "Cannot find type of Kotlin property: $this"
    }
    return myType.asMemberOf(resolver, this, ksType)
}

internal fun KSVariableParameter.typeAsMemberOf(
    resolver: Resolver,
    functionDeclaration: KSFunctionDeclaration,
    ksType: KSType
): KSType {
    val myType: KSType = checkNotNull(type?.requireType()) {
        "Cannot find type of method parameter: $this"
    }
    return myType.asMemberOf(resolver, functionDeclaration, ksType)
}

internal fun KSFunctionDeclaration.returnTypeAsMemberOf(
    resolver: Resolver,
    ksType: KSType
): KSType {
    val myType: KSType = checkNotNull(returnType?.requireType()) {
        "Cannot resolve return type of $this"
    }
    return myType.asMemberOf(resolver, this, ksType)
}

/**
 * Returns `this` type as member of the [other] type.
 *
 * @param resolver The KSP resolver instance
 * @param declaration The KSDeclaration where the owner of this type is defined. Note that this can
 * be different from [KSType.declaration]. For instance, if you have a class `Foo<T>` with property
 * `x : List<T>`, `x`'s type declaration is `kotlin.List` whereas the declaration that
 * should be passed here is `x` (from which the implementation will find `Foo`). On the other hand,
 * `T` of `List<T>`'s declaration is already in `Foo`.
 * @param other The new owner for this type. For instance, if you want to resolve `x` in
 * `Bar<String>`, this would be the star projected type of `Bar`.
 */
internal fun KSType.asMemberOf(
    resolver: Resolver,
    declaration: KSDeclaration,
    other: KSType
): KSType {
    val parent = declaration.closestClassDeclaration() ?: return this
    val parentQName = parent.qualifiedName ?: return this
    val matchingParentType: KSType = (other.declaration as? KSClassDeclaration)
        ?.getAllSuperTypes()
        ?.firstOrNull {
            it.starProjection().declaration.qualifiedName == parentQName
        } ?: return this
    // create a map of replacements.
    val replacements = parent.typeParameters.mapIndexed { index, ksTypeParameter ->
        ksTypeParameter.name to matchingParentType.arguments.getOrNull(index)
    }.toMap()
    return replaceFromMap(resolver, replacements)
}

private fun KSTypeArgument.replaceFromMap(
    resolver: Resolver,
    arguments: Map<KSName, KSTypeArgument?>
): KSTypeArgument {
    val resolvedType = type?.resolve()
    val myTypeDeclaration = resolvedType?.declaration
    if (myTypeDeclaration is KSTypeParameter) {
        val match = arguments[myTypeDeclaration.name] ?: return this
        // workaround for https://github.com/google/ksp/issues/82
        val explicitNullable = resolvedType.makeNullable() == resolvedType
        return if (explicitNullable) {
            match.makeNullable(resolver)
        } else {
            match
        }
    }
    return this
}

private fun KSType.replaceFromMap(
    resolver: Resolver,
    arguments: Map<KSName, KSTypeArgument?>
): KSType {
    val myDeclaration = this.declaration
    if (myDeclaration is KSTypeParameter) {
        val match = arguments[myDeclaration.name]?.type?.resolve() ?: return this
        // workaround for https://github.com/google/ksp/issues/82
        val explicitNullable = this.makeNullable() == this
        return if (explicitNullable) {
            match.makeNullable()
        } else {
            match
        }
    }
    if (this.arguments.isEmpty()) {
        return this
    }
    return replace(this.arguments.map {
        it.replaceFromMap(resolver, arguments)
    })
}

private fun KSTypeArgument.makeNullable(resolver: Resolver): KSTypeArgument {
    val myType = type
    val resolved = myType?.resolve() ?: return this
    if (resolved.nullability == Nullability.NULLABLE) {
        return this
    }
    return resolver.getTypeArgument(myType.swapResolvedType(resolved.makeNullable()), variance)
}