1 /*
2  * 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.AnnotationItem
20 import com.android.tools.metalava.model.DefaultModifierList
21 import com.android.tools.metalava.model.MethodItem
22 import com.android.tools.metalava.model.ParameterItem
23 import com.android.tools.metalava.model.TypeItem
24 import com.android.tools.metalava.model.TypeParameterBindings
25 import com.android.tools.metalava.model.VisibilityLevel
26 import com.android.tools.metalava.model.findAnnotation
27 import com.android.tools.metalava.model.hasAnnotation
28 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
29 import com.android.tools.metalava.model.type.MethodFingerprint
30 import com.intellij.psi.LambdaUtil
31 import com.intellij.psi.PsiArrayType
32 import com.intellij.psi.PsiEllipsisType
33 import com.intellij.psi.PsiParameter
34 import com.intellij.psi.impl.compiled.ClsParameterImpl
35 import org.jetbrains.kotlin.analysis.api.analyze
36 import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
37 import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
38 import org.jetbrains.kotlin.analysis.api.symbols.KtParameterSymbol
39 import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
40 import org.jetbrains.kotlin.analysis.api.types.KtTypeNullability
41 import org.jetbrains.kotlin.psi.KtConstantExpression
42 import org.jetbrains.kotlin.psi.KtFunction
43 import org.jetbrains.kotlin.psi.KtParameter
44 import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
45 import org.jetbrains.uast.UExpression
46 import org.jetbrains.uast.UMethod
47 import org.jetbrains.uast.UParameter
48 import org.jetbrains.uast.UastFacade
49 
50 class PsiParameterItem
51 internal constructor(
52     codebase: PsiBasedCodebase,
53     private val psiParameter: PsiParameter,
54     private val name: String,
55     override val parameterIndex: Int,
56     modifiers: DefaultModifierList,
57     private val type: PsiTypeItem,
58 ) :
59     PsiItem(
60         codebase = codebase,
61         element = psiParameter,
62         modifiers = modifiers,
63         documentation = "",
64     ),
65     ParameterItem {
66     lateinit var containingMethod: PsiMethodItem
67 
68     override var property: PsiPropertyItem? = null
69 
namenull70     override fun name(): String = name
71 
72     override fun psi() = psiParameter
73 
74     override fun publicName(): String? {
75         if (psiParameter.isKotlin()) {
76             // Omit names of some special parameters in Kotlin. None of these parameters may be
77             // set through Kotlin keyword arguments, so there's no need to track their names for
78             // compatibility. This also helps avoid signature file churn if PSI or the compiler
79             // change what name they're using for these parameters.
80 
81             // Receiver parameter of extension function
82             if (isReceiver()) {
83                 return null
84             }
85             // Property setter parameter
86             if (containingMethod.isKotlinProperty()) {
87                 return null
88             }
89             // Continuation parameter of suspend function
90             if (
91                 containingMethod.modifiers.isSuspend() &&
92                     "kotlin.coroutines.Continuation" == type.asClass()?.qualifiedName() &&
93                     containingMethod.parameters().size - 1 == parameterIndex
94             ) {
95                 return null
96             }
97             return name
98         } else {
99             // Java: Look for @ParameterName annotation
100             val annotation = modifiers.findAnnotation(AnnotationItem::isParameterName)
101             if (annotation != null) {
102                 return annotation.attributes.firstOrNull()?.value?.value()?.toString()
103             }
104 
105             // Parameter names from classpath jars are not present as annotations
106             if (
107                 isFromClassPath() &&
108                     (psiParameter is ClsParameterImpl) &&
109                     !psiParameter.isAutoGeneratedName
110             ) {
111                 return name()
112             }
113         }
114 
115         return null
116     }
117 
hasDefaultValuenull118     override fun hasDefaultValue(): Boolean = isDefaultValueKnown()
119 
120     override fun isDefaultValueKnown(): Boolean {
121         return if (psiParameter.isKotlin()) {
122             defaultValue() != INVALID_VALUE
123         } else {
124             // Java: Look for @ParameterName annotation
125             modifiers.hasAnnotation(AnnotationItem::isDefaultValue)
126         }
127     }
128 
129     // Note receiver parameter used to be named $receiver in previous UAST versions, now it is
130     // $this$functionName
isReceivernull131     private fun isReceiver(): Boolean = parameterIndex == 0 && name.startsWith("\$this\$")
132 
133     private fun getKtParameterSymbol(functionSymbol: KtFunctionLikeSymbol): KtParameterSymbol? {
134         if (isReceiver()) {
135             return functionSymbol.receiverParameter
136         }
137 
138         // Perform matching based on parameter names, because indices won't work in the
139         // presence of @JvmOverloads where UAST generates multiple permutations of the
140         // method from the same KtParameters array.
141         val parameters = functionSymbol.valueParameters
142 
143         val index = if (functionSymbol.isExtension) parameterIndex - 1 else parameterIndex
144         val isSuspend = functionSymbol is KtFunctionSymbol && functionSymbol.isSuspend
145         if (isSuspend && index >= parameters.size) {
146             // suspend functions have continuation as a last parameter, which is not
147             // defined in the symbol
148             return null
149         }
150 
151         // Quick lookup first which usually works
152         if (index >= 0) {
153             val parameter = parameters[index]
154             if (parameter.name.asString() == name) {
155                 return parameter
156             }
157         }
158 
159         for (parameter in parameters) {
160             if (parameter.name.asString() == name) {
161                 return parameter
162             }
163         }
164 
165         // Fallback to handle scenario where the real parameter names are hidden by
166         // UAST (see UastKotlinPsiParameter which replaces parameter names to p$index)
167         if (index >= 0) {
168             val parameter = parameters[index]
169             if (!isReceiver()) {
170                 return parameter
171             }
172         }
173 
174         return null
175     }
176 
177     private var defaultValue: String? = null
178 
defaultValuenull179     override fun defaultValue(): String? {
180         if (defaultValue == null) {
181             defaultValue = computeDefaultValue()
182         }
183         return defaultValue
184     }
185 
computeDefaultValuenull186     private fun computeDefaultValue(): String? {
187         if (psiParameter.isKotlin()) {
188             val ktFunction =
189                 ((containingMethod.psiMethod as? UMethod)?.sourcePsi as? KtFunction)
190                     ?: return INVALID_VALUE
191 
192             analyze(ktFunction) {
193                 val function =
194                     if (ktFunction.hasActualModifier()) {
195                         ktFunction.getSymbol().getExpectsForActual().singleOrNull()
196                     } else {
197                         ktFunction.getSymbol()
198                     }
199                 if (function !is KtFunctionLikeSymbol) return INVALID_VALUE
200                 val symbol = getKtParameterSymbol(function) ?: return INVALID_VALUE
201                 if (symbol is KtValueParameterSymbol && symbol.hasDefaultValue) {
202                     val defaultValue =
203                         (symbol.psi as? KtParameter)?.defaultValue ?: return INVALID_VALUE
204                     if (defaultValue is KtConstantExpression) {
205                         return defaultValue.text
206                     }
207 
208                     val defaultExpression =
209                         UastFacade.convertElement(defaultValue, null, UExpression::class.java)
210                             as? UExpression
211                             ?: return INVALID_VALUE
212                     val constant = defaultExpression.evaluate()
213                     return if (constant != null && constant !is Pair<*, *>) {
214                         constantToSource(constant)
215                     } else {
216                         // Expression: Compute from UAST rather than just using the source text
217                         // such that we can ensure references are fully qualified etc.
218                         codebase.printer.toSourceString(defaultExpression)
219                     }
220                 }
221             }
222 
223             return INVALID_VALUE
224         } else {
225             // Java: Look for @ParameterName annotation
226             val annotation = modifiers.findAnnotation(AnnotationItem::isDefaultValue)
227             if (annotation != null) {
228                 return annotation.attributes.firstOrNull()?.value?.value()?.toString()
229             }
230         }
231 
232         return null
233     }
234 
typenull235     override fun type(): TypeItem = type
236 
237     override fun containingMethod(): MethodItem = containingMethod
238 
239     override fun equals(other: Any?): Boolean {
240         if (this === other) {
241             return true
242         }
243         return other is ParameterItem &&
244             parameterIndex == other.parameterIndex &&
245             containingMethod == other.containingMethod()
246     }
247 
hashCodenull248     override fun hashCode(): Int {
249         return parameterIndex
250     }
251 
isVarArgsnull252     override fun isVarArgs(): Boolean {
253         return psiParameter.isVarArgs || modifiers.isVarArg()
254     }
255 
256     /**
257      * Returns whether this parameter is SAM convertible or a Kotlin lambda. If this parameter is
258      * the last parameter, it also means that it could be called in Kotlin using the trailing lambda
259      * syntax.
260      *
261      * Specifically this will attempt to handle the follow cases:
262      * - Java SAM interface = true
263      * - Kotlin SAM interface = false // Kotlin (non-fun) interfaces are not SAM convertible
264      * - Kotlin fun interface = true
265      * - Kotlin lambda = true
266      * - Any other type = false
267      */
isSamCompatibleOrKotlinLambdanull268     fun isSamCompatibleOrKotlinLambda(): Boolean {
269         // Method is defined in Java source
270         if (isJava()) {
271             // Check the parameter type to see if it is defined in Kotlin or not.
272             // Interfaces defined in Kotlin do not support SAM conversion, but `fun` interfaces do.
273             // This is a best-effort check, since external dependencies (bytecode) won't appear to
274             // be Kotlin, and won't have a `fun` modifier visible. To resolve this, we could parse
275             // the kotlin.metadata annotation on the bytecode declaration (and special case
276             // kotlin.jvm.functions.Function* since the actual Kotlin lambda type can always be used
277             // with trailing lambda syntax), but in reality the amount of Java methods with a Kotlin
278             // interface with a single abstract method from an external dependency should be
279             // minimal, so just checking source will make this easier to maintain in the future.
280             val cls = type.asClass()
281             if (cls != null && cls.isKotlin()) {
282                 return cls.isInterface() && cls.modifiers.isFunctional()
283             }
284             // Note: this will return `true` if the interface is defined in Kotlin, hence why we
285             // need the prior check as well
286             return LambdaUtil.isFunctionalType(type.psiType)
287             // Method is defined in Kotlin source
288         } else {
289             // For Kotlin declarations we can re-use the existing utilities for calculating whether
290             // a type is SAM convertible or not, which should handle external dependencies better
291             // and avoid any divergence from the actual compiler behaviour, if there are changes.
292             val parameter = (psi() as? UParameter)?.sourcePsi as? KtParameter ?: return false
293             analyze(parameter) {
294                 val ktType = parameter.getParameterSymbol().returnType
295                 val isSamType = ktType.isFunctionalInterfaceType
296                 val isFunctionalType =
297                     ktType.isFunctionType ||
298                         ktType.isSuspendFunctionType ||
299                         ktType.isKFunctionType ||
300                         ktType.isKSuspendFunctionType
301                 return isSamType || isFunctionalType
302             }
303         }
304     }
305 
306     companion object {
createnull307         internal fun create(
308             codebase: PsiBasedCodebase,
309             fingerprint: MethodFingerprint,
310             psiParameter: PsiParameter,
311             parameterIndex: Int,
312             enclosingMethodTypeItemFactory: PsiTypeItemFactory,
313         ): PsiParameterItem {
314             val name = psiParameter.name
315             val modifiers = createParameterModifiers(codebase, psiParameter)
316             val psiType = psiParameter.type
317             // UAST workaround: nullity of element type in last `vararg` parameter's array type
318             val workaroundPsiType =
319                 if (
320                     psiParameter is UParameter &&
321                         psiParameter.sourcePsi is KtParameter &&
322                         psiParameter.isVarArgs && // last `vararg`
323                         psiType is PsiArrayType
324                 ) {
325                     val ktParameter = psiParameter.sourcePsi as KtParameter
326                     val annotationProvider =
327                         when (codebase.uastResolveService?.nullability(ktParameter)) {
328                             KtTypeNullability.NON_NULLABLE ->
329                                 codebase.getNonNullAnnotationProvider()
330                             KtTypeNullability.NULLABLE -> codebase.getNullableAnnotationProvider()
331                             else -> null
332                         }
333                     val annotatedType =
334                         if (annotationProvider != null) {
335                             psiType.componentType.annotate(annotationProvider)
336                         } else {
337                             psiType.componentType
338                         }
339                     PsiEllipsisType(annotatedType, annotatedType.annotationProvider)
340                 } else {
341                     psiType
342                 }
343             val type =
344                 enclosingMethodTypeItemFactory.getMethodParameterType(
345                     underlyingParameterType = PsiTypeInfo(workaroundPsiType, psiParameter),
346                     itemAnnotations = modifiers.annotations(),
347                     fingerprint = fingerprint,
348                     parameterIndex = parameterIndex,
349                     isVarArg = psiType is PsiEllipsisType,
350                 )
351             val parameter =
352                 PsiParameterItem(
353                     codebase = codebase,
354                     psiParameter = psiParameter,
355                     name = name,
356                     parameterIndex = parameterIndex,
357                     modifiers = modifiers,
358                     // Need to down cast as [isSamCompatibleOrKotlinLambda] needs access to the
359                     // underlying PsiType.
360                     type = type as PsiTypeItem
361                 )
362             return parameter
363         }
364 
createnull365         fun create(
366             original: PsiParameterItem,
367             typeParameterBindings: TypeParameterBindings
368         ): PsiParameterItem {
369             val type = original.type.convertType(typeParameterBindings) as PsiTypeItem
370             val parameter =
371                 PsiParameterItem(
372                     codebase = original.codebase,
373                     psiParameter = original.psiParameter,
374                     name = original.name,
375                     parameterIndex = original.parameterIndex,
376                     modifiers = original.modifiers.duplicate(),
377                     type = type
378                 )
379             return parameter
380         }
381 
createnull382         fun create(
383             original: List<ParameterItem>,
384             typeParameterBindings: TypeParameterBindings
385         ): List<PsiParameterItem> {
386             return original.map { create(it as PsiParameterItem, typeParameterBindings) }
387         }
388 
createParameterModifiersnull389         private fun createParameterModifiers(
390             codebase: PsiBasedCodebase,
391             psiParameter: PsiParameter
392         ): DefaultModifierList {
393             val modifiers = PsiModifierItem.create(codebase, psiParameter)
394             // Method parameters don't have a visibility level; they are visible to anyone that can
395             // call their method. However, Kotlin constructors sometimes appear to specify the
396             // visibility of a constructor parameter by putting visibility inside the constructor
397             // signature. This is really to indicate that the matching property should have the
398             // mentioned visibility.
399             // If the method parameter seems to specify a visibility level, we correct it back to
400             // the default, here, to ensure we don't attempt to incorrectly emit this information
401             // into a signature file.
402             modifiers.setVisibilityLevel(VisibilityLevel.PACKAGE_PRIVATE)
403             return modifiers
404         }
405 
406         /**
407          * Private marker return value from [#computeDefaultValue] signifying that the parameter has
408          * a default value but we were unable to compute a suitable static string representation for
409          * it
410          */
411         private const val INVALID_VALUE = "__invalid_value__"
412     }
413 }
414