From e4cb5496b831c41d1e6b74813f1411a655df1276 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Fri, 24 Mar 2017 12:44:04 +0300 Subject: [PATCH] Optimize getSingleAbstractMethodOrNull Searching for the single abstract method leads to computing whole member scopes of given intrefaces, especially when they contain a lot of methods (i.e. they're definitely not SAMs) On the other side this method is very hot because it's called for each Java interface used as a type for some value parameter (for building SAM adapters) The idea is to apply some heuristics to understand using only JavaMethod and supertypes that this interface is definitely not a SAM --- .../java/sam/SingleAbstractMethodUtils.java | 6 ++--- .../java/descriptors/JavaClassDescriptor.java | 6 +++++ .../descriptors/LazyJavaClassDescriptor.kt | 25 +++++++++++++++++++ .../ExpressionsOfTypeProcessor.kt | 3 ++- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java index 5f4c574b6dc..6a0ccd97509 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/sam/SingleAbstractMethodUtils.java @@ -128,10 +128,8 @@ public class SingleAbstractMethodUtils { } @Nullable - public static FunctionDescriptor getSingleAbstractMethodOrNull(@NotNull ClassDescriptor klass) { - if (klass.getKind() != ClassKind.INTERFACE) { - return null; - } + public static FunctionDescriptor getSingleAbstractMethodOrNull(@NotNull JavaClassDescriptor klass) { + if (klass.isDefinitelyNotSamInterface()) return null; if (DescriptorUtilsKt.getFqNameSafe(klass).asString().equals("android.databinding.DataBindingComponent")) { return null; diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaClassDescriptor.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaClassDescriptor.java index 203a898f5fe..c8f9e978446 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaClassDescriptor.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/descriptors/JavaClassDescriptor.java @@ -23,4 +23,10 @@ import org.jetbrains.kotlin.types.SimpleType; public interface JavaClassDescriptor extends ClassDescriptor { @Nullable SimpleType getFunctionTypeForSamInterface(); + + /** + * May return false even in case when the class is not SAM interface, but returns true only if it's definitely not a SAM. + * But it should work much faster than the exact check. + */ + boolean isDefinitelyNotSamInterface(); } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt index a63f2dead58..e903aaa7a15 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt @@ -48,6 +48,7 @@ import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.utils.addIfNotNull +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.util.* class LazyJavaClassDescriptor( @@ -59,6 +60,11 @@ class LazyJavaClassDescriptor( outerContext.components.sourceElementFactory.source(jClass), /* isExternal = */ false), JavaClassDescriptor { + companion object { + @JvmStatic + private val PUBLIC_METHOD_NAMES_IN_OBJECT = setOf("equals", "hashCode", "getClass", "wait", "notify", "notifyAll", "toString") + } + private val c: LazyJavaResolverContext = outerContext.child(this, jClass) init { @@ -133,6 +139,25 @@ class LazyJavaClassDescriptor( override fun getFunctionTypeForSamInterface(): SimpleType? = c.components.samConversionResolver.resolveFunctionTypeIfSamInterface(this) + override fun isDefinitelyNotSamInterface(): Boolean { + if (kind != ClassKind.INTERFACE) return true + + val candidates = jClass.methods.filter { it.isAbstract && it.typeParameters.isEmpty() } + // From the definition of function interfaces in the Java specification (pt. 9.8): + // "methods that are members of I that do not have the same signature as any public instance method of the class Object" + // It means that if an interface declares `int hashCode()` then the method won't be taken into account when + // checking if the interface is SAM. + // We make here a conservative check just filtering out methods by name. + // If we ignore a method with wrong signature (different from one in Object) it's not very bad, + // we'll just say that the interface MAY BE a SAM when it's not and then more detailed check will be applied. + if (candidates.count { it.name.identifier !in PUBLIC_METHOD_NAMES_IN_OBJECT } > 1) return true + + // Check if any of the super-interfaces contain too many methods to be a SAM + return typeConstructor().supertypes.any { + it.constructor.declarationDescriptor.safeAs()?.isDefinitelyNotSamInterface == true + } + } + override fun getSealedSubclasses(): Collection = emptyList() override fun toString() = "Lazy Java class ${this.fqNameUnsafe}" diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt index 3a1f03b768c..c8153c5f63b 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/search/usagesSearch/ExpressionsOfTypeProcessor.kt @@ -52,6 +52,7 @@ import org.jetbrains.kotlin.idea.util.FuzzyType import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -508,7 +509,7 @@ class ExpressionsOfTypeProcessor( testLog?.add("Resolved java class to descriptor: ${psiClass.qualifiedName}") val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacadeByFile(psiClass.containingFile, JvmPlatform) - val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) + val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) as? JavaClassDescriptor if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) { addSamInterfaceToProcess(psiClass) }