findDecompiledDeclaration: find builtIns more accurately

Use resolveScope of a reference to help searching for builtIn binaries
Do not search non-builtIn descriptors in random scopes
This commit is contained in:
Pavel V. Talanov
2017-03-21 18:38:20 +03:00
parent 265e765ec9
commit ca6516c2a2
8 changed files with 119 additions and 44 deletions
@@ -56,6 +56,11 @@ public class ForTestCompileRuntime {
return assertExists(new File("dist/kotlinc/lib/kotlin-script-runtime.jar")); return assertExists(new File("dist/kotlinc/lib/kotlin-script-runtime.jar"));
} }
@NotNull
public static File runtimeSourcesJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-runtime-sources.jar"));
}
// TODO: Do not use these classes, remove them after stdlib tests are merged in the same build as the compiler // TODO: Do not use these classes, remove them after stdlib tests are merged in the same build as the compiler
@NotNull @NotNull
@Deprecated @Deprecated
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.codeInsight
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.decompiler.navigation.findDecompiledDeclaration import org.jetbrains.kotlin.idea.decompiler.navigation.findDecompiledDeclaration
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -31,21 +32,27 @@ object DescriptorToSourceUtilsIde {
} }
// Returns all PSI elements for descriptor. It can find declarations in builtins or decompiled code. // Returns all PSI elements for descriptor. It can find declarations in builtins or decompiled code.
fun getAllDeclarations(project: Project, targetDescriptor: DeclarationDescriptor): Collection<PsiElement> { fun getAllDeclarations(
val result = getDeclarationsStream(project, targetDescriptor).toHashSet() project: Project,
targetDescriptor: DeclarationDescriptor,
builtInsSearchScope: GlobalSearchScope? = null
): Collection<PsiElement> {
val result = getDeclarationsStream(project, targetDescriptor, builtInsSearchScope).toHashSet()
// filter out elements which are navigate to some other element of the result // filter out elements which are navigate to some other element of the result
// this is needed to avoid duplicated results for references to declaration in same library source file // this is needed to avoid duplicated results for references to declaration in same library source file
return result.filter { element -> result.none { element != it && it.navigationElement == element } } return result.filter { element -> result.none { element != it && it.navigationElement == element } }
} }
private fun getDeclarationsStream(project: Project, targetDescriptor: DeclarationDescriptor): Sequence<PsiElement> { private fun getDeclarationsStream(
project: Project, targetDescriptor: DeclarationDescriptor, builtInsSearchScope: GlobalSearchScope? = null
): Sequence<PsiElement> {
val effectiveReferencedDescriptors = DescriptorToSourceUtils.getEffectiveReferencedDescriptors(targetDescriptor).asSequence() val effectiveReferencedDescriptors = DescriptorToSourceUtils.getEffectiveReferencedDescriptors(targetDescriptor).asSequence()
return effectiveReferencedDescriptors.flatMap { effectiveReferenced -> return effectiveReferencedDescriptors.flatMap { effectiveReferenced ->
// References in library sources should be resolved to corresponding decompiled declarations, // References in library sources should be resolved to corresponding decompiled declarations,
// therefore we put both source declaration and decompiled declaration to stream, and afterwards we filter it in getAllDeclarations // therefore we put both source declaration and decompiled declaration to stream, and afterwards we filter it in getAllDeclarations
sequenceOfLazyValues( sequenceOfLazyValues(
{ DescriptorToSourceUtils.getSourceFromDescriptor(effectiveReferenced) }, { DescriptorToSourceUtils.getSourceFromDescriptor(effectiveReferenced) },
{ findDecompiledDeclaration(project, effectiveReferenced) } { findDecompiledDeclaration(project, effectiveReferenced, builtInsSearchScope) }
) )
}.filterNotNull() }.filterNotNull()
} }
@@ -21,6 +21,7 @@ import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.BinaryModuleInfo import org.jetbrains.kotlin.idea.caches.resolve.BinaryModuleInfo
@@ -46,7 +47,9 @@ import java.util.*
fun findDecompiledDeclaration( fun findDecompiledDeclaration(
project: Project, project: Project,
referencedDescriptor: DeclarationDescriptor referencedDescriptor: DeclarationDescriptor,
// TODO: should not require explicitly specified scope to search for builtIns, use SourceElement to provide such information
builtInsSearchScope: GlobalSearchScope?
): KtDeclaration? { ): KtDeclaration? {
if (ErrorUtils.isError(referencedDescriptor)) return null if (ErrorUtils.isError(referencedDescriptor)) return null
if (isLocal(referencedDescriptor)) return null if (isLocal(referencedDescriptor)) return null
@@ -54,11 +57,26 @@ fun findDecompiledDeclaration(
val binaryInfo = referencedDescriptor.module.getCapability(ModuleInfo.Capability) as? BinaryModuleInfo val binaryInfo = referencedDescriptor.module.getCapability(ModuleInfo.Capability) as? BinaryModuleInfo
val scope = binaryInfo?.binariesScope() ?: binaryInfo?.binariesScope()?.let {
// TODO: can't return null right now, have to deal with builtins return findInScope(referencedDescriptor, it)
KotlinSourceFilterScope.libraryClassFiles(EverythingGlobalScope(project), project) }
if (KotlinBuiltIns.isBuiltIn(referencedDescriptor)) {
// builtin module does not contain information about it's origin
return builtInsSearchScope?.let { findInScope(referencedDescriptor, it) }
// fallback on searching everywhere since builtIns are accessible from any context
?: findInScope(referencedDescriptor, GlobalSearchScope.allScope(project))
?: findInScope(referencedDescriptor, EverythingGlobalScope(project))
}
return null
}
val decompiledFiles = findDecompiledFilesForDescriptor(project, referencedDescriptor, scope) private fun findInScope(referencedDescriptor: DeclarationDescriptor, scope: GlobalSearchScope): KtDeclaration? {
val project = scope.project ?: return null
val decompiledFiles = findCandidateDeclarationsInIndex(
referencedDescriptor, KotlinSourceFilterScope.libraryClassFiles(scope, project), project
).mapNotNullTo(LinkedHashSet()) {
it?.containingFile as? KtDecompiledFile
}
return decompiledFiles.asSequence().mapNotNull { return decompiledFiles.asSequence().mapNotNull {
file -> file ->
@@ -75,20 +93,11 @@ private fun isLocal(descriptor: DeclarationDescriptor): Boolean {
} }
} }
private fun findDecompiledFilesForDescriptor(
project: Project,
referencedDescriptor: DeclarationDescriptor,
scope: GlobalSearchScope
): Collection<KtDecompiledFile> {
return findCandidateDeclarationsInIndex(project, referencedDescriptor, scope).mapNotNullTo(LinkedHashSet()) {
it?.containingFile as? KtDecompiledFile
}
}
private fun findCandidateDeclarationsInIndex( private fun findCandidateDeclarationsInIndex(
project: Project,
referencedDescriptor: DeclarationDescriptor, referencedDescriptor: DeclarationDescriptor,
scope: GlobalSearchScope scope: GlobalSearchScope,
project: Project
): Collection<KtDeclaration?> { ): Collection<KtDeclaration?> {
val containingClass = DescriptorUtils.getParentOfType(referencedDescriptor, ClassDescriptor::class.java, false) val containingClass = DescriptorUtils.getParentOfType(referencedDescriptor, ClassDescriptor::class.java, false)
if (containingClass != null) { if (containingClass != null) {
@@ -97,7 +97,7 @@ abstract class AbstractKtReference<T : KtElement>(element: T)
return listOfNotNull(psiFacade.findPackage(fqName)) return listOfNotNull(psiFacade.findPackage(fqName))
} }
else { else {
return DescriptorToSourceUtilsIde.getAllDeclarations(expression.project, targetDescriptor) return DescriptorToSourceUtilsIde.getAllDeclarations(expression.project, targetDescriptor, expression.resolveScope)
} }
} }
@@ -0,0 +1,6 @@
Sequence.class
public interface <1>Sequence<out T> {
collections.kotlin_builtins
public interface <3>Map<K, out V> {
kotlin.kotlin_builtins
public final class <2><4><5>Int private constructor() : kotlin.Number, kotlin.Comparable<kotlin.Int> {
@@ -0,0 +1,6 @@
Collections.kt
public interface <3>Map<K, out V> {
Primitives.kt
public class <2><4><5>Int private constructor() : Number(), Comparable<Int> {
Sequence.kt
public interface <1>Sequence<out T> {
@@ -0,0 +1,2 @@
fun main(s: Sequence<Int>, m: Map<Int, Int>) {
}
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.testFramework.ModuleTestCase import com.intellij.testFramework.ModuleTestCase
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.idea.caches.resolve.LibraryInfo import org.jetbrains.kotlin.idea.caches.resolve.LibraryInfo
import org.jetbrains.kotlin.idea.caches.resolve.LibrarySourceInfo import org.jetbrains.kotlin.idea.caches.resolve.LibrarySourceInfo
import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo
@@ -39,9 +40,9 @@ import org.jetbrains.kotlin.test.testFramework.runWriteAction
import org.junit.Assert import org.junit.Assert
import java.io.File import java.io.File
val testDataPath = PluginTestCaseBase.getTestDataPathBase() + "/decompiler/navigationMultipleLibs/" class NavigationWithMultipleCustomLibrariesTest : AbstractNavigationWithMultipleLibrariesTest() {
class NavigationWithMultipleLibrariesTest : ModuleTestCase() { override val testDataPath = PluginTestCaseBase.getTestDataPathBase() + "/decompiler/navigationMultipleLibs/"
fun testNavigationToDecompiled() { fun testNavigationToDecompiled() {
doTest(false, "expected.decompiled") doTest(false, "expected.decompiled")
@@ -51,14 +52,64 @@ class NavigationWithMultipleLibrariesTest : ModuleTestCase() {
doTest(true, "expected.sources") doTest(true, "expected.sources")
} }
override fun createProjectLib(libraryName: String, withSources: Boolean): Library {
val librarySrc = testDataPath + "libSrc"
val libraryJar = MockLibraryUtil.compileLibraryToJar(librarySrc, libraryName, withSources, false, false)
val jarRoot = libraryJar.jarRoot()
return runWriteAction {
val library = ProjectLibraryTable.getInstance(project).createLibrary(libraryName)
val modifiableModel = library.modifiableModel
modifiableModel.addRoot(jarRoot, OrderRootType.CLASSES)
if (withSources) {
modifiableModel.addRoot(jarRoot.findChild("src")!!, OrderRootType.SOURCES)
}
modifiableModel.commit()
library
}
}
}
class NavigationWithMultipleRuntimesTest : AbstractNavigationWithMultipleLibrariesTest() {
override val testDataPath = PluginTestCaseBase.getTestDataPathBase() + "/decompiler/navigationMultipleRuntimes/"
fun testNavigationToDecompiled() {
doTest(false, "expected.decompiled")
}
fun testNavigationToLibrarySources() {
doTest(true, "expected.sources")
}
override fun createProjectLib(libraryName: String, withSources: Boolean): Library {
val libraryJar = ForTestCompileRuntime.runtimeJarForTests().copyTo(File(createTempDirectory(), "$libraryName.jar"))
val jarUrl = libraryJar.jarRoot()
return runWriteAction {
val library = ProjectLibraryTable.getInstance(project).createLibrary(libraryName)
val modifiableModel = library.modifiableModel
modifiableModel.addRoot(jarUrl, OrderRootType.CLASSES)
if (withSources) {
val sourcesJar = ForTestCompileRuntime.runtimeSourcesJarForTests().copyTo(File(createTempDirectory(), "$libraryName-sources.jar"))
modifiableModel.addRoot(sourcesJar.jarRoot(), OrderRootType.SOURCES)
}
modifiableModel.commit()
library
}
}
}
abstract class AbstractNavigationWithMultipleLibrariesTest : ModuleTestCase() {
abstract val testDataPath: String
fun doTest(withSources: Boolean, expectedFileName: String) { fun doTest(withSources: Boolean, expectedFileName: String) {
val srcPath = testDataPath + "src" val srcPath = testDataPath + "src"
val moduleA = module("moduleA", srcPath) val moduleA = module("moduleA", srcPath)
val moduleB = module("moduleB", srcPath) val moduleB = module("moduleB", srcPath)
val librarySrc = testDataPath + "libSrc" addDependencyOnProjectLibrary(moduleA, "libA", withSources)
addDependencyOnProjectLibrary(moduleA, "libA", librarySrc, withSources) addDependencyOnProjectLibrary(moduleB, "libB", withSources)
addDependencyOnProjectLibrary(moduleB, "libB", librarySrc, withSources)
// navigation code works by providing first matching declaration from indices // navigation code works by providing first matching declaration from indices
// that's we need to check references in both modules to guard against possibility of code breaking // that's we need to check references in both modules to guard against possibility of code breaking
@@ -76,30 +127,17 @@ class NavigationWithMultipleLibrariesTest : ModuleTestCase() {
} }
private fun findSourceFile(moduleA: Module): PsiFile { private fun findSourceFile(moduleA: Module): PsiFile {
val ioFile = File(moduleA.getModuleDir()).listFiles()[0]; val ioFile = File(moduleA.getModuleDir()).listFiles().first()
val vFile = LocalFileSystem.getInstance().findFileByIoFile(ioFile)!! val vFile = LocalFileSystem.getInstance().findFileByIoFile(ioFile)!!
return PsiManager.getInstance(project).findFile(vFile)!! return PsiManager.getInstance(project).findFile(vFile)!!
} }
private fun addDependencyOnProjectLibrary(mainModule: Module, libraryName: String, librarySrc: String, withSources: Boolean) { private fun addDependencyOnProjectLibrary(mainModule: Module, libraryName: String, withSources: Boolean) {
val library = createProjectLib(libraryName, librarySrc, withSources) val library = createProjectLib(libraryName, withSources)
ModuleRootModificationUtil.addDependency(mainModule, library, DependencyScope.COMPILE, false) ModuleRootModificationUtil.addDependency(mainModule, library, DependencyScope.COMPILE, false)
} }
private fun createProjectLib(libName: String, librarySrc: String, withSources: Boolean): Library { abstract fun createProjectLib(libraryName: String, withSources: Boolean): Library
val libraryJar = MockLibraryUtil.compileLibraryToJar(librarySrc, libName, withSources, false, false)
val jarUrl = StandardFileSystems.getJarRootForLocalFile(LocalFileSystem.getInstance().findFileByIoFile(libraryJar)!!)!!
return runWriteAction {
val library = ProjectLibraryTable.getInstance(project).createLibrary(libName)
val modifiableModel = library.modifiableModel
modifiableModel.addRoot(jarUrl, OrderRootType.CLASSES)
if (withSources) {
modifiableModel.addRoot(jarUrl.findChild("src")!!, OrderRootType.SOURCES)
}
modifiableModel.commit()
library
}
}
} }
@@ -112,4 +150,6 @@ private fun checkLibraryName(referenceTarget: PsiElement, expectedName: String)
else -> error("Couldn't get library name") else -> error("Couldn't get library name")
} }
Assert.assertEquals("Referenced code from unrelated library: ${referenceTarget.text}", expectedName, libraryName) Assert.assertEquals("Referenced code from unrelated library: ${referenceTarget.text}", expectedName, libraryName)
} }
private fun File.jarRoot() = StandardFileSystems.getJarRootForLocalFile(LocalFileSystem.getInstance().findFileByIoFile(this)!!)!!