[MPP] Allow smart casts for properties from dependsOn modules

Smartcasts for public properties from different module are not
stable because module declaring a property in general can be
compiled separately from the module using it. However, if client
module has dependsOn relation with declaring module their simultaneous
compilation is guaranteed which makes this smart cast safe.

Cache all transitive 'expected by' modules in module dependencies.
Extend test to check smart casts are allowed for properties from transitive
'expected by' dependencies and prohibited otherwise.

^KT-42754 Fixed
This commit is contained in:
Pavel Kirpichenkov
2020-11-03 18:40:04 +03:00
parent 778bbd76cb
commit b79b94fe75
15 changed files with 101 additions and 19 deletions
@@ -42,6 +42,8 @@ class FirModuleDescriptor(val session: FirSession) : ModuleDescriptor {
get() = TODO("not implemented")
override val expectedByModules: List<ModuleDescriptor>
get() = TODO("not implemented")
override val allExpectedByModules: Set<ModuleDescriptor>
get() = TODO("not implemented")
override fun <T> getCapability(capability: ModuleCapability<T>): T? {
return null
@@ -155,13 +155,20 @@ class LazyModuleDependencies<M : ModuleInfo>(
override val allDependencies: List<ModuleDescriptorImpl> get() = dependencies()
override val expectedByDependencies by storageManager.createLazyValue {
override val directExpectedByDependencies by storageManager.createLazyValue {
module.expectedBy.map {
@Suppress("UNCHECKED_CAST")
resolverForProject.descriptorForModule(it as M)
}
}
override val allExpectedByDependencies: Set<ModuleDescriptorImpl> by storageManager.createLazyValue {
collectAllExpectedByModules(module).mapTo(HashSet<ModuleDescriptorImpl>()) {
@Suppress("UNCHECKED_CAST")
resolverForProject.descriptorForModule(it as M)
}
}
override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>
get() =
module.modulesWhoseInternalsAreVisible().mapTo(LinkedHashSet()) {
@@ -9,3 +9,17 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
val ModuleDescriptor.moduleInfo: ModuleInfo?
get() = getCapability(ModuleInfo.Capability)
internal fun collectAllExpectedByModules(entryModule: ModuleInfo): Set<ModuleInfo> {
val unprocessedModules = ArrayDeque<ModuleInfo>().apply { addAll(entryModule.expectedBy) }
val expectedByModules = HashSet<ModuleInfo>()
while (unprocessedModules.isNotEmpty()) {
val nextImplemented = unprocessedModules.removeFirst()
if (expectedByModules.add(nextImplemented)) {
unprocessedModules.addAll(nextImplemented.expectedBy)
}
}
return expectedByModules
}
@@ -24,13 +24,23 @@ internal fun PropertyDescriptor.propertyKind(usageModule: ModuleDescriptor?): Da
if (!hasDefaultGetter()) return DataFlowValue.Kind.PROPERTY_WITH_GETTER
if (!isInvisibleFromOtherModules()) {
val declarationModule = DescriptorUtils.getContainingModule(this)
if (usageModule == null || usageModule != declarationModule) {
if (!areCompiledTogether(usageModule, declarationModule)) {
return DataFlowValue.Kind.ALIEN_PUBLIC_PROPERTY
}
}
return DataFlowValue.Kind.STABLE_VALUE
}
internal fun areCompiledTogether(
usageModule: ModuleDescriptor?,
declarationModule: ModuleDescriptor,
): Boolean {
if (usageModule == null) return false
if (usageModule == declarationModule) return true
return declarationModule in usageModule.allExpectedByModules
}
internal fun VariableDescriptor.variableKind(
usageModule: ModuleDescriptor?,
bindingContext: BindingContext,
@@ -52,6 +52,8 @@ interface ModuleDescriptor : DeclarationDescriptor {
val expectedByModules: List<ModuleDescriptor>
val allExpectedByModules: Set<ModuleDescriptor>
fun <T> getCapability(capability: ModuleCapability<T>): T?
class Capability<T>(val name: String) {
@@ -75,7 +75,10 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this }
override val expectedByModules: List<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.expectedByDependencies
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.directExpectedByDependencies
override val allExpectedByModules: Set<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allExpectedByDependencies
override fun getPackage(fqName: FqName): PackageViewDescriptor {
assertValid()
@@ -118,7 +121,7 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
}
fun setDependencies(descriptors: List<ModuleDescriptorImpl>, friends: Set<ModuleDescriptorImpl>) {
setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList()))
setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList(), emptySet()))
}
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean {
@@ -154,11 +157,13 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
interface ModuleDependencies {
val allDependencies: List<ModuleDescriptorImpl>
val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>
val expectedByDependencies: List<ModuleDescriptorImpl>
val directExpectedByDependencies: List<ModuleDescriptorImpl>
val allExpectedByDependencies: Set<ModuleDescriptorImpl>
}
class ModuleDependenciesImpl(
override val allDependencies: List<ModuleDescriptorImpl>,
override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>,
override val expectedByDependencies: List<ModuleDescriptorImpl>
override val directExpectedByDependencies: List<ModuleDescriptorImpl>,
override val allExpectedByDependencies: Set<ModuleDescriptorImpl>,
) : ModuleDependencies
@@ -107,6 +107,12 @@ public class ErrorUtils {
return emptyList();
}
@NotNull
@Override
public Set<ModuleDescriptor> getAllExpectedByModules() {
return emptySet();
}
@Override
public <R, D> R accept(@NotNull DeclarationDescriptorVisitor<R, D> visitor, D data) {
return null;
@@ -189,6 +189,9 @@ private object DebugLabelModuleDescriptor : DeclarationDescriptorImpl(Annotation
override val expectedByModules: List<ModuleDescriptor>
get() = emptyList()
override val allExpectedByModules: Set<ModuleDescriptor>
get() = emptySet()
override fun <T> getCapability(capability: ModuleCapability<T>): T? = null
override val isValid: Boolean
@@ -1,5 +0,0 @@
data class CommonDataClass(val property: CommonObject?)
object CommonObject {
fun doSomething() {}
}
@@ -0,0 +1,5 @@
data class CommonDataClass1(val property: CommonObject1?)
object CommonObject1 {
fun doSomething() {}
}
@@ -0,0 +1,5 @@
data class CommonDataClass2(val property: CommonObject2?)
object CommonObject2 {
fun doSomething() {}
}
@@ -0,0 +1,5 @@
data class CommonDataClass3(val property: CommonObject3?)
object CommonObject3 {
fun doSomething() {}
}
@@ -0,0 +1,5 @@
data class CommonDataClass4(val property: CommonObject4?)
object CommonObject4 {
fun doSomething() {}
}
@@ -1,7 +1,13 @@
MODULE common { platform=[JVM, JS, Native] }
MODULE common1 { platform=[JVM, JS, Native] }
MODULE common2 { platform=[JVM, JS, Native] }
MODULE common3 { platform=[JVM, JS, Native] }
MODULE common4 { platform=[JVM, JS, Native] }
MODULE jvm1 { platform=[JVM] }
MODULE jvm2 { platform=[JVM] }
jvm1 -> common { kind=DEPENDS_ON }
jvm2 -> common { kind=DEPENDS_ON }
common2 -> common1 { kind=DEPENDS_ON }
common3 -> common1 { kind=DEPENDS_ON }
common4 -> common2, common3 { kind=DEPENDS_ON }
jvm1 -> common4 { kind=DEPENDS_ON }
jvm2 -> common4 { kind=DEPENDS_ON }
jvm2 -> jvm1 { kind=DEPENDENCY }
@@ -1,9 +1,21 @@
fun test(fromCommon: CommonDataClass, fromJvm: JvmDataClass) {
if (fromCommon.property != null) {
<!SMARTCAST_IMPOSSIBLE!>fromCommon.property<!>.doSomething()
fun test(c1: CommonDataClass1, c2: CommonDataClass2, c3: CommonDataClass3, c4: CommonDataClass4, jvm: JvmDataClass) {
if (c1.property != null) {
<!DEBUG_INFO_SMARTCAST!>c1.property<!>.doSomething()
}
if (fromJvm.property != null) {
<!SMARTCAST_IMPOSSIBLE!>fromJvm.property<!>.doSomething()
if (c2.property != null) {
<!DEBUG_INFO_SMARTCAST!>c2.property<!>.doSomething()
}
if (c3.property != null) {
<!DEBUG_INFO_SMARTCAST!>c3.property<!>.doSomething()
}
if (c4.property != null) {
<!DEBUG_INFO_SMARTCAST!>c4.property<!>.doSomething()
}
if (jvm.property != null) {
<!SMARTCAST_IMPOSSIBLE!>jvm.property<!>.doSomething()
}
}