ShadowedDeclarationsFilter: check for equivalence

If there are multiple copies of the same library on the classpath,
then ShadowedDeclarationsFilter becomes very slow because it
encounters many equal-signature declarations and thus has to resolve
a lot of calls in order to pick among them.

Having multiple copies of the same library on the classpath is
somewhat common in real-world projects. It occurs in the
JetBrains/intellij-kotlin project, for example. In that project,
ShadowedDeclarationsFilter ends up resolving thousands of calls,
accounting for around 80% of completion time when there are
many completion results (see KT-44276).

We can optimize ShadowedDeclarationsFilter by checking whether the
descriptors in an equal-signature group are structurally equivalent.
If they are, we can just pick one rather than running resolve.

Testing on a small project with Kotlin stdlib duplicated on the
classpath, this change reduces overhead in ShadowedDeclarationsFilter
from 1200 ms to 20 ms when running completion on the prefix 'a'.
End-to-end completion time is cut in half.

Test: JvmBasicCompletionTestGenerated.Common.Shadowing
This commit is contained in:
Matthew Gharrity
2021-01-12 01:51:08 +01:00
committed by Nikita Bobko
parent decfcd28d2
commit e2109c3f8f

View File

@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.idea.resolve.getLanguageVersionSettings
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.CallResolver
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
@@ -87,6 +88,12 @@ class ShadowedDeclarationsFilter(
return listOf(first)
}
// Optimization: if the descriptors are structurally equivalent then there is no need to run resolve.
// This can happen when the classpath contains multiple copies of the same library.
if (descriptors.all { DescriptorEquivalenceForOverrides.areEquivalent(first, it, allowCopiesFromTheSameDeclaration = true) }) {
return listOf(first)
}
val isFunction = first is FunctionDescriptor
val name = when (first) {
is ConstructorDescriptor -> first.constructedClass.name