[GH] Improve invalid Dao method errors

Proposed Changes
This change improves invalid Dao method error messages. In this case an invalid Dao method is a method which is suspending and returns one of the following types:

- any of RxJava2 types
- any of RxJava3 types
- LiveData
- ComputableLiveData
- ListenableFuture
- ReactiveStreams Publisher
- Kotlin Flow

Testing
Test: Added unit tests for Update, Insertion, Deletion and Query methods

Issues Fixed
Fixes: https://issuetracker.google.com/issues/151029087

This is an imported pull request from https://github.com/androidx/androidx/pull/299.

Resolves #299
Github-Pr-Head-Sha: d16de9ba9411fb7747a71d49e0dea587cd545a17
GitOrigin-RevId: 9aeb583b294cb58becf335dc33532bed1afeeef8
Change-Id: I980a099517609f39a37c267951d90b963f48e1d8
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index b024579..1d21c1a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -212,6 +212,24 @@
     val FLOW = ClassName.get("kotlinx.coroutines.flow", "Flow")
 }
 
+val DEFERRED_TYPES = listOf(
+    LifecyclesTypeNames.LIVE_DATA,
+    LifecyclesTypeNames.COMPUTABLE_LIVE_DATA,
+    RxJava2TypeNames.FLOWABLE,
+    RxJava2TypeNames.OBSERVABLE,
+    RxJava2TypeNames.MAYBE,
+    RxJava2TypeNames.SINGLE,
+    RxJava2TypeNames.COMPLETABLE,
+    RxJava3TypeNames.FLOWABLE,
+    RxJava3TypeNames.OBSERVABLE,
+    RxJava3TypeNames.MAYBE,
+    RxJava3TypeNames.SINGLE,
+    RxJava3TypeNames.COMPLETABLE,
+    GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE,
+    KotlinTypeNames.FLOW,
+    ReactiveStreamsTypeNames.PUBLISHER
+)
+
 fun TypeName.defaultValue(): String {
     return if (!isPrimitive) {
         "null"
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 2301cf4e..d75b580 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -22,6 +22,7 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XVariableElement
 import androidx.room.compiler.processing.isSuspendFunction
+import androidx.room.ext.DEFERRED_TYPES
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.L
 import androidx.room.ext.N
@@ -125,6 +126,21 @@
     }
 }
 
+fun MethodProcessorDelegate.isSuspendAndReturnsDeferredType(): Boolean {
+    if (!executableElement.isSuspendFunction()) {
+        return false
+    }
+
+    val deferredTypes = DEFERRED_TYPES.mapNotNull { context.processingEnv.findType(it) }
+
+    val returnType = extractReturnType()
+    val hasDeferredReturnType = deferredTypes.any { deferredType ->
+        deferredType.rawType.isAssignableFrom(returnType.rawType)
+    }
+
+    return hasDeferredReturnType
+}
+
 /**
  * Default delegate for DAO methods.
  */
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 5af3062..283c92d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -175,6 +175,10 @@
     val CANNOT_FIND_UPDATE_RESULT_ADAPTER = "Not sure how to handle update method's " +
         "return type. Currently the supported return types are void, int or Int."
 
+    fun suspendReturnsDeferredType(returnTypeName: String) = "Dao functions that have a suspend " +
+        "modifier must not return a deferred/async type ($returnTypeName). Most probably this " +
+        "is an error. Consider changing the return type or removing the suspend modifier."
+
     val CANNOT_FIND_INSERT_RESULT_ADAPTER = "Not sure how to handle insert method's return type."
 
     val UPDATE_MISSING_PARAMS = "Method annotated with" +
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index ab71220..d9c5a0f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -107,6 +107,12 @@
         val delegate = MethodProcessorDelegate.createFor(context, containing, executableElement)
         val returnType = delegate.extractReturnType()
 
