• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ApiVariantSelectors
20 import com.android.tools.metalava.model.BaseModifierList
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.ClassKind
23 import com.android.tools.metalava.model.ExceptionTypeItem
24 import com.android.tools.metalava.model.ItemDocumentationFactory
25 import com.android.tools.metalava.model.MethodItem
26 import com.android.tools.metalava.model.TypeItem
27 import com.android.tools.metalava.model.TypeParameterList
28 import com.android.tools.metalava.model.VisibilityLevel
29 import com.android.tools.metalava.model.item.DefaultMethodItem
30 import com.android.tools.metalava.model.item.ParameterItemsFactory
31 import com.android.tools.metalava.model.psi.PsiCallableItem.Companion.parameterList
32 import com.android.tools.metalava.model.psi.PsiCallableItem.Companion.throwsTypes
33 import com.android.tools.metalava.model.type.MethodFingerprint
34 import com.android.tools.metalava.reporter.FileLocation
35 import com.intellij.psi.PsiAnnotationMethod
36 import com.intellij.psi.PsiMethod
37 import com.intellij.psi.PsiParameter
38 import org.jetbrains.kotlin.psi.KtNamedFunction
39 import org.jetbrains.kotlin.psi.KtParameter
40 import org.jetbrains.kotlin.psi.KtProperty
41 import org.jetbrains.kotlin.psi.KtPropertyAccessor
42 import org.jetbrains.uast.UAnnotation
43 import org.jetbrains.uast.UAnnotationMethod
44 import org.jetbrains.uast.UMethod
45 import org.jetbrains.uast.kotlin.KotlinUMethodWithFakeLightDelegateBase
46 import org.jetbrains.uast.toUElement
47 
48 internal class PsiMethodItem(
49     override val codebase: PsiBasedCodebase,
50     override val psiMethod: PsiMethod,
51     fileLocation: FileLocation = PsiFileLocation(psiMethod),
52     // Takes ClassItem as this may be duplicated from a PsiBasedCodebase on the classpath into a
53     // TextClassItem.
54     containingClass: ClassItem,
55     name: String,
56     modifiers: BaseModifierList,
57     documentationFactory: ItemDocumentationFactory,
58     returnType: TypeItem,
59     parameterItemsFactory: ParameterItemsFactory,
60     typeParameterList: TypeParameterList,
61     throwsTypes: List<ExceptionTypeItem>,
62 ) :
63     DefaultMethodItem(
64         codebase = codebase,
65         fileLocation = fileLocation,
66         itemLanguage = psiMethod.itemLanguage,
67         modifiers = modifiers,
68         documentationFactory = documentationFactory,
69         variantSelectorsFactory = ApiVariantSelectors.MUTABLE_FACTORY,
70         name = name,
71         containingClass = containingClass,
72         typeParameterList = typeParameterList,
73         returnType = returnType,
74         parameterItemsFactory = parameterItemsFactory,
75         throwsTypes = throwsTypes,
76         callableBodyFactory = { PsiCallableBody(it as PsiCallableItem) },
77     ),
78     PsiCallableItem {
79 
80     override var property: PsiPropertyItem? = null
81 
isExtensionMethodnull82     override fun isExtensionMethod(): Boolean {
83         if (isKotlin()) {
84             val ktParameters =
85                 ((psiMethod as? UMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters
86                     ?: return false
87             return ktParameters.size < parameters().size
88         }
89 
90         return false
91     }
92 
isKotlinPropertynull93     override fun isKotlinProperty(): Boolean {
94         return psiMethod is UMethod &&
95             (psiMethod.sourcePsi is KtProperty ||
96                 psiMethod.sourcePsi is KtPropertyAccessor ||
97                 psiMethod.sourcePsi is KtParameter &&
98                     (psiMethod.sourcePsi as KtParameter).hasValOrVar())
99     }
100 
legacyDefaultValuenull101     override fun legacyDefaultValue(): String {
102         return when (psiMethod) {
103             is UAnnotationMethod -> {
104                 psiMethod.uastDefaultValue?.let { codebase.printer.toSourceString(it) } ?: ""
105             }
106             is PsiAnnotationMethod -> {
107                 psiMethod.defaultValue?.let { codebase.printer.toSourceExpression(it, this) }
108                     ?: super.legacyDefaultValue()
109             }
110             else -> super.legacyDefaultValue()
111         }
112     }
113 
duplicatenull114     override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem {
115         // If duplicating within the same codebase type then map the type variables, otherwise do
116         // not. That is because this can end up substituting a `TypeItem` implementation of one
117         // type in place of a `PsiTypeItem` which can cause casting issues, e.g. in
118         // `PsiParameterItem` which expects its type as `PsiTypeItem`. Falling back to not mapping
119         // will not cause any significant issues as that is what was done before.
120         // TODO(b/324196754): Fix this. It is not clear if this causes problems outside tests, it
121         //  does not seem to break Android build.
122         val typeVariableMap =
123             if (codebase.javaClass === targetContainingClass.codebase.javaClass)
124                 targetContainingClass.mapTypeVariables(containingClass())
125             else emptyMap()
126 
127         return PsiMethodItem(
128                 codebase,
129                 psiMethod,
130                 fileLocation,
131                 targetContainingClass,
132                 name(),
133                 modifiers,
134                 documentation::duplicate,
135                 returnType.convertType(typeVariableMap),
136                 { methodItem ->
137                     parameters().map {
138                         (it as PsiParameterItem).duplicate(methodItem, typeVariableMap)
139                     }
140                 },
141                 typeParameterList,
142                 throwsTypes(),
143             )
144             .also { duplicated ->
145                 duplicated.inheritedFrom = containingClass()
146 
147                 duplicated.updateCopiedMethodState()
148             }
149     }
150 
151     /* Call corresponding PSI utility method -- if I can find it!
152     override fun matches(other: MethodItem): Boolean {
153         if (other !is PsiMethodItem) {
154             return super.matches(other)
155         }
156 
157         // TODO: Find better API: this also checks surrounding class which we don't want!
158         return psiMethod.isEquivalentTo(other.psiMethod)
159     }
160     */
161 
162     companion object {
163         /**
164          * Create a [PsiMethodItem].
165          *
166          * The [containingClass] is not necessarily a [PsiClassItem] as this is used to implement
167          * [MethodItem.duplicate] as well as create [PsiMethodItem] from the underlying Psi source
168          * model.
169          */
createnull170         internal fun create(
171             codebase: PsiBasedCodebase,
172             containingClass: ClassItem,
173             psiMethod: PsiMethod,
174             enclosingClassTypeItemFactory: PsiTypeItemFactory,
175             psiParameters: List<PsiParameter> = psiMethod.psiParameters,
176         ): PsiMethodItem {
177             assert(!psiMethod.isConstructor)
178             // UAST workaround: @JvmName for UMethod with fake LC PSI
179             // TODO: https://youtrack.jetbrains.com/issue/KTIJ-25133
180             val name =
181                 if (psiMethod is KotlinUMethodWithFakeLightDelegateBase<*>) {
182                     psiMethod.sourcePsi
183                         ?.annotationEntries
184                         ?.find { annoEntry ->
185                             val text = annoEntry.typeReference?.text ?: return@find false
186                             JvmName::class.qualifiedName?.contains(text) == true
187                         }
188                         ?.toUElement(UAnnotation::class.java)
189                         ?.takeIf {
190                             // Above `find` deliberately avoids resolution and uses verbatim text.
191                             // Below, we need annotation value anyway, but just double-check
192                             // if the converted annotation is indeed the resolved @JvmName
193                             it.qualifiedName == JvmName::class.qualifiedName
194                         }
195                         ?.findAttributeValue("name")
196                         ?.evaluate() as? String
197                         ?: psiMethod.name
198                 } else {
199                     psiMethod.name
200                 }
201             val modifiers = PsiModifierItem.create(codebase, psiMethod)
202 
203             if (containingClass.classKind == ClassKind.INTERFACE) {
204                 // All interface methods are implicitly public (except in Java 1.9, where they can
205                 // be private.
206                 if (!modifiers.isPrivate()) {
207                     modifiers.setVisibilityLevel(VisibilityLevel.PUBLIC)
208                 }
209             }
210 
211             if (modifiers.isFinal() && containingClass.modifiers.isFinal()) {
212                 // The containing class is final, so it is implied that every method is final as
213                 // well.
214                 // No need to apply 'final' to each method. (We do it here rather than just in the
215                 // signature emit code since we want to make sure that the signature comparison
216                 // methods with super methods also consider this method non-final.)
217                 modifiers.setFinal(false)
218             }
219 
220             // Create the TypeParameterList for this before wrapping any of the other types used by
221             // it as they may reference a type parameter in the list.
222             val (typeParameterList, methodTypeItemFactory) =
223                 PsiTypeParameterList.create(
224                     codebase,
225                     enclosingClassTypeItemFactory,
226                     "method $name",
227                     psiMethod
228                 )
229             val fingerprint = MethodFingerprint(psiMethod.name, psiMethod.parameters.size)
230             val isAnnotationElement = containingClass.isAnnotationType() && !modifiers.isStatic()
231             val returnType =
232                 methodTypeItemFactory.getMethodReturnType(
233                     underlyingReturnType = PsiTypeInfo(psiMethod.returnType!!, psiMethod),
234                     itemAnnotations = modifiers.annotations(),
235                     fingerprint = fingerprint,
236                     isAnnotationElement = isAnnotationElement,
237                 )
238 
239             val method =
240                 PsiMethodItem(
241                     codebase = codebase,
242                     psiMethod = psiMethod,
243                     containingClass = containingClass,
244                     name = name,
245                     documentationFactory = PsiItemDocumentation.factory(psiMethod, codebase),
246                     modifiers = modifiers,
247                     returnType = returnType,
248                     parameterItemsFactory = { containingCallable ->
249                         parameterList(
250                             codebase,
251                             psiMethod,
252                             containingCallable as PsiCallableItem,
253                             methodTypeItemFactory,
254                             psiParameters,
255                         )
256                     },
257                     typeParameterList = typeParameterList,
258                     throwsTypes = throwsTypes(psiMethod, methodTypeItemFactory),
259                 )
260 
261             return method
262         }
263     }
264 }
265