KspAnnotationValue.kt
/*
* Copyright 2021 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 androidx.room.compiler.processing.XAnnotationValue
import androidx.room.compiler.processing.isArray
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.getConstructors
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSValueArgument
internal class KspAnnotationValue(
val env: KspProcessingEnv,
private val owner: KspAnnotation,
val valueArgument: KSValueArgument,
private val valueProvider: () -> Any? = { owner.unwrap(valueArgument) },
) : XAnnotationValue {
override val name: String
get() = valueArgument.name?.asString()
?: error("Value argument $this does not have a name.")
override val value: Any? by lazy { valueProvider.invoke() }
}
internal fun KspAnnotation.unwrap(valueArgument: KSValueArgument): Any? {
fun unwrap(value: Any?): Any? {
return when (value) {
is KSType -> {
val declaration = value.declaration
// Wrap enum entries in enum specific type elements
if (declaration is KSClassDeclaration &&
declaration.classKind == ClassKind.ENUM_ENTRY
) {
KspEnumEntry.create(env, declaration)
} else {
// And otherwise represent class types as generic XType
env.wrap(value, allowPrimitives = true)
}
}
is KSAnnotation -> KspAnnotation(env, value)
// The List implementation further wraps each value as a AnnotationValue.
// We don't use arrays because we don't have a reified type to instantiate the array
// with, and using "Any" prevents the array from being cast to the correct
// type later on.
is List<*> -> value.map { unwrap(it) }
// TODO: https://github.com/google/ksp/issues/429
// If the enum value is from compiled code KSP gives us the actual value an not
// the KSType, so we wrap it as KspEnumEntry for consistency.
is Enum<*> -> {
val declaration =
env.resolver.getClassDeclarationByName(value::class.java.canonicalName)
?: error("Cannot find KSClassDeclaration for Enum '$value'.")
val valueDeclaration = declaration.declarations
.filterIsInstance<KSClassDeclaration>()
.filter { it.classKind == ClassKind.ENUM_ENTRY }
.firstOrNull() { it.simpleName.getShortName() == value.name }
?: error("Cannot find ENUM_ENTRY '$value' in '$declaration'.")
KspEnumEntry.create(env, valueDeclaration)
}
else -> value
}
}
return unwrap(valueArgument.value).let { result ->
when {
result is List<*> -> {
// For lists, wrap each item in a KSPAnnotationValue. This models things similar to
// javac, and allows us to report errors on each individual item rather than just
// the list itself.
result.map { KspAnnotationValue(env, this, valueArgument) { it } }
}
isArrayType(valueArgument) -> {
// TODO: 5/24/21 KSP does not wrap a single item in a list, even though the
// return type should be Class<?>[] (only in sources).
// https://github.com/google/ksp/issues/172
// https://github.com/google/ksp/issues/214
listOf(KspAnnotationValue(env, this, valueArgument) { result })
}
else -> result
}
}
}
private fun KspAnnotation.isArrayType(arg: KSValueArgument): Boolean {
return (ksType.declaration as KSClassDeclaration).getConstructors()
.singleOrNull()
?.parameters
?.firstOrNull { it.name == arg.name }
?.let { env.wrap(it.type).isArray() } == true
}