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:
+15
-26
@@ -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(
|
||||
|
||||
+1
@@ -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")
|
||||
|
||||
+7
-3
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user