Implement methods in KSP

This CL adds more implementation to methods in KSP, specifically,
we can now get executable type and method parameters in KSP.

As Room still generates Java, I implemented Synthetic parameters
for suspend functions and changed their return values to match
Java as well.

Similarly, there are no getters/setters for properties hence I
implemented Synthetics for their elements and types. KAPT does not
generate them for private properties so I also excluded them from
the list.

This CL does not handle JvmOverloads yet.

Bug: 160322705
Bug: 169882122
Test: XExecutableElementTest, XExecutableTypeTest
Change-Id: I0ebb99325e648c81568a5c46a6d8e9566c73b26e
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
index 2ea9db7..f716d04 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSAsMemberOf.kt
@@ -21,11 +21,13 @@
 import org.jetbrains.kotlin.ksp.processing.Resolver
 import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
 import org.jetbrains.kotlin.ksp.symbol.KSDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
 import org.jetbrains.kotlin.ksp.symbol.KSName
 import org.jetbrains.kotlin.ksp.symbol.KSPropertyDeclaration
 import org.jetbrains.kotlin.ksp.symbol.KSType
 import org.jetbrains.kotlin.ksp.symbol.KSTypeArgument
 import org.jetbrains.kotlin.ksp.symbol.KSTypeParameter
+import org.jetbrains.kotlin.ksp.symbol.KSVariableParameter
 import org.jetbrains.kotlin.ksp.symbol.Nullability
 
 /**
@@ -42,6 +44,27 @@
     return myType.asMemberOf(resolver, this, ksType)
 }
 
+internal fun KSVariableParameter.typeAsMemberOf(
+    resolver: Resolver,
+    functionDeclaration: KSFunctionDeclaration,
+    ksType: KSType
+): KSType {
+    val myType: KSType = checkNotNull(type?.requireType()) {
+        "Cannot find type of method parameter: $this"
+    }
+    return myType.asMemberOf(resolver, functionDeclaration, ksType)
+}
+
+internal fun KSFunctionDeclaration.returnTypeAsMemberOf(
+    resolver: Resolver,
+    ksType: KSType
+): KSType {
+    val myType: KSType = checkNotNull(returnType?.requireType()) {
+        "Cannot resolve return type of $this"
+    }
+    return myType.asMemberOf(resolver, this, ksType)
+}
+
 /**
  * Returns `this` type as member of the [other] type.
  *
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
index 7c95149..6f38f4b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
@@ -22,7 +22,7 @@
 import androidx.room.compiler.processing.XTypeElement
 import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
 
-internal open class KspExecutableElement(
+internal abstract class KspExecutableElement(
     env: KspProcessingEnv,
     val containing: KspTypeElement,
     override val declaration: KSFunctionDeclaration
@@ -39,13 +39,15 @@
         declaration.requireEnclosingTypeElement(env)
     }
 
-    override val parameters: List<XExecutableParameterElement>
-        get() = TODO(
-            """
-            implement parameters. need to be careful w/ suspend functions as they need to fake
-            an additional parameter to look like java
-        """.trimIndent()
-        )
+    override val parameters: List<XExecutableParameterElement> by lazy {
+        declaration.parameters.map {
+            KspExecutableParameterElement(
+                env = env,
+                method = this,
+                parameter = it
+            )
+        }
+    }
 
     override fun isVarArgs(): Boolean {
         return declaration.parameters.any {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
new file mode 100644
index 0000000..8fa582d
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.processing.XAnnotationBox
+import androidx.room.compiler.processing.XDeclaredType
+import androidx.room.compiler.processing.XEquality
+import androidx.room.compiler.processing.XExecutableParameterElement
+import androidx.room.compiler.processing.XType
+import org.jetbrains.kotlin.ksp.symbol.KSVariableParameter
+import kotlin.reflect.KClass
+
+internal class KspExecutableParameterElement(
+    val env: KspProcessingEnv,
+    val method: KspExecutableElement,
+    val parameter: KSVariableParameter
+) : XExecutableParameterElement, XEquality {
+
+    override val equalityItems: Array<out Any?>
+        get() = arrayOf(method, parameter)
+
+    override val name: String
+        get() = parameter.name?.asString() ?: "_no_param_name"
+
+    override val type: XType by lazy {
+        parameter.typeAsMemberOf(
+            resolver = env.resolver,
+            functionDeclaration = method.declaration,
+            ksType = method.containing.declaration.asStarProjectedType()
+        ).let(env::wrap)
+    }
+
+    override fun asMemberOf(other: XDeclaredType): XType {
+        if (method.containing.type.isSameType(other)) {
+            return type
+        }
+        check(other is KspType)
+        return parameter.typeAsMemberOf(
+            resolver = env.resolver,
+            functionDeclaration = method.declaration,
+            ksType = other.ksType
+        ).let(env::wrap)
+    }
+
+    override fun kindName(): String {
+        return "function parameter"
+    }
+
+    override fun <T : Annotation> toAnnotationBox(annotation: KClass<T>): XAnnotationBox<T>? {
+        TODO("Not yet implemented")
+    }
+
+    override fun hasAnnotationWithPackage(pkg: String): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun hasAnnotation(annotation: KClass<out Annotation>): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return XEquality.equals(this, other)
+    }
+
+    override fun hashCode(): Int {
+        return XEquality.hashCode(equalityItems)
+    }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
index 74d352b..148b753 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
@@ -17,14 +17,20 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XDeclaredType
+import androidx.room.compiler.processing.XExecutableParameterElement
 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.synthetic.KspSyntheticContinuationParameterElement
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
+import org.jetbrains.kotlin.ksp.symbol.ClassKind
+import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
 import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
 import org.jetbrains.kotlin.ksp.symbol.Modifier
+import org.jetbrains.kotlin.ksp.symbol.Origin
 
-internal class KspMethodElement(
+internal sealed class KspMethodElement(
     env: KspProcessingEnv,
     containing: KspTypeElement,
     declaration: KSFunctionDeclaration
@@ -38,44 +44,128 @@
         declaration.simpleName.asString()
     }
 
-    override val returnType: XType
-        get() = TODO(
-            """
-            Implement return type.
-            Need to handle suspend functions where their signature is different as long as we
-            generate java code.
-        """.trimIndent()
+    override val executableType: XMethodType by lazy {
+        KspMethodType.create(
+            env = env,
+            origin = this,
+            containing = this.containing.type
         )
-
-    override val executableType: XMethodType
-        get() = TODO("Not yet implemented")
+    }
 
     override fun isJavaDefault(): Boolean {
         return declaration.modifiers.contains(Modifier.JAVA_DEFAULT) || declaration.isJvmDefault()
     }
 
     override fun asMemberOf(other: XDeclaredType): XMethodType {
-        TODO("Not yet implemented")
+        check(other is KspType)
+        return KspMethodType.create(
+            env = env,
+            origin = this,
+            containing = other
+        )
     }
 
     override fun hasKotlinDefaultImpl(): Boolean {
-        TODO("Not yet implemented")
-    }
-
-    override fun isSuspendFunction(): Boolean {
-        return declaration.modifiers.contains(Modifier.SUSPEND)
+        // see https://github.com/google/ksp/issues/32
+        val parentDeclaration = declaration.parentDeclaration
+        // if parent declaration is an interface and we are not marked as an abstract method,
+        // we should have a default implementation
+        return parentDeclaration is KSClassDeclaration &&
+            parentDeclaration.classKind == ClassKind.INTERFACE &&
+                !declaration.isAbstract
     }
 
     override fun overrides(other: XMethodElement, owner: XTypeElement): Boolean {
-        return other is KspMethodElement && declaration.overrides(other.declaration)
+        if (other is KspMethodElement) {
+            return try {
+                declaration.overrides(other.declaration)
+            } catch (castException: ClassCastException) {
+                // TODO remove the try catch once that bug is fixed.
+                // see https://github.com/google/ksp/issues/94
+                false
+            }
+        }
+        // TODO https://github.com/google/ksp/issues/93
+        //  remove this custom implementation when KSP supports this out of the box
+        // if our declaration is coming from java, it can override property getters/setters as well
+        val checkForJavaToKotlinOverride = other.isOverrideableIgnoringContainer() &&
+            (declaration.origin == Origin.JAVA || declaration.origin == Origin.CLASS)
+        return if (checkForJavaToKotlinOverride && name == other.name) {
+            when (other) {
+                is KspSyntheticPropertyMethodElement.Getter -> {
+                    return parameters.isEmpty() &&
+                        asMemberOf(owner.type).returnType.isSameType(
+                            other.asMemberOf(owner.type).returnType
+                        )
+                }
+                is KspSyntheticPropertyMethodElement.Setter -> {
+                    return parameters.size == 1 &&
+                        parameters.first().asMemberOf(owner.type).isSameType(
+                            other.parameters.first().asMemberOf(owner.type)
+                        )
+                }
+                else -> false
+            }
+        } else {
+            false
+        }
     }
 
-    override fun copyTo(newContainer: XTypeElement): XMethodElement {
+    override fun copyTo(newContainer: XTypeElement): KspMethodElement {
         check(newContainer is KspTypeElement)
-        return KspMethodElement(
+        return create(
             env = env,
             containing = newContainer,
             declaration = declaration
         )
     }
+
+    private class KspNormalMethodElement(
+        env: KspProcessingEnv,
+        containing: KspTypeElement,
+        declaration: KSFunctionDeclaration
+    ) : KspMethodElement(
+        env, containing, declaration
+    ) {
+        override val returnType: XType by lazy {
+            env.wrap(checkNotNull(declaration.returnType) {
+                "return type on a method declaration cannot be null"
+            })
+        }
+        override fun isSuspendFunction() = false
+    }
+
+    private class KspSuspendMethodElement(
+        env: KspProcessingEnv,
+        containing: KspTypeElement,
+        declaration: KSFunctionDeclaration
+    ) : KspMethodElement(
+        env, containing, declaration
+    ) {
+        override fun isSuspendFunction() = true
+
+        override val returnType: XType by lazy {
+            env.wrap(env.resolver.builtIns.anyType.makeNullable())
+        }
+
+        override val parameters: List<XExecutableParameterElement>
+            get() = super.parameters + KspSyntheticContinuationParameterElement(
+                env = env,
+                containing = this
+            )
+    }
+
+    companion object {
+        fun create(
+            env: KspProcessingEnv,
+            containing: KspTypeElement,
+            declaration: KSFunctionDeclaration
+        ): KspMethodElement {
+            return if (declaration.modifiers.contains(Modifier.SUSPEND)) {
+                KspSuspendMethodElement(env, containing, declaration)
+            } else {
+                KspNormalMethodElement(env, containing, declaration)
+            }
+        }
+    }
 }
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
new file mode 100644
index 0000000..192a52b
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.processing.XMethodType
+import androidx.room.compiler.processing.XType
+import com.squareup.javapoet.TypeVariableName
+
+internal sealed class KspMethodType(
+    val env: KspProcessingEnv,
+    val origin: KspMethodElement,
+    val containing: KspType
+) : XMethodType {
+    override val parameterTypes: List<XType> by lazy {
+        origin.parameters.map {
+            it.asMemberOf(containing)
+        }
+    }
+
+    override val typeVariableNames: List<TypeVariableName> by lazy {
+        origin.declaration.typeParameters.map {
+            TypeVariableName.get(
+                it.name.asString(),
+                *(it.bounds.map {
+                    it.typeName()
+                }.toTypedArray())
+            )
+        }
+    }
+
+    private class KspNormalMethodType(
+        env: KspProcessingEnv,
+        origin: KspMethodElement,
+        containing: KspType
+    ) : KspMethodType(env, origin, containing) {
+        override val returnType: XType by lazy {
+            env.wrap(
+                origin.declaration.returnTypeAsMemberOf(
+                    resolver = env.resolver,
+                    ksType = containing.ksType
+                )
+            )
+        }
+
+        override fun getSuspendFunctionReturnType(): XType {
+            throw IllegalStateException(
+                "cannot call getSuspendFunctionReturnType on a non-suspend method. $this"
+            )
+        }
+    }
+
+    private class KspSuspendMethodType(
+        env: KspProcessingEnv,
+        origin: KspMethodElement,
+        containing: KspType
+    ) : KspMethodType(env, origin, containing) {
+        override val returnType: XType
+            // suspend functions always return Any?, no need to call asMemberOf
+            get() = origin.returnType
+
+        override fun getSuspendFunctionReturnType(): XType {
+            return env.wrap(
+                origin.declaration.returnTypeAsMemberOf(
+                    resolver = env.resolver,
+                    ksType = containing.ksType
+                )
+            )
+        }
+    }
+
+    companion object {
+        fun create(
+            env: KspProcessingEnv,
+            origin: KspMethodElement,
+            containing: KspType
+        ) = if (origin.isSuspendFunction()) {
+            KspSuspendMethodType(env, origin, containing)
+        } else {
+            KspNormalMethodType(env, origin, containing)
+        }
+    }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 959d0eb..1244d41 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -185,7 +185,7 @@
                 // filter out constructors
                 it.simpleName.asString() != name
             }.map {
-                KspMethodElement(
+                KspMethodElement.create(
                     env = env,
                     containing = this,
                     declaration = it
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
index 9e53d6d..d490a38 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
@@ -25,3 +25,5 @@
 internal fun Resolver.requireClass(qName: String) = checkNotNull(findClass(qName)) {
     "cannot find class $qName"
 }
+
+internal fun Resolver.requireContinuationClass() = requireClass("kotlin.coroutines.Continuation")
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
new file mode 100644
index 0000000..334bb1f
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.XAnnotationBox
+import androidx.room.compiler.processing.XDeclaredType
+import androidx.room.compiler.processing.XEquality
+import androidx.room.compiler.processing.XExecutableParameterElement
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.ksp.KspExecutableElement
+import androidx.room.compiler.processing.ksp.KspProcessingEnv
+import androidx.room.compiler.processing.ksp.KspType
+import androidx.room.compiler.processing.ksp.requireContinuationClass
+import androidx.room.compiler.processing.ksp.returnTypeAsMemberOf
+import androidx.room.compiler.processing.ksp.swapResolvedType
+import org.jetbrains.kotlin.ksp.symbol.Variance
+import kotlin.reflect.KClass
+
+/**
+ * XProcessing adds an additional argument to each suspend function for the continiuation because
+ * this is what KAPT generates and Room needs it as long as it generates java code.
+ */
+internal class KspSyntheticContinuationParameterElement(
+    private val env: KspProcessingEnv,
+    private val containing: KspExecutableElement
+) : XExecutableParameterElement, XEquality {
+
+    override val name: String
+        get() = "_syntheticContinuation"
+
+    override val equalityItems: Array<out Any?> by lazy {
+        arrayOf("continuation", containing)
+    }
+
+    override val type: XType by lazy {
+        val continuation = env.resolver.requireContinuationClass()
+        val contType = continuation.asType(
+            listOf(
+                env.resolver.getTypeArgument(
+                    checkNotNull(containing.declaration.returnType) {
+                        "cannot find return type for $this"
+                    },
+                    Variance.CONTRAVARIANT
+                )
+            )
+        )
+        env.wrap(contType)
+    }
+
+    override fun asMemberOf(other: XDeclaredType): XType {
+        check(other is KspType)
+        val continuation = env.resolver.requireContinuationClass()
+        val asMember = containing.declaration.returnTypeAsMemberOf(
+            resolver = env.resolver,
+            ksType = other.ksType
+        )
+        val returnTypeRef = checkNotNull(containing.declaration.returnType) {
+            "cannot find return type reference for $this"
+        }
+        val returnTypeAsTypeArgument = env.resolver.getTypeArgument(
+            returnTypeRef.swapResolvedType(asMember),
+            Variance.CONTRAVARIANT
+        )
+        val contType = continuation.asType(listOf(returnTypeAsTypeArgument))
+        return env.wrap(contType)
+    }
+
+    override fun kindName(): String {
+        return "synthetic continuation parameter"
+    }
+
+    override fun <T : Annotation> toAnnotationBox(annotation: KClass<T>): XAnnotationBox<T>? {
+        TODO("Not yet implemented")
+    }
+
+    override fun hasAnnotationWithPackage(pkg: String): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun hasAnnotation(annotation: KClass<out Annotation>): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return XEquality.equals(this, other)
+    }
+
+    override fun hashCode(): Int {
+        return XEquality.hashCode(equalityItems)
+    }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 26327f1..e401b10 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -38,6 +38,7 @@
  *
  * @see KspSyntheticPropertyMethodElement.Getter
  * @see KspSyntheticPropertyMethodElement.Setter
