1 /*
<lambda>null2  * Copyright (C) 2017 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.function.Predicate
20 
21 @MetalavaApi
22 interface MethodItem : MemberItem, TypeParameterListOwner {
23     /**
24      * The property this method is an accessor for; inverse of [PropertyItem.getter] and
25      * [PropertyItem.setter]
26      */
27     val property: PropertyItem?
28         get() = null
29 
30     /** Whether this method is a constructor */
31     @MetalavaApi fun isConstructor(): Boolean
32 
33     /** The type of this field. Returns the containing class for constructors */
34     @MetalavaApi fun returnType(): TypeItem
35 
36     /** The list of parameters */
37     @MetalavaApi fun parameters(): List<ParameterItem>
38 
39     /** Returns true if this method is a Kotlin extension method */
40     fun isExtensionMethod(): Boolean
41 
42     /** Returns the super methods that this method is overriding */
43     fun superMethods(): List<MethodItem>
44 
45     override fun type() = returnType()
46 
47     override fun findCorrespondingItemIn(
48         codebase: Codebase,
49         superMethods: Boolean,
50         duplicate: Boolean,
51     ): MethodItem? {
52         val correspondingClassItem = containingClass().findCorrespondingItemIn(codebase)
53         val correspondingMethodItem =
54             correspondingClassItem?.findMethod(
55                 this,
56                 includeSuperClasses = superMethods,
57                 includeInterfaces = superMethods,
58             )
59         return if (
60             correspondingMethodItem != null &&
61                 duplicate &&
62                 correspondingMethodItem.containingClass() !== correspondingClassItem
63         )
64             correspondingMethodItem.duplicate(correspondingClassItem)
65         else correspondingMethodItem
66     }
67 
68     /** Returns the main documentation for the method (the documentation before any tags). */
69     fun findMainDocumentation(): String
70 
71     fun allSuperMethods(): Sequence<MethodItem> {
72         val original = superMethods().firstOrNull() ?: return emptySequence()
73         return generateSequence(original) { item ->
74             val superMethods = item.superMethods()
75             superMethods.firstOrNull()
76         }
77     }
78 
79     /** Types of exceptions that this method can throw */
80     fun throwsTypes(): List<ExceptionTypeItem>
81 
82     /** Returns true if this method throws the given exception */
83     fun throws(qualifiedName: String): Boolean {
84         for (type in throwsTypes()) {
85             val throwableClass = type.erasedClass ?: continue
86             if (throwableClass.extends(qualifiedName)) {
87                 return true
88             }
89         }
90 
91         return false
92     }
93 
94     fun filteredThrowsTypes(predicate: Predicate<Item>): Collection<ExceptionTypeItem> {
95         if (throwsTypes().isEmpty()) {
96             return emptyList()
97         }
98         return filteredThrowsTypes(predicate, LinkedHashSet())
99     }
100 
101     private fun filteredThrowsTypes(
102         predicate: Predicate<Item>,
103         throwsTypes: LinkedHashSet<ExceptionTypeItem>
104     ): LinkedHashSet<ExceptionTypeItem> {
105         for (exceptionType in throwsTypes()) {
106             if (exceptionType is VariableTypeItem) {
107                 throwsTypes.add(exceptionType)
108             } else {
109                 val classItem = exceptionType.erasedClass ?: continue
110                 if (predicate.test(classItem)) {
111                     throwsTypes.add(exceptionType)
112                 } else {
113                     // Excluded, but it may have super class throwables that are included; if so,
114                     // include those.
115                     classItem
116                         .allSuperClasses()
117                         .firstOrNull { superClass -> predicate.test(superClass) }
118                         ?.let { superClass -> throwsTypes.add(superClass.type()) }
119                 }
120             }
121         }
122         return throwsTypes
123     }
124 
125     /**
126      * If this method requires override in the child class to prevent error when compiling the stubs
127      */
128     @Deprecated("This property should not be accessed directly.") var _requiresOverride: Boolean?
129 
130     /**
131      * Duplicates this method item.
132      *
133      * Override to specialize the return type.
134      */
135     override fun duplicate(targetContainingClass: ClassItem): MethodItem
136 
137     fun findPredicateSuperMethod(predicate: Predicate<Item>): MethodItem? {
138         if (isConstructor()) {
139             return null
140         }
141 
142         val superMethods = superMethods()
143         for (method in superMethods) {
144             if (predicate.test(method)) {
145                 return method
146             }
147         }
148 
149         for (method in superMethods) {
150             val found = method.findPredicateSuperMethod(predicate)
151             if (found != null) {
152                 return found
153             }
154         }
155 
156         return null
157     }
158 
159     override fun baselineElementId() = buildString {
160         append(containingClass().qualifiedName())
161         append("#")
162         append(name())
163         append("(")
164         parameters().joinTo(this) { it.type().toSimpleType() }
165         append(")")
166     }
167 
168     override fun accept(visitor: ItemVisitor) {
169         visitor.visit(this)
170     }
171 
172     override fun toStringForItem(): String {
173         return "${if (isConstructor()) "constructor" else "method"} ${
174             containingClass().qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
175     }
176 
177     companion object {
178         private fun compareMethods(
179             o1: MethodItem,
180             o2: MethodItem,
181             overloadsInSourceOrder: Boolean
182         ): Int {
183             val name1 = o1.name()
184             val name2 = o2.name()
185             if (name1 == name2) {
186                 if (overloadsInSourceOrder) {
187                     val rankDelta = o1.sortingRank - o2.sortingRank
188                     if (rankDelta != 0) {
189                         return rankDelta
190                     }
191                 }
192 
193                 // Compare by the rest of the signature to ensure stable output (we don't need to
194                 // sort
195                 // by return value or modifiers or modifiers or throws-lists since methods can't be
196                 // overloaded
197                 // by just those attributes
198                 val p1 = o1.parameters()
199                 val p2 = o2.parameters()
200                 val p1n = p1.size
201                 val p2n = p2.size
202                 for (i in 0 until minOf(p1n, p2n)) {
203                     val compareTypes =
204                         p1[i]
205                             .type()
206                             .toTypeString()
207                             .compareTo(p2[i].type().toTypeString(), ignoreCase = true)
208                     if (compareTypes != 0) {
209                         return compareTypes
210                     }
211                     // (Don't compare names; they're not part of the signatures)
212                 }
213                 return p1n.compareTo(p2n)
214             }
215 
216             return name1.compareTo(name2)
217         }
218 
219         val comparator: Comparator<MethodItem> = Comparator { o1, o2 ->
220             compareMethods(o1, o2, false)
221         }
222         val sourceOrderComparator: Comparator<MethodItem> = Comparator { o1, o2 ->
223             val delta = o1.sortingRank - o2.sortingRank
224             if (delta == 0) {
225                 // Within a source file all the items will have unique sorting ranks, but since
226                 // we copy methods in from hidden super classes it's possible for ranks to clash,
227                 // and in that case we'll revert to a signature based comparison
228                 comparator.compare(o1, o2)
229             } else {
230                 delta
231             }
232         }
233         val sourceOrderForOverloadedMethodsComparator: Comparator<MethodItem> =
234             Comparator { o1, o2 ->
235                 compareMethods(o1, o2, true)
236             }
237 
238         /**
239          * Compare two types to see if they are considered the same.
240          *
241          * Same means, functionally equivalent at both compile time and runtime.
242          *
243          * TODO: Compare annotations to see for example whether you've refined the nullness policy;
244          *   if so, that should be included
245          */
246         private fun sameType(
247             t1: TypeItem,
248             t2: TypeItem,
249             addAdditionalOverrides: Boolean,
250         ): Boolean {
251             // Compare the types in two ways.
252             // 1. Using `TypeItem.equals(TypeItem)` which is basically a textual comparison that
253             //    ignores type parameter bounds but includes everuthing else that is present in the
254             //    string representation of the type apart from white space differences. This is
255             //    needed to preserve methods that change annotations, e.g. adding `@NonNull`, which
256             //    are significant to the API, and also to preserver legacy behavior to reduce churn
257             //    in API signature files.
258             // 2. Comparing their erased types which takes into account type parameter bounds but
259             //    ignores annotations and generic types. Comparing erased types will retain more
260             //    methods overrides in the signature file so only do it when adding additional
261             //    overrides.
262             return t1 == t2 &&
263                 (!addAdditionalOverrides || t1.toErasedTypeString() == t2.toErasedTypeString())
264         }
265 
266         fun sameSignature(
267             method: MethodItem,
268             superMethod: MethodItem,
269             addAdditionalOverrides: Boolean,
270         ): Boolean {
271             // If the return types differ, override it (e.g. parent implements clone(),
272             // subclass overrides with more specific return type)
273             if (
274                 !sameType(
275                     method.returnType(),
276                     superMethod.returnType(),
277                     addAdditionalOverrides = addAdditionalOverrides
278                 )
279             ) {
280                 return false
281             }
282 
283             if (
284                 method.effectivelyDeprecated != superMethod.effectivelyDeprecated &&
285                     !method.effectivelyDeprecated
286             ) {
287                 return false
288             }
289 
290             // Compare modifier lists; note that here we need to
291             // skip modifiers that don't apply in compat mode if set
292             if (!method.modifiers.equivalentTo(superMethod.modifiers)) {
293                 return false
294             }
295 
296             val parameterList1 = method.parameters()
297             val parameterList2 = superMethod.parameters()
298 
299             if (parameterList1.size != parameterList2.size) {
300                 return false
301             }
302 
303             assert(parameterList1.size == parameterList2.size)
304             for (i in parameterList1.indices) {
305                 val p1 = parameterList1[i]
306                 val p2 = parameterList2[i]
307                 val pt1 = p1.type()
308                 val pt2 = p2.type()
309 
310                 if (!sameType(pt1, pt2, addAdditionalOverrides)) {
311                     return false
312                 }
313             }
314 
315             // Also compare throws lists
316             val throwsList12 = method.throwsTypes()
317             val throwsList2 = superMethod.throwsTypes()
318 
319             if (throwsList12.size != throwsList2.size) {
320                 return false
321             }
322 
323             assert(throwsList12.size == throwsList2.size)
324             for (i in throwsList12.indices) {
325                 val p1 = throwsList12[i]
326                 val p2 = throwsList2[i]
327                 val pt1 = p1.toTypeString()
328                 val pt2 = p2.toTypeString()
329                 if (pt1 != pt2) { // assumes throws lists are sorted!
330                     return false
331                 }
332             }
333 
334             return true
335         }
336     }
337 
338     fun formatParameters(): String? {
339         // TODO: Generalize, allow callers to control whether to include annotations, whether to
340         // erase types,
341         // whether to include names, etc
342         if (parameters().isEmpty()) {
343             return ""
344         }
345         val sb = StringBuilder()
346         for (parameter in parameters()) {
347             if (sb.isNotEmpty()) {
348                 sb.append(", ")
349             }
350             sb.append(parameter.type().toTypeString())
351         }
352 
353         return sb.toString()
354     }
355 
356     fun isImplicitConstructor(): Boolean {
357         return isConstructor() && modifiers.isPublic() && parameters().isEmpty()
358     }
359 
360     /**
361      * Finds uncaught exceptions actually thrown inside this method (as opposed to ones declared in
362      * the signature)
363      */
364     fun findThrownExceptions(): Set<ClassItem> = codebase.unsupported()
365 
366     /** If annotation method, returns the default value as a source expression */
367     fun defaultValue(): String = ""
368 
369     fun hasDefaultValue(): Boolean {
370         return defaultValue() != ""
371     }
372 
373     /**
374      * Returns true if overloads of the method should be checked separately when checking signature
375      * of the method.
376      *
377      * This works around the issue of actual method not generating overloads for @JvmOverloads
378      * annotation when the default is specified on expect side
379      * (https://youtrack.jetbrains.com/issue/KT-57537).
380      */
381     fun shouldExpandOverloads(): Boolean = false
382 
383     /**
384      * Returns true if this method is a signature match for the given method (e.g. can be
385      * overriding). This checks that the name and parameter lists match, but ignores differences in
386      * parameter names, return value types and throws list types.
387      */
388     fun matches(other: MethodItem): Boolean {
389         if (this === other) return true
390 
391         if (name() != other.name()) {
392             return false
393         }
394 
395         val parameters1 = parameters()
396         val parameters2 = other.parameters()
397 
398         if (parameters1.size != parameters2.size) {
399             return false
400         }
401 
402         for (i in parameters1.indices) {
403             val parameter1Type = parameters1[i].type()
404             val parameter2Type = parameters2[i].type()
405             if (parameter1Type == parameter2Type) continue
406             if (parameter1Type.toErasedTypeString() == parameter2Type.toErasedTypeString()) continue
407 
408             val convertedType =
409                 parameter1Type.convertType(other.containingClass(), containingClass())
410             if (convertedType != parameter2Type) return false
411         }
412         return true
413     }
414 
415     /**
416      * Returns whether this method has any types in its signature that does not match the given
417      * filter
418      */
419     fun hasHiddenType(filterReference: Predicate<Item>): Boolean {
420         for (parameter in parameters()) {
421             if (parameter.type().hasHiddenType(filterReference)) return true
422         }
423 
424         if (returnType().hasHiddenType(filterReference)) return true
425 
426         for (typeParameter in typeParameterList) {
427             if (typeParameter.typeBounds().any { it.hasHiddenType(filterReference) }) return true
428         }
429 
430         return false
431     }
432 
433     /** Checks if there is a reference to a hidden class anywhere in the type. */
434     private fun TypeItem.hasHiddenType(filterReference: Predicate<Item>): Boolean {
435         return when (this) {
436             is PrimitiveTypeItem -> false
437             is ArrayTypeItem -> componentType.hasHiddenType(filterReference)
438             is ClassTypeItem ->
439                 asClass()?.let { !filterReference.test(it) } == true ||
440                     outerClassType?.hasHiddenType(filterReference) == true ||
441                     arguments.any { it.hasHiddenType(filterReference) }
442             is VariableTypeItem -> !filterReference.test(asTypeParameter)
443             is WildcardTypeItem ->
444                 extendsBound?.hasHiddenType(filterReference) == true ||
445                     superBound?.hasHiddenType(filterReference) == true
446             else -> throw IllegalStateException("Unrecognized type: $this")
447         }
448     }
449 
450     /** Whether this method is a getter/setter for an underlying Kotlin property (val/var) */
451     fun isKotlinProperty(): Boolean = false
452 
453     /**
454      * Determines if the method is a method that needs to be overridden in any child classes that
455      * extend this [MethodItem] in order to prevent errors when compiling the stubs or the reverse
456      * dependencies of stubs.
457      *
458      * @return Boolean value indicating whether the method needs to be overridden in the child
459      *   classes
460      */
461     @Suppress("DEPRECATION")
462     private fun requiresOverride(): Boolean {
463         _requiresOverride?.let {
464             return _requiresOverride as Boolean
465         }
466 
467         _requiresOverride = computeRequiresOverride()
468 
469         return _requiresOverride as Boolean
470     }
471 
472     private fun computeRequiresOverride(): Boolean {
473         val isVisible = !hidden || hasShowAnnotation()
474 
475         // When the method is a concrete, non-default method, its overriding method is not required
476         // to be shown in the signature file.
477         return if (!modifiers.isAbstract() && !modifiers.isDefault()) {
478             false
479         } else if (superMethods().isEmpty()) {
480             // If the method is abstract and is not overriding any parent methods,
481             // it requires override in the child class if it is visible
482             isVisible
483         } else {
484             // If the method is abstract and is overriding any visible parent methods:
485             // it needs to be overridden if:
486             // - it is visible or
487             // - all super methods are either java.lang.Object method or requires override
488             isVisible ||
489                 superMethods().all {
490                     it.containingClass().isJavaLangObject() || it.requiresOverride()
491                 }
492         }
493     }
494 
495     private fun getUniqueSuperInterfaceMethods(
496         superInterfaceMethods: List<MethodItem>
497     ): List<MethodItem> {
498         val visitCountMap = mutableMapOf<ClassItem, Int>()
499 
500         // perform BFS on all super interfaces of each super interface methods'
501         // containing interface to determine the leaf interface of each unique hierarchy.
502         superInterfaceMethods.forEach {
503             val superInterface = it.containingClass()
504             val queue = mutableListOf(superInterface)
505             while (queue.isNotEmpty()) {
506                 val s = queue.removeFirst()
507                 visitCountMap[s] = visitCountMap.getOrDefault(s, 0) + 1
508                 queue.addAll(
509                     s.interfaceTypes().mapNotNull { interfaceType -> interfaceType.asClass() }
510                 )
511             }
512         }
513 
514         // If visit count is greater than 1, it means the interface is within the hierarchy of
515         // another method, thus filter out.
516         return superInterfaceMethods.filter { visitCountMap[it.containingClass()]!! == 1 }
517     }
518 
519     /**
520      * Determines if the method needs to be added to the signature file in order to prevent errors
521      * when compiling the stubs or the reverse dependencies of the stubs.
522      *
523      * @return Boolean value indicating whether the method needs to be added to the signature file
524      */
525     fun isRequiredOverridingMethodForTextStub(): Boolean {
526         return (containingClass().isClass() &&
527             !modifiers.isAbstract() &&
528             superMethods().isNotEmpty() &&
529             superMethods().let {
530                 if (it.size == 1 && it.first().containingClass().isJavaLangObject()) {
531                     // If the method is extending a java.lang.Object method,
532                     // it only required override when it is directly (not transitively) overriding
533                     // it and the signature differs (e.g. visibility or modifier
534                     // changes)
535                     !sameSignature(
536                         this,
537                         it.first(),
538                         // This method is only called when add-additional-overrides=yes.
539                         addAdditionalOverrides = true,
540                     )
541                 } else {
542                     // Since a class can extend a single class except Object,
543                     // there is only one non-Object super class method at max.
544                     val superClassMethods =
545                         it.firstOrNull { superMethod ->
546                             superMethod.containingClass().isClass() &&
547                                 !superMethod.containingClass().isJavaLangObject()
548                         }
549 
550                     // Assume a class implements two interfaces A and B;
551                     // A provides a default super method, and B provides an abstract super method.
552                     // In such case, the child method is a required overriding method when:
553                     // - A and B do not extend each other or
554                     // - A is a super interface of B
555                     // On the other hand, the child method is not a required overriding method when:
556                     // - B is a super interface of A
557                     // Given this, we should make decisions only based on the leaf interface of each
558                     // unique hierarchy.
559                     val uniqueSuperInterfaceMethods =
560                         getUniqueSuperInterfaceMethods(
561                             it.filter { superMethod -> superMethod.containingClass().isInterface() }
562                         )
563 
564                     // If super method is non-null, whether this method is required
565                     // is determined by whether the super method requires override.
566                     // If super method is null, this method is required if there is a
567                     // unique super interface that requires override.
568                     superClassMethods?.requiresOverride()
569                         ?: uniqueSuperInterfaceMethods.any { s -> s.requiresOverride() }
570                 }
571             }) ||
572             // To inherit methods with override-equivalent signatures
573             // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.4.1.3
574             (containingClass().isInterface() &&
575                 superMethods().count { it.modifiers.isAbstract() || it.modifiers.isDefault() } > 1)
576     }
577 }
578 
579 /**
580  * Check to see if the method is overrideable.
581  *
582  * Private and static methods cannot be overridden.
583  */
MethodItemnull584 private fun MethodItem.isOverrideable(): Boolean = !modifiers.isPrivate() && !modifiers.isStatic()
585 
586 /**
587  * Compute the super methods of this method.
588  *
589  * A super method is a method from a super class or super interface that is directly overridden by
590  * this method.
591  */
592 fun MethodItem.computeSuperMethods(): List<MethodItem> {
593     // Constructors and methods that are not overrideable will have no super methods.
594     if (isConstructor() || !isOverrideable()) {
595         return emptyList()
596     }
597 
598     // TODO(b/321216636): Remove this awful hack.
599     // For some reason `psiMethod.findSuperMethods()` would return an empty list for this
600     // specific method. That is incorrect as it clearly overrides a method in `DrawScope` in
601     // the same package. However, it is unclear what makes this method distinct from any
602     // other method including overloaded methods in the same class that also override
603     // methods in`DrawScope`. Returning a correct non-empty list for that method results in
604     // the method being removed from an API signature file even though the super method is
605     // abstract and this is concrete. That is because AndroidX does not yet set
606     // `add-additional-overrides=yes`. When it does then this hack can be removed.
607     if (
608         containingClass().qualifiedName() ==
609             "androidx.compose.ui.graphics.drawscope.CanvasDrawScope" &&
610             name() == "drawImage" &&
611             toString() ==
612                 "method androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage(androidx.compose.ui.graphics.ImageBitmap, long, long, long, long, float, androidx.compose.ui.graphics.drawscope.DrawStyle, androidx.compose.ui.graphics.ColorFilter, int)"
613     ) {
614         return emptyList()
615     }
616 
617     // Ideally, the search for super methods would start from this method's ClassItem.
618     // Unfortunately, due to legacy reasons for methods that were inherited from another ClassItem
619     // it is necessary to start the search from the original ClassItem. That is because the psi
620     // model's implementation behaved this way and the code that is built of top of superMethods,
621     // like the code to determine if overriding methods should be elided from the API signature file
622     // relied on that behavior.
623     val startingClass = inheritedFrom ?: containingClass()
624     return buildSet { appendSuperMethods(this, startingClass) }.toList()
625 }
626 
627 /**
628  * Append the super methods of this method from the [cls] hierarchy to the [methods] set.
629  *
630  * @param methods the mutable, order preserving set of super [MethodItem].
631  * @param cls the [ClassItem] whose super class and implemented interfaces will be searched for
632  *   matching methods.
633  */
MethodItemnull634 private fun MethodItem.appendSuperMethods(methods: MutableSet<MethodItem>, cls: ClassItem) {
635     // Method from SuperClass or its ancestors
636     cls.superClass()?.let { superClass ->
637         // Search for a matching method in the super class.
638         val superMethod = superClass.findMethod(this)
639         if (superMethod == null) {
640             // No matching method was found so continue searching in the super class.
641             appendSuperMethods(methods, superClass)
642         } else {
643             // Matching does not check modifiers match so make sure that the matched method is
644             // overrideable.
645             if (superMethod.isOverrideable()) {
646                 methods.add(superMethod)
647             }
648         }
649     }
650 
651     // Methods implemented from direct interfaces or its ancestors
652     appendSuperMethodsFromInterfaces(methods, cls)
653 }
654 
655 /**
656  * Append the super methods of this method from the interface hierarchy of [cls] to the [methods]
657  * set.
658  *
659  * @param methods the mutable, order preserving set of super [MethodItem].
660  * @param cls the [ClassItem] whose implemented interfaces will be searched for matching methods.
661  */
MethodItemnull662 private fun MethodItem.appendSuperMethodsFromInterfaces(
663     methods: MutableSet<MethodItem>,
664     cls: ClassItem
665 ) {
666     for (itf in cls.interfaceTypes()) {
667         val itfClass = itf.asClass() ?: continue
668 
669         // Find the method in the interface.
670         itfClass.findMethod(this)?.let { superMethod ->
671             // A matching method was found so add it to the super methods if it is overrideable.
672             if (superMethod.isOverrideable()) {
673                 methods.add(superMethod)
674             }
675         }
676         // A method could not be found in this interface so search its interfaces.
677         ?: appendSuperMethodsFromInterfaces(methods, itfClass)
678     }
679 }
680 
681 /**
682  * Update the state of a [MethodItem] that has been copied from one [ClassItem] to another.
683  *
684  * This will update the [MethodItem] on which it is called to ensure that it is consistent with the
685  * [ClassItem] to which it now belongs. Called from the implementations of [MethodItem.duplicate]
686  * and [ClassItem.inheritMethodFromNonApiAncestor].
687  */
MethodItemnull688 fun MethodItem.updateCopiedMethodState() {
689     val mutableModifiers = mutableModifiers()
690     if (mutableModifiers.isDefault() && !containingClass().isInterface()) {
691         mutableModifiers.setDefault(false)
692     }
693 }
694