+        context.checker.check(
+            !delegate.isSuspendAndReturnsDeferredType(),
+            executableElement,
+            ProcessorErrors.suspendReturnsDeferredType(returnType.rawType.typeName.toString())
+        )
+
         val query = if (input != null) {
             val query = SqlParser.parse(input)
             context.checker.check(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
index d2bfc80..7e59ed5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
@@ -50,6 +50,13 @@
             returnTypeName, executableElement,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS
         )
+
+        context.checker.check(
+            !delegate.isSuspendAndReturnsDeferredType(),
+            executableElement,
+            ProcessorErrors.suspendReturnsDeferredType(returnType.rawType.typeName.toString())
+        )
+
         val observedTableNames = processObservedTables()
         val query = SqlParser.rawQueryForTables(observedTableNames)
         // build the query but don't calculate result info since we just guessed it.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
index 59adf60..2f1aff8 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
@@ -44,7 +44,15 @@
         return annotation
     }
 
-    fun extractReturnType() = delegate.extractReturnType()
+    fun extractReturnType(): XType {
+        val returnType = delegate.extractReturnType()
+        context.checker.check(
+            !delegate.isSuspendAndReturnsDeferredType(),
+            executableElement,
+            ProcessorErrors.suspendReturnsDeferredType(returnType.rawType.typeName.toString())
+        )
+        return returnType
+    }
 
     fun extractParams(
         targetEntityType: XType?,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
index acf8ec6..404f9fb 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/TransactionMethodProcessor.kt
@@ -16,14 +16,10 @@
 
 package androidx.room.processor
 
-import androidx.room.ext.GuavaUtilConcurrentTypeNames
-import androidx.room.ext.LifecyclesTypeNames
-import androidx.room.ext.RxJava2TypeNames
-import androidx.room.ext.RxJava3TypeNames
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
-import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.DEFERRED_TYPES
 import androidx.room.vo.TransactionMethod
 
 class TransactionMethodProcessor(
@@ -49,7 +45,7 @@
 
         DEFERRED_TYPES.firstOrNull { className ->
             context.processingEnv.findType(className)?.let {
-                rawReturnType.isAssignableFrom(it)
+                it.rawType.isAssignableFrom(rawReturnType)
             } ?: false
         }?.let { returnTypeName ->
             context.logger.e(
@@ -81,22 +77,4 @@
             methodBinder = delegate.findTransactionMethodBinder(callType)
         )
     }
-
-    companion object {
-        val DEFERRED_TYPES = listOf(
-            LifecyclesTypeNames.LIVE_DATA,
-            RxJava2TypeNames.FLOWABLE,
-            RxJava2TypeNames.OBSERVABLE,
-            RxJava2TypeNames.MAYBE,
-            RxJava2TypeNames.SINGLE,
-            RxJava2TypeNames.COMPLETABLE,
-            RxJava3TypeNames.FLOWABLE,
-            RxJava3TypeNames.OBSERVABLE,
-            RxJava3TypeNames.MAYBE,
-            RxJava3TypeNames.SINGLE,
-            RxJava3TypeNames.COMPLETABLE,
-            GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE,
-            KotlinTypeNames.FLOW
-        )
-    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
index 0a60819..733575d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertionMethodProcessorTest.kt
@@ -25,6 +25,10 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.GuavaUtilConcurrentTypeNames
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.solver.shortcut.result.InsertMethodAdapter
@@ -53,6 +57,20 @@
                 @Dao
                 abstract class MyClass {
                 """
+        const val DAO_PREFIX_KT = """
+                package foo.bar
+                import androidx.room.*
+                import java.util.*
+                import io.reactivex.*         
+                io.reactivex.rxjava3.core.*
+                androidx.lifecycle.*
+                com.google.common.util.concurrent.*
+                org.reactivestreams.*
+                kotlinx.coroutines.flow.*
+            
+                @Dao
+                abstract class MyClass {
+                """
         const val DAO_SUFFIX = "}"
         val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
         val USERNAME_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Username")
@@ -920,6 +938,39 @@
         }
     }
 
+    @Test
+    fun suspendReturnsDeferredType() {
+        listOf(
+            "${RxJava2TypeNames.FLOWABLE}<Int>",
+            "${RxJava2TypeNames.OBSERVABLE}<Int>",
+            "${RxJava2TypeNames.MAYBE}<Int>",
+            "${RxJava2TypeNames.SINGLE}<Int>",
+            "${RxJava2TypeNames.COMPLETABLE}",
+            "${RxJava3TypeNames.FLOWABLE}<Int>",
+            "${RxJava3TypeNames.OBSERVABLE}<Int>",
+            "${RxJava3TypeNames.MAYBE}<Int>",
+            "${RxJava3TypeNames.SINGLE}<Int>",
+            "${RxJava3TypeNames.COMPLETABLE}",
+            "${LifecyclesTypeNames.LIVE_DATA}<Int>",
+            "${LifecyclesTypeNames.COMPUTABLE_LIVE_DATA}<Int>",
+            "${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
+            "${ReactiveStreamsTypeNames.PUBLISHER}<Int>",
+            "${KotlinTypeNames.FLOW}<Int>"
+        ).forEach { type ->
+            singleInsertMethodKotlin(
+                """
+                @Insert
+                abstract suspend fun foo(user: User): $type
+                """
+            ) { _, invocation ->
+                invocation.assertCompilationResult {
+                    val rawTypeName = type.substringBefore("<")
+                    hasErrorContaining(ProcessorErrors.suspendReturnsDeferredType(rawTypeName))
+                }
+            }
+        }
+    }
+
     fun singleInsertMethod(
         vararg input: String,
         additionalSources: List<Source> = emptyList(),
@@ -958,4 +1009,45 @@
             handler(processed, invocation)
         }
     }
+
+    fun singleInsertMethodKotlin(
+        vararg input: String,
+        additionalSources: List<Source> = emptyList(),
+        handler: (InsertionMethod, XTestInvocation) -> Unit
+    ) {
+        val inputSource = Source.kotlin(
+            "MyClass.kt",
+            DAO_PREFIX_KT + input.joinToString("\n") + DAO_SUFFIX
+        )
+        val commonSources = listOf(
+            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
+            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX2_FLOWABLE, COMMON.RX2_OBSERVABLE,
+            COMMON.RX3_COMPLETABLE, COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.RX3_FLOWABLE,
+            COMMON.RX3_OBSERVABLE, COMMON.LISTENABLE_FUTURE, COMMON.LIVE_DATA,
+            COMMON.COMPUTABLE_LIVE_DATA, COMMON.PUBLISHER, COMMON.FLOW, COMMON.GUAVA_ROOM
+        )
+
+        runProcessorTest(
+            sources = commonSources + additionalSources + inputSource
+        ) { invocation ->
+            val (owner, methods) = invocation.roundEnv
+                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
+                .filterIsInstance<XTypeElement>()
+                .map {
+                    Pair(
+                        it,
+                        it.getAllMethods().filter {
+                            it.hasAnnotation(Insert::class)
+                        }.toList()
+                    )
+                }.first { it.second.isNotEmpty() }
+            val processor = InsertionMethodProcessor(
+                baseContext = invocation.context,
+                containing = owner.type,
+                executableElement = methods.first()
+            )
+            val processed = processor.process()
+            handler(processed, invocation)
+        }
+    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 0223913..35285cf 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -25,9 +25,13 @@
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.LifecyclesTypeNames
 import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.ext.RxJava3TypeNames
 import androidx.room.ext.typeName
 import androidx.room.parser.QueryType
 import androidx.room.parser.Table
@@ -82,6 +86,20 @@
                 @Dao
                 abstract class MyClass {
                 """
+        const val DAO_PREFIX_KT = """
+                package foo.bar
+                import androidx.room.*
+                import java.util.*
+                import io.reactivex.*         
+                io.reactivex.rxjava3.core.*
+                androidx.lifecycle.*
+                com.google.common.util.concurrent.*
+                org.reactivestreams.*
+                kotlinx.coroutines.flow.*
+            
+                @Dao
+                abstract class MyClass {
+                """
         const val DAO_SUFFIX = "}"
         val POJO: ClassName = ClassName.get("foo.bar", "MyClass.Pojo")
         @Parameterized.Parameters(name = "enableDbVerification={0}")
@@ -1344,6 +1362,58 @@
         }
     }
 
+    private fun <T : QueryMethod> singleQueryMethodKotlin(
+        vararg input: String,
+        additionalSources: Iterable<Source> = emptyList(),
+        options: Map<String, String> = emptyMap(),
+        handler: (T, XTestInvocation) -> Unit
+    ) {
+        val inputSource = Source.kotlin(
+            "MyClass.kt",
+            DAO_PREFIX_KT + input.joinToString("\n") + DAO_SUFFIX
+        )
+        val commonSources = listOf(
+            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
+            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX2_FLOWABLE, COMMON.RX2_OBSERVABLE,
+            COMMON.RX3_COMPLETABLE, COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.RX3_FLOWABLE,
+            COMMON.RX3_OBSERVABLE, COMMON.LISTENABLE_FUTURE, COMMON.LIVE_DATA,
+            COMMON.COMPUTABLE_LIVE_DATA, COMMON.PUBLISHER, COMMON.FLOW, COMMON.GUAVA_ROOM
+        )
+
+        runProcessorTest(
+            sources = additionalSources + commonSources + inputSource,
+            options = options
+        ) { invocation ->
+            val (owner, methods) = invocation.roundEnv
+                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
+                .filterIsInstance<XTypeElement>()
+                .map { typeElement ->
+                    Pair(
+                        typeElement,
+                        typeElement.getAllMethods().filter { method ->
+                            method.hasAnnotation(Query::class)
+                        }.toList()
+                    )
+                }.first { it.second.isNotEmpty() }
+            val verifier = if (enableVerification) {
+                createVerifierFromEntitiesAndViews(invocation).also(
+                    invocation.context::attachDatabaseVerifier
+                )
+            } else {
+                null
+            }
+            val parser = QueryMethodProcessor(
+                baseContext = invocation.context,
+                containing = owner.type,
+                executableElement = methods.first(),
+                dbVerifier = verifier
+            )
+            val parsedQuery = parser.process()
+            @Suppress("UNCHECKED_CAST")
+            handler(parsedQuery as T, invocation)
+        }
+    }
+
     @Test
     fun testInvalidLinkedListCollectionInMultimapJoin() {
         singleQueryMethod<ReadQueryMethod>(
@@ -1611,4 +1681,37 @@
             }
         }
     }
+
+    @Test
+    fun suspendReturnsDeferredType() {
+        listOf(
+            "${RxJava2TypeNames.FLOWABLE}<Int>",
+            "${RxJava2TypeNames.OBSERVABLE}<Int>",
+            "${RxJava2TypeNames.MAYBE}<Int>",
+            "${RxJava2TypeNames.SINGLE}<Int>",
+            "${RxJava2TypeNames.COMPLETABLE}",
+            "${RxJava3TypeNames.FLOWABLE}<Int>",
+            "${RxJava3TypeNames.OBSERVABLE}<Int>",
+            "${RxJava3TypeNames.MAYBE}<Int>",
+            "${RxJava3TypeNames.SINGLE}<Int>",
+            "${RxJava3TypeNames.COMPLETABLE}",
+            "${LifecyclesTypeNames.LIVE_DATA}<Int>",
+            "${LifecyclesTypeNames.COMPUTABLE_LIVE_DATA}<Int>",
+            "${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
+            "${ReactiveStreamsTypeNames.PUBLISHER}<Int>",
+            "${KotlinTypeNames.FLOW}<Int>"
+        ).forEach { type ->
+            singleQueryMethodKotlin<WriteQueryMethod>(
+                """
+                @Query("DELETE from User where uid = :id")
+                abstract suspend fun foo(id: Int): $type
+                """
+            ) { _, invocation ->
+                invocation.assertCompilationResult {
+                    val rawTypeName = type.substringBefore("<")
+                    hasErrorContaining(ProcessorErrors.suspendReturnsDeferredType(rawTypeName))
+                }
+            }
+        }
+    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index 9acb444..78d8ba7 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -23,7 +23,13 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.GuavaUtilConcurrentTypeNames
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LifecyclesTypeNames
 import androidx.room.ext.PagingTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
+import androidx.room.ext.RxJava2TypeNames
+import androidx.room.ext.RxJava3TypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
 import androidx.room.testing.context
@@ -572,6 +578,39 @@
         }
     }
 
+    @Test
+    fun suspendReturnsDeferredType() {
+        listOf(
+            "${RxJava2TypeNames.FLOWABLE}<Int>",
+            "${RxJava2TypeNames.OBSERVABLE}<Int>",
+            "${RxJava2TypeNames.MAYBE}<Int>",
+            "${RxJava2TypeNames.SINGLE}<Int>",
+            "${RxJava2TypeNames.COMPLETABLE}",
+            "${RxJava3TypeNames.FLOWABLE}<Int>",
+            "${RxJava3TypeNames.OBSERVABLE}<Int>",
+            "${RxJava3TypeNames.MAYBE}<Int>",
+            "${RxJava3TypeNames.SINGLE}<Int>",
+            "${RxJava3TypeNames.COMPLETABLE}",
+            "${LifecyclesTypeNames.LIVE_DATA}<Int>",
+            "${LifecyclesTypeNames.COMPUTABLE_LIVE_DATA}<Int>",
+            "${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
+            "${ReactiveStreamsTypeNames.PUBLISHER}<Int>",
+            "${KotlinTypeNames.FLOW}<Int>"
+        ).forEach { type ->
+            singleQueryMethodKotlin(
+                """
+                @RawQuery
+                abstract suspend fun foo(query: SupportSQLiteQuery): $type
+                """
+            ) { _, invocation ->
+                invocation.assertCompilationResult {
+                    val rawTypeName = type.substringBefore("<")
+                    hasErrorContaining(ProcessorErrors.suspendReturnsDeferredType(rawTypeName))
+                }
+            }
+        }
+    }
+
     private fun singleQueryMethod(
         vararg input: String,
         handler: (RawQueryMethod, XTestInvocation) -> Unit
@@ -612,6 +651,47 @@
         }
     }
 
