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