[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:
+2
-2
@@ -96,7 +96,7 @@ val ModuleDescriptor.implementingDescriptors: List<ModuleDescriptor>
|
|||||||
return implementingModuleInfos.mapNotNull { it.toDescriptor() }
|
return implementingModuleInfos.mapNotNull { it.toDescriptor() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Module.toInfo(type: SourceType): ModuleSourceInfo? = when (type) {
|
fun Module.toInfo(type: SourceType): ModuleSourceInfo? = when (type) {
|
||||||
PRODUCTION -> productionSourceInfo()
|
PRODUCTION -> productionSourceInfo()
|
||||||
TEST -> testSourceInfo()
|
TEST -> testSourceInfo()
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ val ModuleDescriptor.implementedDescriptors: List<ModuleDescriptor>
|
|||||||
return moduleSourceInfo.expectedBy.mapNotNull { it.toDescriptor() }
|
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
|
.getResolutionFacadeByModuleInfo(this, platform)?.moduleDescriptor
|
||||||
|
|
||||||
fun PsiElement.getPlatformModuleInfo(desiredPlatform: TargetPlatform): PlatformModuleInfo? {
|
fun PsiElement.getPlatformModuleInfo(desiredPlatform: TargetPlatform): PlatformModuleInfo? {
|
||||||
|
|||||||
+82
-14
@@ -5,9 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.project
|
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.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors
|
import org.jetbrains.kotlin.idea.caches.project.*
|
||||||
import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors
|
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.ModulePath
|
||||||
import org.jetbrains.kotlin.resolve.ModuleStructureOracle
|
import org.jetbrains.kotlin.resolve.ModuleStructureOracle
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -18,31 +21,53 @@ class IdeaModuleStructureOracle : ModuleStructureOracle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun findAllReversedDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
|
override fun findAllReversedDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
|
||||||
val currentPath: Stack<ModuleDescriptor> = Stack()
|
val currentPath: Stack<ModuleInfo> = Stack()
|
||||||
|
|
||||||
return sequence<ModulePath> {
|
return sequence<ModuleInfoPath> {
|
||||||
yieldPathsFromSubgraph(module, currentPath, getChilds = { it.implementingDescriptors })
|
val root = module.moduleInfo
|
||||||
|
if (root != null) {
|
||||||
|
yieldPathsFromSubgraph(
|
||||||
|
root,
|
||||||
|
currentPath,
|
||||||
|
getChilds = {
|
||||||
|
with(DependsOnGraphHelper) { it.unwrapModuleSourceInfo()?.predecessorsInDependsOnGraph() ?: emptyList() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
it.toModulePath()
|
||||||
}.toList()
|
}.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findAllDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
|
override fun findAllDependsOnPaths(module: ModuleDescriptor): List<ModulePath> {
|
||||||
val currentPath: Stack<ModuleDescriptor> = Stack()
|
val currentPath: Stack<ModuleInfo> = Stack()
|
||||||
|
|
||||||
return sequence<ModulePath> {
|
return sequence<ModuleInfoPath> {
|
||||||
yieldPathsFromSubgraph(module, currentPath, getChilds = { it.implementedDescriptors })
|
val root = module.moduleInfo
|
||||||
|
if (root != null) {
|
||||||
|
yieldPathsFromSubgraph(
|
||||||
|
root,
|
||||||
|
currentPath,
|
||||||
|
getChilds = {
|
||||||
|
with(DependsOnGraphHelper) { it.unwrapModuleSourceInfo()?.successorsInDependsOnGraph() ?: emptyList() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
it.toModulePath()
|
||||||
}.toList()
|
}.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun SequenceScope<ModulePath>.yieldPathsFromSubgraph(
|
private suspend fun SequenceScope<ModuleInfoPath>.yieldPathsFromSubgraph(
|
||||||
root: ModuleDescriptor,
|
root: ModuleInfo,
|
||||||
currentPath: Stack<ModuleDescriptor>,
|
currentPath: Stack<ModuleInfo>,
|
||||||
getChilds: (ModuleDescriptor) -> List<ModuleDescriptor>
|
getChilds: (ModuleInfo) -> List<ModuleInfo>
|
||||||
) {
|
) {
|
||||||
currentPath.push(root)
|
currentPath.push(root)
|
||||||
|
|
||||||
val childs = getChilds(root)
|
val childs = getChilds(root)
|
||||||
if (childs.isEmpty()) {
|
if (childs.isEmpty()) {
|
||||||
yield(ModulePath(currentPath.toList()))
|
yield(ModuleInfoPath(currentPath.toList()))
|
||||||
} else {
|
} else {
|
||||||
childs.forEach {
|
childs.forEach {
|
||||||
yieldPathsFromSubgraph(it, currentPath, getChilds)
|
yieldPathsFromSubgraph(it, currentPath, getChilds)
|
||||||
@@ -51,4 +76,47 @@ class IdeaModuleStructureOracle : ModuleStructureOracle {
|
|||||||
|
|
||||||
currentPath.pop()
|
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
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
actual class A {
|
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!> {
|
||||||
actual fun foo(): Int = 45
|
actual fun foo(): Int = 45
|
||||||
fun fromBottom(): Int = 0
|
fun fromBottom(): Int = 0
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,6 @@ fun main() {
|
|||||||
// Any behaviour is acceptable, as the code is erroneous.
|
// Any behaviour is acceptable, as the code is erroneous.
|
||||||
// At the time of writing this test, we resolve to nearest A, i.e.
|
// At the time of writing this test, we resolve to nearest A, i.e.
|
||||||
// 'fromBottom' is resolved, and 'fromLeft' is not.
|
// 'fromBottom' is resolved, and 'fromLeft' is not.
|
||||||
A().<!UNRESOLVED_REFERENCE("fromLeft")!>fromLeft<!>()
|
A().fromLeft()
|
||||||
A().fromBottom()
|
A().<!UNRESOLVED_REFERENCE("fromBottom")!>fromBottom<!>()
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "bottom for JVM, left")!>A<!> {
|
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, bottom.kt"), AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, bottom.kt")!>A<!> {
|
||||||
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "bottom for JVM, left")!>foo<!>(): Int
|
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, bottom.kt"), AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, bottom.kt")!>foo<!>(): Int
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
expect class A {
|
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, right.kt"), AMBIGUOUS_ACTUALS("Class 'A'", "left.kt, right.kt")!>A<!> {
|
||||||
fun foo(): Int
|
fun <!AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, right.kt"), AMBIGUOUS_ACTUALS("Function 'foo'", "left.kt, right.kt")!>foo<!>(): Int
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
actual class A
|
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "bottom for JVM, middle")!>A<!>
|
expect class <!AMBIGUOUS_ACTUALS("Class 'A'", "middle.kt, bottom.kt")!>A<!>
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
class <!ACTUAL_MISSING!>ActualInMiddleCompatibleInBottom<!>
|
class <!ACTUAL_MISSING, PACKAGE_OR_CLASSIFIER_REDECLARATION("ActualInMiddleCompatibleInBottom")!>ActualInMiddleCompatibleInBottom<!>
|
||||||
actual class CompatibleInMiddleActualInBottom
|
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("CompatibleInMiddleActualInBottom")!>CompatibleInMiddleActualInBottom<!>
|
||||||
|
|
||||||
class <!ACTUAL_MISSING!>CompatibleInMiddleAndBottom<!>
|
class <!ACTUAL_MISSING, PACKAGE_OR_CLASSIFIER_REDECLARATION("CompatibleInMiddleAndBottom")!>CompatibleInMiddleAndBottom<!>
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
expect class <!AMBIGUOUS_ACTUALS("Class 'ActualInMiddleCompatibleInBottom'", "bottom for JVM, middle")!>ActualInMiddleCompatibleInBottom<!>
|
expect class <!AMBIGUOUS_ACTUALS("Class 'ActualInMiddleCompatibleInBottom'", "middle.kt, bottom.kt")!>ActualInMiddleCompatibleInBottom<!>
|
||||||
expect class <!AMBIGUOUS_ACTUALS("Class 'CompatibleInMiddleActualInBottom'", "bottom for JVM, middle")!>CompatibleInMiddleActualInBottom<!>
|
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
-1
@@ -1,3 +1,3 @@
|
|||||||
package foo
|
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
-1
@@ -1,3 +1,3 @@
|
|||||||
package foo
|
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
@@ -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
-1
@@ -1,3 +1,3 @@
|
|||||||
// Note that here we have no ambiguity, becuase we don't consider
|
// Note that here we have no ambiguity, becuase we don't consider
|
||||||
// declarations without 'expect' as potential weakly-compatible 'expect'-counterpart
|
// declarations without 'expect' as potential weakly-compatible 'expect'-counterpart
|
||||||
actual class A
|
actual class <!PACKAGE_OR_CLASSIFIER_REDECLARATION("A")!>A<!>
|
||||||
Reference in New Issue
Block a user