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.ClassItem 20 import com.android.tools.metalava.model.DefaultModifierList 21 import com.android.tools.metalava.model.ExceptionTypeItem 22 import com.android.tools.metalava.model.MethodItem 23 import com.android.tools.metalava.model.TypeItem 24 import com.android.tools.metalava.model.TypeParameterList 25 import com.android.tools.metalava.model.computeSuperMethods 26 import com.android.tools.metalava.model.type.MethodFingerprint 27 import com.android.tools.metalava.model.updateCopiedMethodState 28 import com.android.tools.metalava.reporter.FileLocation 29 import com.intellij.psi.PsiAnnotationMethod 30 import com.intellij.psi.PsiMethod 31 import com.intellij.psi.PsiParameter 32 import org.jetbrains.kotlin.name.JvmStandardClassIds 33 import org.jetbrains.kotlin.psi.KtFunction 34 import org.jetbrains.kotlin.psi.KtNamedFunction 35 import org.jetbrains.kotlin.psi.KtParameter 36 import org.jetbrains.kotlin.psi.KtProperty 37 import org.jetbrains.kotlin.psi.KtPropertyAccessor 38 import org.jetbrains.uast.UAnnotation 39 import org.jetbrains.uast.UAnnotationMethod 40 import org.jetbrains.uast.UElement 41 import org.jetbrains.uast.UMethod 42 import org.jetbrains.uast.UThrowExpression 43 import org.jetbrains.uast.UTryExpression 44 import org.jetbrains.uast.getParentOfType 45 import org.jetbrains.uast.kotlin.KotlinUMethodWithFakeLightDelegateBase 46 import org.jetbrains.uast.toUElement 47 import org.jetbrains.uast.visitor.AbstractUastVisitor 48 49 open class PsiMethodItem( 50 codebase: PsiBasedCodebase, 51 val psiMethod: PsiMethod, 52 fileLocation: FileLocation = PsiFileLocation(psiMethod), 53 // Takes ClassItem as this may be duplicated from a PsiBasedCodebase on the classpath into a 54 // TextClassItem. 55 containingClass: ClassItem, 56 name: String, 57 modifiers: DefaultModifierList, 58 documentation: String, 59 private val returnType: TypeItem, 60 private val parameters: List<PsiParameterItem>, 61 override val typeParameterList: TypeParameterList, 62 private val throwsTypes: List<ExceptionTypeItem> 63 ) : 64 PsiMemberItem( 65 codebase = codebase, 66 modifiers = modifiers, 67 documentation = documentation, 68 element = psiMethod, 69 fileLocation = fileLocation, 70 containingClass = containingClass, 71 name = name, 72 ), 73 MethodItem { 74 75 init { 76 for (parameter in parameters) { 77 @Suppress("LeakingThis") 78 parameter.containingMethod = this 79 } 80 } 81 82 override var inheritedFrom: ClassItem? = null 83 84 override var property: PsiPropertyItem? = null 85 86 @Deprecated("This property should not be accessed directly.") 87 override var _requiresOverride: Boolean? = null 88 89 override fun equals(other: Any?): Boolean { 90 // TODO: Allow mix and matching with other MethodItems? 91 if (this === other) return true 92 if (javaClass != other?.javaClass) return false 93 94 other as PsiMethodItem 95 96 if (psiMethod != other.psiMethod) return false 97 98 return true 99 } 100 101 override fun hashCode(): Int { 102 return psiMethod.hashCode() 103 } 104 105 override fun findMainDocumentation(): String { 106 if (documentation == "") return documentation 107 val comment = codebase.getComment(documentation) 108 val end = findFirstTag(comment)?.textRange?.startOffset ?: documentation.length 109 return comment.text.substring(0, end) 110 } 111 112 override fun isConstructor(): Boolean = false 113 114 override fun isImplicitConstructor(): Boolean = false 115 116 override fun returnType(): TypeItem = returnType 117 118 override fun parameters(): List<PsiParameterItem> = parameters 119 120 override fun psi() = psiMethod 121 122 private var superMethods: List<MethodItem>? = null 123 124 override fun superMethods(): List<MethodItem> { 125 if (superMethods == null) { 126 superMethods = computeSuperMethods() 127 } 128 129 return superMethods!! 130 } 131 132 override fun throwsTypes() = throwsTypes 133 134 override fun isExtensionMethod(): Boolean { 135 if (isKotlin()) { 136 val ktParameters = 137 ((psiMethod as? UMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters 138 ?: return false 139 return ktParameters.size < parameters.size 140 } 141 142 return false 143 } 144 145 override fun isKotlinProperty(): Boolean { 146 return psiMethod is UMethod && 147 (psiMethod.sourcePsi is KtProperty || 148 psiMethod.sourcePsi is KtPropertyAccessor || 149 psiMethod.sourcePsi is KtParameter && 150 (psiMethod.sourcePsi as KtParameter).hasValOrVar()) 151 } 152 153 override fun findThrownExceptions(): Set<ClassItem> { 154 val method = psiMethod as? UMethod ?: return emptySet() 155 if (!isKotlin()) { 156 return emptySet() 157 } 158 159 val exceptions = mutableSetOf<ClassItem>() 160 161 method.accept( 162 object : AbstractUastVisitor() { 163 override fun visitThrowExpression(node: UThrowExpression): Boolean { 164 val type = node.thrownExpression.getExpressionType() 165 if (type != null) { 166 val typeItemFactory = 167 codebase.globalTypeItemFactory.from(this@PsiMethodItem) 168 val exceptionClass = typeItemFactory.getType(type).asClass() 169 if (exceptionClass != null && !isCaught(exceptionClass, node)) { 170 exceptions.add(exceptionClass) 171 } 172 } 173 return super.visitThrowExpression(node) 174 } 175 176 private fun isCaught(exceptionClass: ClassItem, node: UThrowExpression): Boolean { 177 var current: UElement = node 178 while (true) { 179 val tryExpression = 180 current.getParentOfType<UTryExpression>( 181 UTryExpression::class.java, 182 true, 183 UMethod::class.java 184 ) 185 ?: return false 186 187 for (catchClause in tryExpression.catchClauses) { 188 for (type in catchClause.types) { 189 val qualifiedName = type.canonicalText 190 if (exceptionClass.extends(qualifiedName)) { 191 return true 192 } 193 } 194 } 195 196 current = tryExpression 197 } 198 } 199 } 200 ) 201 202 return exceptions 203 } 204 205 internal fun areAllParametersOptional(): Boolean { 206 for (param in parameters) { 207 if (!param.hasDefaultValue()) { 208 return false 209 } 210 } 211 return true 212 } 213 214 override fun defaultValue(): String { 215 return when (psiMethod) { 216 is UAnnotationMethod -> { 217 psiMethod.uastDefaultValue?.let { codebase.printer.toSourceString(it) } ?: "" 218 } 219 is PsiAnnotationMethod -> { 220 psiMethod.defaultValue?.let { codebase.printer.toSourceExpression(it, this) } 221 ?: super.defaultValue() 222 } 223 else -> super.defaultValue() 224 } 225 } 226 227 override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem { 228 val duplicated = 229 create( 230 codebase, 231 targetContainingClass, 232 psiMethod, 233 // Use the scope from this class to resolve type parameter references as the target 234 // class may have a completely different set. 235 codebase.globalTypeItemFactory.from(containingClass) 236 ) 237 238 duplicated.inheritedFrom = containingClass 239 240 // Preserve flags that may have been inherited (propagated) from surrounding packages 241 if (targetContainingClass.hidden) { 242 duplicated.hidden = true 243 } 244 if (targetContainingClass.removed) { 245 duplicated.removed = true 246 } 247 if (targetContainingClass.docOnly) { 248 duplicated.docOnly = true 249 } 250 251 duplicated.updateCopiedMethodState() 252 253 return duplicated 254 } 255 256 /* Call corresponding PSI utility method -- if I can find it! 257 override fun matches(other: MethodItem): Boolean { 258 if (other !is PsiMethodItem) { 259 return super.matches(other) 260 } 261 262 // TODO: Find better API: this also checks surrounding class which we don't want! 263 return psiMethod.isEquivalentTo(other.psiMethod) 264 } 265 */ 266 267 override fun shouldExpandOverloads(): Boolean { 268 val ktFunction = (psiMethod as? UMethod)?.sourcePsi as? KtFunction ?: return false 269 return modifiers.isActual() && 270 psiMethod.hasAnnotation(JvmStandardClassIds.JVM_OVERLOADS_FQ_NAME.asString()) && 271 // It is /technically/ invalid to have actual functions with default values, but 272 // some places suppress the compiler error, so we should handle it here too. 273 ktFunction.valueParameters.none { it.hasDefaultValue() } && 274 parameters.any { it.hasDefaultValue() } 275 } 276 277 companion object { 278 /** 279 * Create a [PsiMethodItem]. 280 * 281 * The [containingClass] is not necessarily a [PsiClassItem] as this is used to implement 282 * [MethodItem.duplicate] as well as create [PsiMethodItem] from the underlying Psi source 283 * model. 284 */ 285 internal fun create( 286 codebase: PsiBasedCodebase, 287 containingClass: ClassItem, 288 psiMethod: PsiMethod, 289 enclosingClassTypeItemFactory: PsiTypeItemFactory, 290 ): PsiMethodItem { 291 assert(!psiMethod.isConstructor) 292 // UAST workaround: @JvmName for UMethod with fake LC PSI 293 // TODO: https://youtrack.jetbrains.com/issue/KTIJ-25133 294 val name = 295 if (psiMethod is KotlinUMethodWithFakeLightDelegateBase<*>) { 296 psiMethod.sourcePsi 297 ?.annotationEntries 298 ?.find { annoEntry -> 299 val text = annoEntry.typeReference?.text ?: return@find false 300 JvmName::class.qualifiedName?.contains(text) == true 301 } 302 ?.toUElement(UAnnotation::class.java) 303 ?.takeIf { 304 // Above `find` deliberately avoids resolution and uses verbatim text. 305 // Below, we need annotation value anyway, but just double-check 306 // if the converted annotation is indeed the resolved @JvmName 307 it.qualifiedName == JvmName::class.qualifiedName 308 } 309 ?.findAttributeValue("name") 310 ?.evaluate() as? String 311 ?: psiMethod.name 312 } else { 313 psiMethod.name 314 } 315 val commentText = javadoc(psiMethod, codebase.allowReadingComments) 316 val modifiers = modifiers(codebase, psiMethod, commentText) 317 // Create the TypeParameterList for this before wrapping any of the other types used by 318 // it as they may reference a type parameter in the list. 319 val (typeParameterList, methodTypeItemFactory) = 320 PsiTypeParameterList.create( 321 codebase, 322 enclosingClassTypeItemFactory, 323 "method $name", 324 psiMethod 325 ) 326 val parameters = parameterList(codebase, psiMethod, methodTypeItemFactory) 327 val fingerprint = MethodFingerprint(psiMethod.name, psiMethod.parameters.size) 328 val isAnnotationElement = containingClass.isAnnotationType() && !modifiers.isStatic() 329 val returnType = 330 methodTypeItemFactory.getMethodReturnType( 331 underlyingReturnType = PsiTypeInfo(psiMethod.returnType!!, psiMethod), 332 itemAnnotations = modifiers.annotations(), 333 fingerprint = fingerprint, 334 isAnnotationElement = isAnnotationElement, 335 ) 336 337 val method = 338 PsiMethodItem( 339 codebase = codebase, 340 psiMethod = psiMethod, 341 containingClass = containingClass, 342 name = name, 343 documentation = commentText, 344 modifiers = modifiers, 345 returnType = returnType, 346 parameters = parameters, 347 typeParameterList = typeParameterList, 348 throwsTypes = throwsTypes(psiMethod, methodTypeItemFactory), 349 ) 350 if (modifiers.isFinal() && containingClass.modifiers.isFinal()) { 351 // The containing class is final, so it is implied that every method is final as 352 // well. 353 // No need to apply 'final' to each method. (We do it here rather than just in the 354 // signature emit code since we want to make sure that the signature comparison 355 // methods with super methods also consider this method non-final.) 356 modifiers.setFinal(false) 357 } 358 359 return method 360 } 361 362 /** 363 * Create a [PsiMethodItem] from a [PsiMethodItem] in a hidden super class. 364 * 365 * @see ClassItem.inheritMethodFromNonApiAncestor 366 */ 367 internal fun create(containingClass: PsiClassItem, original: PsiMethodItem): PsiMethodItem { 368 val typeParameterBindings = containingClass.mapTypeVariables(original.containingClass()) 369 val returnType = original.returnType.convertType(typeParameterBindings) as PsiTypeItem 370 371 // This results in a PsiMethodItem that is inconsistent, compared with other 372 // PsiMethodItem. PsiMethodItems created directly from the source are such that: 373 // 374 // psiMethod.containingClass === containingClass().psiClass 375 // 376 // However, the PsiMethodItem created here contains a psiMethod from a different class, 377 // usually the super class, so: 378 // 379 // psiMethod.containingClass !== containingClass().psiClass 380 // 381 // If the method was created from the super class then: 382 // 383 // psiMethod.containingClass === containingClass().superClass().psiClass 384 // 385 // The consequence of this is that the PsiMethodItem does not behave as might be 386 // expected. e.g. superMethods() will find super methods of the method in the super 387 // class, not the PsiMethodItem's containing class. 388 val method = 389 PsiMethodItem( 390 codebase = original.codebase, 391 psiMethod = original.psiMethod, 392 containingClass = containingClass, 393 name = original.name(), 394 documentation = original.documentation, 395 modifiers = original.modifiers.duplicate(), 396 returnType = returnType, 397 parameters = 398 PsiParameterItem.create(original.parameters(), typeParameterBindings), 399 // This is probably incorrect as the type parameter bindings probably need 400 // applying here but this is the same behavior as before. 401 // TODO: Investigate whether the above comment is correct and fix if necessary. 402 typeParameterList = original.typeParameterList, 403 throwsTypes = original.throwsTypes, 404 ) 405 406 return method 407 } 408 409 internal fun parameterList( 410 codebase: PsiBasedCodebase, 411 psiMethod: PsiMethod, 412 enclosingTypeItemFactory: PsiTypeItemFactory, 413 ): List<PsiParameterItem> { 414 val psiParameters = psiMethod.psiParameters 415 val fingerprint = MethodFingerprint(psiMethod.name, psiParameters.size) 416 return psiParameters.mapIndexed { index, parameter -> 417 PsiParameterItem.create( 418 codebase, 419 fingerprint, 420 parameter, 421 index, 422 enclosingTypeItemFactory 423 ) 424 } 425 } 426 427 internal fun throwsTypes( 428 psiMethod: PsiMethod, 429 enclosingTypeItemFactory: PsiTypeItemFactory, 430 ): List<ExceptionTypeItem> { 431 val throwsClassTypes = psiMethod.throwsList.referencedTypes 432 if (throwsClassTypes.isEmpty()) { 433 return emptyList() 434 } 435 436 return throwsClassTypes 437 // Convert the PsiType to an ExceptionTypeItem and wrap it in a ThrowableType. 438 .map { psiType -> enclosingTypeItemFactory.getExceptionType(PsiTypeInfo(psiType)) } 439 // We're sorting the names here even though outputs typically do their own sorting, 440 // since for example the MethodItem.sameSignature check wants to do an 441 // element-by-element comparison to see if the signature matches, and that should 442 // match overrides even if they specify their elements in different orders. 443 .sortedWith(ExceptionTypeItem.fullNameComparator) 444 } 445 } 446 } 447 448 /** Get the [PsiParameter]s for a [PsiMethod]. */ 449 val PsiMethod.psiParameters: List<PsiParameter> 450 get() = if (this is UMethod) uastParameters else parameterList.parameters.toList() 451