• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.model
18 
19 import java.util.Objects
20 
21 /** Common to [MethodItem] and [ConstructorItem]. */
22 @MetalavaApi
23 interface CallableItem : MemberItem, TypeParameterListOwner {
24 
25     /** Whether this callable is a constructor or a method. */
26     @MetalavaApi fun isConstructor(): Boolean
27 
28     /**
29      * The return type of this callable.
30      *
31      * Returns the [ClassItem.type] of the [containingClass] for constructors.
32      */
33     @MetalavaApi fun returnType(): TypeItem
34 
35     /** The list of parameters */
36     @MetalavaApi fun parameters(): List<ParameterItem>
37 
38     override fun type() = returnType()
39 
40     /** Types of exceptions that this callable can throw */
41     fun throwsTypes(): List<ExceptionTypeItem>
42 
43     /** The body of this, may not be available. */
44     val body: CallableBody
45 
46     /** Returns true if this callable throws the given exception */
47     fun throws(qualifiedName: String): Boolean {
48         for (type in throwsTypes()) {
49             val throwableClass = type.erasedClass ?: continue
50             if (throwableClass.extends(qualifiedName)) {
51                 return true
52             }
53         }
54 
55         return false
56     }
57 
58     fun filteredThrowsTypes(predicate: FilterPredicate): Collection<ExceptionTypeItem> {
59         if (throwsTypes().isEmpty()) {
60             return emptyList()
61         }
62         return filteredThrowsTypes(predicate, LinkedHashSet())
63     }
64 
65     private fun filteredThrowsTypes(
66         predicate: FilterPredicate,
67         throwsTypes: LinkedHashSet<ExceptionTypeItem>
68     ): LinkedHashSet<ExceptionTypeItem> {
69         for (exceptionType in throwsTypes()) {
70             if (exceptionType is VariableTypeItem) {
71                 throwsTypes.add(exceptionType)
72             } else {
73                 val classItem = exceptionType.erasedClass ?: continue
74                 if (predicate.test(classItem)) {
75                     throwsTypes.add(exceptionType)
76                 } else {
77                     // Excluded, but it may have super class throwables that are included; if so,
78                     // include those.
79                     classItem
80                         .allSuperClasses()
81                         .firstOrNull { superClass -> predicate.test(superClass) }
82                         ?.let { superClass -> throwsTypes.add(superClass.type()) }
83                 }
84             }
85         }
86         return throwsTypes
87     }
88 
89     /** Override to specialize return type. */
90     override fun findCorrespondingItemIn(
91         codebase: Codebase,
92         superMethods: Boolean,
93         duplicate: Boolean,
94     ): CallableItem?
95 
96     override fun baselineElementId() = buildString {
97         append(containingClass().qualifiedName())
98         append("#")
99         append(name())
100         append("(")
101         parameters().joinTo(this) { it.type().toSimpleType() }
102         append(")")
103     }
104 
105     override fun toStringForItem(): String {
106         return "${if (isConstructor()) "constructor" else "method"} ${
107             containingClass().qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
108     }
109 
110     override fun equalsToItem(other: Any?): Boolean {
111         if (this === other) return true
112         if (other !is CallableItem) return false
113 
114         // The name check will ensure that methods and constructors do not compare equally to each
115         // other even if the rest of this method would return true. That is because constructor
116         // names MUST be the class' `simpleName`, whereas method names MUST NOT be the class'
117         // `simpleName`.
118         if (name() != other.name()) {
119             return false
120         }
121 
122         if (containingClass() != other.containingClass()) {
123             return false
124         }
125 
126         val parameters1 = parameters()
127         val parameters2 = other.parameters()
128 
129         if (parameters1.size != parameters2.size) {
130             return false
131         }
132 
133         for (i in parameters1.indices) {
134             val parameter1 = parameters1[i]
135             val parameter2 = parameters2[i]
136             if (parameter1.type() != parameter2.type()) {
137                 return false
138             }
139         }
140 
141         val typeParameters1 = typeParameterList
142         val typeParameters2 = other.typeParameterList
143 
144         if (typeParameters1.size != typeParameters2.size) {
145             return false
146         }
147 
148         for (i in typeParameters1.indices) {
149             val typeParameter1 = typeParameters1[i]
150             val typeParameter2 = typeParameters2[i]
151             if (typeParameter1.typeBounds() != typeParameter2.typeBounds()) {
152                 return false
153             }
154         }
155         return true
156     }
157 
158     override fun hashCodeForItem(): Int {
159         // Just use the callable name, containing class and number of parameters.
160         return Objects.hash(name(), containingClass(), parameters().size)
161     }
162 
163     /**
164      * Returns true if this callable is a signature match for the given callable (e.g. can be
165      * overriding if it is a method). This checks that the name and parameter lists match, but
166      * ignores differences in parameter names, return value types and throws list types.
167      */
168     fun matches(other: CallableItem): Boolean {
169         if (this === other) return true
170 
171         // The name check will ensure that methods and constructors do not compare equally to each
172         // other even if the rest of this method would return true. That is because constructor
173         // names MUST be the class' `simpleName`, whereas method names MUST NOT be the class'
174         // `simpleName`.
175         if (name() != other.name()) {
176             return false
177         }
178 
179         val parameters1 = parameters()
180         val parameters2 = other.parameters()
181 
182         if (parameters1.size != parameters2.size) {
183             return false
184         }
185 
186         for (i in parameters1.indices) {
187             val parameter1Type = parameters1[i].type()
188             val parameter2Type = parameters2[i].type()
189             if (parameter1Type == parameter2Type) continue
190             if (parameter1Type.toErasedTypeString() == parameter2Type.toErasedTypeString()) continue
191 
192             val convertedType =
193                 parameter1Type.convertType(other.containingClass(), containingClass())
194             if (convertedType != parameter2Type) return false
195         }
196         return true
197     }
198 
199     /**
200      * Returns whether this callable has any types in its signature that does not match the given
201      * filter.
202      */
203     fun hasHiddenType(filterReference: FilterPredicate): Boolean {
204         for (parameter in parameters()) {
205             if (parameter.type().hasHiddenType(filterReference)) return true
206         }
207 
208         if (returnType().hasHiddenType(filterReference)) return true
209 
210         for (typeParameter in typeParameterList) {
211             if (typeParameter.typeBounds().any { it.hasHiddenType(filterReference) }) return true
212         }
213 
214         return false
215     }
216 
217     /** Checks if there is a reference to a hidden class anywhere in the type. */
218     private fun TypeItem.hasHiddenType(filterReference: FilterPredicate): Boolean {
219         return when (this) {
220             is PrimitiveTypeItem -> false
221             is ArrayTypeItem -> componentType.hasHiddenType(filterReference)
222             is ClassTypeItem ->
223                 asClass()?.let { !filterReference.test(it) } == true ||
224                     outerClassType?.hasHiddenType(filterReference) == true ||
225                     arguments.any { it.hasHiddenType(filterReference) }
226             is VariableTypeItem ->
227                 // There is no need to check if a type variable contains a reference to a hidden
228                 // class as it is only a reference to a type parameter, and they are checked above
229                 // to make sure that their type bounds do not contain a reference to a hidden
230                 // class.
231                 false
232             is WildcardTypeItem ->
233                 extendsBound?.hasHiddenType(filterReference) == true ||
234                     superBound?.hasHiddenType(filterReference) == true
235             else -> throw IllegalStateException("Unrecognized type: $this")
236         }
237     }
238 
239     companion object {
240         private fun compareCallables(
241             o1: CallableItem,
242             o2: CallableItem,
243             overloadsInSourceOrder: Boolean
244         ): Int {
245             val name1 = o1.name()
246             val name2 = o2.name()
247             if (name1 == name2) {
248                 if (overloadsInSourceOrder) {
249                     val rankDelta = o1.sortingRank - o2.sortingRank
250                     if (rankDelta != 0) {
251                         return rankDelta
252                     }
253                 }
254 
255                 // Compare by the rest of the signature to ensure stable output (we don't need to
256                 // sort
257                 // by return value or modifiers or modifiers or throws-lists since methods can't be
258                 // overloaded
259                 // by just those attributes
260                 val p1 = o1.parameters()
261                 val p2 = o2.parameters()
262                 val p1n = p1.size
263                 val p2n = p2.size
264                 for (i in 0 until minOf(p1n, p2n)) {
265                     val compareTypes =
266                         p1[i]
267                             .type()
268                             .toTypeString()
269                             .compareTo(p2[i].type().toTypeString(), ignoreCase = true)
270                     if (compareTypes != 0) {
271                         return compareTypes
272                     }
273                     // (Don't compare names; they're not part of the signatures)
274                 }
275                 return p1n.compareTo(p2n)
276             }
277 
278             return name1.compareTo(name2)
279         }
280 
281         val comparator: Comparator<CallableItem> = Comparator { o1, o2 ->
282             compareCallables(o1, o2, false)
283         }
284         val sourceOrderForOverloadedMethodsComparator: Comparator<CallableItem> =
285             Comparator { o1, o2 ->
286                 compareCallables(o1, o2, true)
287             }
288     }
289 }
290 
291 /**
292  * Get the JVM-like descriptor of this [CallableItem] for just parameters (not return type) and
293  * using dots ('.') instead of slash (`/`) and dollar sign (`$`) characters.
294  *
295  * Due to legacy reasons it will return `null` for the constructor of an inner class.
296  */
CallableItemnull297 fun CallableItem.getCallableParameterDescriptorUsingDots(): String? {
298     return if (
299         isConstructor() &&
300             containingClass().isNestedClass() &&
301             !containingClass().modifiers.isStatic()
302     )
303         null
304     else
305         buildString {
306             append("(")
307             for (parameter in parameters()) {
308                 append(parameter.type().internalName().replace('/', '.').replace('$', '.'))
309             }
310             append(")")
311         }
312 }
313