[go: nahoru, domu]

Add XMethodType.typeVariables APIs

Also deprecate XMethodType.typeVariableNames since it is replaceable with XMethodType.typeVariables along with `asTypeName().toJavaPoet()`.

KSP does not model type variables as Javac does, it only models declaration (element), and through a KSTypeReference it links to the type var, therefore the implementation of the API uses as bare-bone implementation of XType instead of inheriting from KspType.

Bug: 247851395
Test: ./gradlew room:room-compiler-processing:test
Change-Id: I0a14d9688ac167d3f8582385c9829bfbdcb8a660
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index 08e61c1..61bca7a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -22,6 +22,7 @@
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.TypeSpec
 import com.squareup.kotlinpoet.javapoet.JClassName
+import com.squareup.kotlinpoet.javapoet.JTypeVariableName
 import java.lang.Character.isISOControl
 import javax.lang.model.SourceVersion
 import javax.lang.model.element.Modifier
@@ -183,7 +184,7 @@
     ): MethodSpec.Builder {
         return MethodSpec.methodBuilder(executableElement.jvmName).apply {
             addTypeVariables(
-                resolvedType.typeVariableNames
+                resolvedType.typeVariables.map { it.asTypeName().java as JTypeVariableName }
             )
             resolvedType.parameterTypes.forEachIndexed { index, paramType ->
                 addParameter(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMethodType.kt
index 9fb09bc..6647d68 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMethodType.kt
@@ -30,9 +30,18 @@
      */
     val returnType: XType
 
+    val typeVariables: List<XTypeVariableType>
+
     /**
      * Returns the names of [TypeVariableName]s for this executable.
      */
+    @Deprecated(
+        message = "Use typeVariables property and convert to JavaPoet names.",
+        replaceWith = ReplaceWith(
+            expression = "typeVariables.map { it.asTypeName().toJavaPoet() }",
+            imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+        )
+    )
     val typeVariableNames: List<TypeVariableName>
 }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index 9ad98bf..5933211 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -49,7 +49,7 @@
     fun asTypeName(): XTypeName
 
     /**
-     * Returns the rawType of this type. (e.g. `List<String>` to `List`.
+     * Returns the rawType of this type. (e.g. `List<String>` to `List`).
      */
     val rawType: XRawType
 
@@ -72,8 +72,8 @@
     /**
      * The [XTypeElement] that represents this type.
      *
-     * Note that it might be null if the type is not backed by a type element (e.g. if it is a
-     * primitive, wildcard etc)
+     * Note that it will be null if the type is not backed by a type element (e.g. it is a
+     * primitive, type variable, wildcard, etc)
      *
      * @see isTypeElement
      */
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/DefaultJavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/DefaultJavacType.kt
index f829a4b..d4074a9 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/DefaultJavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/DefaultJavacType.kt
@@ -19,7 +19,8 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
-import androidx.room.compiler.processing.javac.kotlin.nullability
+import com.google.auto.common.MoreTypes
+import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
 
 /**
@@ -76,6 +77,30 @@
          */
         get() = emptyList()
 
+    override fun boxed(): JavacType {
+        return when {
+            typeMirror.kind.isPrimitive -> {
+                env.wrap(
+                    typeMirror =
+                    env.typeUtils.boxedClass(MoreTypes.asPrimitiveType(typeMirror)).asType(),
+                    kotlinType = kotlinType,
+                    elementNullability = XNullability.NULLABLE
+                )
+            }
+            typeMirror.kind == TypeKind.VOID -> {
+                env.wrap(
+                    typeMirror =
+                    env.elementUtils.getTypeElement("java.lang.Void").asType(),
+                    kotlinType = kotlinType,
+                    elementNullability = XNullability.NULLABLE
+                )
+            }
+            else -> {
+                this
+            }
+        }
+    }
+
     override fun copyWithNullability(nullability: XNullability): JavacType {
         return DefaultJavacType(
             env = env,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
index e48dd79..fd384c2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacArrayType.kt
@@ -22,7 +22,6 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
-import androidx.room.compiler.processing.javac.kotlin.nullability
 import javax.lang.model.type.ArrayType
 
 internal class JavacArrayType private constructor(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacDeclaredType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacDeclaredType.kt
index a00819f..8a3f7b9 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacDeclaredType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacDeclaredType.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
-import androidx.room.compiler.processing.javac.kotlin.nullability
 import javax.lang.model.type.DeclaredType
 
 /**
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodType.kt
index 54287bb..9d4cee8 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodType.kt
@@ -42,12 +42,23 @@
         )
     }
 
-    override val typeVariableNames by lazy {
-        executableType.typeVariables.map {
-            TypeVariableName.get(it)
+    override val typeVariables: List<JavacTypeVariableType> by lazy {
+        executableType.typeVariables.mapIndexed { index, typeVariable ->
+            env.wrap(typeVariable, element.kotlinMetadata?.typeParameters?.get(index))
         }
     }
 
+    @Deprecated(
+        "Use typeVariables property and convert to JavaPoet names.",
+        replaceWith = ReplaceWith(
+            "typeVariables.map { it.asTypeName().toJavaPoet() }",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
+    override val typeVariableNames by lazy {
+        typeVariables.map { it.asTypeName().java as TypeVariableName }
+    }
+
     private class NormalMethodType(
         env: JavacProcessingEnv,
         element: JavacMethodElement,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index cec6636..89bfdb6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -24,6 +24,7 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
+import androidx.room.compiler.processing.javac.kotlin.KmTypeParameterContainer
 import com.google.auto.common.GeneratedAnnotations
 import com.google.auto.common.MoreTypes
 import java.util.Locale
@@ -36,6 +37,7 @@
 import javax.lang.model.element.VariableElement
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
 import javax.lang.model.util.Elements
 import javax.lang.model.util.Types
 
@@ -163,6 +165,27 @@
 
     fun wrapTypeElement(element: TypeElement) = typeElementStore[element]
 
+    fun wrap(
+        typeMirror: TypeVariable,
+        kotlinType: KmTypeParameterContainer?,
+    ): JavacTypeVariableType {
+        return when {
+            kotlinType != null -> {
+                JavacTypeVariableType(
+                    env = this,
+                    typeMirror = MoreTypes.asTypeVariable(typeMirror),
+                    kotlinType = kotlinType
+                )
+            }
+            else -> {
+                JavacTypeVariableType(
+                    env = this,
+                    typeMirror = MoreTypes.asTypeVariable(typeMirror)
+                )
+            }
+        }
+    }
+
     /**
      * Wraps the given java processing type into an XType.
      *
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 89420be..2d66b79 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -24,6 +24,7 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.javac.kotlin.KmBaseTypeContainer
 import androidx.room.compiler.processing.javac.kotlin.KmClassContainer
 import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
 import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME
@@ -42,7 +43,7 @@
 ) : XType, XEquality, InternalXAnnotated {
 
     // Kotlin type information about the type if this type is driven from Kotlin code.
-    abstract val kotlinType: KmTypeContainer?
+    abstract val kotlinType: KmBaseTypeContainer?
 
     override val rawType: XRawType by lazy {
         JavacRawType(env, this)
@@ -107,12 +108,12 @@
     }
 
     override fun getAllAnnotations(): List<XAnnotation> {
-        return kotlinType?.annotations?.map {
+        return (kotlinType as? KmTypeContainer)?.annotations?.map {
             JavacKmAnnotation(env, it)
         } ?: typeMirror.annotationMirrors.map { mirror -> JavacAnnotation(env, mirror) }
-                .flatMap { annotation ->
-                    annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
-                }
+            .flatMap { annotation ->
+                annotation.unwrapRepeatedAnnotationsFromContainer() ?: listOf(annotation)
+            }
     }
 
     override fun hasAnnotationWithPackage(pkg: String): Boolean {
@@ -142,26 +143,7 @@
     }
 
     override fun boxed(): JavacType {
-        return when {
-            typeMirror.kind.isPrimitive -> {
-                env.wrap(
-                    typeMirror = env.typeUtils.boxedClass(MoreTypes.asPrimitiveType(typeMirror))
-                        .asType(),
-                    kotlinType = kotlinType,
-                    elementNullability = XNullability.NULLABLE
-                )
-            }
-            typeMirror.kind == TypeKind.VOID -> {
-                env.wrap(
-                    typeMirror = env.elementUtils.getTypeElement("java.lang.Void").asType(),
-                    kotlinType = kotlinType,
-                    elementNullability = XNullability.NULLABLE
-                )
-            }
-            else -> {
-                this
-            }
-        }
+        return this
     }
 
     override fun isNone() = typeMirror.kind == TypeKind.NONE
@@ -174,7 +156,7 @@
         return typeMirror.extendsBound()?.let {
             env.wrap<JavacType>(
                 typeMirror = it,
-                kotlinType = kotlinType?.extendsBound,
+                kotlinType = (kotlinType as? KmTypeContainer)?.extendsBound,
                 elementNullability = maybeNullability
             )
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt
index 98a3703..a0dcb17 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt
@@ -19,8 +19,7 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeVariableType
-import androidx.room.compiler.processing.javac.kotlin.KmTypeContainer
-import androidx.room.compiler.processing.javac.kotlin.nullability
+import androidx.room.compiler.processing.javac.kotlin.KmBaseTypeContainer
 import com.google.auto.common.MoreTypes.asIntersection
 import javax.lang.model.type.TypeKind
 import javax.lang.model.type.TypeVariable
@@ -29,7 +28,7 @@
     env: JavacProcessingEnv,
     override val typeMirror: TypeVariable,
     nullability: XNullability?,
-    override val kotlinType: KmTypeContainer?
+    override val kotlinType: KmBaseTypeContainer?
 ) : JavacType(env, typeMirror, nullability), XTypeVariableType {
     constructor(
         env: JavacProcessingEnv,
@@ -44,7 +43,7 @@
     constructor(
         env: JavacProcessingEnv,
         typeMirror: TypeVariable,
-        kotlinType: KmTypeContainer
+        kotlinType: KmBaseTypeContainer
     ) : this(
         env = env,
         typeMirror = typeMirror,
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 7d93ec0..48b5336 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
@@ -51,6 +51,12 @@
     val flags: Flags
 }
 
+internal interface KmBaseTypeContainer : KmFlags {
+    val upperBounds: List<KmTypeContainer>
+    val nullability: XNullability
+    fun isNullable() = Flag.Type.IS_NULLABLE(flags)
+}
+
 internal class KmClassContainer(
     private val kmClass: KmClass
 ) : KmFlags {
@@ -286,9 +292,9 @@
     val typeArguments: List<KmTypeContainer>,
     /** The extends bounds are only non-null for wildcard (i.e. in/out variant) types. */
     val extendsBound: KmTypeContainer? = null,
-    /** The upper bounds are only non-null for type variable types with upper bounds. */
-    val upperBounds: List<KmTypeContainer>? = null
-) : KmFlags {
+    /** The upper bounds are only non-empty for type variable types with upper bounds. */
+    override val upperBounds: List<KmTypeContainer> = emptyList()
+) : KmBaseTypeContainer {
     override val flags: Flags
         get() = kmType.flags
 
@@ -303,15 +309,17 @@
 
     fun isExtensionType() =
         kmType.annotations.any { it.className == "kotlin/ExtensionFunctionType" }
-    fun isNullable() = Flag.Type.IS_NULLABLE(flags)
 
     fun erasure(): KmTypeContainer = KmTypeContainer(
         kmType = kmType,
         typeArguments = emptyList(),
         extendsBound = extendsBound?.erasure(),
         // The erasure of a type variable is equal to the erasure of the first upper bound.
-        upperBounds = upperBounds?.firstOrNull()?.erasure()?.let { listOf(it) },
+        upperBounds = upperBounds.firstOrNull()?.erasure()?.let { listOf(it) } ?: emptyList(),
     )
+
+    override val nullability: XNullability
+        get() = computeTypeNullability(this.isNullable(), this.upperBounds, this.extendsBound)
 }
 
 internal class KmAnnotationContainer(private val kmAnnotation: KmAnnotation) {
@@ -357,26 +365,17 @@
     }
 }
 
-internal val KmTypeContainer.nullability: XNullability
-    get() = if (isNullable()) {
-        XNullability.NULLABLE
-    } else {
-        // if there is an upper bound information, use its nullability (e.g. it might be T : Foo?)
-        if (upperBounds?.all { it.nullability == XNullability.NULLABLE } == true) {
-            XNullability.NULLABLE
-        } else {
-            extendsBound?.nullability ?: XNullability.NONNULL
-        }
-    }
-
 internal class KmTypeParameterContainer(
     private val kmTypeParameter: KmTypeParameter,
-    val upperBounds: List<KmTypeContainer>
-) : KmFlags {
+    override val upperBounds: List<KmTypeContainer>
+) : KmBaseTypeContainer {
     override val flags: Flags
         get() = kmTypeParameter.flags
     val name: String
         get() = kmTypeParameter.name
+
+    override val nullability: XNullability
+        get() = computeTypeNullability(this.isNullable(), this.upperBounds, null)
 }
 
 internal class KmValueParameterContainer(
@@ -392,6 +391,21 @@
     fun hasDefault() = Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
 }
 
+private fun computeTypeNullability(
+    isNullable: Boolean,
+    upperBounds: List<KmTypeContainer>,
+    extendsBound: KmTypeContainer?
+): XNullability {
+    if (isNullable) {
+        return XNullability.NULLABLE
+    }
+    // if there is an upper bound information, use its nullability (e.g. it might be T : Foo?)
+    if (upperBounds.isNotEmpty() && upperBounds.all { it.nullability == XNullability.NULLABLE }) {
+        return XNullability.NULLABLE
+    }
+    return extendsBound?.nullability ?: XNullability.NONNULL
+}
+
 private fun KmFunction.asContainer(): KmFunctionContainer =
     KmFunctionContainerImpl(
         kmFunction = this,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
index 600bb01..04bd525 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.processing.XMethodType
 import androidx.room.compiler.processing.XSuspendMethodType
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeVariableType
 import com.squareup.javapoet.TypeVariableName
 
 internal sealed class KspMethodType(
@@ -26,18 +27,24 @@
     override val origin: KspMethodElement,
     containing: KspType?
 ) : KspExecutableType(env, origin, containing), XMethodType {
-    override val typeVariableNames: List<TypeVariableName> by lazy {
+
+    override val typeVariables: List<XTypeVariableType> by lazy {
         origin.declaration.typeParameters.map {
-            val typeParameterBounds = it.bounds.map {
-                it.asJTypeName(env.resolver)
-            }.toList().toTypedArray()
-            TypeVariableName.get(
-                it.name.asString(),
-                *typeParameterBounds
-            )
+            KspMethodTypeVariableType(env, it)
         }
     }
 
+    @Deprecated(
+        "Use typeVariables property and convert to JavaPoet names.",
+        replaceWith = ReplaceWith(
+            "typeVariables.map { it.asTypeName().toJavaPoet() }",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
+    override val typeVariableNames: List<TypeVariableName> by lazy {
+        typeVariables.map { it.asTypeName().java as TypeVariableName }
+    }
+
     private class KspNormalMethodType(
         env: KspProcessingEnv,
         origin: KspMethodElement,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt
new file mode 100644
index 0000000..5a9d520
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodTypeVariableType.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp
+
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.processing.XEquality
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XRawType
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.XTypeVariableType
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.squareup.javapoet.TypeName
+import kotlin.reflect.KClass
+
+/**
+ * A KSP [XType] representing the type var type declared in a function.
+ *
+ * This class is different than [KspTypeVariableType] due to KSP having no type var model and only
+ * type var declaration (i.e. [KSTypeParameter]).
+ */
+internal class KspMethodTypeVariableType(
+    env: KspProcessingEnv,
+    val ksTypeVariable: KSTypeParameter,
+) : KspAnnotated(env), XTypeVariableType, XEquality {
+
+    override val typeName: TypeName by lazy {
+        xTypeName.java
+    }
+
+    override fun asTypeName() = xTypeName
+
+    private val xTypeName: XTypeName by lazy {
+        XTypeName(
+            ksTypeVariable.asJTypeName(env.resolver),
+            ksTypeVariable.asKTypeName(env.resolver),
+            nullability
+        )
+    }
+
+    override val upperBounds: List<XType> = ksTypeVariable.bounds.map(env::wrap).toList()
+
+    override fun annotations(): Sequence<KSAnnotation> {
+        return ksTypeVariable.annotations
+    }
+
+    override val rawType: XRawType by lazy {
+        object : XRawType {
+            override val typeName: TypeName
+                get() = this@KspMethodTypeVariableType.typeName
+
+            override fun asTypeName(): XTypeName =
+                this@KspMethodTypeVariableType.asTypeName()
+
+            override fun isAssignableFrom(other: XRawType): Boolean {
+                return this.typeName == other.typeName
+            }
+        }
+    }
+
+    override val nullability: XNullability
+        get() = XNullability.UNKNOWN
+
+    override val superTypes: List<XType> by lazy {
+        val anyType = env.requireType(XTypeName.ANY_OBJECT).makeNullable()
+        if (upperBounds.size == 1 && upperBounds.single() == anyType) {
+            upperBounds
+        } else {
+            listOf(anyType) + upperBounds
+        }
+    }
+
+    override val typeElement: XTypeElement?
+        get() = null
+
+    override val typeArguments: List<XType>
+        get() = emptyList()
+
+    override fun isAssignableFrom(other: XType): Boolean {
+        val typeVar = when (other) {
+            is KspTypeVariableType -> other.ksTypeVariable
+            is KspMethodTypeVariableType -> other.ksTypeVariable
+            else -> null
+        }
+        return ksTypeVariable == typeVar
+    }
+
+    override fun isError(): Boolean {
+        return false
+    }
+
+    override fun defaultValue(): String {
+        return "null"
+    }
+
+    override fun boxed(): KspMethodTypeVariableType {
+        return this
+    }
+
+    override fun isNone(): Boolean {
+        return false
+    }
+
+    override fun isTypeOf(other: KClass<*>): Boolean {
+        return false
+    }
+
+    override fun isSameType(other: XType): Boolean {
+        val typeVar = when (other) {
+            is KspTypeVariableType -> other.ksTypeVariable
+            is KspMethodTypeVariableType -> other.ksTypeVariable
+            else -> null
+        }
+        return ksTypeVariable == typeVar
+    }
+
+    override fun extendsBound(): XType? {
+        return null
+    }
+
+    override fun makeNullable(): XType {
+        return this
+    }
+
+    override fun makeNonNullable(): XType {
+        return this
+    }
+
+    override val equalityItems: Array<out Any?> by lazy {
+        arrayOf(ksTypeVariable)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return XEquality.equals(this, other)
+    }
+
+    override fun hashCode(): Int {
+        return XEquality.hashCode(equalityItems)
+    }
+
+    override fun toString(): String {
+        return ksTypeVariable.toString()
+    }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index e17e57f3..0eff801 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -269,7 +269,7 @@
         }
         val qName = ksType.declaration.qualifiedName?.asString()
         if (declaration is KSTypeParameter) {
-            return KspTypeVariableType(this, ksType, originalAnnotations)
+            return KspTypeVariableType(this, declaration, ksType, originalAnnotations)
         }
         if (allowPrimitives && qName != null && ksType.nullability == Nullability.NOT_NULL) {
             // check for primitives
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt
index 04ea9f7..33f7600 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt
@@ -24,30 +24,30 @@
 import com.squareup.kotlinpoet.javapoet.JTypeName
 import com.squareup.kotlinpoet.javapoet.KTypeName
 
+/**
+ * An [XType] representing the type var type in a function parameter, return type or class
+ * declaration.
+ *
+ * This is different than [KspMethodTypeVariableType] because the [KSType] has as reference the
+ * [KSTypeParameter] declaration.
+ */
 internal class KspTypeVariableType(
     env: KspProcessingEnv,
+    val ksTypeVariable: KSTypeParameter,
     ksType: KSType,
-    originalKSAnnotations: Sequence<KSAnnotation> = ksType.annotations,
+    originalKSAnnotations: Sequence<KSAnnotation> = ksTypeVariable.annotations,
     scope: KSTypeVarianceResolverScope? = null,
 ) : KspType(env, ksType, originalKSAnnotations, scope, null), XTypeVariableType {
-    private val typeVariable: KSTypeParameter by lazy {
-        // Note: This is a workaround for a bug in KSP where we may get ERROR_TYPE in the bounds
-        // (https://github.com/google/ksp/issues/1250). To work around it we get the matching
-        // KSTypeParameter from the parent declaration instead.
-        ksType.declaration.parentDeclaration!!.typeParameters
-            .filter { it.name == (ksType.declaration as KSTypeParameter).name }
-            .single()
-    }
 
     override fun resolveJTypeName(): JTypeName {
-        return typeVariable.asJTypeName(env.resolver)
+        return ksTypeVariable.asJTypeName(env.resolver)
     }
 
     override fun resolveKTypeName(): KTypeName {
-        return typeVariable.asKTypeName(env.resolver)
+        return ksTypeVariable.asKTypeName(env.resolver)
     }
 
-    override val upperBounds: List<XType> = typeVariable.bounds.map(env::wrap).toList()
+    override val upperBounds: List<XType> = ksTypeVariable.bounds.map(env::wrap).toList()
 
     override fun boxed(): KspTypeVariableType {
         return this
@@ -59,5 +59,15 @@
         originalKSAnnotations: Sequence<KSAnnotation>,
         scope: KSTypeVarianceResolverScope?,
         typeAlias: KSType?
-    ) = KspTypeVariableType(env, ksType, originalKSAnnotations, scope)
+    ) = KspTypeVariableType(
+        env,
+        ksType.declaration as KSTypeParameter,
+        ksType,
+        originalKSAnnotations,
+        scope
+    )
+
+    override val equalityItems: Array<out Any?> by lazy {
+        arrayOf(ksTypeVariable)
+    }
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt
index 7341b8a..c14bec0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodType.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.processing.XExecutableType
 import androidx.room.compiler.processing.XMethodType
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeVariableType
 import androidx.room.compiler.processing.ksp.KSTypeVarianceResolverScope
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.KspType
@@ -47,6 +48,16 @@
         }
     }
 
+    override val typeVariables: List<XTypeVariableType>
+        get() = emptyList()
+
+    @Deprecated(
+        "Use typeVariables property and convert to JavaPoet names.",
+        replaceWith = ReplaceWith(
+            "typeVariables.map { it.asTypeName().toJavaPoet() }",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
     override val typeVariableNames: List<TypeVariableName>
         get() = emptyList()
 
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
index 95adf50..4d9dcea 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableTypeTest.kt
@@ -17,10 +17,13 @@
 package androidx.room.compiler.processing
 
 import androidx.kruth.assertThat
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.asMutableClassName
 import androidx.room.compiler.processing.util.CONTINUATION_JCLASS_NAME
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.UNIT_JCLASS_NAME
 import androidx.room.compiler.processing.util.getMethodByJvmName
+import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth
@@ -458,7 +461,7 @@
                 }
 
                 assertThat(method.parameterTypes).isEmpty()
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
             checkMethods("getMutableT", subject) { method ->
                 assertThat(method.returnType.typeName).isEqualTo(String::class.typeName())
@@ -468,7 +471,7 @@
                     assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
                 }
                 assertThat(method.parameterTypes).isEmpty()
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
             checkMethods("setMutableT", subject) { method ->
                 assertThat(method.returnType.typeName).isEqualTo(TypeName.VOID)
@@ -476,7 +479,7 @@
                     .isEqualTo(XNullability.NULLABLE)
                 assertThat(method.parameterTypes.first().typeName)
                     .isEqualTo(String::class.typeName())
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
             checkMethods("getList", subject) { method ->
                 assertThat(method.returnType.typeName).isEqualTo(
@@ -509,7 +512,7 @@
                     assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
                 }
                 assertThat(method.parameterTypes).isEmpty()
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
 
             checkMethods("getMutableT", nullableSubject) { method ->
@@ -520,7 +523,7 @@
                     assertThat(method.returnType.nullability).isEqualTo(XNullability.NULLABLE)
                 }
                 assertThat(method.parameterTypes).isEmpty()
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
 
             checkMethods("setMutableT", nullableSubject) { method ->
@@ -529,7 +532,7 @@
                     .isEqualTo(XNullability.NULLABLE)
                 assertThat(method.parameterTypes.first().typeName)
                     .isEqualTo(String::class.typeName())
-                assertThat(method.typeVariableNames).isEmpty()
+                assertThat(method.typeVariables).isEmpty()
             }
 
             checkMethods("getList", nullableSubject) { method ->
@@ -570,4 +573,78 @@
             }
         }
     }
+
+    @Test
+    fun typeVariableTest() {
+        val kotlinSrc = Source.kotlin(
+            "KotlinSubject.kt",
+            """
+            class KotlinSubject {
+              fun <T> oneTypeVar(): Unit = TODO()
+              fun <T : MutableList<*>> oneBoundedTypeVar(): Unit = TODO()
+              fun <A, B> twoTypeVar(param: B): A = TODO()
+            }
+            """.trimIndent()
+        )
+        val javaSrc = Source.java(
+            "JavaSubject",
+            """
+            import java.util.List;
+            class JavaSubject {
+              <T> void oneTypeVar() {}
+              <T extends List<?>> void oneBoundedTypeVar() { }
+              <A, B> A twoTypeVar(B param) { return null; }
+            }
+            """.trimIndent()
+        )
+        runKspTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+            listOf("KotlinSubject", "JavaSubject",).forEach { subjectFqn ->
+                val subject = invocation.processingEnv.requireTypeElement(subjectFqn)
+                subject.getMethodByJvmName("oneTypeVar").let {
+                    val typeVar = it.executableType.typeVariables.single()
+                    assertThat(typeVar.asTypeName())
+                        .isEqualTo(XTypeName.getTypeVariableName("T"))
+                    assertThat(typeVar.superTypes.map { it.asTypeName() })
+                        .containsExactly(XTypeName.ANY_OBJECT.copy(nullable = true))
+                    assertThat(typeVar.typeArguments).isEmpty()
+                    assertThat(typeVar.typeElement).isNull()
+                }
+                subject.getMethodByJvmName("oneBoundedTypeVar").let {
+                    val typeVar = it.executableType.typeVariables.single()
+                    assertThat(typeVar.asTypeName())
+                        .isEqualTo(
+                            XTypeName.getTypeVariableName(
+                                name = "T",
+                                bounds = listOf(
+                                    List::class.asMutableClassName()
+                                        .parametrizedBy(XTypeName.ANY_WILDCARD)
+                                )
+                            )
+                        )
+                    assertThat(typeVar.superTypes.map { it.asTypeName() })
+                        .containsExactly(
+                            XTypeName.ANY_OBJECT.copy(nullable = true),
+                            List::class.asMutableClassName()
+                                .parametrizedBy(XTypeName.ANY_WILDCARD)
+                        )
+                    assertThat(typeVar.typeArguments).isEmpty()
+                    assertThat(typeVar.typeElement).isNull()
+                }
+                subject.getMethodByJvmName("twoTypeVar").let {
+                    // TODO(b/294102849): Figure out origin JAVA bounds difference between type
+                    //  var declaration and usage.
+                    if (invocation.isKsp && subjectFqn == "JavaSubject") {
+                        return@let
+                    }
+                    val firstTypeVar = it.executableType.typeVariables[0]
+                    assertThat(firstTypeVar.isSameType(it.returnType)).isTrue()
+                    assertThat(firstTypeVar).isNotEqualTo(it.parameters.single().type)
+
+                    val secondTypeVar = it.executableType.typeVariables[1].asTypeName()
+                    assertThat(secondTypeVar).isNotEqualTo(it.returnType.asTypeName())
+                    assertThat(secondTypeVar).isEqualTo(it.parameters.single().type.asTypeName())
+                }
+            }
+        }
+    }
 }
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 eb676b6..62cb00e 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
@@ -683,10 +683,10 @@
             val (_, withUpperBounds) = getMetadataElement(invocation, "WithUpperBounds")
             assertThat(withUpperBounds.type.typeArguments).hasSize(2)
             assertThat(withUpperBounds.type.typeArguments[0].upperBounds).hasSize(1)
-            assertThat(withUpperBounds.type.typeArguments[0].upperBounds!![0].isNullable())
+            assertThat(withUpperBounds.type.typeArguments[0].upperBounds[0].isNullable())
                 .isFalse()
             assertThat(withUpperBounds.type.typeArguments[1].upperBounds).hasSize(1)
-            assertThat(withUpperBounds.type.typeArguments[1].upperBounds!![0].isNullable())
+            assertThat(withUpperBounds.type.typeArguments[1].upperBounds[0].isNullable())
                 .isTrue()
 
             val (_, withSuperType) = getMetadataElement(invocation, "WithSuperType")