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