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
This commit is contained in:
+2
-4
@@ -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;
|
||||
|
||||
+6
@@ -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();
|
||||
}
|
||||
|
||||
+25
@@ -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<LazyJavaClassDescriptor>()?.isDefinitelyNotSamInterface == true
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSealedSubclasses(): Collection<ClassDescriptor> = emptyList()
|
||||
|
||||
override fun toString() = "Lazy Java class ${this.fqNameUnsafe}"
|
||||
|
||||
+2
-1
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user