Merge changes Ibcd0a287,I4a8b5d75,I78936db3 am: 9708180ac0 am: d267cfb1aa am: e8dfa0ca95

Original change: https://android-review.googlesource.com/c/platform/tools/metalava/+/2526700

Change-Id: I5a40fc11ef0dfe20f42f9b1841131d48fc3d71ce
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
index f15bd34..8c7ecc2 100644
--- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -856,7 +856,7 @@
                 if (!method.isConstructor()) {
                     checkTypeReferencesHidden(
                         method,
-                        method.returnType()!!
+                        method.returnType()
                     ) // returnType is nullable only for constructors
                 }
 
@@ -880,7 +880,7 @@
                         method.mutableModifiers().removeAnnotation(it)
                         // Have to also clear the annotation out of the return type itself, if it's a type
                         // use annotation
-                        method.returnType()?.scrubAnnotations()
+                        method.returnType().scrubAnnotations()
                     }
                 }
             }
@@ -987,7 +987,7 @@
                     }
 
                     val returnType = m.returnType()
-                    if (!m.deprecated && !cl.deprecated && returnType != null && returnType.asClass()?.deprecated == true) {
+                    if (!m.deprecated && !cl.deprecated && returnType.asClass()?.deprecated == true) {
                         reporter.report(
                             Issues.REFERENCES_DEPRECATED, m,
                             "Return type of deprecated type $returnType in ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated"
@@ -996,7 +996,7 @@
 
                     var hiddenClass = findHiddenClasses(returnType, stubImportPackages)
                     if (hiddenClass != null && !hiddenClass.isFromClassPath()) {
-                        if (hiddenClass.qualifiedName() == returnType?.asClass()?.qualifiedName()) {
+                        if (hiddenClass.qualifiedName() == returnType.asClass()?.qualifiedName()) {
                             // Return type is hidden
                             reporter.report(
                                 Issues.UNAVAILABLE_SYMBOL, m,
@@ -1043,7 +1043,7 @@
                     }
 
                     val t = m.returnType()
-                    if (t != null && !t.primitive && !m.deprecated && !cl.deprecated && t.asClass()?.deprecated == true) {
+                    if (!t.primitive && !m.deprecated && !cl.deprecated && t.asClass()?.deprecated == true) {
                         reporter.report(
                             Issues.REFERENCES_DEPRECATED, m,
                             "Returning deprecated type $t from ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated"
@@ -1234,7 +1234,7 @@
                 cantStripThis(thrown, filter, notStrippable, stubImportPackages, method, "as exception")
             }
             val returnType = method.returnType()
-            if (returnType != null && !returnType.primitive) {
+            if (!returnType.primitive) {
                 val returnTypeClass = returnType.asClass()
                 if (returnTypeClass != null) {
                     cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages, method, "as return type")
diff --git a/src/main/java/com/android/tools/metalava/ApiLint.kt b/src/main/java/com/android/tools/metalava/ApiLint.kt
index 06c963e..3d27d1e 100644
--- a/src/main/java/com/android/tools/metalava/ApiLint.kt
+++ b/src/main/java/com/android/tools/metalava/ApiLint.kt
@@ -241,11 +241,9 @@
     override fun visitMethod(method: MethodItem) {
         checkMethod(method, filterReference)
         val returnType = method.returnType()
-        if (returnType != null) {
-            checkType(returnType, method)
-            checkNullableCollections(returnType, method)
-            checkMethodSuffixListenableFutureReturn(returnType, method)
-        }
+        checkType(returnType, method)
+        checkNullableCollections(returnType, method)
+        checkMethodSuffixListenableFutureReturn(returnType, method)
         for (parameter in method.parameters()) {
             checkType(parameter.type(), parameter)
         }
@@ -577,7 +575,7 @@
             method.parameters().size == 1 &&
                 method.name().startsWith("on") &&
                 !method.parameters().first().type().primitive &&
-                method.returnType()?.toTypeString() == Void.TYPE.name
+                method.returnType().toTypeString() == Void.TYPE.name
 
         if (!methods.all(::isSingleParamCallbackMethod)) return
 
@@ -915,7 +913,7 @@
     }
 
     private fun checkIntentBuilder(method: MethodItem) {
-        if (method.returnType()?.toTypeString() == "android.content.Intent") {
+        if (method.returnType().toTypeString() == "android.content.Intent") {
             val name = method.name()
             if (name.startsWith("create") && name.endsWith("Intent")) {
                 return
@@ -1052,27 +1050,25 @@
                 )
             } else if (name.startsWith("set") || name.startsWith("add") || name.startsWith("clear")) {
                 val returnType = method.returnType()
-                if (returnType != null) {
-                    val returnsClassType = if (
-                        returnType is PsiTypeItem && clsType is PsiTypeItem
-                    ) {
-                        clsType.isAssignableFromWithoutUnboxing(returnType)
-                    } else {
-                        // fallback to a limited text based check
-                        val returnTypeBounds = returnType
-                            .asTypeParameter(context = method)
-                            ?.typeBounds()?.map {
-                                it.toTypeString()
-                            } ?: emptyList()
-                        returnTypeBounds.contains(clsType.toTypeString()) || returnType == clsType
-                    }
-                    if (!returnsClassType) {
-                        report(
-                            SETTER_RETURNS_THIS, method,
-                            "Methods must return the builder object (return type " +
-                                "$clsType instead of $returnType): ${method.describe()}"
-                        )
-                    }
+                val returnsClassType = if (
+                    returnType is PsiTypeItem && clsType is PsiTypeItem
+                ) {
+                    clsType.isAssignableFromWithoutUnboxing(returnType)
+                } else {
+                    // fallback to a limited text based check
+                    val returnTypeBounds = returnType
+                        .asTypeParameter(context = method)
+                        ?.typeBounds()?.map {
+                            it.toTypeString()
+                        } ?: emptyList()
+                    returnTypeBounds.contains(clsType.toTypeString()) || returnType == clsType
+                }
+                if (!returnsClassType) {
+                    report(
+                        SETTER_RETURNS_THIS, method,
+                        "Methods must return the builder object (return type " +
+                            "$clsType instead of $returnType): ${method.describe()}"
+                    )
                 }
 
                 if (method.modifiers.isNullable()) {
@@ -1245,16 +1241,14 @@
 
         for (method in methodsAndConstructors) {
             val returnType = method.returnType()
-            if (returnType != null) { // not a constructor
-                val returnTypeRank = getTypeRank(returnType)
-                if (returnTypeRank != -1 && returnTypeRank < classRank) {
-                    report(
-                        PACKAGE_LAYERING, cls,
-                        "Method return type `${returnType.toTypeString()}` violates package layering: nothing in `$classPackage` should depend on `${getTypePackage(
-                            returnType
-                        )}`"
-                    )
-                }
+            val returnTypeRank = getTypeRank(returnType)
+            if (returnTypeRank != -1 && returnTypeRank < classRank) {
+                report(
+                    PACKAGE_LAYERING, cls,
+                    "Method return type `${returnType.toTypeString()}` violates package layering: nothing in `$classPackage` should depend on `${getTypePackage(
+                        returnType
+                    )}`"
+                )
             }
 
             for (parameter in method.parameters()) {
@@ -1303,7 +1297,7 @@
         }
 
         fun isGetter(method: MethodItem): Boolean {
-            val returnType = method.returnType() ?: return false
+            val returnType = method.returnType()
             return method.parameters().isEmpty() && returnType.primitive && returnType.toTypeString() == "boolean"
         }
 
@@ -1544,7 +1538,7 @@
             )
         }
         for (method in methods) {
-            if (method.returnType()?.asClass() == cls) {
+            if (method.returnType().asClass() == cls) {
                 report(
                     MANAGER_LOOKUP, method,
                     "Managers must always be obtained from Context (`${method.name()}`)"
@@ -1597,7 +1591,7 @@
                 is MethodItem -> {
                     // For methods requiresNullnessInfo and hasNullnessInfo considers both parameters and return,
                     // only warn about non-annotated returns here as parameters will get visited individually.
-                    if (item.isConstructor() || item.returnType()?.primitive == true) return
+                    if (item.isConstructor() || item.returnType().primitive) return
                     if (item.modifiers.hasNullnessInfo()) return
                     "method `${item.name()}` return"
                 }
@@ -1634,9 +1628,8 @@
 
     private fun anySuperMethodIsNonNull(method: MethodItem): Boolean {
         return method.superMethods().any { superMethod ->
-            superMethod.modifiers.isNonNull() &&
-                // Disable check for generics
-                superMethod.returnType()?.isTypeParameter() != true
+            // Disable check for generics
+            superMethod.modifiers.isNonNull() && !superMethod.returnType().isTypeParameter()
         }
     }
 
@@ -1656,9 +1649,8 @@
 
     private fun anySuperMethodLacksNullnessInfo(method: MethodItem): Boolean {
         return method.superMethods().any { superMethod ->
-            !superMethod.hasNullnessInfo() &&
-                // Disable check for generics
-                superMethod.returnType()?.isTypeParameter() != true
+            // Disable check for generics
+            !superMethod.hasNullnessInfo() && !superMethod.returnType().isTypeParameter()
         }
     }
 
@@ -2124,7 +2116,7 @@
             return
         }
         for (method in methods) {
-            val returnType = method.returnType() ?: continue
+            val returnType = method.returnType()
             if (returnType.primitive) {
                 return
             }
@@ -2164,7 +2156,7 @@
     }
 
     private fun checkUnits(method: MethodItem) {
-        val returnType = method.returnType() ?: return
+        val returnType = method.returnType()
         var type = returnType.toTypeString()
         val name = method.name()
         if (type == "int" || type == "long" || type == "short") {
@@ -2254,7 +2246,7 @@
                 }
                 // https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
                 "inc", "dec" -> {
-                    if (method.parameters().isEmpty() && method.returnType()?.toTypeString() != "void") {
+                    if (method.parameters().isEmpty() && method.returnType().toTypeString() != "void") {
                         flagKotlinOperator(
                             method, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin: `$name`"
                         )
@@ -2272,7 +2264,7 @@
                     if (methods.any {
                         it.name() == assignName &&
                             it.parameters().size == 1 &&
-                            it.returnType()?.toTypeString() == "void"
+                            it.returnType().toTypeString() == "void"
                     }
                     ) {
                         report(
@@ -2283,7 +2275,7 @@
                 }
                 // https://kotlinlang.org/docs/reference/operator-overloading.html#in
                 "contains" -> {
-                    if (method.parameters().size == 1 && method.returnType()?.toTypeString() == "boolean") {
+                    if (method.parameters().size == 1 && method.returnType().toTypeString() == "boolean") {
                         flagKotlinOperator(
                             method, "Method can be invoked as a \"in\" operator from Kotlin: `$name`"
                         )
@@ -2315,7 +2307,7 @@
                 }
                 // https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
                 "plusAssign", "minusAssign", "timesAssign", "divAssign", "remAssign", "modAssign" -> {
-                    if (method.parameters().size == 1 && method.returnType()?.toTypeString() == "void") {
+                    if (method.parameters().size == 1 && method.returnType().toTypeString() == "void") {
                         flagKotlinOperator(
                             method, "Method can be invoked as a compound assignment operator from Kotlin: `$name`"
                         )
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index abf2c7f..89d3645 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -354,7 +354,7 @@
 
         val oldReturnType = old.returnType()
         val newReturnType = new.returnType()
-        if (!new.isConstructor() && oldReturnType != null && newReturnType != null) {
+        if (!new.isConstructor()) {
             val oldTypeParameter = oldReturnType.asTypeParameter(old)
             val newTypeParameter = newReturnType.asTypeParameter(new)
             var compatible = true
diff --git a/src/main/java/com/android/tools/metalava/DexApiWriter.kt b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
index 1142b2e..c2e844e 100644
--- a/src/main/java/com/android/tools/metalava/DexApiWriter.kt
+++ b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
@@ -59,7 +59,7 @@
             writer.print("V")
         } else {
             val returnType = method.returnType()
-            writer.print(returnType?.internalName(method) ?: "V")
+            writer.print(returnType.internalName(method))
         }
         writer.print("\n")
     }
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
index 2ad39c7..4dd3b36 100644
--- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -455,8 +455,8 @@
 
                 if (isConstructor()) {
                     sb.append(escapeXml(containingClass().simpleName()))
-                } else if (returnType() != null) {
-                    sb.append(escapeXml(returnType()!!.toTypeString()))
+                } else {
+                    sb.append(escapeXml(returnType().toTypeString()))
                     sb.append(' ')
                     sb.append(escapeXml(name()))
                 }
diff --git a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
index cb6a114..607b21c 100644
--- a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
+++ b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
@@ -207,7 +207,7 @@
 
         writer.print("<method name=\"")
         writer.print(method.name())
-        method.returnType()?.let {
+        method.returnType().let {
             writer.print("\"\n return=\"")
             writer.print(XmlUtils.toXmlAttributeValue(formatType(it)))
         }
diff --git a/src/main/java/com/android/tools/metalava/NullnessMigration.kt b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
index 77c9a83..3cccb11 100644
--- a/src/main/java/com/android/tools/metalava/NullnessMigration.kt
+++ b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
@@ -50,8 +50,8 @@
     override fun compare(old: MethodItem, new: MethodItem) {
         @Suppress("ConstantConditionIf")
         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
-            val newType = new.returnType() ?: return
-            val oldType = old.returnType() ?: return
+            val newType = new.returnType()
+            val oldType = old.returnType()
             checkType(oldType, newType)
         }
     }
diff --git a/src/main/java/com/android/tools/metalava/model/Item.kt b/src/main/java/com/android/tools/metalava/model/Item.kt
index f309fe4..462403c 100644
--- a/src/main/java/com/android/tools/metalava/model/Item.kt
+++ b/src/main/java/com/android/tools/metalava/model/Item.kt
@@ -299,7 +299,7 @@
             }
             builder.append(' ')
             if (includeReturnValue && !item.isConstructor()) {
-                builder.append(item.returnType()?.toSimpleType())
+                builder.append(item.returnType().toSimpleType())
                 builder.append(' ')
             }
             appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
index e4eb38e..fce301a 100644
--- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -32,8 +32,8 @@
     /** Whether this method is a constructor */
     fun isConstructor(): Boolean
 
-    /** The type of this field, or null for constructors */
-    fun returnType(): TypeItem?
+    /** The type of this field. Returns the containing class for constructors */
+    fun returnType(): TypeItem
 
     /** The list of parameters */
     fun parameters(): List<ParameterItem>
@@ -71,7 +71,7 @@
         }
 
         sb.append(")")
-        sb.append(if (voidConstructorTypes && isConstructor()) "V" else returnType()?.internalName() ?: "V")
+        sb.append(if (voidConstructorTypes && isConstructor()) "V" else returnType().internalName())
         return sb.toString()
     }
 
@@ -214,9 +214,7 @@
 
         if (!isConstructor()) {
             val type = returnType()
-            if (type != null) { // always true when not a constructor
-                visitor.visitType(type, this)
-            }
+            visitor.visitType(type, this)
         }
 
         for (parameter in parameters()) {
@@ -229,9 +227,7 @@
 
         if (!isConstructor()) {
             val type = returnType()
-            if (type != null) {
-                visitor.visitType(type, this)
-            }
+            visitor.visitType(type, this)
         }
     }
 
@@ -369,7 +365,7 @@
         return when {
             modifiers.hasJvmSyntheticAnnotation() -> false
             isConstructor() -> false
-            (returnType()?.primitive != true) -> true
+            (!returnType().primitive) -> true
             parameters().any { !it.type().primitive } -> true
             else -> false
         }
@@ -380,7 +376,7 @@
             return true
         }
 
-        if (!isConstructor() && returnType()?.primitive != true) {
+        if (!isConstructor() && !returnType().primitive) {
             if (!modifiers.hasNullnessInfo()) {
                 return false
             }
@@ -479,16 +475,14 @@
         }
 
         val returnType = returnType()
-        if (returnType != null) {
-            val returnTypeClass = returnType.asClass()
-            if (returnTypeClass != null && !filterReference.test(returnTypeClass)) {
-                return true
-            }
-            if (returnType.hasTypeArguments()) {
-                for (argument in returnType.typeArgumentClasses()) {
-                    if (!filterReference.test(argument)) {
-                        return true
-                    }
+        val returnTypeClass = returnType.asClass()
+        if (returnTypeClass != null && !filterReference.test(returnTypeClass)) {
+            return true
+        }
+        if (returnType.hasTypeArguments()) {
+            for (argument in returnType.typeArgumentClasses()) {
+                if (!filterReference.test(argument)) {
+                    return true
                 }
             }
         }
diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
index 8f93e52..960b471 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -95,6 +95,15 @@
         return s
     }
 
+    /**
+     * Returns the element type if the type is an array or contains a vararg.
+     * If the element is not an array or does not contain a vararg,
+     * returns the original type string.
+     */
+    fun toElementType(): String {
+        return toErasedTypeString().replace("...", "").replace("[]", "")
+    }
+
     val primitive: Boolean
 
     fun typeArgumentClasses(): List<ClassItem>
@@ -140,6 +149,53 @@
     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
 
     /**
+     * If the item has type arguments, return a list of type arguments.
+     * If simplified is true, returns the simplified forms of the type arguments.
+     * e.g. when type arguments are <K, V extends some.arbitrary.Class>, [K, V] will be returned.
+     * If the item does not have any type arguments, return an empty list.
+     */
+    fun typeArguments(simplified: Boolean = false): List<String> {
+        if (!hasTypeArguments()) {
+            return emptyList()
+        }
+        val typeString = toTypeString()
+        val bracketRemovedTypeString = toTypeString().indexOf('<')
+            .let { typeString.substring(it + 1, typeString.length - 1) }
+        val typeArguments = mutableListOf<String>()
+        var builder = StringBuilder()
+        var balance = 0
+        var idx = 0
+        while (idx < bracketRemovedTypeString.length) {
+            when (val s = bracketRemovedTypeString[idx]) {
+                ',' -> {
+                    if (balance == 0) {
+                        typeArguments.add(builder.toString())
+                        builder = StringBuilder()
+                    } else {
+                        builder.append(s)
+                    }
+                }
+                '<' -> {
+                    balance += 1
+                    builder.append(s)
+                }
+                '>' -> {
+                    balance -= 1
+                    builder.append(s)
+                }
+                else -> builder.append(s)
+            }
+            idx += 1
+        }
+        typeArguments.add(builder.toString())
+
+        if (simplified) {
+            return typeArguments.map { it.substringBefore(" extends ").trim() }
+        }
+        return typeArguments.map { it.trim() }
+    }
+
+    /**
      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
      * The optional [context] provides the method or class where this type parameter
      * appears, and can be used for example to resolve the bounds for a type variable
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
index 167173f..52ce6b1 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
@@ -110,7 +110,7 @@
 
     override fun isImplicitConstructor(): Boolean = false
 
-    override fun returnType(): TypeItem? = returnType
+    override fun returnType(): TypeItem = returnType
 
     override fun parameters(): List<PsiParameterItem> = parameters
 
@@ -316,7 +316,7 @@
         }
 
         val returnType = method.returnType()
-        sb.append(returnType?.convertTypeString(replacementMap))
+        sb.append(returnType.convertTypeString(replacementMap))
 
         sb.append(' ')
         sb.append(method.name())
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
index 98a3257..cae85c0 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
@@ -324,5 +324,105 @@
 
             return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)
         }
+
+        private fun hasEqualTypeVar(
+            type1: TypeItem,
+            class1: ClassItem,
+            type2: TypeItem,
+            class2: ClassItem
+        ): Boolean {
+
+            // Given a type and its containing class,
+            // find the interface types that contains the type.
+            // For instance, for a method that looks like:
+            // class SomeClass implements InterfaceA<some.return.Type>, InterfaceB<some.return.Type>
+            //     Type foo()
+            // this function will return [InterfaceA, InterfaceB] when Type and SomeClass
+            // are passed as inputs.
+            val typeContainingInterfaces = {
+                t: TypeItem, cl: ClassItem ->
+                val interfaceTypes = cl.interfaceTypes()
+                    .plus(cl.toType())
+                    .plus(cl.superClassType())
+                    .filterNotNull()
+                interfaceTypes.filter {
+                    val typeArgs = it.typeArguments(simplified = true)
+                    t.toString() in typeArgs ||
+                        t.toElementType() in typeArgs ||
+                        t.asClass()?.superClass()?.qualifiedName() in typeArgs
+                }
+            }
+
+            val typeContainingInterfaces1 = typeContainingInterfaces(type1, class1)
+            val typeContainingInterfaces2 = typeContainingInterfaces(type2, class2)
+
+            if (typeContainingInterfaces1.isEmpty() || typeContainingInterfaces2.isEmpty()) {
+                return false
+            }
+
+            val interfaceTypesAreCovariant = {
+                t1: TypeItem, t2: TypeItem ->
+                t1.toErasedTypeString() == t2.toErasedTypeString() ||
+                    t1.asClass()?.superClass()?.qualifiedName() == t2.asClass()?.qualifiedName() ||
+                    t2.asClass()?.superClass()?.qualifiedName() == t1.asClass()?.qualifiedName()
+            }
+
+            // Check if the return type containing interfaces of the two methods have an intersection.
+            return typeContainingInterfaces1.any {
+                typeInterface1 ->
+                typeContainingInterfaces2.any {
+                    typeInterface2 ->
+                    interfaceTypesAreCovariant(typeInterface1, typeInterface2)
+                }
+            }
+        }
+
+        private fun hasEqualTypeBounds(method1: MethodItem, method2: MethodItem): Boolean {
+            val typeInTypeParams = {
+                t: TypeItem, m: MethodItem ->
+                t in m.typeParameterList().typeParameters().map { it.toType() }
+            }
+
+            val getTypeBounds = {
+                t: TypeItem, m: MethodItem ->
+                m.typeParameterList().typeParameters().single { it.toType() == t }.typeBounds().toSet()
+            }
+
+            val returnType1 = method1.returnType()
+            val returnType2 = method2.returnType()
+
+            // The following two methods are considered equal:
+            // method public <A extends some.package.SomeClass> A foo (Class<A>);
+            // method public <T extends some.package.SomeClass> T foo (Class<T>);
+            // This can be verified by converting return types to bounds ([some.package.SomeClass])
+            // and compare equivalence.
+            return typeInTypeParams(returnType1, method1) && typeInTypeParams(returnType2, method2) &&
+                getTypeBounds(returnType1, method1) == getTypeBounds(returnType2, method2)
+        }
+
+        /**
+         * Compares two [MethodItem]s and determines if the two methods have equal return types.
+         * The two methods' return types are considered equal even if the two are not identical,
+         * but are compatible in compiler level. For instance, return types in a same hierarchy tree
+         * are considered equal.
+         *
+         * @param method1 first [MethodItem] to compare the return type
+         * @param method2 second [MethodItem] to compare the return type
+         * @return a [Boolean] value representing if the two methods' return types are equal
+         */
+        fun hasEqualReturnType(method1: MethodItem, method2: MethodItem): Boolean {
+            val returnType1 = method1.returnType()
+            val returnType2 = method2.returnType()
+            val class1 = method1.containingClass()
+            val class2 = method2.containingClass()
+
+            if (returnType1 == returnType2) return true
+
+            if (hasEqualTypeVar(returnType1, class1, returnType2, class2)) return true
+
+            if (hasEqualTypeBounds(method1, method2)) return true
+
+            return false
+        }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
index 63382cd..4577801 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
@@ -24,7 +24,7 @@
     name: String,
     containingClass: TextClassItem,
     modifiers: TextModifiers,
-    returnType: TextTypeItem?,
+    returnType: TextTypeItem,
     position: SourcePositionInfo
 ) : TextMethodItem(codebase, name, containingClass, modifiers, returnType, position),
     ConstructorItem {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
index 9dba79a..a010667 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
@@ -31,7 +31,7 @@
     name: String,
     containingClass: TextClassItem,
     modifiers: TextModifiers,
-    private val returnType: TextTypeItem?,
+    private val returnType: TextTypeItem,
     position: SourcePositionInfo
 ) : TextMemberItem(
     codebase, name, containingClass, position,
@@ -79,7 +79,7 @@
 
     override fun isConstructor(): Boolean = false
 
-    override fun returnType(): TypeItem? = returnType
+    override fun returnType(): TypeItem = returnType
 
     override fun superMethods(): List<MethodItem> {
         if (isConstructor()) {
diff --git a/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt b/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
index b757078..41e77ed 100644
--- a/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
@@ -353,7 +353,7 @@
 
         val returnType = method.returnType()
         writer.print(
-            returnType?.toTypeString(
+            returnType.toTypeString(
                 outerAnnotations = false,
                 innerAnnotations = generateAnnotations,
                 filter = filterReference
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiMethodItemTest.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiMethodItemTest.kt
index 390643f..6a99f85 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/PsiMethodItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiMethodItemTest.kt
@@ -16,8 +16,10 @@
 
 package com.android.tools.metalava.model.psi
 
+import com.android.tools.metalava.java
 import com.android.tools.metalava.kotlin
 import org.junit.Test
+import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.assertSame
@@ -48,4 +50,29 @@
             assertNull(component1.property)
         }
     }
+
+    @Test
+    fun `method return type is non-null`() {
+        val codebase = java(
+            """
+            public class Foo {
+                public Foo() {}
+                public void bar() {}
+            }
+            """
+        )
+        testCodebase(codebase) { c ->
+            val ctorItem = c.assertClass("Foo").findMethod("Foo", "")
+            val ctorReturnType = ctorItem!!.returnType()
+
+            val methodItem = c.assertClass("Foo").findMethod("bar", "")
+            val methodReturnType = methodItem!!.returnType()
+
+            assertNotNull(ctorReturnType)
+            assertEquals("Foo", ctorReturnType.toString(), "Return type of the constructor item must be the containing class.")
+
+            assertNotNull(methodReturnType)
+            assertEquals("void", methodReturnType.toString(), "Return type of an method item should match the expected value.")
+        }
+    }
 }
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextClassItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextClassItemTest.kt
new file mode 100644
index 0000000..e2f5c1a
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/text/TextClassItemTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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 com.android.tools.metalava.model.text
+
+import org.junit.Test
+import kotlin.test.assertTrue
+
+class TextClassItemTest {
+    @Test
+    fun `test hasEqualReturnType() when return types are derived from interface type variables`() {
+        val codebase = ApiFile.parseApi(
+            "test",
+            """
+            package java.lang {
+              public final class Float extends java.lang.Number implements java.lang.Comparable<java.lang.Float> {
+              }
+            }
+            package java.time {
+              public final class LocalDateTime implements java.time.chrono.ChronoLocalDateTime<java.time.LocalDate> java.io.Serializable java.time.temporal.Temporal java.time.temporal.TemporalAdjuster {
+                method public java.time.LocalDate toLocalDate();
+              }
+            }
+            package java.time.chrono {
+              public interface ChronoLocalDateTime<D extends java.time.chrono.ChronoLocalDate> extends java.time.temporal.Temporal java.lang.Comparable<java.time.chrono.ChronoLocalDateTime<?>> java.time.temporal.TemporalAdjuster {
+                method public D toLocalDate();
+              }
+            }
+            package android.animation {
+              public interface TypeEvaluator<T> {
+                method public T evaluate(float, T, T);
+              }
+              public class FloatEvaluator implements android.animation.TypeEvaluator<java.lang.Number> {
+                method public Float evaluate(float, Number, Number);
+              }
+            }
+            package android.widget {
+              public abstract class AdapterView<T extends android.widget.Adapter> extends android.view.ViewGroup {
+                method public abstract T getAdapter();
+              }
+              public abstract class AbsListView extends android.widget.AdapterView<android.widget.ListAdapter> implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener {
+              }
+              public interface ListAdapter extends android.widget.Adapter {
+              }
+              @android.widget.RemoteViews.RemoteView public class ListView extends android.widget.AbsListView {
+                method public android.widget.ListAdapter getAdapter();
+              }
+            }
+            package android.content {
+              public abstract class AsyncTaskLoader<D> extends android.content.Loader<D> {
+                method public abstract D loadInBackground();
+              }
+              public class CursorLoader extends android.content.AsyncTaskLoader<android.database.Cursor> {
+                method public android.database.Cursor loadInBackground();
+              }
+            }
+            package android.database {
+              public final class CursorJoiner implements java.lang.Iterable<android.database.CursorJoiner.Result> java.util.Iterator<android.database.CursorJoiner.Result> {
+                method public android.database.CursorJoiner.Result next();
+              }
+            }
+            package java.util {
+              public interface Iterator<E> {
+                method public E next();
+              }
+            }
+            package java.lang.invoke {
+              public final class MethodType implements java.io.Serializable java.lang.invoke.TypeDescriptor.OfMethod<java.lang.Class<?>,java.lang.invoke.MethodType> {
+                method public java.lang.invoke.MethodType changeParameterType(int, Class<?>);
+                method public Class<?>[] parameterArray();
+              }
+              public static interface TypeDescriptor.OfMethod<F extends java.lang.invoke.TypeDescriptor.OfField<F>, M extends java.lang.invoke.TypeDescriptor.OfMethod<F, M>> extends java.lang.invoke.TypeDescriptor {
+                method public M changeParameterType(int, F);
+                method public F[] parameterArray();
+              }
+            }
+            """.trimIndent(),
+            false
+        )
+
+        val toLocalDate1 = codebase.getOrCreateClass("java.time.LocalDateTime").findMethod("toLocalDate", "")!!
+        val toLocalDate2 = codebase.getOrCreateClass("java.time.chrono.ChronoLocalDateTime").findMethod("toLocalDate", "")!!
+        val evaluate1 = codebase.getOrCreateClass("android.animation.TypeEvaluator<T>").findMethod("evaluate", "float, T, T")!!
+        val evaluate2 = codebase.getOrCreateClass("android.animation.FloatEvaluator").findMethod("evaluate", "float, java.lang.Number, java.lang.Number")!!
+        val loadInBackground1 = codebase.getOrCreateClass("android.content.AsyncTaskLoader<D>").findMethod("loadInBackground", "")!!
+        val loadInBackground2 = codebase.getOrCreateClass("android.content.CursorLoader").findMethod("loadInBackground", "")!!
+        val next1 = codebase.getOrCreateClass("android.database.CursorJoiner").findMethod("next", "")!!
+        val next2 = codebase.getOrCreateClass("java.util.Iterator<E>").findMethod("next", "")!!
+        val changeParameterType1 = codebase.getOrCreateClass("java.lang.invoke.MethodType").findMethod("changeParameterType", "int, java.lang.Class")!!
+        val changeParameterType2 = codebase.getOrCreateClass("java.lang.invoke.TypeDescriptor.OfMethod").findMethod("changeParameterType", "int, java.lang.invoke.TypeDescriptor.OfField")!!
+
+        assertTrue(TextClassItem.hasEqualReturnType(toLocalDate1, toLocalDate2))
+        assertTrue(TextClassItem.hasEqualReturnType(evaluate1, evaluate2))
+        assertTrue(TextClassItem.hasEqualReturnType(loadInBackground1, loadInBackground2))
+        assertTrue(TextClassItem.hasEqualReturnType(next1, next2))
+        assertTrue(TextClassItem.hasEqualReturnType(changeParameterType1, changeParameterType2))
+    }
+
+    @Test
+    fun `test hasEqualReturnType() with equal bounds return types`() {
+        val codebase = ApiFile.parseApi(
+            "test",
+            """
+            package java.lang {
+              public final class Class<T> implements java.lang.reflect.AnnotatedElement {
+                method @Nullable public <A extends java.lang.annotation.Annotation> A getAnnotation(@NonNull Class<A>);
+              }
+              public interface AnnotatedElement {
+                method @Nullable public <T extends java.lang.annotation.Annotation> T getAnnotation(@NonNull Class<T>);
+              }
+            }
+            """.trimIndent(),
+            false
+        )
+
+        val getAnnotation1 = codebase.getOrCreateClass("java.lang.Class<T>").findMethod("getAnnotation", "java.lang.Class")!!
+        val getAnnotation2 = codebase.getOrCreateClass("java.lang.AnnotatedElement").findMethod("getAnnotation", "java.lang.Class")!!
+
+        assertTrue(TextClassItem.hasEqualReturnType(getAnnotation1, getAnnotation2))
+    }
+}
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextMethodItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextMethodItemTest.kt
new file mode 100644
index 0000000..ab9db49
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/text/TextMethodItemTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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 com.android.tools.metalava.model.text
+
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+class TextMethodItemTest {
+    @Test
+    fun `text method item return type is non-null`() {
+        val codebase = ApiFile.parseApi(
+            "test",
+            """
+            package test.pkg {
+              public class Foo {
+                ctor public Foo();
+                method public void bar();
+              }
+            }
+            """.trimIndent(),
+            false
+        )
+
+        val cls = codebase.findClass("test.pkg.Foo")!!
+        val ctorItem = cls.findMethod("Foo", "")!!
+        val methodItem = cls.findMethod("bar", "")!!
+
+        assertNotNull(ctorItem.returnType())
+        assertEquals("test.pkg.Foo", ctorItem.returnType().toString(), "Return type of the constructor item must be the containing class.")
+        assertNotNull(methodItem.returnType())
+        assertEquals("void", methodItem.returnType().toString(), "Return type of an method item should match the expected value.")
+    }
+}