[Expect/Actual] Adjust IdeaModuleStructureOracle to old IDE Resolve

The main issue is that we have CombinedModuleInfo in old IDE Resolve,
which essentially merges platfor-modules together with its
dependsOn-parents. It conceals real module structure, which is fine for
practically all clients except for ExpectedActualDeclarationChecker.

In particular, it is nearly impossible to check expect/actual via
ModuleDescriptors, because you'll always get one and the same
'merged'-module descriptor.

So, this commit rewrites IdeaModuleStructureOracle in the following way:
- use ModuleInfos instead of ModuleDescriptor, as they allow more
fine-grained way to work with modules
- use 'unwrapModuleSourceInfo' in seemingly random places in order to
get real, non-merged moduleInfos
- carefully convert them back to ModuleDescriptors, because EADC work
with descriptors

This is still not an ideal solution. In particular, last step is flawed:
conversion back to ModuleDescriptors will always return merged
moduleDescriptor for platform-modules. There's no easy way to fix that
except for removing CombinedModuleInfo altogether, so we leave it as
known issue.

Also, there are some weird changes in testdata (again, because sometimes
we see several modules merged together), but they are mostly confusing
rather than plain incorrect.
This commit is contained in:
Dmitry Savvinov
2019-06-03 09:33:38 +03:00
parent 54b8f9da28
commit 8df759b05c
13 changed files with 103 additions and 35 deletions
@@ -96,7 +96,7 @@ val ModuleDescriptor.implementingDescriptors: List<ModuleDescriptor>
return implementingModuleInfos.mapNotNull { it.toDescriptor() }
}
private fun Module.toInfo(type: SourceType): ModuleSourceInfo? = when (type) {
fun Module.toInfo(type: SourceType): ModuleSourceInfo? = when (type) {
PRODUCTION -> productionSourceInfo()
TEST -> testSourceInfo()
}
@@ -115,7 +115,7 @@ val ModuleDescriptor.implementedDescriptors: List<ModuleDescriptor>
return moduleSourceInfo.expectedBy.mapNotNull { it.toDescriptor() }
}
private fun ModuleSourceInfo.toDescriptor() = KotlinCacheService.getInstance(module.project)
fun ModuleSourceInfo.toDescriptor() = KotlinCacheService.getInstance(module.project)
.getResolutionFacadeByModuleInfo(this, platform)?.moduleDescriptor
fun PsiElement.getPlatformModuleInfo(desiredPlatform: TargetPlatform): PlatformModuleInfo? {
@@ -5,9 +5,12 @@
package org.jetbrains.kotlin.idea.project
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors
import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors
import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.caches.project.sourceType
import org.jetbrains.kotlin.idea.core.unwrapModuleSourceInfo
import org.jetbrains.kotlin.resolve.ModulePath
import org.jetbrains.kotlin.resolve.ModuleStructureOracle
import java.util.*
@@ -18,31 +21,53 @@ class IdeaModuleStructureOracle : ModuleStructureOracle {
}
override fun findAllReversedDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
val currentPath: Stack<ModuleDescriptor> = Stack()
val currentPath: Stack<ModuleInfo> = Stack()
return sequence<ModulePath> {
yieldPathsFromSubgraph(module, currentPath, getChilds = { it.implementingDescriptors })
return sequence<ModuleInfoPath> {
val root = module.moduleInfo
if (root != null) {
yieldPathsFromSubgraph(
root,
currentPath,
getChilds = {
with(DependsOnGraphHelper) { it.unwrapModuleSourceInfo()?.predecessorsInDependsOnGraph() ?: emptyList() }
}
)
}
}.map {
it.toModulePath()
}.toList()
}
override fun findAllDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
val currentPath: Stack<ModuleDescriptor> = Stack()
val currentPath: Stack<ModuleInfo> = Stack()
return sequence<ModulePath> {
yieldPathsFromSubgraph(module, currentPath, getChilds = { it.implementedDescriptors })
return sequence<ModuleInfoPath> {
val root = module.moduleInfo
if (root != null) {
yieldPathsFromSubgraph(
root,
currentPath,
getChilds = {
with(DependsOnGraphHelper) { it.unwrapModuleSourceInfo()?.successorsInDependsOnGraph() ?: emptyList() }
}
)
}
}.map {
it.toModulePath()
}.toList()
}
private suspend fun SequenceScope<ModulePath>.yieldPathsFromSubgraph(
root: ModuleDescriptor,
currentPath: Stack<ModuleDescriptor>,
getChilds: (ModuleDescriptor) -> List<ModuleDescriptor>
private suspend fun SequenceScope<ModuleInfoPath>.yieldPathsFromSubgraph(
root: ModuleInfo,
currentPath: Stack<ModuleInfo>,
getChilds: (ModuleInfo) -> List<ModuleInfo>
) {
currentPath.push(root)
val childs = getChilds(root)
if (childs.isEmpty()) {
yield(ModulePath(currentPath.toList()))
yield(ModuleInfoPath(currentPath.toList()))
} else {
childs.forEach {
yieldPathsFromSubgraph(it, currentPath, getChilds)
@@ -51,4 +76,47 @@ class IdeaModuleStructureOracle : ModuleStructureOracle {
currentPath.pop()
}
}
private class ModuleInfoPath(val nodes: List<ModuleInfo>)
private fun ModuleInfoPath.toModulePath(): ModulePath =
ModulePath(nodes.mapNotNull { it.unwrapModuleSourceInfo()?.toDescriptor() })
}
object DependsOnGraphHelper {
fun ModuleDescriptor.predecessorsInDependsOnGraph(): List<ModuleDescriptor> {
return moduleSourceInfo
?.predecessorsInDependsOnGraph()
?.mapNotNull { it.toDescriptor() }
?: emptyList()
}
fun ModuleSourceInfo.predecessorsInDependsOnGraph(): List<ModuleSourceInfo> {
return this.module.predecessorsInDependsOnGraph().mapNotNull { it.toInfo(sourceType) }
}
fun Module.predecessorsInDependsOnGraph(): List<Module> {
return implementingModules
}
fun ModuleDescriptor.successorsInDependsOnGraph(): List<ModuleDescriptor> {
return moduleSourceInfo
?.successorsInDependsOnGraph()
?.mapNotNull { it.toDescriptor() }
?: emptyList()
}
fun ModuleSourceInfo.successorsInDependsOnGraph(): List<ModuleSourceInfo> {
return module.successorsInDependsOnGraph().mapNotNull { it.toInfo(sourceType) }
}
fun Module.successorsInDependsOnGraph(): List<Module> {
return implementedModules
}
}
private val ModuleDescriptor.moduleInfo: ModuleInfo?
get() = getCapability(ModuleInfo.Capability)?.unwrapModuleSourceInfo()
private val ModuleDescriptor.moduleSourceInfo: ModuleSourceInfo?
get() = moduleInfo as? ModuleSourceInfo
@@ -1,6 +1,6 @@
package sample
actual class A {
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!> {
actual fun foo(): Int = 45
fun fromBottom(): Int = 0
}
@@ -11,6 +11,6 @@ fun main() {
// Any behaviour is acceptable, as the code is erroneous.
// At the time of writing this test, we resolve to nearest A, i.e.
// 'fromBottom' is resolved, and 'fromLeft' is not.
A().<!UNRESOLVED_REFERENCE("fromLeft")!>fromLeft<!>()
A().fromBottom()
A().fromLeft()
A().<!UNRESOLVED_REFERENCE("fromBottom")!>fromBottom<!>()
}
@@ -1,5 +1,5 @@
package sample
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "bottom for JVM, left")!>A<!> {
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "bottom for JVM, left")!>foo<!>(): Int
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, bottom.kt"), AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, bottom.kt")!>A<!> {
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, bottom.kt"), AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, bottom.kt")!>foo<!>(): Int
}
@@ -1,5 +1,5 @@
package sample
expect class A {
fun foo(): Int
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, right.kt"), AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, right.kt")!>A<!> {
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, right.kt"), AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, right.kt")!>foo<!>(): Int
}
@@ -1,3 +1,3 @@
package foo
actual class A
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>
@@ -1,3 +1,3 @@
package foo
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "bottom for JVM, middle")!>A<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "middle.kt, bottom.kt")!>A<!>
@@ -1,6 +1,6 @@
package foo
class <!ACTUAL_MISSING!>ActualInMiddleCompatibleInBottom<!>
actual class CompatibleInMiddleActualInBottom
class <!ACTUAL_MISSING, PACKAGE_OR_CLASSIFIER_REDECLARATION("ActualInMiddleCompatibleInBottom")!>ActualInMiddleCompatibleInBottom<!>
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("CompatibleInMiddleActualInBottom")!>CompatibleInMiddleActualInBottom<!>
class <!ACTUAL_MISSING!>CompatibleInMiddleAndBottom<!>
class <!ACTUAL_MISSING, PACKAGE_OR_CLASSIFIER_REDECLARATION("CompatibleInMiddleAndBottom")!>CompatibleInMiddleAndBottom<!>
@@ -1,6 +1,6 @@
package foo
expect class <!AMBIGUOUS_ACTUALS("Class 'ActualInMiddleCompatibleInBottom'", "bottom for JVM, middle")!>ActualInMiddleCompatibleInBottom<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'CompatibleInMiddleActualInBottom'", "bottom for JVM, middle")!>CompatibleInMiddleActualInBottom<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'ActualInMiddleCompatibleInBottom'", "middle.kt, bottom.kt")!>ActualInMiddleCompatibleInBottom<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'CompatibleInMiddleActualInBottom'", "bottom.kt, middle.kt")!>CompatibleInMiddleActualInBottom<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'CompatibleInMiddleAndBottom'", "bottom for JVM, middle")!>CompatibleInMiddleAndBottom<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'CompatibleInMiddleAndBottom'", "middle.kt, bottom.kt")!>CompatibleInMiddleAndBottom<!>
@@ -1,3 +1,3 @@
package foo
<!ACTUAL_WITHOUT_EXPECT("Class 'A'", " The following declaration is incompatible because visibility is different: public final expect class A ")!>private<!> class A
<!ACTUAL_WITHOUT_EXPECT("Class 'A'", " The following declaration is incompatible because visibility is different: public final expect class A ")!>private<!> class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>
@@ -1,3 +1,3 @@
package foo
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "middle, bottom for JVM")!>A<!>
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "middle.kt, bottom.kt")!>A<!>
@@ -1 +1 @@
actual class <!AMBIGUOUS_EXPECTS("Actual class 'A'", "right, left")!>A<!>
actual class <!AMBIGUOUS_EXPECTS("Actual class 'A'", "left.kt, right.kt"), PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>
@@ -1,3 +1,3 @@
// Note that here we have no ambiguity, becuase we don't consider
// declarations without 'expect' as potential weakly-compatible 'expect'-counterpart
actual class A
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>