blob: f716d0464b5f39791b4c4823555382a65066582d [file] [log] [blame]
/*
* 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)
}