Merge "Read property accessor meteadata in KAPT" into androidx-main
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
index 30f0cac..ca4d05a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -16,6 +16,7 @@
package androidx.room.compiler.processing.javac.kotlin
+import androidx.room.compiler.processing.util.sanitizeAsJavaParameterName
import kotlinx.metadata.ClassName
import kotlinx.metadata.Flag
import kotlinx.metadata.Flags
@@ -25,15 +26,19 @@
import kotlinx.metadata.KmExtensionType
import kotlinx.metadata.KmFunctionExtensionVisitor
import kotlinx.metadata.KmFunctionVisitor
+import kotlinx.metadata.KmPropertyExtensionVisitor
import kotlinx.metadata.KmPropertyVisitor
import kotlinx.metadata.KmTypeParameterVisitor
import kotlinx.metadata.KmTypeVisitor
import kotlinx.metadata.KmValueParameterVisitor
import kotlinx.metadata.KmVariance
import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor
+import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor
import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor
import kotlinx.metadata.jvm.KotlinClassMetadata
+import java.util.Locale
// represents a function or constructor
internal interface KmExecutable {
@@ -44,6 +49,14 @@
* Represents the kotlin metadata of a function
*/
internal data class KmFunction(
+ /**
+ * Name of the function in byte code
+ */
+ val jvmName: String,
+ /**
+ * Name of the function in source code
+ */
+ val name: String,
val descriptor: String,
private val flags: Flags,
override val parameters: List<KmValueParameter>,
@@ -65,7 +78,9 @@
internal data class KmProperty(
val name: String,
- val type: KmType
+ val type: KmType,
+ val getter: KmFunction?,
+ val setter: KmFunction?
) {
val typeParameters
get() = type.typeArguments
@@ -118,7 +133,7 @@
override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor {
return object : KmFunctionVisitor() {
- lateinit var descriptor: String
+ lateinit var methodSignature: JvmMethodSignature
val parameters = mutableListOf<KmValueParameter>()
lateinit var returnType: KmType
@@ -137,7 +152,7 @@
}
return object : JvmFunctionExtensionVisitor() {
override fun visit(signature: JvmMethodSignature?) {
- descriptor = signature!!.asString()
+ methodSignature = signature!!
}
}
}
@@ -149,7 +164,16 @@
}
override fun visitEnd() {
- result.add(KmFunction(descriptor, flags, parameters, returnType))
+ result.add(
+ KmFunction(
+ name = name,
+ jvmName = methodSignature.name,
+ descriptor = methodSignature.asString(),
+ flags = flags,
+ parameters = parameters,
+ returnType = returnType
+ )
+ )
}
}
}
@@ -239,13 +263,45 @@
getterFlags: Flags,
setterFlags: Flags
): KmPropertyVisitor {
+ var setterParam: KmValueParameter? = null
+ var getter: JvmMethodSignature? = null
+ var setter: JvmMethodSignature? = null
return object : KmPropertyVisitor() {
lateinit var returnType: KmType
override fun visitEnd() {
result.add(
KmProperty(
type = returnType,
- name = name
+ name = name,
+ setter = setter?.let { setterSignature ->
+ // setter parameter visitor may not be invoked when not declared
+ // explicitly
+ val param = setterParam ?: KmValueParameter(
+ // 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),
+ type = returnType,
+ flags = 0
+ )
+ KmFunction(
+ jvmName = setterSignature.name,
+ name = computeSetterName(name),
+ descriptor = setterSignature.asString(),
+ flags = 0,
+ parameters = listOf(param),
+ returnType = KM_VOID_TYPE
+ )
+ },
+ getter = getter?.let { getterSignature ->
+ KmFunction(
+ jvmName = getterSignature.name,
+ name = computeGetterName(name),
+ descriptor = getterSignature.asString(),
+ flags = flags,
+ parameters = emptyList(),
+ returnType = returnType
+ )
+ }
)
)
}
@@ -255,6 +311,35 @@
returnType = it
}
}
+
+ override fun visitSetterParameter(
+ flags: Flags,
+ name: String
+ ): KmValueParameterVisitor {
+ return ValueParameterReader(
+ name = name,
+ flags = flags
+ ) {
+ setterParam = it
+ }
+ }
+
+ override fun visitExtensions(type: KmExtensionType): KmPropertyExtensionVisitor? {
+ if (type != JvmPropertyExtensionVisitor.TYPE) {
+ return null
+ }
+ return object : JvmPropertyExtensionVisitor() {
+ override fun visit(
+ jvmFlags: Flags,
+ fieldSignature: JvmFieldSignature?,
+ getterSignature: JvmMethodSignature?,
+ setterSignature: JvmMethodSignature?
+ ) {
+ getter = getterSignature
+ setter = setterSignature
+ }
+ }
+ }
}
}
}
@@ -388,3 +473,47 @@
}
}
}
+
+/**
+ * Computes the getter name based on the property. Note that this might be different than the
+ * JVM name for internal properties.
+ * See: https://kotlinlang.org/docs/java-to-kotlin-interop.html#properties
+ */
+private fun computeGetterName(
+ propName: String
+): String {
+ return if (propName.startsWith("is")) {
+ propName
+ } else {
+ val capitalizedName = propName.replaceFirstChar {
+ if (it.isLowerCase()) it.titlecase(
+ Locale.US
+ ) else it.toString()
+ }
+ "get$capitalizedName"
+ }
+}
+
+/**
+ * Computes the getter name based on the property. Note that this might be different than the
+ * JVM name for internal properties.
+ * See: https://kotlinlang.org/docs/java-to-kotlin-interop.html#properties
+ */
+private fun computeSetterName(propName: String): String {
+ return if (propName.startsWith("is")) {
+ "set${propName.substring(2)}"
+ } else {
+ val capitalizedName = propName.replaceFirstChar {
+ if (it.isLowerCase()) it.titlecase(
+ Locale.US
+ ) else it.toString()
+ }
+ "set$capitalizedName"
+ }
+}
+
+private val KM_VOID_TYPE = KmType(
+ flags = 0,
+ typeArguments = emptyList(),
+ extendsBound = null
+)
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
index 10b7be7..adb2631 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
@@ -71,7 +71,23 @@
"must pass an element type of method"
}
val methodSignature = method.descriptor
- return functionList.firstOrNull { it.descriptor == methodSignature }
+ functionList.firstOrNull { it.descriptor == methodSignature }?.let {
+ return it
+ }
+ // might be a property getter or setter
+ return propertyList.firstNotNullOfOrNull { property ->
+ when {
+ property.getter?.descriptor == methodSignature -> {
+ property.getter
+ }
+ property.setter?.descriptor == methodSignature -> {
+ property.setter
+ }
+ else -> {
+ null
+ }
+ }
+ }
}
fun getConstructorMetadata(method: ExecutableElement): KmConstructor? {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/NamingUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/NamingUtils.kt
index 1d96f2d..7889069 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/NamingUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/util/NamingUtils.kt
@@ -27,5 +27,5 @@
): String = if (this != null && SourceVersion.isName(this)) {
this
} else {
- "arg$argIndex"
+ "p$argIndex"
}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
index b4cbf09..fa4306c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/FallbackLocationInformationTest.kt
@@ -49,10 +49,10 @@
class JavaSubject {
String field1;
// naming this arg0 because javac cannot read the real param name after compilation
- JavaSubject(int arg0) {
+ JavaSubject(int p0) {
}
// naming this arg0 because javac cannot read the real param name after compilation
- void method1(int arg0) {}
+ void method1(int p0) {}
}
""".trimIndent()
)
@@ -109,7 +109,7 @@
assertThat(
propSetter.parameters.first().fallbackLocationText
).isEqualTo(
- "arg0 in foo.bar.KotlinSubject.setProp(java.lang.String)"
+ "p0 in foo.bar.KotlinSubject.setProp(java.lang.String)"
)
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 7a03f87..a30674a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -416,6 +416,7 @@
protected set
protected var prop7: String
private set
+ internal var prop8: String
}
""".trimIndent()
)
@@ -429,19 +430,24 @@
"setX", "setProp1", "setProp3", "setZ", "setProp4", "getProp4", "setProp7"
)
)
- listOf("getX", "getProp1", "getProp2", "getProp3", "getProp5", "getProp6").forEach {
+ listOf("getX", "getProp1", "getProp2", "getProp3", "getProp5", "getProp6",
+ "getProp8\$main").forEach {
klass.getMethod(it).let { method ->
assertThat(method.returnType.typeName).isEqualTo(String::class.typeName())
assertThat(method.parameters).isEmpty()
+ assertThat(method.returnType.nullability).isEqualTo(XNullability.NONNULL)
}
}
- listOf("setY", "setProp2").forEach {
+ listOf("setY", "setProp2", "setProp8\$main").forEach {
klass.getMethod(it).let { method ->
assertThat(method.returnType.typeName).isEqualTo(TypeName.VOID)
assertThat(method.parameters.first().type.typeName).isEqualTo(
String::class.typeName()
)
assertThat(method.isPublic()).isTrue()
+ assertThat(method.parameters.first().type.nullability).isEqualTo(
+ XNullability.NONNULL
+ )
}
}
listOf("getProp5", "getProp7").forEach {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
index 6825149..79b3ae2 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -16,22 +16,29 @@
package androidx.room.compiler.processing.javac.kotlin
+import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.javac.nullability
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.runJavaProcessorTest
import androidx.room.compiler.processing.util.runKaptTest
+import androidx.room.compiler.processing.util.sanitizeAsJavaParameterName
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.AssumptionViolatedException
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import org.junit.runners.Parameterized
import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import javax.lang.model.util.ElementFilter
-@RunWith(JUnit4::class)
-class KotlinMetadataElementTest {
+@RunWith(Parameterized::class)
+class KotlinMetadataElementTest(
+ private val preCompiled: Boolean
+) {
@Test
fun constructorParameters() {
@@ -197,7 +204,7 @@
"suspendFunction" to true,
"functionWithParams" to false,
"suspendFunctionWithParams" to false,
- "getConstructorParam" to null // synthetic getter for constructor property
+ "getConstructorParam" to false // synthetic getter for constructor property
)
}
}
@@ -307,6 +314,250 @@
}
@Test
+ fun accessors() {
+ val src = Source.kotlin(
+ "Kotlin.kt",
+ """
+ @JvmInline
+ value class ValueClass(val value: String)
+ class Subject {
+ val immutableProperty: String = ""
+ var mutableProperty: String = ""
+ var isProperty: String? = ""
+ var customSetter: String= ""
+ get(): String = ""
+ set(myValue) { field = myValue }
+ var privateSetter: String = ""
+ private set
+ internal var internalProp: String? = ""
+ internal var isInternalProp2: String = ""
+ // these won't show up in KAPT stubs since they don't have a valid JVM name but
+ // we are still testing them for consistency as they'll show up in metadata
+ var valueProp: ValueClass? = null
+ // these won't show up in KAPT stubs since they don't have a valid JVM name but
+ // we are still testing them for consistency as they'll show up in metadata
+ internal var internalValueProp: ValueClass = ValueClass("?")
+ }
+ """.trimIndent()
+ )
+ simpleRun(listOf(src)) { invocation ->
+ val (element, metadata) = getMetadataElement(
+ invocation,
+ "Subject"
+ )
+
+ fun assertSetter(
+ kmFunction: KmFunction?,
+ name: String,
+ jvmName: String,
+ paramNullable: Boolean
+ ) {
+ checkNotNull(kmFunction)
+ assertWithMessage(kmFunction.toString()).apply {
+ that(kmFunction.name).isEqualTo(name)
+ that(kmFunction.jvmName).isEqualTo(jvmName)
+ // void is non null
+ that(kmFunction.returnType.nullability).isEqualTo(XNullability.NONNULL)
+ that(kmFunction.parameters).hasSize(1)
+ that(
+ kmFunction.parameters.single().isNullable()
+ ).isEqualTo(paramNullable)
+ }
+ }
+
+ fun assertSetter(
+ metadata: KotlinMetadataElement,
+ method: ExecutableElement,
+ name: String,
+ jvmName: String,
+ paramNullable: Boolean
+ ) {
+ val kmFunction = metadata.getFunctionMetadata(method)
+ assertSetter(
+ kmFunction = kmFunction,
+ name = name,
+ jvmName = jvmName,
+ paramNullable = paramNullable
+ )
+ val paramName = kmFunction!!.parameters.single().name
+ val javacElementName = method.parameters.single().simpleName.toString()
+ assertWithMessage(
+ kmFunction.toString()
+ ).that(
+ paramName
+ ).isEqualTo(
+ javacElementName.sanitizeAsJavaParameterName(0)
+ )
+ }
+
+ fun assertGetter(
+ kmFunction: KmFunction?,
+ name: String,
+ jvmName: String,
+ returnsNullable: Boolean
+ ) {
+ checkNotNull(kmFunction)
+ assertWithMessage(kmFunction.toString()).apply {
+ that(kmFunction.name).isEqualTo(name)
+ that(kmFunction.jvmName).isEqualTo(jvmName)
+ that(kmFunction.returnType.isNullable()).isEqualTo(returnsNullable)
+ that(kmFunction.parameters).isEmpty()
+ }
+ }
+ assertGetter(
+ kmFunction = metadata.getFunctionMetadata(
+ element.getDeclaredMethod("getImmutableProperty")
+ ),
+ name = "getImmutableProperty",
+ jvmName = "getImmutableProperty",
+ returnsNullable = false
+ )
+ assertGetter(
+ kmFunction = metadata.getFunctionMetadata(
+ element.getDeclaredMethod("getMutableProperty")
+ ),
+ name = "getMutableProperty",
+ jvmName = "getMutableProperty",
+ returnsNullable = false
+ )
+ assertSetter(
+ metadata = metadata,
+ method = element.getDeclaredMethod("setMutableProperty"),
+ name = "setMutableProperty",
+ jvmName = "setMutableProperty",
+ paramNullable = false
+ )
+ assertSetter(
+ metadata = metadata,
+ method = element.getDeclaredMethod("setProperty"),
+ name = "setProperty",
+ jvmName = "setProperty",
+ paramNullable = true
+ )
+ assertGetter(
+ kmFunction = metadata.getFunctionMetadata(
+ element.getDeclaredMethod("isProperty")
+ ),
+ name = "isProperty",
+ jvmName = "isProperty",
+ returnsNullable = true
+ )
+ assertGetter(
+ kmFunction = metadata.getFunctionMetadata(
+ element.getDeclaredMethod("getInternalProp\$main")
+ ),
+ name = "getInternalProp",
+ jvmName = "getInternalProp\$main",
+ returnsNullable = true
+ )
+ assertSetter(
+ metadata = metadata,
+ method = element.getDeclaredMethod("setInternalProp\$main"),
+ name = "setInternalProp",
+ jvmName = "setInternalProp\$main",
+ paramNullable = true
+ )
+ assertGetter(
+ kmFunction = metadata.getFunctionMetadata(
+ element.getDeclaredMethod("isInternalProp2\$main")
+ ),
+ name = "isInternalProp2",
+ jvmName = "isInternalProp2\$main",
+ returnsNullable = false
+ )
+ assertSetter(
+ metadata = metadata,
+ method = element.getDeclaredMethod("setInternalProp2\$main"),
+ name = "setInternalProp2",
+ jvmName = "setInternalProp2\$main",
+ paramNullable = false
+ )
+ // read custom setter name properly
+ metadata.getFunctionMetadata(
+ element.getDeclaredMethod("setCustomSetter")
+ ).let { kmFunction ->
+ checkNotNull(kmFunction)
+ assertThat(
+ kmFunction.parameters.single().name
+ ).isEqualTo("myValue")
+ }
+ // tests value class properties. They won't show up in KAPT stubs since they don't have
+ // valid java source names but we still validate them here for consistency. Maybe one
+ // day we'll change Javac element to include these if we support Kotlin codegen in KAPT
+ metadata.getPropertyMetadata("valueProp").let { valueProp ->
+ assertGetter(
+ kmFunction = valueProp?.getter,
+ name = "getValueProp",
+ jvmName = "getValueProp-4LjoGxk",
+ returnsNullable = true
+ )
+ assertSetter(
+ kmFunction = valueProp?.setter,
+ name = "setValueProp",
+ jvmName = "setValueProp-d8IPsTA",
+ paramNullable = true
+ )
+ }
+ metadata.getPropertyMetadata("internalValueProp").let { valueProp ->
+ assertGetter(
+ kmFunction = valueProp?.getter,
+ name = "getInternalValueProp",
+ jvmName = "getInternalValueProp-NMFyIOA\$main",
+ returnsNullable = false
+ )
+ assertSetter(
+ kmFunction = valueProp?.setter,
+ name = "setInternalValueProp",
+ jvmName = "setInternalValueProp-FVOWAsA\$main",
+ paramNullable = false
+ )
+ }
+ }
+ }
+
+ @Test
+ fun internalMethodName() {
+ val src = Source.kotlin(
+ "Kotlin.kt",
+ """
+ class Subject {
+ internal fun internalFun() {}
+ fun normalFun() {}
+ // there is no test case for functions receiving/returning value classes because
+ // they are not visible through KAPT
+ }
+ """.trimIndent()
+ )
+ simpleRun(listOf(src)) { invocation ->
+ val (element, metadata) = getMetadataElement(
+ invocation,
+ "Subject"
+ )
+ metadata.getFunctionMetadata(
+ element.getDeclaredMethod("internalFun\$main")
+ ).let { functionMetadata ->
+ assertThat(
+ functionMetadata?.jvmName
+ ).isEqualTo("internalFun\$main")
+ assertThat(
+ functionMetadata?.name
+ ).isEqualTo("internalFun")
+ }
+
+ metadata.getFunctionMetadata(
+ element.getDeclaredMethod("normalFun")
+ ).let { functionMetadata ->
+ assertThat(
+ functionMetadata?.jvmName
+ ).isEqualTo("normalFun")
+ assertThat(
+ functionMetadata?.name
+ ).isEqualTo("normalFun")
+ }
+ }
+ }
+
+ @Test
fun genericParameterNullability() {
val src = Source.kotlin(
"Subject.kt",
@@ -470,6 +721,9 @@
@Test
fun withoutKotlinInClasspath() {
+ if (preCompiled) {
+ throw AssumptionViolatedException("this test doesn't care for precompiled code")
+ }
val libSource = Source.kotlin(
"lib.kt",
"""
@@ -505,11 +759,17 @@
private fun TypeElement.getConstructors() = ElementFilter.constructorsIn(enclosedElements)
+ @Suppress("NAME_SHADOWING") // intentional
private fun simpleRun(
sources: List<Source> = emptyList(),
handler: (ProcessingEnvironment) -> Unit
) {
- runKaptTest(sources) {
+ val (sources, classpath) = if (preCompiled) {
+ emptyList<Source>() to compileFiles(sources)
+ } else {
+ sources to emptyList()
+ }
+ runKaptTest(sources = sources, classpath = classpath) {
val processingEnv = it.processingEnv
if (processingEnv !is JavacProcessingEnv) {
throw AssumptionViolatedException("This test only works for java/kapt compilation")
@@ -522,4 +782,10 @@
processingEnv.elementUtils.getTypeElement(qName).let {
it to KotlinMetadataElement.createFor(it)!!
}
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "preCompiled_{0}")
+ fun params() = arrayOf(false, true)
+ }
}
\ No newline at end of file