[go: nahoru, domu]

blob: 93b7fb722055ebfb623c85ac0530a1e3ddc0a414 [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 androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.processing.XAnnotated
import androidx.room.compiler.processing.XConstructorElement
import androidx.room.compiler.processing.XEnumEntry
import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMemberContainer
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XPackageElement
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.XTypeParameterElement
import androidx.room.compiler.processing.collectAllMethods
import androidx.room.compiler.processing.collectFieldsIncludingPrivateSupers
import androidx.room.compiler.processing.filterMethodsByConfig
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticConstructorElement
import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
import androidx.room.compiler.processing.tryBox
import androidx.room.compiler.processing.util.MemoizedSequence
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getConstructors
import com.google.devtools.ksp.isConstructor
import com.google.devtools.ksp.isOpen
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclarationContainer
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.symbol.Modifier
import com.google.devtools.ksp.symbol.Origin.JAVA
import com.google.devtools.ksp.symbol.Origin.JAVA_LIB
import com.google.devtools.ksp.symbol.Origin.KOTLIN
import com.google.devtools.ksp.symbol.Origin.KOTLIN_LIB
import com.squareup.javapoet.ClassName
import com.squareup.kotlinpoet.javapoet.JClassName
import com.squareup.kotlinpoet.javapoet.KClassName
internal sealed class KspTypeElement(
env: KspProcessingEnv,
override val declaration: KSClassDeclaration
) : KspElement(env, declaration),
XTypeElement,
XHasModifiers by KspHasModifiers.create(declaration),
XAnnotated by KspAnnotated.create(env, declaration, NO_USE_SITE),
KspMemberContainer {
override val name: String by lazy {
declaration.simpleName.asString()
}
override val packageName: String by lazy {
packageElement.qualifiedName
}
override val packageElement: XPackageElement by lazy {
KspPackageElement(env, declaration.packageName.asString())
}
override val enclosingTypeElement: XTypeElement? by lazy {
// if it is a file, don't return it
declaration.findEnclosingMemberContainer(env) as? XTypeElement
}
override val enclosingElement: XMemberContainer?
get() = enclosingTypeElement
override val typeParameters: List<XTypeParameterElement> by lazy {
declaration.typeParameters.map { KspTypeParameterElement(env, it) }
}
override val qualifiedName: String by lazy {
(declaration.qualifiedName ?: declaration.simpleName).asString()
}
override val type: KspType by lazy {
env.wrap(
ksType = declaration.asType(emptyList()),
allowPrimitives = false
)
}
override val superClass: XType? by lazy {
if (isInterface()) {
// interfaces don't have super classes (they do have super types)
null
} else if (this == env.commonTypes.anyType.typeElement) {
null
} else {
declaration.superTypes
.singleOrNull {
val declaration = it.resolve().declaration.replaceTypeAliases()
declaration is KSClassDeclaration && declaration.classKind == ClassKind.CLASS
}?.let { env.wrap(it) }
?: env.commonTypes.anyType
}
}
override val superInterfaces by lazy {
declaration.superTypes.asSequence()
.filter {
val declaration = it.resolve().declaration.replaceTypeAliases()
declaration is KSClassDeclaration && declaration.classKind == ClassKind.INTERFACE
}.mapTo(mutableListOf()) { env.wrap(it) }
}
@Deprecated(
"Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
replaceWith = ReplaceWith(
"asClassName().toJavaPoet()",
"androidx.room.compiler.codegen.toJavaPoet"
)
)
override val className: ClassName by lazy {
xClassName.java
}
private val xClassName: XClassName by lazy {
val java = declaration.asJTypeName(env.resolver).tryBox().also { typeName ->
check(typeName is JClassName) {
"Internal error. The type name for $declaration should be a class name but " +
"received ${typeName::class}"
}
} as JClassName
val kotlin = declaration.asKTypeName(env.resolver) as KClassName
XClassName(java, kotlin, XNullability.NONNULL)
}
override fun asClassName() = xClassName
private val allMethods = MemoizedSequence {
collectAllMethods(this)
}
private val allFieldsIncludingPrivateSupers = MemoizedSequence {
collectFieldsIncludingPrivateSupers(this)
}
override fun getAllMethods(): Sequence<XMethodElement> = allMethods
override fun getAllFieldsIncludingPrivateSupers() = allFieldsIncludingPrivateSupers
/**
* This list includes fields for all properties in this class and its static companion
* properties. They are not necessarily fields as it might include properties of interfaces.
*/
private val _declaredProperties by lazy {
buildList {
addAll(declaration.getDeclarationsInSourceOrder())
addAll(
declaration.findCompanionObject().getDeclarationsInSourceOrder()
.filter { it.isStatic() }
)
}.filterIsInstance(KSPropertyDeclaration::class.java)
.map {
KspFieldElement(
env = env,
declaration = it
)
}
}
private val _constructors by lazy {
if (isAnnotationClass()) {
emptyList()
} else {
val constructors = declaration.getConstructors().toList()
buildList {
addAll(
constructors.map { env.wrapFunctionDeclaration(it) as XConstructorElement }
)
constructors
.filter { it.hasOverloads() }
.forEach { addAll(enumerateSyntheticConstructors(it)) }
// To match KAPT if all params in the primary constructor have default values then
// synthesize a no-arg constructor if one is not already present.
val hasNoArgConstructor = constructors.any { it.parameters.isEmpty() }
if (!hasNoArgConstructor) {
declaration.primaryConstructor?.let {
if (!it.hasOverloads() && it.parameters.all { it.hasDefault }) {
add(
KspSyntheticConstructorElement(
env = env,
declaration = it,
valueParameters = emptyList()
)
)
}
}
}
}
}
}
private val _declaredFields by lazy {
_declaredProperties.filter {
it.declaration.hasBackingField
}
}
private fun syntheticGetterSetterMethods(field: KspFieldElement): List<XMethodElement> {
if (declaration.isCompanionObject) {
return field.syntheticAccessors
}
if (field.isStatic() && !field.declaration.hasJvmStaticAnnotation()) {
return field.syntheticAccessors.filter {
it.accessor.hasJvmStaticAnnotation()
}
}
if (field.isStatic() && field.declaration.hasJvmStaticAnnotation()) {
// Getter and setter are copied from companion object into current type
// element by Compiler in KAPT when @JvmStatic is present, in this case, the
// copied over method element should swap its enclosing element to be
// current type element instead of companion object.
return field.syntheticAccessors.map { element ->
KspSyntheticPropertyMethodElement.create(
env, field, element.accessor, isSyntheticStatic = true
)
}
}
return field.syntheticAccessors
}
override fun isNested(): Boolean {
return declaration.findEnclosingMemberContainer(env) is XTypeElement
}
override fun isInterface(): Boolean {
return declaration.classKind == ClassKind.INTERFACE
}
override fun isKotlinObject(): Boolean {
return declaration.classKind == ClassKind.OBJECT
}
override fun isCompanionObject(): Boolean {
return declaration.isCompanionObject
}
override fun isAnnotationClass(): Boolean {
return declaration.classKind == ClassKind.ANNOTATION_CLASS
}
override fun isClass(): Boolean {
return declaration.classKind == ClassKind.CLASS
}
override fun isDataClass(): Boolean {
return Modifier.DATA in declaration.modifiers
}
override fun isValueClass(): Boolean = declaration.isValueClass()
override fun isFunctionalInterface(): Boolean {
return Modifier.FUN in declaration.modifiers
}
override fun isExpect(): Boolean {
return Modifier.EXPECT in declaration.modifiers
}
override fun isFinal(): Boolean {
// workaround for https://github.com/android/kotlin/issues/128
return !isInterface() && !declaration.isOpen()
}
override fun isRecordClass(): Boolean {
// Need to also check super type since @JvmRecord is @Retention(SOURCE)
return hasAnnotation(JvmRecord::class) ||
superClass?.isTypeOf(java.lang.Record::class) == true
}
override fun getDeclaredFields(): List<XFieldElement> {
return _declaredFields
}
override fun findPrimaryConstructor(): XConstructorElement? {
if (isAnnotationClass()) {
return null
}
return declaration.primaryConstructor?.let {
KspConstructorElement(
env = env,
declaration = it
)
}
}
private val _declaredMethods by lazy {
buildList {
declaration.getDeclarationsInSourceOrder()
.forEach {
if (it is KSFunctionDeclaration && !it.isConstructor()) {
add(
KspMethodElement.create(
env = env,
declaration = it
)
)
} else if (it is KSPropertyDeclaration) {
addAll(
syntheticGetterSetterMethods(
KspFieldElement(
env = env,
declaration = it
)
)
)
}
}
declaration.findCompanionObject().getDeclarationsInSourceOrder()
.forEach {
if (it.hasJvmStaticAnnotation() &&
it is KSFunctionDeclaration &&
!it.isConstructor()) {
add(
KspMethodElement.create(
env = env,
declaration = it,
isSyntheticStatic = true
)
)
} else if (it is KSPropertyDeclaration) {
addAll(
syntheticGetterSetterMethods(
KspFieldElement(
env = env,
declaration = it
)
)
)
}
}
}.filterMethodsByConfig(env)
}
override fun getDeclaredMethods(): List<XMethodElement> {
return _declaredMethods
}
override fun getConstructors(): List<XConstructorElement> {
return _constructors
}
private fun enumerateSyntheticConstructors(
declaration: KSFunctionDeclaration
): List<KspSyntheticConstructorElement> {
val parameters = declaration.parameters
val defaultParamsCount = parameters.count { it.hasDefault }
if (defaultParamsCount < 1) { return emptyList() }
val constructorEnumeration = mutableListOf<KspSyntheticConstructorElement>()
for (defaultParameterToUseCount in 0..defaultParamsCount - 1) {
val parameterEnumeration = mutableListOf<KSValueParameter>()
var defaultParameterUsedCount = 0
for (parameter in parameters) {
if (parameter.hasDefault) {
if (defaultParameterUsedCount++ >= defaultParameterToUseCount) {
continue
}
}
parameterEnumeration.add(parameter)
}
constructorEnumeration.add(
KspSyntheticConstructorElement(env, declaration, parameterEnumeration))
}
val isPreCompiled =
declaration.origin == KOTLIN_LIB || declaration.origin == JAVA_LIB
return if (isPreCompiled) constructorEnumeration.reversed() else constructorEnumeration
}
override fun getSuperInterfaceElements() = superInterfaces.mapNotNull { it.typeElement }
override fun getEnclosedTypeElements(): List<XTypeElement> {
return declaration.declarations.filterIsInstance<KSClassDeclaration>()
.filterNot { it.classKind == ClassKind.ENUM_ENTRY }
.map { env.wrapClassDeclaration(it) }
.toList()
}
override fun isFromJava(): Boolean {
return when (declaration.origin) {
JAVA, JAVA_LIB -> true
else -> false
}
}
override fun isFromKotlin(): Boolean {
return when (declaration.origin) {
KOTLIN, KOTLIN_LIB -> true
else -> false
}
}
private class DefaultKspTypeElement(
env: KspProcessingEnv,
declaration: KSClassDeclaration
) : KspTypeElement(env, declaration)
private class KspEnumTypeElement(
env: KspProcessingEnv,
declaration: KSClassDeclaration
) : KspTypeElement(env, declaration), XEnumTypeElement {
override val entries: Set<XEnumEntry> by lazy {
declaration.declarations
.filterIsInstance<KSClassDeclaration>()
.filter { it.classKind == ClassKind.ENUM_ENTRY }
.mapTo(mutableSetOf()) {
KspEnumEntry(env, it, this)
}
}
}
@OptIn(KspExperimental::class)
fun KSDeclarationContainer?.getDeclarationsInSourceOrder() = this?.let {
env.resolver.getDeclarationsInSourceOrder(it)
} ?: emptySequence()
companion object {
fun create(
env: KspProcessingEnv,
ksClassDeclaration: KSClassDeclaration
): KspTypeElement {
return when (ksClassDeclaration.classKind) {
ClassKind.ENUM_CLASS -> KspEnumTypeElement(env, ksClassDeclaration)
ClassKind.ENUM_ENTRY -> error("Expected declaration to not be an enum entry.")
else -> DefaultKspTypeElement(env, ksClassDeclaration)
}
}
}
}