Implement module accessibility checks differently in compiler and IDE

Essentially, the logic that was previously in
JvmModuleAccessibilityChecker.diagnosticFor, is moved into a new
abstract method JavaModuleResolver.checkAccessibility, which is
implemented differently in the compiler and in the IDE. In the compiler,
we use our JavaModuleInfo and JavaModuleGraph, as previously. In the
IDE, we use intellij's PsiJavaModule and JavaModuleGraphUtil.

This fixes strange behavior in IDE where some modules could be observed
in an invalid state. The cause of that was the JavaModuleGraph instance
caching modules in IdeJavaModuleResolver, which is a project component.

Moreover, this will allow to report an error "named module does not read
unnamed module" in the compiler, and avoid reporting it in the IDE (see
the comment in IdeJavaModuleResolver about that)
This commit is contained in:
Alexander Udalov
2017-07-07 12:45:29 +03:00
parent 7edb352aeb
commit 5b8e58fe36
6 changed files with 97 additions and 53 deletions
@@ -34,8 +34,9 @@ import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.checkers.ClassifierUsageChecker
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.*
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver.AccessError.*
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
@@ -61,33 +62,21 @@ class JvmModuleAccessibilityChecker(project: Project) : CallChecker {
): Diagnostic? {
val referencedFile = findVirtualFile(targetClassOrPackage, originalDescriptor) ?: return null
// TODO: do not resolve our JavaModule every time, invent a way to obtain it from ModuleDescriptor and do it once in constructor
val ourModule = fileFromOurModule?.let(moduleResolver::findJavaModule)
val theirModule = moduleResolver.findJavaModule(referencedFile)
val referencedPackageFqName =
DescriptorUtils.getParentOfType(targetClassOrPackage, PackageFragmentDescriptor::class.java, false)?.fqName
val diagnostic = moduleResolver.checkAccessibility(fileFromOurModule, referencedFile, referencedPackageFqName)
// If we're both in the unnamed module, it's OK, no error should be reported
if (ourModule == null && theirModule == null) return null
if (theirModule == null) {
// We should probably prohibit this usage according to JPMS (named module cannot use types from unnamed module),
// but we cannot be sure that a module without module-info.java is going to be actually used as an unnamed module.
// It could also be an automatic module, in which case it would be read by every module.
return null
return when (diagnostic) {
is ModuleDoesNotReadUnnamedModule ->
// TODO: report this error as soon as module path is used instead of class path for modular compilation
// JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE.on(reportOn)
null
is ModuleDoesNotReadModule ->
JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE.on(reportOn, diagnostic.dependencyModuleName)
is ModuleDoesNotExportPackage ->
JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE.on(reportOn, diagnostic.dependencyModuleName, referencedPackageFqName!!.asString())
else -> null
}
if (ourModule?.name == theirModule.name) return null
if (ourModule != null && !moduleResolver.moduleGraph.reads(ourModule.name, theirModule.name)) {
return ErrorsJvm.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE.on(reportOn, theirModule.name)
}
val containingPackage = DescriptorUtils.getParentOfType(targetClassOrPackage, PackageFragmentDescriptor::class.java, false)
val fqName = containingPackage?.fqName ?: return null
if (!theirModule.exports(fqName) && (ourModule == null || !theirModule.exportsTo(fqName, ourModule.name))) {
return ErrorsJvm.JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE.on(reportOn, theirModule.name, fqName.asString())
}
return null
}
private fun findVirtualFile(
@@ -115,6 +115,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(OBSOLETE_SUSPEND_INLINE_FUNCTIONS_ABI, "Cannot inline suspend function built with compiler version less than 1.1.4/1.2-M1");
MAP.put(JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE, "Symbol is declared in module ''{0}'' which current module does not depend on", STRING);
MAP.put(JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE, "Symbol is declared in unnamed module which is not read by current module");
MAP.put(JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE, "Symbol is declared in module ''{0}'' which does not export package ''{1}''", STRING, STRING);
}
@@ -96,6 +96,7 @@ public interface ErrorsJvm {
DiagnosticFactory0<PsiElement> OBSOLETE_SUSPEND_INLINE_FUNCTIONS_ABI = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE = DiagnosticFactory2.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
@@ -19,12 +19,16 @@ package org.jetbrains.kotlin.resolve.jvm.modules
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.name.FqName
interface JavaModuleResolver {
val moduleGraph: JavaModuleGraph
fun checkAccessibility(fileFromOurModule: VirtualFile?, referencedFile: VirtualFile, referencedPackage: FqName?): AccessError?
// For any .java, .kt or .class file in the project, returns the corresponding module or null if there's none
fun findJavaModule(file: VirtualFile): JavaModule?
sealed class AccessError {
object ModuleDoesNotReadUnnamedModule : AccessError()
data class ModuleDoesNotReadModule(val dependencyModuleName: String) : AccessError()
data class ModuleDoesNotExportPackage(val dependencyModuleName: String) : AccessError()
}
companion object SERVICE {
fun getInstance(project: Project): JavaModuleResolver =