• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.MethodItem
20 import com.android.tools.metalava.model.ParameterItem
21 import com.android.tools.metalava.model.TypeItem
22 import com.android.tools.metalava.model.VisibilityLevel
23 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
24 import com.intellij.psi.PsiParameter
25 import org.jetbrains.kotlin.psi.KtConstantExpression
26 import org.jetbrains.kotlin.psi.KtFunction
27 import org.jetbrains.kotlin.psi.KtParameter
28 import org.jetbrains.uast.UExpression
29 import org.jetbrains.uast.UMethod
30 import org.jetbrains.uast.UastFacade
31 
32 class PsiParameterItem(
33     override val codebase: PsiBasedCodebase,
34     private val psiParameter: PsiParameter,
35     private val name: String,
36     override val parameterIndex: Int,
37     modifiers: PsiModifierItem,
38     documentation: String,
39     private val type: PsiTypeItem
40 ) : PsiItem(
41     codebase = codebase,
42     modifiers = modifiers,
43     documentation = documentation,
44     element = psiParameter
45 ),
46     ParameterItem {
47     lateinit var containingMethod: PsiMethodItem
48 
49     override var property: PsiPropertyItem? = null
50 
namenull51     override fun name(): String = name
52 
53     override fun publicName(): String? {
54         if (isKotlin(psiParameter)) {
55             // Omit names of some special parameters in Kotlin. None of these parameters may be
56             // set through Kotlin keyword arguments, so there's no need to track their names for
57             // compatibility. This also helps avoid signature file churn if PSI or the compiler
58             // change what name they're using for these parameters.
59 
60             // Receiver parameter of extension function
61             if (isReceiver()) {
62                 return null
63             }
64             // Property setter parameter
65             if (containingMethod.isKotlinProperty()) {
66                 return null
67             }
68             // Continuation parameter of suspend function
69             if (containingMethod.modifiers.isSuspend() &&
70                 "kotlin.coroutines.Continuation" == type.asClass()?.qualifiedName() &&
71                 containingMethod.parameters().size - 1 == parameterIndex
72             ) {
73                 return null
74             }
75             return name
76         } else {
77             // Java: Look for @ParameterName annotation
78             val annotation = modifiers.annotations().firstOrNull { it.isParameterName() }
79             if (annotation != null) {
80                 return annotation.attributes.firstOrNull()?.value?.value()?.toString()
81             }
82         }
83 
84         return null
85     }
86 
hasDefaultValuenull87     override fun hasDefaultValue(): Boolean = isDefaultValueKnown()
88 
89     override fun isDefaultValueKnown(): Boolean {
90         return if (isKotlin(psiParameter)) {
91             getKtParameter()?.hasDefaultValue() ?: false && defaultValue() != INVALID_VALUE
92         } else {
93             // Java: Look for @ParameterName annotation
94             modifiers.annotations().any { it.isDefaultValue() }
95         }
96     }
97 
98     // Note receiver parameter used to be named $receiver in previous UAST versions, now it is $this$functionName
isReceivernull99     private fun isReceiver(): Boolean = parameterIndex == 0 && name.startsWith("\$this\$")
100 
101     private fun getKtParameter(): KtParameter? {
102         val ktParameters =
103             ((containingMethod.psiMethod as? UMethod)?.sourcePsi as? KtFunction)?.valueParameters
104                 ?: return null
105 
106         // Perform matching based on parameter names, because indices won't work in the
107         // presence of @JvmOverloads where UAST generates multiple permutations of the
108         // method from the same KtParameters array.
109 
110         // Quick lookup first which usually works (lined up from the end to account
111         // for receivers for extension methods etc)
112         val rem = containingMethod.parameters().size - parameterIndex
113         val index = ktParameters.size - rem
114         if (index >= 0) {
115             val parameter = ktParameters[index]
116             if (parameter.name == name) {
117                 return parameter
118             }
119         }
120 
121         for (parameter in ktParameters) {
122             if (parameter.name == name) {
123                 return parameter
124             }
125         }
126 
127         // Fallback to handle scenario where the real parameter names are hidden by
128         // UAST (see UastKotlinPsiParameter which replaces parameter names to p$index)
129         if (index >= 0) {
130             val parameter = ktParameters[index]
131             if (!isReceiver()) {
132                 return parameter
133             }
134         }
135 
136         return null
137     }
138 
139     override val synthetic: Boolean get() = containingMethod.isEnumSyntheticMethod()
140 
141     private var defaultValue: String? = null
142 
defaultValuenull143     override fun defaultValue(): String? {
144         if (defaultValue == null) {
145             defaultValue = computeDefaultValue()
146         }
147         return defaultValue
148     }
149 
computeDefaultValuenull150     private fun computeDefaultValue(): String? {
151         if (isKotlin(psiParameter)) {
152             val ktParameter = getKtParameter() ?: return null
153             if (ktParameter.hasDefaultValue()) {
154                 val defaultValue = ktParameter.defaultValue ?: return null
155                 if (defaultValue is KtConstantExpression) {
156                     return defaultValue.text
157                 }
158 
159                 val defaultExpression: UExpression = UastFacade.convertElement(
160                     defaultValue, null,
161                     UExpression::class.java
162                 ) as? UExpression ?: return INVALID_VALUE
163                 val constant = defaultExpression.evaluate()
164                 return if (constant != null && constant !is Pair<*, *>) {
165                     constantToSource(constant)
166                 } else {
167                     // Expression: Compute from UAST rather than just using the source text
168                     // such that we can ensure references are fully qualified etc.
169                     codebase.printer.toSourceString(defaultExpression)
170                 }
171             }
172 
173             return INVALID_VALUE
174         } else {
175             // Java: Look for @ParameterName annotation
176             val annotation = modifiers.annotations().firstOrNull { it.isDefaultValue() }
177             if (annotation != null) {
178                 return annotation.attributes.firstOrNull()?.value?.value()?.toString()
179             }
180         }
181 
182         return null
183     }
184 
typenull185     override fun type(): TypeItem = type
186     override fun containingMethod(): MethodItem = containingMethod
187 
188     override fun equals(other: Any?): Boolean {
189         if (this === other) {
190             return true
191         }
192         return other is ParameterItem && parameterIndex == other.parameterIndex && containingMethod == other.containingMethod()
193     }
194 
hashCodenull195     override fun hashCode(): Int {
196         return parameterIndex
197     }
198 
toStringnull199     override fun toString(): String = "parameter ${name()}"
200 
201     override fun isVarArgs(): Boolean {
202         return psiParameter.isVarArgs || modifiers.isVarArg()
203     }
204 
205     companion object {
createnull206         fun create(
207             codebase: PsiBasedCodebase,
208             psiParameter: PsiParameter,
209             parameterIndex: Int
210         ): PsiParameterItem {
211             val name = psiParameter.name
212             val commentText = "" // no javadocs on individual parameters
213             val modifiers = createParameterModifiers(codebase, psiParameter, commentText)
214             val type = codebase.getType(psiParameter.type)
215             val parameter = PsiParameterItem(
216                 codebase = codebase,
217                 psiParameter = psiParameter,
218                 name = name,
219                 parameterIndex = parameterIndex,
220                 documentation = commentText,
221                 modifiers = modifiers,
222                 type = type
223             )
224             parameter.modifiers.setOwner(parameter)
225             return parameter
226         }
227 
createnull228         fun create(
229             codebase: PsiBasedCodebase,
230             original: PsiParameterItem
231         ): PsiParameterItem {
232             val parameter = PsiParameterItem(
233                 codebase = codebase,
234                 psiParameter = original.psiParameter,
235                 name = original.name,
236                 parameterIndex = original.parameterIndex,
237                 documentation = original.documentation,
238                 modifiers = PsiModifierItem.create(codebase, original.modifiers),
239                 type = PsiTypeItem.create(codebase, original.type)
240             )
241             parameter.modifiers.setOwner(parameter)
242             return parameter
243         }
244 
createnull245         fun create(
246             codebase: PsiBasedCodebase,
247             original: List<ParameterItem>
248         ): List<PsiParameterItem> {
249             return original.map { create(codebase, it as PsiParameterItem) }
250         }
251 
createParameterModifiersnull252         private fun createParameterModifiers(
253             codebase: PsiBasedCodebase,
254             psiParameter: PsiParameter,
255             commentText: String
256         ): PsiModifierItem {
257             val modifiers = PsiModifierItem
258                 .create(codebase, psiParameter, commentText)
259             // Method parameters don't have a visibility level; they are visible to anyone that can
260             // call their method. However, Kotlin constructors sometimes appear to specify the
261             // visibility of a constructor parameter by putting visibility inside the constructor
262             // signature. This is really to indicate that the matching property should have the
263             // mentioned visibility.
264             // If the method parameter seems to specify a visibility level, we correct it back to
265             // the default, here, to ensure we don't attempt to incorrectly emit this information
266             // into a signature file.
267             modifiers.setVisibilityLevel(VisibilityLevel.PACKAGE_PRIVATE)
268             return modifiers
269         }
270 
271         /**
272          * Private marker return value from [#computeDefaultValue] signifying that the parameter
273          * has a default value but we were unable to compute a suitable static string representation for it
274          */
275         private const val INVALID_VALUE = "__invalid_value__"
276     }
277 }
278