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:
+5
@@ -56,6 +56,11 @@ public class ForTestCompileRuntime {
|
||||
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
|
||||
@NotNull
|
||||
@Deprecated
|
||||
|
||||
+11
-4
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.codeInsight
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.decompiler.navigation.findDecompiledDeclaration
|
||||
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.
|
||||
fun getAllDeclarations(project: Project, targetDescriptor: DeclarationDescriptor): Collection<PsiElement> {
|
||||
val result = getDeclarationsStream(project, targetDescriptor).toHashSet()
|
||||
fun getAllDeclarations(
|
||||
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
|
||||
// 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 } }
|
||||
}
|
||||
|
||||
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()
|
||||
return effectiveReferencedDescriptors.flatMap { effectiveReferenced ->
|
||||
// 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
|
||||
sequenceOfLazyValues(
|
||||
{ DescriptorToSourceUtils.getSourceFromDescriptor(effectiveReferenced) },
|
||||
{ findDecompiledDeclaration(project, effectiveReferenced) }
|
||||
{ findDecompiledDeclaration(project, effectiveReferenced, builtInsSearchScope) }
|
||||
)
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
+25
-16
@@ -21,6 +21,7 @@ import com.intellij.psi.search.EverythingGlobalScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.BinaryModuleInfo
|
||||
@@ -46,7 +47,9 @@ import java.util.*
|
||||
|
||||
fun findDecompiledDeclaration(
|
||||
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? {
|
||||
if (ErrorUtils.isError(referencedDescriptor)) return null
|
||||
if (isLocal(referencedDescriptor)) return null
|
||||
@@ -54,11 +57,26 @@ fun findDecompiledDeclaration(
|
||||
|
||||
val binaryInfo = referencedDescriptor.module.getCapability(ModuleInfo.Capability) as? BinaryModuleInfo
|
||||
|
||||
val scope = binaryInfo?.binariesScope() ?:
|
||||
// TODO: can't return null right now, have to deal with builtins
|
||||
KotlinSourceFilterScope.libraryClassFiles(EverythingGlobalScope(project), project)
|
||||
binaryInfo?.binariesScope()?.let {
|
||||
return findInScope(referencedDescriptor, it)
|
||||
}
|
||||
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 {
|
||||
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(
|
||||
project: Project,
|
||||
referencedDescriptor: DeclarationDescriptor,
|
||||
scope: GlobalSearchScope
|
||||
scope: GlobalSearchScope,
|
||||
project: Project
|
||||
): Collection<KtDeclaration?> {
|
||||
val containingClass = DescriptorUtils.getParentOfType(referencedDescriptor, ClassDescriptor::class.java, false)
|
||||
if (containingClass != null) {
|
||||
|
||||
@@ -97,7 +97,7 @@ abstract class AbstractKtReference<T : KtElement>(element: T)
|
||||
return listOfNotNull(psiFacade.findPackage(fqName))
|
||||
}
|
||||
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>) {
|
||||
}
|
||||
+63
-23
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
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.LibrarySourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo
|
||||
@@ -39,9 +40,9 @@ import org.jetbrains.kotlin.test.testFramework.runWriteAction
|
||||
import org.junit.Assert
|
||||
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() {
|
||||
doTest(false, "expected.decompiled")
|
||||
@@ -51,14 +52,64 @@ class NavigationWithMultipleLibrariesTest : ModuleTestCase() {
|
||||
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) {
|
||||
val srcPath = testDataPath + "src"
|
||||
val moduleA = module("moduleA", srcPath)
|
||||
val moduleB = module("moduleB", srcPath)
|
||||
|
||||
val librarySrc = testDataPath + "libSrc"
|
||||
addDependencyOnProjectLibrary(moduleA, "libA", librarySrc, withSources)
|
||||
addDependencyOnProjectLibrary(moduleB, "libB", librarySrc, withSources)
|
||||
addDependencyOnProjectLibrary(moduleA, "libA", withSources)
|
||||
addDependencyOnProjectLibrary(moduleB, "libB", withSources)
|
||||
|
||||
// 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
|
||||
@@ -76,30 +127,17 @@ class NavigationWithMultipleLibrariesTest : ModuleTestCase() {
|
||||
}
|
||||
|
||||
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)!!
|
||||
return PsiManager.getInstance(project).findFile(vFile)!!
|
||||
}
|
||||
|
||||
private fun addDependencyOnProjectLibrary(mainModule: Module, libraryName: String, librarySrc: String, withSources: Boolean) {
|
||||
val library = createProjectLib(libraryName, librarySrc, withSources)
|
||||
private fun addDependencyOnProjectLibrary(mainModule: Module, libraryName: String, withSources: Boolean) {
|
||||
val library = createProjectLib(libraryName, withSources)
|
||||
ModuleRootModificationUtil.addDependency(mainModule, library, DependencyScope.COMPILE, false)
|
||||
}
|
||||
|
||||
private fun createProjectLib(libName: String, librarySrc: 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
|
||||
}
|
||||
}
|
||||
abstract fun createProjectLib(libraryName: String, withSources: Boolean): Library
|
||||
}
|
||||
|
||||
|
||||
@@ -112,4 +150,6 @@ private fun checkLibraryName(referenceTarget: PsiElement, expectedName: String)
|
||||
else -> error("Couldn't get library name")
|
||||
}
|
||||
Assert.assertEquals("Referenced code from unrelated library: ${referenceTarget.text}", expectedName, libraryName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.jarRoot() = StandardFileSystems.getJarRootForLocalFile(LocalFileSystem.getInstance().findFileByIoFile(this)!!)!!
|
||||
|
||||
Reference in New Issue
Block a user