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