+    private fun singleQueryMethodKotlin(
+        vararg input: String,
+        handler: (RawQueryMethod, XTestInvocation) -> Unit
+    ) {
+        val inputSource = Source.kotlin(
+            "MyClass.kt",
+            DAO_PREFIX_KT +
+                input.joinToString("\n") +
+                DAO_SUFFIX
+        )
+        val commonSources = listOf(
+            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
+            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX2_FLOWABLE, COMMON.RX2_OBSERVABLE,
+            COMMON.RX3_COMPLETABLE, COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.RX3_FLOWABLE,
+            COMMON.RX3_OBSERVABLE, COMMON.LISTENABLE_FUTURE, COMMON.LIVE_DATA,
+            COMMON.COMPUTABLE_LIVE_DATA, COMMON.PUBLISHER, COMMON.FLOW, COMMON.GUAVA_ROOM
+        )
+        runProcessorTest(
+            sources = commonSources + inputSource
+        ) { invocation ->
+            val (owner, methods) = invocation.roundEnv
+                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
+                .filterIsInstance<XTypeElement>()
+                .map {
+                    Pair(
+                        it,
+                        it.getAllMethods().filter {
+                            it.hasAnnotation(RawQuery::class)
+                        }.toList()
+                    )
+                }.first { it.second.isNotEmpty() }
+            val parser = RawQueryMethodProcessor(
+                baseContext = invocation.context,
+                containing = owner.type,
+                executableElement = methods.first()
+            )
+            val parsedQuery = parser.process()
+            handler(parsedQuery, invocation)
+        }
+    }
+
     companion object {
         private const val DAO_PREFIX = """
                 package foo.bar;
@@ -624,6 +704,20 @@
                 @Dao
                 abstract class MyClass {
                 """
+        const val DAO_PREFIX_KT = """
+                package foo.bar
+                import androidx.room.*
+                import java.util.*
+                import io.reactivex.*         
+                io.reactivex.rxjava3.core.*
+                androidx.lifecycle.*
+                com.google.common.util.concurrent.*
+                org.reactivestreams.*
+                kotlinx.coroutines.flow.*
+                
+                @Dao
+                abstract class MyClass {
+                """
         private const val DAO_SUFFIX = "}"
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
index 72e4704..bbf5cbe 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
@@ -26,6 +26,9 @@
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.LifecyclesTypeNames
+import androidx.room.ext.ReactiveStreamsTypeNames
 import androidx.room.ext.RxJava2TypeNames
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.testing.context
@@ -53,6 +56,21 @@
                 @Dao
                 abstract class MyClass {
                 """
+        const val DAO_PREFIX_KT = """
+                package foo.bar
+                import androidx.room.*
+                import java.util.*
+                import io.reactivex.*         
+                io.reactivex.rxjava3.core.*
+                androidx.lifecycle.*
+                com.google.common.util.concurrent.*
+                org.reactivestreams.*
+                kotlinx.coroutines.flow.*
+            
+                @Dao
+                abstract class MyClass {
+                """
+
         const val DAO_SUFFIX = "}"
         val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
         val USERNAME_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Username")
@@ -342,6 +360,39 @@
     }
 
     @Test
+    fun suspendReturnsDeferredType() {
+        listOf(
+            "${RxJava2TypeNames.FLOWABLE}<Int>",
+            "${RxJava2TypeNames.OBSERVABLE}<Int>",
+            "${RxJava2TypeNames.MAYBE}<Int>",
+            "${RxJava2TypeNames.SINGLE}<Int>",
+            "${RxJava2TypeNames.COMPLETABLE}",
+            "${RxJava3TypeNames.FLOWABLE}<Int>",
+            "${RxJava3TypeNames.OBSERVABLE}<Int>",
+            "${RxJava3TypeNames.MAYBE}<Int>",
+            "${RxJava3TypeNames.SINGLE}<Int>",
+            "${RxJava3TypeNames.COMPLETABLE}",
+            "${LifecyclesTypeNames.LIVE_DATA}<Int>",
+            "${LifecyclesTypeNames.COMPUTABLE_LIVE_DATA}<Int>",
+            "${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
+            "${ReactiveStreamsTypeNames.PUBLISHER}<Int>",
+            "${KotlinTypeNames.FLOW}<Int>"
+        ).forEach { type ->
+            singleShortcutMethodKotlin(
+                """
+                @${annotation.java.canonicalName}
+                abstract suspend fun foo(user: User): $type
+                """
+            ) { _, invocation ->
+                invocation.assertCompilationResult {
+                    val rawTypeName = type.substringBefore("<")
+                    hasErrorContaining(ProcessorErrors.suspendReturnsDeferredType(rawTypeName))
+                }
+            }
+        }
+    }
+
+    @Test
     fun targetEntity() {
         val usernameSource = Source.java(
             "foo.bar.Username",
@@ -621,4 +672,46 @@
             handler(processed, invocation)
         }
     }
+
+    fun singleShortcutMethodKotlin(
+        vararg input: String,
+        additionalSources: List<Source> = emptyList(),
+        handler: (T, XTestInvocation) -> Unit
+    ) {
+        val inputSource = Source.kotlin(
+            "MyClass.kt",
+            DAO_PREFIX_KT + input.joinToString("\n") + DAO_SUFFIX
+        )
+
+        val commonSources = listOf(
+            COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
+            COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX2_FLOWABLE, COMMON.RX2_OBSERVABLE,
+            COMMON.RX3_COMPLETABLE, COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.RX3_FLOWABLE,
+            COMMON.RX3_OBSERVABLE, COMMON.LISTENABLE_FUTURE, COMMON.LIVE_DATA,
+            COMMON.COMPUTABLE_LIVE_DATA, COMMON.PUBLISHER, COMMON.FLOW, COMMON.GUAVA_ROOM
+        )
+
+        runProcessorTest(
+            sources = commonSources + additionalSources + inputSource
+        ) { invocation ->
+            val (owner, methods) = invocation.roundEnv
+                .getElementsAnnotatedWith(Dao::class.qualifiedName!!)
+                .filterIsInstance<XTypeElement>()
+                .map {
+                    Pair(
+                        it,
+                        it.getAllMethods().filter {
+                            it.hasAnnotation(annotation)
+                        }.toList()
+                    )
+                }.first { it.second.isNotEmpty() }
+
+            val processed = process(
+                baseContext = invocation.context,
+                containing = owner.type,
+                executableElement = methods.first()
+            )
+            handler(processed, invocation)
+        }
+    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
index 1dc88b9..4d72db8 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TransactionMethodProcessorTest.kt
@@ -42,6 +42,7 @@
                 import java.util.*;
                 import androidx.lifecycle.*;
                 import com.google.common.util.concurrent.*;
+                import org.reactivestreams.*;
                 @Dao
                 abstract class MyClass {
                 """
@@ -133,6 +134,25 @@
     }
 
     @Test
+    fun deferredReturnType_computableLiveData() {
+        singleTransactionMethod(
+            """
+                @Transaction
+                public ComputableLiveData<String> doInTransaction(int param) { return null; }
+                """
+        ) { transaction, invocation ->
+            assertThat(transaction.jvmName, `is`("doInTransaction"))
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.transactionMethodAsync(
+                        "androidx.lifecycle.ComputableLiveData"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun deferredReturnType_rx2_flowable() {
         singleTransactionMethod(
             """
@@ -271,6 +291,25 @@
         }
     }
 
+    @Test
+    fun deferredReturnType_publisher() {
+        singleTransactionMethod(
+            """
+                @Transaction
+                public Publisher<String> doInTransaction(int param) { return null; }
+                """
+        ) { transaction, invocation ->
+            assertThat(transaction.jvmName, `is`("doInTransaction"))
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.transactionMethodAsync(
+                        "org.reactivestreams.Publisher"
+                    )
+                )
+            }
+        }
+    }
+
     private val TransactionMethod.jvmName: String
         get() = element.jvmName
 
@@ -285,8 +324,8 @@
             )
         )
         val otherSources = listOf(
-            COMMON.LIVE_DATA, COMMON.RX2_FLOWABLE, COMMON.PUBLISHER, COMMON.RX2_COMPLETABLE,
-            COMMON.RX2_SINGLE, COMMON.RX3_FLOWABLE, COMMON.RX3_COMPLETABLE,
+            COMMON.LIVE_DATA, COMMON.COMPUTABLE_LIVE_DATA, COMMON.RX2_FLOWABLE, COMMON.PUBLISHER,
+            COMMON.RX2_COMPLETABLE, COMMON.RX2_SINGLE, COMMON.RX3_FLOWABLE, COMMON.RX3_COMPLETABLE,
             COMMON.RX3_SINGLE, COMMON.LISTENABLE_FUTURE, COMMON.FLOW
         )
         runProcessorTest(