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.psi
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.DefaultModifierList
21 import com.android.tools.metalava.model.ExceptionTypeItem
22 import com.android.tools.metalava.model.MethodItem
23 import com.android.tools.metalava.model.TypeItem
24 import com.android.tools.metalava.model.TypeParameterList
25 import com.android.tools.metalava.model.computeSuperMethods
26 import com.android.tools.metalava.model.type.MethodFingerprint
27 import com.android.tools.metalava.model.updateCopiedMethodState
28 import com.android.tools.metalava.reporter.FileLocation
29 import com.intellij.psi.PsiAnnotationMethod
30 import com.intellij.psi.PsiMethod
31 import com.intellij.psi.PsiParameter
32 import org.jetbrains.kotlin.name.JvmStandardClassIds
33 import org.jetbrains.kotlin.psi.KtFunction
34 import org.jetbrains.kotlin.psi.KtNamedFunction
35 import org.jetbrains.kotlin.psi.KtParameter
36 import org.jetbrains.kotlin.psi.KtProperty
37 import org.jetbrains.kotlin.psi.KtPropertyAccessor
38 import org.jetbrains.uast.UAnnotation
39 import org.jetbrains.uast.UAnnotationMethod
40 import org.jetbrains.uast.UElement
41 import org.jetbrains.uast.UMethod
42 import org.jetbrains.uast.UThrowExpression
43 import org.jetbrains.uast.UTryExpression
44 import org.jetbrains.uast.getParentOfType
45 import org.jetbrains.uast.kotlin.KotlinUMethodWithFakeLightDelegateBase
46 import org.jetbrains.uast.toUElement
47 import org.jetbrains.uast.visitor.AbstractUastVisitor
48 
49 open class PsiMethodItem(
50     codebase: PsiBasedCodebase,
51     val psiMethod: PsiMethod,
52     fileLocation: FileLocation = PsiFileLocation(psiMethod),
53     // Takes ClassItem as this may be duplicated from a PsiBasedCodebase on the classpath into a
54     // TextClassItem.
55     containingClass: ClassItem,
56     name: String,
57     modifiers: DefaultModifierList,
58     documentation: String,
59     private val returnType: TypeItem,
60     private val parameters: List<PsiParameterItem>,
61     override val typeParameterList: TypeParameterList,
62     private val throwsTypes: List<ExceptionTypeItem>
63 ) :
64     PsiMemberItem(
65         codebase = codebase,
66         modifiers = modifiers,
67         documentation = documentation,
68         element = psiMethod,
69         fileLocation = fileLocation,
70         containingClass = containingClass,
71         name = name,
72     ),
73     MethodItem {
74 
75     init {
76         for (parameter in parameters) {
77             @Suppress("LeakingThis")
78             parameter.containingMethod = this
79         }
80     }
81 
82     override var inheritedFrom: ClassItem? = null
83 
84     override var property: PsiPropertyItem? = null
85 
86     @Deprecated("This property should not be accessed directly.")
87     override var _requiresOverride: Boolean? = null
88 
89     override fun equals(other: Any?): Boolean {
90         // TODO: Allow mix and matching with other MethodItems?
91         if (this === other) return true
92         if (javaClass != other?.javaClass) return false
93 
94         other as PsiMethodItem
95 
96         if (psiMethod != other.psiMethod) return false
97 
98         return true
99     }
100 
101     override fun hashCode(): Int {
102         return psiMethod.hashCode()
103     }
104 
105     override fun findMainDocumentation(): String {
106         if (documentation == "") return documentation
107         val comment = codebase.getComment(documentation)
108         val end = findFirstTag(comment)?.textRange?.startOffset ?: documentation.length
109         return comment.text.substring(0, end)
110     }
111 
112     override fun isConstructor(): Boolean = false
113 
114     override fun isImplicitConstructor(): Boolean = false
115 
116     override fun returnType(): TypeItem = returnType
117 
118     override fun parameters(): List<PsiParameterItem> = parameters
119 
120     override fun psi() = psiMethod
121 
122     private var superMethods: List<MethodItem>? = null
123 
124     override fun superMethods(): List<MethodItem> {
125         if (superMethods == null) {
126             superMethods = computeSuperMethods()
127         }
128 
129         return superMethods!!
130     }
131 
132     override fun throwsTypes() = throwsTypes
133 
134     override fun isExtensionMethod(): Boolean {
135         if (isKotlin()) {
136             val ktParameters =
137                 ((psiMethod as? UMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters
138                     ?: return false
139             return ktParameters.size < parameters.size
140         }
141 
142         return false
143     }
144 
145     override fun isKotlinProperty(): Boolean {
146         return psiMethod is UMethod &&
147             (psiMethod.sourcePsi is KtProperty ||
148                 psiMethod.sourcePsi is KtPropertyAccessor ||
149                 psiMethod.sourcePsi is KtParameter &&
150                     (psiMethod.sourcePsi as KtParameter).hasValOrVar())
151     }
152 
153     override fun findThrownExceptions(): Set<ClassItem> {
154         val method = psiMethod as? UMethod ?: return emptySet()
155         if (!isKotlin()) {
156             return emptySet()
157         }
158 
159         val exceptions = mutableSetOf<ClassItem>()
160 
161         method.accept(
162             object : AbstractUastVisitor() {
163                 override fun visitThrowExpression(node: UThrowExpression): Boolean {
164                     val type = node.thrownExpression.getExpressionType()
165                     if (type != null) {
166                         val typeItemFactory =
167                             codebase.globalTypeItemFactory.from(this@PsiMethodItem)
168                         val exceptionClass = typeItemFactory.getType(type).asClass()
169                         if (exceptionClass != null && !isCaught(exceptionClass, node)) {
170                             exceptions.add(exceptionClass)
171                         }
172                     }
173                     return super.visitThrowExpression(node)
174                 }
175 
176                 private fun isCaught(exceptionClass: ClassItem, node: UThrowExpression): Boolean {
177                     var current: UElement = node
178                     while (true) {
179                         val tryExpression =
180                             current.getParentOfType<UTryExpression>(
181                                 UTryExpression::class.java,
182                                 true,
183                                 UMethod::class.java
184                             )
185                                 ?: return false
186 
187                         for (catchClause in tryExpression.catchClauses) {
188                             for (type in catchClause.types) {
189                                 val qualifiedName = type.canonicalText
190                                 if (exceptionClass.extends(qualifiedName)) {
191                                     return true
192                                 }
193                             }
194                         }
195 
196                         current = tryExpression
197                     }
198                 }
199             }
200         )
201 
202         return exceptions
203     }
204 
205     internal fun areAllParametersOptional(): Boolean {
206         for (param in parameters) {
207             if (!param.hasDefaultValue()) {
208                 return false
209             }
210         }
211         return true
212     }
213 
214     override fun defaultValue(): String {
215         return when (psiMethod) {
216             is UAnnotationMethod -> {
217                 psiMethod.uastDefaultValue?.let { codebase.printer.toSourceString(it) } ?: ""
218             }
219             is PsiAnnotationMethod -> {
220                 psiMethod.defaultValue?.let { codebase.printer.toSourceExpression(it, this) }
221                     ?: super.defaultValue()
222             }
223             else -> super.defaultValue()
224         }
225     }
226 
227     override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem {
228         val duplicated =
229             create(
230                 codebase,
231                 targetContainingClass,
232                 psiMethod,
233                 // Use the scope from this class to resolve type parameter references as the target
234                 // class may have a completely different set.
235                 codebase.globalTypeItemFactory.from(containingClass)
236             )
237 
238         duplicated.inheritedFrom = containingClass
239 
240         // Preserve flags that may have been inherited (propagated) from surrounding packages
241         if (targetContainingClass.hidden) {
242             duplicated.hidden = true
243         }
244         if (targetContainingClass.removed) {
245             duplicated.removed = true
246         }
247         if (targetContainingClass.docOnly) {
248             duplicated.docOnly = true
249         }
250 
251         duplicated.updateCopiedMethodState()
252 
253         return duplicated
254     }
255 
256     /* Call corresponding PSI utility method -- if I can find it!
257     override fun matches(other: MethodItem): Boolean {
258         if (other !is PsiMethodItem) {
259             return super.matches(other)
260         }
261 
262         // TODO: Find better API: this also checks surrounding class which we don't want!
263         return psiMethod.isEquivalentTo(other.psiMethod)
264     }
265     */
266 
267     override fun shouldExpandOverloads(): Boolean {
268         val ktFunction = (psiMethod as? UMethod)?.sourcePsi as? KtFunction ?: return false
269         return modifiers.isActual() &&
270             psiMethod.hasAnnotation(JvmStandardClassIds.JVM_OVERLOADS_FQ_NAME.asString()) &&
271             // It is /technically/ invalid to have actual functions with default values, but
272             // some places suppress the compiler error, so we should handle it here too.
273             ktFunction.valueParameters.none { it.hasDefaultValue() } &&
274             parameters.any { it.hasDefaultValue() }
275     }
276 
277     companion object {
278         /**
279          * Create a [PsiMethodItem].
280          *
281          * The [containingClass] is not necessarily a [PsiClassItem] as this is used to implement
282          * [MethodItem.duplicate] as well as create [PsiMethodItem] from the underlying Psi source
283          * model.
284          */
285         internal fun create(
286             codebase: PsiBasedCodebase,
287             containingClass: ClassItem,
288             psiMethod: PsiMethod,
289             enclosingClassTypeItemFactory: PsiTypeItemFactory,
290         ): PsiMethodItem {
291             assert(!psiMethod.isConstructor)
292             // UAST workaround: @JvmName for UMethod with fake LC PSI
293             // TODO: https://youtrack.jetbrains.com/issue/KTIJ-25133
294             val name =
295                 if (psiMethod is KotlinUMethodWithFakeLightDelegateBase<*>) {
296                     psiMethod.sourcePsi
297                         ?.annotationEntries
298                         ?.find { annoEntry ->
299                             val text = annoEntry.typeReference?.text ?: return@find false
300                             JvmName::class.qualifiedName?.contains(text) == true
301                         }
302                         ?.toUElement(UAnnotation::class.java)
303                         ?.takeIf {
304                             // Above `find` deliberately avoids resolution and uses verbatim text.
305                             // Below, we need annotation value anyway, but just double-check
306                             // if the converted annotation is indeed the resolved @JvmName
307                             it.qualifiedName == JvmName::class.qualifiedName
308                         }
309                         ?.findAttributeValue("name")
310                         ?.evaluate() as? String
311                         ?: psiMethod.name
312                 } else {
313                     psiMethod.name
314                 }
315             val commentText = javadoc(psiMethod, codebase.allowReadingComments)
316             val modifiers = modifiers(codebase, psiMethod, commentText)
317             // Create the TypeParameterList for this before wrapping any of the other types used by
318             // it as they may reference a type parameter in the list.
319             val (typeParameterList, methodTypeItemFactory) =
320                 PsiTypeParameterList.create(
321                     codebase,
322                     enclosingClassTypeItemFactory,
323                     "method $name",
324                     psiMethod
325                 )
326             val parameters = parameterList(codebase, psiMethod, methodTypeItemFactory)
327             val fingerprint = MethodFingerprint(psiMethod.name, psiMethod.parameters.size)
328             val isAnnotationElement = containingClass.isAnnotationType() && !modifiers.isStatic()
329             val returnType =
330                 methodTypeItemFactory.getMethodReturnType(
331                     underlyingReturnType = PsiTypeInfo(psiMethod.returnType!!, psiMethod),
332                     itemAnnotations = modifiers.annotations(),
333                     fingerprint = fingerprint,
334                     isAnnotationElement = isAnnotationElement,
335                 )
336 
337             val method =
338                 PsiMethodItem(
339                     codebase = codebase,
340                     psiMethod = psiMethod,
341                     containingClass = containingClass,
342                     name = name,
343                     documentation = commentText,
344                     modifiers = modifiers,
345                     returnType = returnType,
346                     parameters = parameters,
347                     typeParameterList = typeParameterList,
348                     throwsTypes = throwsTypes(psiMethod, methodTypeItemFactory),
349                 )
350             if (modifiers.isFinal() && containingClass.modifiers.isFinal()) {
351                 // The containing class is final, so it is implied that every method is final as
352                 // well.
353                 // No need to apply 'final' to each method. (We do it here rather than just in the
354                 // signature emit code since we want to make sure that the signature comparison
355                 // methods with super methods also consider this method non-final.)
356                 modifiers.setFinal(false)
357             }
358 
359             return method
360         }
361 
362         /**
363          * Create a [PsiMethodItem] from a [PsiMethodItem] in a hidden super class.
364          *
365          * @see ClassItem.inheritMethodFromNonApiAncestor
366          */
367         internal fun create(containingClass: PsiClassItem, original: PsiMethodItem): PsiMethodItem {
368             val typeParameterBindings = containingClass.mapTypeVariables(original.containingClass())
369             val returnType = original.returnType.convertType(typeParameterBindings) as PsiTypeItem
370 
371             // This results in a PsiMethodItem that is inconsistent, compared with other
372             // PsiMethodItem. PsiMethodItems created directly from the source are such that:
373             //
374             //    psiMethod.containingClass === containingClass().psiClass
375             //
376             // However, the PsiMethodItem created here contains a psiMethod from a different class,
377             // usually the super class, so:
378             //
379             //    psiMethod.containingClass !== containingClass().psiClass
380             //
381             // If the method was created from the super class then:
382             //
383             //    psiMethod.containingClass === containingClass().superClass().psiClass
384             //
385             // The consequence of this is that the PsiMethodItem does not behave as might be
386             // expected. e.g. superMethods() will find super methods of the method in the super
387             // class, not the PsiMethodItem's containing class.
388             val method =
389                 PsiMethodItem(
390                     codebase = original.codebase,
391                     psiMethod = original.psiMethod,
392                     containingClass = containingClass,
393                     name = original.name(),
394                     documentation = original.documentation,
395                     modifiers = original.modifiers.duplicate(),
396                     returnType = returnType,
397                     parameters =
398                         PsiParameterItem.create(original.parameters(), typeParameterBindings),
399                     // This is probably incorrect as the type parameter bindings probably need
400                     // applying here but this is the same behavior as before.
401                     // TODO: Investigate whether the above comment is correct and fix if necessary.
402                     typeParameterList = original.typeParameterList,
403                     throwsTypes = original.throwsTypes,
404                 )
405 
406             return method
407         }
408 
409         internal fun parameterList(
410             codebase: PsiBasedCodebase,
411             psiMethod: PsiMethod,
412             enclosingTypeItemFactory: PsiTypeItemFactory,
413         ): List<PsiParameterItem> {
414             val psiParameters = psiMethod.psiParameters
415             val fingerprint = MethodFingerprint(psiMethod.name, psiParameters.size)
416             return psiParameters.mapIndexed { index, parameter ->
417                 PsiParameterItem.create(
418                     codebase,
419                     fingerprint,
420                     parameter,
421                     index,
422                     enclosingTypeItemFactory
423                 )
424             }
425         }
426 
427         internal fun throwsTypes(
428             psiMethod: PsiMethod,
429             enclosingTypeItemFactory: PsiTypeItemFactory,
430         ): List<ExceptionTypeItem> {
431             val throwsClassTypes = psiMethod.throwsList.referencedTypes
432             if (throwsClassTypes.isEmpty()) {
433                 return emptyList()
434             }
435 
436             return throwsClassTypes
437                 // Convert the PsiType to an ExceptionTypeItem and wrap it in a ThrowableType.
438                 .map { psiType -> enclosingTypeItemFactory.getExceptionType(PsiTypeInfo(psiType)) }
439                 // We're sorting the names here even though outputs typically do their own sorting,
440                 // since for example the MethodItem.sameSignature check wants to do an
441                 // element-by-element comparison to see if the signature matches, and that should
442                 // match overrides even if they specify their elements in different orders.
443                 .sortedWith(ExceptionTypeItem.fullNameComparator)
444         }
445     }
446 }
447 
448 /** Get the [PsiParameter]s for a [PsiMethod]. */
449 val PsiMethod.psiParameters: List<PsiParameter>
450     get() = if (this is UMethod) uastParameters else parameterList.parameters.toList()
451