+ * @see KspSyntheticPropertyMethodType
  */
 internal sealed class KspSyntheticPropertyMethodElement(
     env: KspProcessingEnv,
@@ -62,11 +63,18 @@
 
     final override fun isVarArgs() = false
 
-    final override val executableType: XMethodType
-        get() = TODO()
+    final override val executableType: XMethodType by lazy {
+        KspSyntheticPropertyMethodType.create(
+            element = this,
+            container = field.containing.type
+        )
+    }
 
     final override fun asMemberOf(other: XDeclaredType): XMethodType {
-        TODO()
+        return KspSyntheticPropertyMethodType.create(
+            element = this,
+            container = other
+        )
     }
 
     internal class Getter(
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt
new file mode 100644
index 0000000..485de45
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.XDeclaredType
+import androidx.room.compiler.processing.XMethodType
+import androidx.room.compiler.processing.XType
+import com.squareup.javapoet.TypeVariableName
+
+/**
+ * @see KspSyntheticPropertyMethodElement
+ */
+internal sealed class KspSyntheticPropertyMethodType(
+    val origin: KspSyntheticPropertyMethodElement,
+    val containing: XDeclaredType
+) : XMethodType {
+
+    override val parameterTypes: List<XType> by lazy {
+        origin.parameters.map {
+            it.asMemberOf(containing)
+        }
+    }
+
+    override val typeVariableNames: List<TypeVariableName>
+        get() = emptyList()
+
+    override fun getSuspendFunctionReturnType(): XType {
+        throw IllegalStateException("Property types are not suspend functions.")
+    }
+
+    companion object {
+        fun create(
+            element: KspSyntheticPropertyMethodElement,
+            container: XDeclaredType
+        ): XMethodType {
+            return when (element) {
+                is KspSyntheticPropertyMethodElement.Getter ->
+                    Getter(
+                        origin = element,
+                        containingType = container
+                    )
+                is KspSyntheticPropertyMethodElement.Setter ->
+                    Setter(
+                        origin = element,
+                        containingType = container
+                    )
+            }
+        }
+    }
+
+    private class Getter(
+        origin: KspSyntheticPropertyMethodElement.Getter,
+        containingType: XDeclaredType
+    ) : KspSyntheticPropertyMethodType(
+        origin = origin,
+        containing = containingType
+    ) {
+        override val returnType: XType by lazy {
+            origin.field.asMemberOf(containingType)
+        }
+    }
+
+    private class Setter(
+        origin: KspSyntheticPropertyMethodElement.Setter,
+        containingType: XDeclaredType
+    ) : KspSyntheticPropertyMethodType(
+        origin = origin,
+        containing = containingType
+    ) {
+        override val returnType: XType
+            // setters always return Unit, no need to get it as type of
+            get() = origin.returnType
+    }
+}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 33a65a5..7e4dd73 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -16,13 +16,16 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.processing.util.KotlinTypeNames
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.getDeclaredMethod
 import androidx.room.compiler.processing.util.getMethod
 import androidx.room.compiler.processing.util.getParameter
-import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
+import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.WildcardTypeName
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -31,8 +34,8 @@
 class XExecutableElementTest {
     @Test
     fun basic() {
-        runProcessorTest(
-            listOf(
+        runProcessorTestIncludingKsp(
+            sources = listOf(
                 Source.java(
                     "foo.bar.Baz", """
                 package foo.bar;
@@ -53,22 +56,23 @@
                 assertThat(method.isOverrideableIgnoringContainer()).isFalse()
                 assertThat(method.parameters).isEmpty()
                 val returnType = method.returnType
-                assertThat(returnType.isVoid()).isTrue()
+                // check both as in KSP, it will show up as Unit
+                assertThat(returnType.isVoid() || returnType.isKotlinUnit()).isTrue()
                 assertThat(returnType.defaultValue()).isEqualTo("null")
             }
             element.getDeclaredMethod("bar").let { method ->
                 assertThat(method.isOverrideableIgnoringContainer()).isTrue()
                 assertThat(method.parameters).hasSize(1)
-                val stringTypeName = ClassName.get("java.lang", "String")
                 method.getParameter("param1").let { param ->
                     assertThat(param.type.isArray()).isTrue()
                     assertThat(param.type.asArray().componentType.typeName)
-                        .isEqualTo(stringTypeName)
+                        .isEqualTo(it.types.string)
                 }
-                assertThat(method.returnType.typeName).isEqualTo(stringTypeName)
+                assertThat(method.returnType.typeName).isEqualTo(it.types.string)
             }
         }
     }
+
     @Test
     fun isVarArgs() {
         val subject = Source.java(
@@ -79,7 +83,7 @@
             }
         """.trimIndent()
         )
-        runProcessorTest(
+        runProcessorTestIncludingKsp(
             sources = listOf(subject)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
@@ -107,7 +111,7 @@
             }
         """.trimIndent()
         )
-        runProcessorTest(
+        runProcessorTestIncludingKsp(
             sources = listOf(subject)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
@@ -138,4 +142,261 @@
             }
         }
     }
+
+    @Test
+    fun suspendMethod() {
+        val src = Source.kotlin(
+            "Foo.kt", """
+            class Subject {
+                suspend fun noArg():Unit = TODO()
+                suspend fun intReturn(): Int = TODO()
+                suspend fun twoParams(param1:String, param2:Int): Pair<String, Int> = TODO()
+            }
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(
+            sources = listOf(src)
+        ) { invocation ->
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
+            subject.getMethod("noArg").let { method ->
+                assertThat(method.parameters).hasSize(1)
+                assertThat(method.isSuspendFunction()).isTrue()
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
+                assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
+                method.executableType.parameterTypes.last().let { cont ->
+                    assertThat(cont.typeName).isEqualTo(
+                        ParameterizedTypeName.get(
+                            KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                            WildcardTypeName.supertypeOf(KotlinTypeNames.UNIT_CLASS_NAME)
+                        )
+                    )
+                    assertThat(cont.nullability).isEqualTo(XNullability.NONNULL)
+                }
+            }
+            subject.getMethod("intReturn").let { method ->
+                assertThat(method.parameters).hasSize(1)
+                assertThat(method.parameters.last().type.typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                        WildcardTypeName.supertypeOf(invocation.types.boxedInt)
+                    )
+                )
+                assertThat(method.isSuspendFunction()).isTrue()
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
+                method.executableType.parameterTypes.last().let { cont ->
+                    assertThat(cont.typeName).isEqualTo(
+                        ParameterizedTypeName.get(
+                            KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                            WildcardTypeName.supertypeOf(invocation.types.boxedInt)
+                        )
+                    )
+                }
+            }
+            subject.getMethod("twoParams").let { method ->
+                assertThat(method.parameters).hasSize(3)
+                assertThat(method.parameters[0].type.typeName).isEqualTo(
+                    invocation.types.string
+                )
+                assertThat(method.parameters[1].type.typeName).isEqualTo(
+                    invocation.types.int
+                )
+                assertThat(method.isSuspendFunction()).isTrue()
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
+                method.executableType.parameterTypes.last().let { cont ->
+                    assertThat(cont.typeName).isEqualTo(
+                        ParameterizedTypeName.get(
+                            KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                            WildcardTypeName.supertypeOf(
+                                ParameterizedTypeName.get(
+                                    KotlinTypeNames.PAIR_CLASS_NAME,
+                                    invocation.types.string,
+                                    invocation.types.boxedInt
+                                )
+                            )
+                        )
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun kotlinProperties() {
+        val src = Source.kotlin(
+            "Foo.kt", """
+            data class MyDataClass(val x:String, var y:String, private val z:String) {
+                val prop1: String = ""
+                var prop2: String = ""
+                var prop3: String = TODO()
+                    private set
+                    get() = TODO()
+                private val prop4:String = ""
+            }
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(sources = listOf(src)) { invocation ->
+            val klass = invocation.processingEnv.requireTypeElement("MyDataClass")
+            val methodNames = klass.getAllMethods().map {
+                it.name
+            }
+            assertThat(methodNames).containsNoneIn(
+                listOf(
+                    "setX", "setProp3", "setZ", "setProp4", "getProp4"
+                )
+            )
+            listOf("getX", "getProp1", "getProp2", "getProp3").forEach {
+                klass.getMethod(it).let { method ->
+                    assertThat(method.returnType.typeName).isEqualTo(invocation.types.string)
+                    assertThat(method.parameters).isEmpty()
+                }
+            }
+            listOf("setY", "setProp2").forEach {
+                klass.getMethod(it).let { method ->
+                    assertThat(method.returnType.typeName).isEqualTo(invocation.types.voidOrUnit)
+                    assertThat(method.parameters.first().type.typeName).isEqualTo(
+                        invocation.types.string
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun parametersAsMemberOf() {
+        val source = Source.kotlin(
+            "Foo.kt", """
+            open class Base<T> {
+                fun foo(t:T, nullableT:T?): List<T?> = TODO()
+            }
+            class Subject : Base<String>()
+            class NullableSubject: Base<String?>()
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(sources = listOf(source)) { invocation ->
+            val base = invocation.processingEnv.requireTypeElement("Base")
+            val subject = invocation.processingEnv.requireType("Subject")
+                .asDeclaredType()
+            val nullableSubject = invocation.processingEnv.requireType("NullableSubject")
+                .asDeclaredType()
+            val method = base.getMethod("foo")
+            method.getParameter("t").let { param ->
+                param.asMemberOf(subject).let {
+                    assertThat(it.typeName).isEqualTo(invocation.types.string)
+                    assertThat(it.nullability).isEqualTo(XNullability.NONNULL)
+                }
+                param.asMemberOf(nullableSubject).let {
+                    assertThat(it.typeName).isEqualTo(invocation.types.string)
+                    if (invocation.isKsp) {
+                        // kapt implementation is unable to read this properly
+                        assertThat(it.nullability).isEqualTo(XNullability.NULLABLE)
+                    }
+                }
+            }
+            method.getParameter("nullableT").let { param ->
+                param.asMemberOf(subject).let {
+                    assertThat(it.typeName).isEqualTo(invocation.types.string)
+                    assertThat(it.nullability).isEqualTo(XNullability.NULLABLE)
+                }
+                param.asMemberOf(nullableSubject).let {
+                    assertThat(it.typeName).isEqualTo(invocation.types.string)
+                    assertThat(it.nullability).isEqualTo(XNullability.NULLABLE)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun kotlinPropertyOverrides() {
+        val src = Source.kotlin(
+            "Foo.kt", """
+            interface MyInterface {
+                val x:Int
+                var y:Int
+            }
+            class MyImpl : MyInterface {
+                override var x: Int = 1
+                override var y: Int = 1
+            }
+        """.trimIndent()
+        )
+        val javaSrc = Source.java(
+            "JavaImpl", """
+            class JavaImpl implements MyInterface {
+                public int getX() {
+                    return 1;
+                }
+                public int getY() {
+                    return 1;
+                }
+                public void setY(int value) {
+                }
+            }
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(sources = listOf(src, javaSrc)) { invocation ->
+            val base = invocation.processingEnv.requireTypeElement("MyInterface")
+            val impl = invocation.processingEnv.requireTypeElement("MyImpl")
+            val javaImpl = invocation.processingEnv.requireTypeElement("JavaImpl")
+
+            fun overrides(
+                owner: XTypeElement,
+                ownerMethodName: String,
+                base: XTypeElement,
+                baseMethodName: String = ownerMethodName
+            ): Boolean {
+                val overridee = owner.getMethod(ownerMethodName)
+                val overridden = base.getMethod(baseMethodName)
+                return overridee.overrides(
+                    overridden, owner
+                )
+            }
+            listOf(impl, javaImpl).forEach { subject ->
+                listOf("getY", "getX", "setY").forEach { methodName ->
+                    Truth.assertWithMessage("${subject.className}:$methodName").that(
+                        overrides(
+                            owner = subject,
+                            ownerMethodName = methodName,
+                            base = base
+                        )
+                    ).isTrue()
+                }
+
+                Truth.assertWithMessage(subject.className.canonicalName()).that(
+                    overrides(
+                        owner = subject,
+                        ownerMethodName = "getY",
+                        base = base,
+                        baseMethodName = "getX"
+                    )
+                ).isFalse()
+
+                Truth.assertWithMessage(subject.className.canonicalName()).that(
+                    overrides(
+                        owner = subject,
+                        ownerMethodName = "getY",
+                        base = subject,
+                        baseMethodName = "getX"
+                    )
+                ).isFalse()
+
+                Truth.assertWithMessage(subject.className.canonicalName()).that(
+                    overrides(
+                        owner = base,
+                        ownerMethodName = "getX",
+                        base = subject,
+                        baseMethodName = "getX"
+                    )
+                ).isFalse()
+
+                Truth.assertWithMessage(subject.className.canonicalName()).that(
+                    overrides(
+                        owner = subject,
+                        ownerMethodName = "setY",
+                        base = base,
+                        baseMethodName = "getY"
+                    )
+                ).isFalse()
+            }
+        }
+    }
 }
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
new file mode 100644
index 0000000..1b5c7ab
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -0,0 +1,248 @@
+/*
+ * 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
+
+import androidx.room.compiler.processing.util.KotlinTypeNames
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.getMethod
+import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.WildcardTypeName
+import org.junit.Test
+
+class XExecutableTypeTest {
+    @Test
+    fun inheritanceResolution() {
+        val src = Source.kotlin(
+            "Foo.kt", """
+            interface MyInterface<T> {
+                fun getT(): T
+                fun setT(t:T): Unit
+                suspend fun suspendGetT(): T
+                suspend fun suspendSetT(t:T): Unit
+            }
+            abstract class Subject : MyInterface<String>
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(
+            sources = listOf(src)
+        ) { invocation ->
+            val myInterface = invocation.processingEnv.requireTypeElement("MyInterface")
+
+            // helper method to get executable types both from sub class and also as direct child of
+            // the given type
+            fun checkMethods(
+                methodName: String,
+                vararg subjects: XTypeElement,
+                callback: (XMethodType) -> Unit
+            ) {
+                assertThat(subjects).isNotEmpty()
+                subjects.forEach {
+                    callback(myInterface.getMethod(methodName).asMemberOf(it.type))
+                    callback(it.getMethod(methodName).executableType)
+                }
+            }
+
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
+            checkMethods("getT", subject) { type ->
+                assertThat(type.parameterTypes).isEmpty()
+                assertThat(type.returnType.typeName).isEqualTo(invocation.types.string)
+            }
+            checkMethods("setT", subject) { type ->
+                assertThat(type.parameterTypes).containsExactly(
+                    invocation.processingEnv.requireType(invocation.types.string)
+                )
+                assertThat(type.returnType.typeName).isEqualTo(invocation.types.voidOrUnit)
+            }
+            checkMethods("suspendGetT", subject) { type ->
+                assertThat(type.parameterTypes.first().typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                        WildcardTypeName.supertypeOf(invocation.types.string)
+                    )
+                )
+                assertThat(type.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
+            }
+            checkMethods("suspendSetT", subject) { type ->
+                assertThat(type.parameterTypes.first().typeName).isEqualTo(
+                    invocation.types.string
+                )
+                assertThat(type.parameterTypes[1].typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        KotlinTypeNames.CONTINUATION_CLASS_NAME,
+                        WildcardTypeName.supertypeOf(KotlinTypeNames.UNIT_CLASS_NAME)
+                    )
+                )
+                assertThat(type.returnType.typeName).isEqualTo(invocation.types.objectOrAny)
+            }
+        }
+    }
+
+    @Test
+    fun kotlinPropertyInheritance() {
+        val src = Source.kotlin(
+            "Foo.kt", """
+            interface MyInterface<T> {
+                val immutableT: T
+                var mutableT: T?
+                val list: List<T>
+                val nullableList: List<T?>
+            }
+            abstract class Subject : MyInterface<String>
+            abstract class NullableSubject: MyInterface<String?>
+        """.trimIndent()
+        )
+        runProcessorTestIncludingKsp(sources = listOf(src)) { invocation ->
+            val myInterface = invocation.processingEnv.requireTypeElement("MyInterface")
+
+            // helper method to get executable types both from sub class and also as direct child of
+            // the given type
+            fun checkMethods(
+                methodName: String,
+                vararg subjects: XTypeElement,
+                callback: (XMethodType) -> Unit
+            ) {
+                assertThat(subjects).isNotEmpty()
+                subjects.forEach {
+                    callback(myInterface.getMethod(methodName).asMemberOf(it.type))
+                    callback(it.getMethod(methodName).executableType)
+                }
+            }
+
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
+            checkMethods("getImmutableT", subject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.string)
+                if (invocation.isKsp) {
+                    // we don't get proper nullable here for kapt
+                    // partially related to b/169629272
+                    assertThat(method.returnType.nullability).isEqualTo(XNullability.NONNULL)
+                }
+
+                assertThat(method.parameterTypes).isEmpty()
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+            checkMethods("getMutableT", subject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.string)
+                if (invocation.isKsp) {
+                    // we don't get proper nullable here for kapt
+                    // partially related to b/169629272
+                    assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
+                }
+                assertThat(method.parameterTypes).isEmpty()
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+            checkMethods("setMutableT", subject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.voidOrUnit)
+                assertThat(method.parameterTypes.first().nullability)
+                    .isEqualTo(XNullability.NULLABLE)
+                assertThat(method.parameterTypes.first().typeName)
+                    .isEqualTo(invocation.types.string)
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+            checkMethods("getList", subject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        invocation.types.list,
+                        invocation.types.string
+                    )
+                )
+                assertThat(method.returnType.nullability).isEqualTo(
+                    XNullability.NONNULL
+                )
+                if (invocation.isKsp) {
+                    // kapt cannot read type parameter nullability yet
+                    assertThat(
+                        method.returnType.asDeclaredType().typeArguments.first().nullability
+                    ).isEqualTo(
+                        XNullability.NONNULL
+                    )
+                }
+            }
+
+            val nullableSubject = invocation.processingEnv.requireTypeElement("NullableSubject")
+            // check that nullability is inferred from type parameters as well
+            checkMethods("getImmutableT", nullableSubject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.string)
+                if (invocation.isKsp) {
+                    // we don't get proper nullable here for kapt
+                    // partially related to b/169629272
+                    assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
+                }
+                assertThat(method.parameterTypes).isEmpty()
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+
+            checkMethods("getMutableT", nullableSubject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.string)
+                if (invocation.isKsp) {
+                    // we don't get proper nullable here for kapt
+                    // partially related to b/169629272
+                    assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
+                }
+                assertThat(method.parameterTypes).isEmpty()
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+
+            checkMethods("setMutableT", nullableSubject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(invocation.types.voidOrUnit)
+                assertThat(method.parameterTypes.first().nullability)
+                    .isEqualTo(XNullability.NULLABLE)
+                assertThat(method.parameterTypes.first().typeName)
+                    .isEqualTo(invocation.types.string)
+                assertThat(method.typeVariableNames).isEmpty()
+            }
+
+            checkMethods("getList", nullableSubject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        invocation.types.list,
+                        invocation.types.string
+                    )
+                )
+                assertThat(method.returnType.nullability).isEqualTo(
+                    XNullability.NONNULL
+                )
+                if (invocation.isKsp) {
+                    assertThat(
+                        method.returnType.asDeclaredType().typeArguments.first().nullability
+                    ).isEqualTo(
+                        XNullability.NULLABLE
+                    )
+                }
+            }
+            checkMethods("getNullableList", subject, nullableSubject) { method ->
+                assertThat(method.returnType.typeName).isEqualTo(
+                    ParameterizedTypeName.get(
+                        invocation.types.list,
+                        invocation.types.string
+                    )
+                )
+                assertThat(method.returnType.nullability).isEqualTo(
+                    XNullability.NONNULL
+                )
+                if (invocation.isKsp) {
+                    assertThat(
+                        method.returnType.asDeclaredType().typeArguments.first().nullability
+                    ).isEqualTo(
+                        XNullability.NULLABLE
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/KotlinTypeNames.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/KotlinTypeNames.kt
index 32d77be..644a0a3 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/KotlinTypeNames.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/KotlinTypeNames.kt
@@ -27,4 +27,5 @@
     val MUTABLELIST_CLASS_NAME = ClassName.get("kotlin.collections", "MutableList")
     val MAP_CLASS_NAME = ClassName.get("kotlin.collections", "Map")
     val PAIR_CLASS_NAME = ClassName.get(Pair::class.java)
+    val CONTINUATION_CLASS_NAME = ClassName.get("kotlin.coroutines", "Continuation")
 }
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
index 9398adf..9411922 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestInvocation.kt
@@ -25,6 +25,8 @@
 class TestInvocation(
     val processingEnv: XProcessingEnv
 ) {
+    val isKsp = processingEnv is KspProcessingEnv
+
     val kspResolver: Resolver
         get() = (processingEnv as KspProcessingEnv).resolver
 
@@ -35,7 +37,8 @@
                 voidOrUnit = KotlinTypeNames.UNIT_CLASS_NAME,
                 objectOrAny = KotlinTypeNames.ANY_CLASS_NAME,
                 boxedInt = KotlinTypeNames.INT_CLASS_NAME,
-                int = KotlinTypeNames.INT_CLASS_NAME
+                int = KotlinTypeNames.INT_CLASS_NAME,
+                list = KotlinTypeNames.LIST_CLASS_NAME
             )
         } else {
             Types(
@@ -43,7 +46,8 @@
                 voidOrUnit = TypeName.VOID,
                 objectOrAny = TypeName.OBJECT,
                 boxedInt = TypeName.INT.box(),
-                int = TypeName.INT
+                int = TypeName.INT,
+                list = ClassName.get("java.util", "List")
             )
         }
     }
@@ -57,6 +61,7 @@
         val voidOrUnit: TypeName,
         val objectOrAny: ClassName,
         val boxedInt: TypeName,
-        val int: TypeName
+        val int: TypeName,
+        val list: ClassName
     )
 }