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"));
|
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
|
||||||
|
|||||||
+11
-4
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-16
@@ -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>) {
|
||||||
|
}
|
||||||
+63
-23
@@ -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)!!)!!
|
||||||
|
|||||||
Reference in New Issue
Block a user