[go: nahoru, domu]

blob: 7d93ec0ea20e73a1123c46a28ed85bdfdd5351bc [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.javac.kotlin
import androidx.room.compiler.processing.XArrayType
import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.javac.JavacKmAnnotation
import androidx.room.compiler.processing.javac.JavacKmAnnotationValue
import androidx.room.compiler.processing.javac.JavacProcessingEnv
import androidx.room.compiler.processing.util.sanitizeAsJavaParameterName
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
import javax.tools.Diagnostic
import kotlinx.metadata.Flag
import kotlinx.metadata.Flags
import kotlinx.metadata.KmAnnotation
import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmConstructor
import kotlinx.metadata.KmFunction
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmType
import kotlinx.metadata.KmTypeParameter
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.annotations
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
import kotlinx.metadata.jvm.syntheticMethodForAnnotations
internal interface KmFlags {
val flags: Flags
}
internal class KmClassContainer(
private val kmClass: KmClass
) : KmFlags {
override val flags: Flags
get() = kmClass.flags
val type: KmTypeContainer by lazy {
KmTypeContainer(
kmType = KmType(flags).apply {
classifier = KmClassifier.Class(kmClass.name)
},
typeArguments = kmClass.typeParameters.map { kmTypeParameter ->
KmTypeContainer(
kmType = KmType(kmTypeParameter.flags).apply {
classifier = KmClassifier.Class(kmTypeParameter.name)
},
typeArguments = emptyList(),
upperBounds = kmTypeParameter.upperBounds.map { it.asContainer() }
)
}
)
}
val superType: KmTypeContainer? by lazy {
kmClass.supertypes.firstOrNull()?.asContainer()
}
val superTypes: List<KmTypeContainer> by lazy {
kmClass.supertypes.map { it.asContainer() }
}
val typeParameters: List<KmTypeParameterContainer> by lazy {
kmClass.typeParameters.map { it.asContainer() }
}
private val functionList: List<KmFunctionContainer> by lazy {
kmClass.functions.map { it.asContainer() }
}
private val constructorList: List<KmConstructorContainer> by lazy {
kmClass.constructors.map { it.asContainer(type) }
}
private val propertyList: List<KmPropertyContainer> by lazy {
kmClass.properties.map { it.asContainer() }
}
val primaryConstructorSignature: String? by lazy {
constructorList.firstOrNull { it.isPrimary() }?.descriptor
}
fun isObject() = Flag.Class.IS_OBJECT(flags)
fun isCompanionObject() = Flag.Class.IS_COMPANION_OBJECT(flags)
fun isAnnotationClass() = Flag.Class.IS_ANNOTATION_CLASS(flags)
fun isClass() = Flag.Class.IS_CLASS(flags)
fun isInterface() = Flag.Class.IS_INTERFACE(flags)
fun isDataClass() = Flag.Class.IS_DATA(flags)
fun isValueClass() = Flag.Class.IS_VALUE(flags)
fun isFunctionalInterface() = Flag.Class.IS_FUN(flags)
fun isExpect() = Flag.Class.IS_EXPECT(flags)
fun getFunctionMetadata(method: ExecutableElement): KmFunctionContainer? {
check(method.kind == ElementKind.METHOD) {
"must pass an element type of method"
}
return functionByDescriptor[method.descriptor()]
}
private val functionByDescriptor: Map<String, KmFunctionContainer> by lazy {
buildMap {
functionList.forEach { put(it.descriptor, it) }
propertyList.forEach { property ->
property.getter?.descriptor?.let { put(it, property.getter) }
property.setter?.descriptor?.let { put(it, property.setter) }
property.syntheticMethodForAnnotations?.descriptor?.let {
put(it, property.syntheticMethodForAnnotations)
}
}
}
}
fun getConstructorMetadata(method: ExecutableElement): KmConstructorContainer? {
check(method.kind == ElementKind.CONSTRUCTOR) {
"must pass an element type of constructor"
}
val methodSignature = method.descriptor()
return constructorList.firstOrNull { it.descriptor == methodSignature }
}
fun getPropertyMetadata(propertyName: String): KmPropertyContainer? =
propertyList.firstOrNull { it.name == propertyName }
companion object {
/**
* Creates a [KmClassContainer] for the given element if it contains Kotlin metadata,
* otherwise this method returns null.
*
* Usually the [element] passed must represent a class. For example, if Kotlin metadata is
* desired for a method, then the containing class should be used as parameter.
*/
fun createFor(env: JavacProcessingEnv, element: Element): KmClassContainer? {
val metadataAnnotation = getMetadataAnnotation(element) ?: return null
val classMetadata = KotlinClassMetadata.read(metadataAnnotation)
if (classMetadata == null) {
env.delegate.messager.printMessage(
Diagnostic.Kind.WARNING,
"Unable to read Kotlin metadata due to unsupported metadata version.",
element
)
}
return when (classMetadata) {
is KotlinClassMetadata.Class -> KmClassContainer(classMetadata.toKmClass())
// Synthetic classes generated for various Kotlin features ($DefaultImpls,
// $WhenMappings, etc) are ignored because the data contained does not affect
// the metadata derived APIs. These classes are never referenced by user code but
// could be discovered by processors when inspecting inner classes.
is KotlinClassMetadata.SyntheticClass,
// Multi file classes are also ignored, the elements contained in these might be
// referenced by user code in method bodies but not part of the AST, however it
// is possible for a processor to discover them by inspecting elements under a
// package.
is KotlinClassMetadata.FileFacade,
is KotlinClassMetadata.MultiFileClassFacade,
is KotlinClassMetadata.MultiFileClassPart -> null
else -> {
env.delegate.messager.printMessage(
Diagnostic.Kind.ERROR,
"Unable to read Kotlin metadata due to unsupported metadata " +
"kind: $classMetadata.",
element
)
null
}
}
}
/**
* Search for Kotlin's Metadata annotation across the element's hierarchy.
*/
private fun getMetadataAnnotation(element: Element?): Metadata? =
if (element != null) {
element.getAnnotation(Metadata::class.java)
?: getMetadataAnnotation(element.enclosingElement)
} else {
null
}
}
}
internal interface KmFunctionContainer : KmFlags {
/** Name of the function in source code */
val name: String
/** Name of the function in byte code */
val jvmName: String
val descriptor: String
val typeParameters: List<KmTypeParameterContainer>
val parameters: List<KmValueParameterContainer>
val returnType: KmTypeContainer
fun isSyntheticMethodForAnnotations() =
(this as? KmPropertyFunctionContainerImpl)?.syntheticMethodForAnnotations == true
fun isPropertyFunction() = this is KmPropertyFunctionContainerImpl
fun isSuspend() = Flag.Function.IS_SUSPEND(flags)
fun isExtension() =
(this as? KmFunctionContainerImpl)?.kmFunction?.receiverParameterType != null
}
private class KmFunctionContainerImpl(
val kmFunction: KmFunction,
override val returnType: KmTypeContainer,
) : KmFunctionContainer {
override val flags: Flags
get() = kmFunction.flags
override val name: String
get() = kmFunction.name
override val jvmName: String
get() = kmFunction.signature!!.name
override val descriptor: String
get() = kmFunction.signature!!.asString()
override val typeParameters: List<KmTypeParameterContainer>
get() = kmFunction.typeParameters.map { it.asContainer() }
override val parameters: List<KmValueParameterContainer>
get() = kmFunction.valueParameters.map { it.asContainer() }
}
private open class KmPropertyFunctionContainerImpl(
override val flags: Flags,
override val name: String,
override val jvmName: String,
override val descriptor: String,
override val parameters: List<KmValueParameterContainer>,
override val returnType: KmTypeContainer,
val syntheticMethodForAnnotations: Boolean = false
) : KmFunctionContainer {
override val typeParameters: List<KmTypeParameterContainer> = emptyList()
}
internal class KmConstructorContainer(
private val kmConstructor: KmConstructor,
override val returnType: KmTypeContainer,
) : KmFunctionContainer {
override val flags: Flags
get() = kmConstructor.flags
override val name: String = "<init>"
override val jvmName: String = name
override val descriptor: String
get() = checkNotNull(kmConstructor.signature).asString()
override val typeParameters: List<KmTypeParameterContainer> = emptyList()
override val parameters: List<KmValueParameterContainer> by lazy {
kmConstructor.valueParameters.map { it.asContainer() }
}
fun isPrimary() = !Flag.Constructor.IS_SECONDARY(flags)
}
internal class KmPropertyContainer(
private val kmProperty: KmProperty,
val type: KmTypeContainer,
val getter: KmFunctionContainer?,
val setter: KmFunctionContainer?,
val syntheticMethodForAnnotations: KmFunctionContainer?,
) : KmFlags {
override val flags: Flags
get() = kmProperty.flags
val name: String
get() = kmProperty.name
val typeParameters: List<KmTypeContainer>
get() = type.typeArguments
fun isNullable() = type.isNullable()
}
internal class KmTypeContainer(
private val kmType: KmType,
val typeArguments: List<KmTypeContainer>,
/** The extends bounds are only non-null for wildcard (i.e. in/out variant) types. */
val extendsBound: KmTypeContainer? = null,
/** The upper bounds are only non-null for type variable types with upper bounds. */
val upperBounds: List<KmTypeContainer>? = null
) : KmFlags {
override val flags: Flags
get() = kmType.flags
val className: String? = kmType.classifier.let {
when (it) {
is KmClassifier.Class -> it.name.replace('/', '.')
else -> null
}
}
val annotations = kmType.annotations.map { it.asContainer() }
fun isExtensionType() =
kmType.annotations.any { it.className == "kotlin/ExtensionFunctionType" }
fun isNullable() = Flag.Type.IS_NULLABLE(flags)
fun erasure(): KmTypeContainer = KmTypeContainer(
kmType = kmType,
typeArguments = emptyList(),
extendsBound = extendsBound?.erasure(),
// The erasure of a type variable is equal to the erasure of the first upper bound.
upperBounds = upperBounds?.firstOrNull()?.erasure()?.let { listOf(it) },
)
}
internal class KmAnnotationContainer(private val kmAnnotation: KmAnnotation) {
val className = kmAnnotation.className.replace('/', '.')
fun getArguments(env: JavacProcessingEnv): Map<String, KmAnnotationArgumentContainer> {
return kmAnnotation.arguments.mapValues { (_, arg) ->
arg.asContainer(env)
}
}
}
internal class KmAnnotationArgumentContainer(
private val env: JavacProcessingEnv,
private val kmAnnotationArgument: KmAnnotationArgument
) {
fun getValue(method: XMethodElement): Any? {
return kmAnnotationArgument.let {
when (it) {
is KmAnnotationArgument.LiteralValue<*> -> it.value
is KmAnnotationArgument.ArrayValue -> {
it.elements.map {
val valueType = (method.returnType as XArrayType).componentType
JavacKmAnnotationValue(method, valueType, it.asContainer(env))
}
}
is KmAnnotationArgument.EnumValue -> {
val enumTypeElement = env.findTypeElement(
it.enumClassName.replace('/', '.')) as XEnumTypeElement
enumTypeElement.entries.associateBy { it.name }[it.enumEntryName]
}
is KmAnnotationArgument.AnnotationValue -> {
val kmAnnotation = KmAnnotation(
it.annotation.className,
it.annotation.arguments
).asContainer()
JavacKmAnnotation(env, kmAnnotation)
}
is KmAnnotationArgument.KClassValue -> {
env.requireType(it.className.replace('/', '.'))
}
}
}
}
}
internal val KmTypeContainer.nullability: XNullability
get() = if (isNullable()) {
XNullability.NULLABLE
} else {
// if there is an upper bound information, use its nullability (e.g. it might be T : Foo?)
if (upperBounds?.all { it.nullability == XNullability.NULLABLE } == true) {
XNullability.NULLABLE
} else {
extendsBound?.nullability ?: XNullability.NONNULL
}
}
internal class KmTypeParameterContainer(
private val kmTypeParameter: KmTypeParameter,
val upperBounds: List<KmTypeContainer>
) : KmFlags {
override val flags: Flags
get() = kmTypeParameter.flags
val name: String
get() = kmTypeParameter.name
}
internal class KmValueParameterContainer(
private val kmValueParameter: KmValueParameter,
val type: KmTypeContainer
) : KmFlags {
override val flags: Flags
get() = kmValueParameter.flags
val name: String
get() = kmValueParameter.name
fun isVarArgs() = kmValueParameter.varargElementType != null
fun isNullable() = type.isNullable()
fun hasDefault() = Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
}
private fun KmFunction.asContainer(): KmFunctionContainer =
KmFunctionContainerImpl(
kmFunction = this,
returnType = this.returnType.asContainer()
)
private fun KmConstructor.asContainer(returnType: KmTypeContainer): KmConstructorContainer =
KmConstructorContainer(
kmConstructor = this,
returnType = returnType
)
private fun KmProperty.asContainer(): KmPropertyContainer =
KmPropertyContainer(
kmProperty = this,
type = this.returnType.asContainer(),
getter = getterSignature?.let {
KmPropertyFunctionContainerImpl(
flags = this.getterFlags,
name = JvmAbi.computeGetterName(this.name),
jvmName = it.name,
descriptor = it.asString(),
parameters = emptyList(),
returnType = this.returnType.asContainer(),
)
},
setter = setterSignature?.let {
// setter parameter visitor may not be available when not declared explicitly
val param = this.setterParameter ?: KmValueParameter(
flags = 0,
// kotlinc will set this to set-? but it is better to not expose
// it here since it is not valid name
name = "set-?".sanitizeAsJavaParameterName(0)
).apply { type = this@asContainer.returnType }
val returnType = KmType(0).apply { classifier = KmClassifier.Class("Unit") }
KmPropertyFunctionContainerImpl(
flags = this.setterFlags,
name = JvmAbi.computeSetterName(this.name),
jvmName = it.name,
descriptor = it.asString(),
parameters = listOf(param.asContainer()),
returnType = returnType.asContainer(),
)
},
syntheticMethodForAnnotations = syntheticMethodForAnnotations?.let {
val returnType = KmType(0).apply { classifier = KmClassifier.Class("Unit") }
KmPropertyFunctionContainerImpl(
flags = 0,
name = JvmAbi.computeSyntheticMethodForAnnotationsName(this.name),
jvmName = it.name,
descriptor = it.asString(),
parameters = emptyList(),
returnType = returnType.asContainer(),
syntheticMethodForAnnotations = true,
)
},
)
private fun KmType.asContainer(): KmTypeContainer =
KmTypeContainer(
kmType = this,
typeArguments = this.arguments.mapNotNull { it.type?.asContainer() }
)
private fun KmTypeParameter.asContainer(): KmTypeParameterContainer =
KmTypeParameterContainer(
kmTypeParameter = this,
upperBounds = this.upperBounds.map { it.asContainer() }
)
private fun KmValueParameter.asContainer(): KmValueParameterContainer =
KmValueParameterContainer(
kmValueParameter = this,
type = this.type.asContainer()
)
private fun KmAnnotation.asContainer() = KmAnnotationContainer(this)
private fun KmAnnotationArgument.asContainer(env: JavacProcessingEnv) =
KmAnnotationArgumentContainer(env, this)