blob: 3c35af4dca85d80c1d9901ec604804710cc9fb19 [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.synthetic
import androidx.room.compiler.processing.XAnnotated
import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XExecutableParameterElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMemberContainer
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XMethodType
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.ksp.KspAnnotated
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_GETTER
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_SETTER
import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_SET_PARAM
import androidx.room.compiler.processing.ksp.KspFieldElement
import androidx.room.compiler.processing.ksp.KspHasModifiers
import androidx.room.compiler.processing.ksp.KspProcessingEnv
import androidx.room.compiler.processing.ksp.KspTypeElement
import androidx.room.compiler.processing.ksp.findEnclosingMemberContainer
import androidx.room.compiler.processing.ksp.overrides
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.symbol.KSPropertyAccessor
import com.google.devtools.ksp.symbol.KSPropertyGetter
import com.google.devtools.ksp.symbol.KSPropertySetter
import java.util.Locale
/**
* Kotlin properties don't have getters/setters in KSP. As Room expects Java code, we synthesize
* them.
*
* @see KspSyntheticPropertyMethodElement.Getter
* @see KspSyntheticPropertyMethodElement.Setter
* @see KspSyntheticPropertyMethodType
*/
internal sealed class KspSyntheticPropertyMethodElement(
val env: KspProcessingEnv,
val field: KspFieldElement,
accessor: KSPropertyAccessor?
) : XMethodElement,
XEquality,
XHasModifiers by KspHasModifiers.createForSyntheticAccessor(
field.declaration,
accessor
) {
// NOTE: modifiers of the property are not necessarily my modifiers.
// that being said, it only matters if it is private in which case KAPT does not generate the
// synthetic hence we don't either.
final override fun isJavaDefault() = false
final override fun hasKotlinDefaultImpl() = false
final override fun isSuspendFunction() = false
final override val enclosingElement: XMemberContainer
get() = this.field.enclosingElement
final override fun isVarArgs() = false
final override val executableType: XMethodType by lazy {
KspSyntheticPropertyMethodType.create(
element = this,
container = field.containing.type
)
}
override val docComment: String? by lazy {
// Not yet implemented in KSP.
// https://github.com/google/ksp/issues/392
null
}
final override fun asMemberOf(other: XType): XMethodType {
return KspSyntheticPropertyMethodType.create(
element = this,
container = other
)
}
override fun equals(other: Any?): Boolean {
return XEquality.equals(this, other)
}
override fun hashCode(): Int {
return XEquality.hashCode(equalityItems)
}
final override fun overrides(other: XMethodElement, owner: XTypeElement): Boolean {
return env.resolver.overrides(this, other)
}
internal class Getter(
env: KspProcessingEnv,
field: KspFieldElement
) : KspSyntheticPropertyMethodElement(
env = env,
field = field,
accessor = field.declaration.getter
),
XAnnotated by KspAnnotated.create(
env = env,
delegate = field.declaration.getter,
filter = NO_USE_SITE_OR_GETTER
) {
override val equalityItems: Array<out Any?> by lazy {
arrayOf(field, "getter")
}
@OptIn(KspExperimental::class)
override val name: String by lazy {
field.declaration.getter?.let {
env.resolver.getJvmName(it)
} ?: computeGetterName(field.name)
}
override val returnType: XType by lazy {
field.type
}
override val parameters: List<XExecutableParameterElement>
get() = emptyList()
override fun kindName(): String {
return "synthetic property getter"
}
override fun copyTo(newContainer: XTypeElement): XMethodElement {
check(newContainer is KspTypeElement)
return Getter(
env = env,
field = field.copyTo(newContainer)
)
}
companion object {
private fun computeGetterName(propName: String): String {
// see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties
return if (propName.startsWith("is")) {
propName
} else {
"get${propName.capitalize(Locale.US)}"
}
}
}
}
internal class Setter(
env: KspProcessingEnv,
field: KspFieldElement
) : KspSyntheticPropertyMethodElement(
env = env,
field = field,
accessor = field.declaration.setter
),
XAnnotated by KspAnnotated.create(
env = env,
delegate = field.declaration.setter,
filter = NO_USE_SITE_OR_SETTER
) {
override val equalityItems: Array<out Any?> by lazy {
arrayOf(field, "setter")
}
@OptIn(KspExperimental::class)
override val name: String by lazy {
field.declaration.setter?.let {
env.resolver.getJvmName(it)
} ?: computeSetterName(field.name)
}
override val returnType: XType by lazy {
env.voidType
}
override val parameters: List<XExecutableParameterElement> by lazy {
listOf(
SyntheticExecutableParameterElement(
env = env,
origin = this
)
)
}
override fun kindName(): String {
return "synthetic property getter"
}
override fun copyTo(newContainer: XTypeElement): XMethodElement {
check(newContainer is KspTypeElement)
return Setter(
env = env,
field = field.copyTo(newContainer)
)
}
private class SyntheticExecutableParameterElement(
env: KspProcessingEnv,
private val origin: Setter
) : XExecutableParameterElement,
XAnnotated by KspAnnotated.create(
env = env,
delegate = origin.field.declaration.setter?.parameter,
filter = NO_USE_SITE_OR_SET_PARAM
) {
override val name: String by lazy {
origin.field.declaration.setter?.parameter?.name?.asString() ?: "value"
}
override val type: XType
get() = origin.field.type
override val fallbackLocationText: String
get() = "$name in ${origin.fallbackLocationText}"
override fun asMemberOf(other: XType): XType {
return origin.field.asMemberOf(other)
}
override val docComment: String? by lazy {
// Not yet implemented in KSP.
// https://github.com/google/ksp/issues/392
null
}
override fun kindName(): String {
return "method parameter"
}
}
companion object {
private fun computeSetterName(propName: String): String {
// see https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#properties
return if (propName.startsWith("is")) {
"set${propName.substring(2)}"
} else {
"set${propName.capitalize(Locale.US)}"
}
}
}
}
companion object {
fun create(
env: KspProcessingEnv,
propertyAccessor: KSPropertyAccessor
): KspSyntheticPropertyMethodElement {
val enclosingType = propertyAccessor.receiver.findEnclosingMemberContainer(env)
checkNotNull(enclosingType) {
"XProcessing does not currently support annotations on top level " +
"properties with KSP. Cannot process $propertyAccessor."
}
val field = KspFieldElement(
env,
propertyAccessor.receiver,
enclosingType
)
return when (propertyAccessor) {
is KSPropertyGetter -> {
Getter(env, field)
}
is KSPropertySetter -> {
Setter(env, field)
}
else -> error("Unsupported property accessor $propertyAccessor")
}
}
}
}