Implement Java 9 module visibility checks

In this commit, only IDE tests are added, because we look for module
declarations in the IDE across the whole project, whereas in the
compiler we should do this on the module path only and that requires
separate work (KT-18599) which is done in the following commits.

(The change in Cache.kt is needed so that
JvmModuleAccessibilityChecker.ClassifierUsage, which is an inner class,
would be injected properly.)

 #KT-18598 In Progress
 #KT-18599 In Progress
This commit is contained in:
Alexander Udalov
2017-05-18 19:05:53 +03:00
parent 2275068c94
commit e32880d9a3
56 changed files with 678 additions and 36 deletions
@@ -44,9 +44,11 @@ internal class ClasspathRootsResolver(
private val contentRootToVirtualFile: (JvmContentRoot) -> VirtualFile?
) {
private val javaModuleFinder = CliJavaModuleFinder(VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL))
private val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
val javaModuleGraph = JavaModuleGraph(javaModuleFinder)
fun convertClasspathRoots(contentRoots: Iterable<ContentRoot>): List<JavaRoot> {
data class RootsAndModules(val roots: List<JavaRoot>, val modules: List<JavaModule>)
fun convertClasspathRoots(contentRoots: Iterable<ContentRoot>): RootsAndModules {
val result = mutableListOf<JavaRoot>()
val modules = ArrayList<JavaModule>()
@@ -79,7 +81,7 @@ internal class ClasspathRootsResolver(
addModularRoots(modules, result)
return result
return RootsAndModules(result, modules)
}
private fun modularSourceRoot(root: VirtualFile): JavaModule.Explicit? {
@@ -78,6 +78,7 @@ import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.index.*
import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar
import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
@@ -98,6 +99,7 @@ import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
@@ -194,7 +196,9 @@ class KotlinCoreEnvironment private constructor(
classpathRootsResolver = ClasspathRootsResolver(PsiManager.getInstance(project), messageCollector, this::contentRootToVirtualFile)
initialRoots = classpathRootsResolver.convertClasspathRoots(configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS))
val (initialRoots, javaModules) =
classpathRootsResolver.convertClasspathRoots(configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS))
this.initialRoots = initialRoots
if (!configuration.getBoolean(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK) && messageCollector != null) {
JvmRuntimeVersionsConsistencyChecker.checkCompilerClasspathConsistency(
@@ -219,6 +223,11 @@ class KotlinCoreEnvironment private constructor(
configuration.getBoolean(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING)
)
project.registerService(
JavaModuleResolver::class.java,
CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules)
)
val finderFactory = CliVirtualFileFinderFactory(rootsIndex)
project.registerService(MetadataFinderFactory::class.java, finderFactory)
project.registerService(VirtualFileFinderFactory::class.java, finderFactory)
@@ -277,8 +286,9 @@ class KotlinCoreEnvironment private constructor(
}
}
internal fun updateClasspath(contentRoots: List<ContentRoot>): List<File>? {
val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots)
fun updateClasspath(contentRoots: List<ContentRoot>): List<File>? {
// TODO: add new Java modules to CliJavaModuleResolver
val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots).roots
for (packagePartProvider in packagePartProviders) {
packagePartProvider.addRoots(newRoots)
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.modules
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleGraph
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
class CliJavaModuleResolver(
override val moduleGraph: JavaModuleGraph,
private val javaModules: List<JavaModule>
) : JavaModuleResolver {
override fun findJavaModule(file: VirtualFile): JavaModule? {
val isBinary = file.fileType == JavaClassFileType.INSTANCE
return javaModules.firstOrNull { module ->
module.isBinary == isBinary && VfsUtilCore.isAncestor(module.moduleRoot, file, false)
}
}
}
@@ -18,8 +18,7 @@ package org.jetbrains.kotlin.container
import com.intellij.util.containers.ContainerUtil
import java.lang.reflect.*
import java.util.ArrayList
import java.util.LinkedHashSet
import java.util.*
private object ClassTraversalCache {
private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap<Class<*>, ClassInfo>()
@@ -82,7 +81,11 @@ private fun getConstructorInfo(c: Class<*>): ConstructorInfo? {
return null
val constructor = constructors.single()
return ConstructorInfo(constructor, constructor.genericParameterTypes.toList())
val parameterTypes =
if (c.declaringClass != null && !Modifier.isStatic(c.modifiers))
listOf(c.declaringClass, *constructor.genericParameterTypes)
else constructor.genericParameterTypes.toList()
return ConstructorInfo(constructor, parameterTypes)
}
@@ -0,0 +1,134 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.jvm.checkers
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
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.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberDescriptor
class JvmModuleAccessibilityChecker(project: Project) : CallChecker {
private val moduleResolver = JavaModuleResolver.getInstance(project)
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val descriptor = resolvedCall.resultingDescriptor
// javac seems to check only the containing class of the member being called. Note that it's fine to call, for example,
// members with parameter types or return type from an unexported package
val targetDescriptor = DescriptorUtils.getParentOfType(descriptor, ClassOrPackageFragmentDescriptor::class.java) ?: return
val fileFromOurModule = DescriptorToSourceUtils.getContainingFile(context.scope.ownerDescriptor)?.virtualFile
diagnosticFor(targetDescriptor, descriptor, fileFromOurModule, reportOn)?.let(context.trace::report)
}
private fun diagnosticFor(
targetClassOrPackage: ClassOrPackageFragmentDescriptor,
originalDescriptor: DeclarationDescriptorWithSource?,
fileFromOurModule: VirtualFile?,
reportOn: PsiElement
): 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)
// 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
}
val containingPackage = DescriptorUtils.getParentOfType(targetClassOrPackage, PackageFragmentDescriptor::class.java, false)
val fqName = containingPackage?.fqNameUnsafe?.toSafe() ?: return null
if (theirModule.exports(fqName)) return null
if (ourModule != null) {
if (ourModule.name == theirModule.name) return null
if (!moduleResolver.moduleGraph.reads(ourModule.name, theirModule.name)) {
return ErrorsJvm.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE.on(reportOn, theirModule.name)
}
if (theirModule.exportsTo(fqName, ourModule.name)) return null
}
return ErrorsJvm.JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE.on(reportOn, theirModule.name, fqName.asString())
}
private fun findVirtualFile(
descriptor: ClassOrPackageFragmentDescriptor,
originalDescriptor: DeclarationDescriptorWithSource?
): VirtualFile? {
val source = descriptor.source
when (source) {
is KotlinJvmBinarySourceElement -> (source.binaryClass as? VirtualFileKotlinClass)?.file?.let { return it }
is JavaSourceElement -> (source.javaElement as? VirtualFileBoundJavaClass)?.virtualFile?.let { return it }
is KotlinJvmBinaryPackageSourceElement -> {
if (originalDescriptor is DeserializedMemberDescriptor) {
(source.getContainingBinaryClass(originalDescriptor) as? VirtualFileKotlinClass)?.file?.let { return it }
}
}
}
source.getPsi()?.containingFile?.virtualFile?.let { return it }
return originalDescriptor?.source?.getPsi()?.containingFile?.virtualFile
}
inner class ClassifierUsage : ClassifierUsageChecker {
override fun check(
targetDescriptor: ClassifierDescriptor,
trace: BindingTrace,
element: PsiElement,
languageVersionSettings: LanguageVersionSettings
) {
val targetClassOrPackage = when (targetDescriptor) {
is ClassDescriptor -> targetDescriptor
is TypeAliasDescriptor -> {
DescriptorUtils.getParentOfType(targetDescriptor, ClassOrPackageFragmentDescriptor::class.java) ?: return
}
else -> return
}
diagnosticFor(targetClassOrPackage, targetDescriptor, element.containingFile.virtualFile, element)?.let(trace::report)
}
}
}
@@ -112,7 +112,10 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(INTERFACE_STATIC_METHOD_CALL_FROM_JAVA6_TARGET, "Calls to static methods in Java interfaces are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'");
MAP.put(INLINE_FROM_HIGHER_PLATFORM, "Cannot inline bytecode built with {0} into bytecode that is being built with {1}. Please specify proper ''-jvm-target'' option", STRING, STRING);
MAP.put(ErrorsJvm.OBSOLETE_SUSPEND_INLINE_FUNCTIONS_ABI, "Cannot inline suspend function built with compiler version less than 1.1.4/1.2-M1");
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_EXPORT_PACKAGE, "Symbol is declared in module ''{0}'' which does not export package ''{1}''", STRING, STRING);
}
@NotNull
@@ -95,6 +95,9 @@ public interface ErrorsJvm {
DiagnosticFactory2<PsiElement, String, String> INLINE_FROM_HIGHER_PLATFORM = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<PsiElement> OBSOLETE_SUSPEND_INLINE_FUNCTIONS_ABI = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<PsiElement, String, String> JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE = DiagnosticFactory2.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -29,6 +29,7 @@ class JavaModuleGraph(finder: JavaModuleFinder) {
visited += "java.base"
fun dfs(moduleName: String) {
// Automatic modules have no transitive exports, so we only consider explicit modules here
val moduleInfo = (module(moduleName) as? JavaModule.Explicit)?.moduleInfo ?: return
for ((dependencyModuleName, isTransitive) in moduleInfo.requires) {
if (!visited.add(dependencyModuleName) && isTransitive) {
@@ -40,4 +41,30 @@ class JavaModuleGraph(finder: JavaModuleFinder) {
moduleNames.forEach(::dfs)
return visited.toList()
}
fun reads(moduleName: String, dependencyName: String): Boolean {
if (moduleName == dependencyName || dependencyName == "java.base") return true
val visited = linkedSetOf<String>()
fun dfs(name: String): Boolean {
if (!visited.add(name)) return false
val module = module(name)
when (module) {
is JavaModule.Automatic -> return true
is JavaModule.Explicit -> {
for ((dependencyModuleName, isTransitive) in module.moduleInfo.requires) {
if (dependencyModuleName == dependencyName) return true
if (isTransitive && dfs(dependencyName)) return true
}
return false
}
null -> return false
else -> error("Unsupported module type: $module")
}
}
return dfs(moduleName)
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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
interface JavaModuleResolver {
val moduleGraph: JavaModuleGraph
// 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?
companion object SERVICE {
fun getInstance(project: Project): JavaModuleResolver =
ServiceManager.getService(project, JavaModuleResolver::class.java)
}
}
@@ -86,6 +86,8 @@ object JvmPlatformConfigurator : PlatformConfigurator(
container.useImpl<JavaSyntheticScopes>()
container.useImpl<InterfaceDefaultMethodCallChecker>()
container.useImpl<InlinePlatformCompatibilityChecker>()
container.useImpl<JvmModuleAccessibilityChecker>()
container.useImpl<JvmModuleAccessibilityChecker.ClassifierUsage>()
container.useInstance(JvmTypeSpecificityComparator)
}
}
@@ -501,7 +501,7 @@ public class KotlinTestUtils {
JvmContentRootsKt.addJvmClasspathRoots(configuration, PathUtil.getJdkClassesRootsFromJre(getJreHome(jdk6)));
}
else if (jdkKind == TestJdkKind.FULL_JDK_9) {
File home = getJre9HomeIfPossible();
File home = getJdk9HomeIfPossible();
if (home != null) {
configuration.put(JVMConfigurationKeys.JDK_HOME, home);
}
@@ -529,8 +529,11 @@ public class KotlinTestUtils {
}
@Nullable
public static File getJre9HomeIfPossible() {
public static File getJdk9HomeIfPossible() {
String jdk9 = System.getenv("JDK_19");
if (jdk9 == null) {
jdk9 = System.getenv("JDK_9");
}
if (jdk9 == null) {
// TODO: replace this with a failure as soon as Java 9 is installed on all TeamCity agents
System.err.println("Environment variable JDK_19 is not set, the test will be skipped");
@@ -46,11 +46,11 @@ object MockLibraryUtil {
jarName: String,
addSources: Boolean = false,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList()
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
): File {
return compileLibraryToJar(
sourcesPath, KotlinTestUtils.tmpDir("testLibrary-" + jarName), jarName, addSources, extraOptions, extraClasspath
)
return compileLibraryToJar(sourcesPath, KotlinTestUtils.tmpDir("testLibrary-" + jarName), jarName, addSources,
extraOptions, extraClasspath, useJava9)
}
@JvmStatic
@@ -61,7 +61,8 @@ object MockLibraryUtil {
jarName: String,
addSources: Boolean = false,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList()
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
): File {
val classesDir = File(contentDir, "classes")
@@ -91,7 +92,14 @@ object MockLibraryUtil {
"-d", classesDir.path
)
KotlinTestUtils.compileJavaFiles(javaFiles, options)
val compile =
if (useJava9) KotlinTestUtils::compileJavaFilesExternallyWithJava9
else KotlinTestUtils::compileJavaFiles
val success = compile(javaFiles, options)
if (!success) {
throw AssertionError("Java files are not compiled successfully")
}
}
return createJarFile(contentDir, classesDir, jarName, sourcesPath.takeIf { addSources })
@@ -44,7 +44,7 @@ public abstract class AbstractDiagnosticsWithJdk9Test extends AbstractDiagnostic
@NotNull Map<String, ModuleAndDependencies> modules,
@NotNull List<TestFile> testFiles
) {
if (KotlinTestUtils.getJre9HomeIfPossible() == null) {
if (KotlinTestUtils.getJdk9HomeIfPossible() == null) {
// Skip this test if no Java 9 is found
return;
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.modules
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiCompiledElement
import com.intellij.psi.PsiJavaModule
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.file.impl.JavaFileManager
import com.intellij.psi.impl.light.LightJavaModule
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.resolve.jvm.modules.*
class IdeJavaModuleResolver(project: Project) : JavaModuleResolver {
private val psiManager = PsiManager.getInstance(project)
private val fileManager = JavaFileManager.SERVICE.getInstance(project)
private val allScope = GlobalSearchScope.allScope(project)
override val moduleGraph: JavaModuleGraph = JavaModuleGraph(
object : JavaModuleFinder {
override fun findModule(name: String): JavaModule? {
return fileManager.findModules(name, allScope).singleOrNull()?.toJavaModule()
}
}
)
override fun findJavaModule(file: VirtualFile): JavaModule? {
val psiFile = psiManager.findFile(file) ?: return null
return JavaModuleGraphUtil.findDescriptorByElement(psiFile)?.toJavaModule()
}
private fun PsiJavaModule.toJavaModule(): JavaModule {
if (this is LightJavaModule) {
return JavaModule.Automatic(name, rootVirtualFile)
}
val virtualFile = containingFile?.virtualFile ?: error("No VirtualFile found for module $this ($javaClass)")
return JavaModule.Explicit(JavaModuleInfo.create(this), virtualFile.parent, virtualFile, this is PsiCompiledElement)
}
}
@@ -19,8 +19,10 @@ package org.jetbrains.kotlin.idea.test;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import java.io.File;
@@ -38,19 +40,36 @@ public class PluginTestCaseBase {
@NotNull
private static Sdk getSdk(String sdkHome, String name) {
return JavaSdk.getInstance().createJdk(name + " JDK", sdkHome, true);
return JavaSdk.getInstance().createJdk(name, sdkHome, true);
}
@NotNull
public static Sdk mockJdk() {
return getSdk("compiler/testData/mockJDK/jre", "Mock");
return getSdk("compiler/testData/mockJDK/jre", "Mock JDK");
}
@NotNull
public static Sdk fullJdk() {
String javaHome = System.getProperty("java.home");
assert new File(javaHome).isDirectory();
return getSdk(javaHome, "Full");
return getSdk(javaHome, "Full JDK");
}
@NotNull
public static Sdk jdk(@NotNull TestJdkKind kind) {
switch (kind) {
case MOCK_JDK:
return mockJdk();
case FULL_JDK_9:
File jre9 = KotlinTestUtils.getJdk9HomeIfPossible();
assert jre9 != null : "JDK_19 environment variable is not set";
VfsRootAccess.allowRootAccess(jre9.getPath());
return getSdk(jre9.getPath(), "Full JDK 9");
case FULL_JDK:
return fullJdk();
default:
throw new UnsupportedOperationException(kind.toString());
}
}
public static boolean isAllFilesPresentTest(@NotNull String testName) {
+3
View File
@@ -245,6 +245,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager"
serviceImplementation="org.jetbrains.kotlin.idea.util.IdeModuleVisibilityManagerImpl"/>
<projectService serviceInterface="org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver"
serviceImplementation="org.jetbrains.kotlin.idea.modules.IdeJavaModuleResolver"/>
<projectService serviceInterface="org.jetbrains.kotlin.asJava.LightClassGenerationSupport"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.IDELightClassGenerationSupport"/>
@@ -0,0 +1,2 @@
module dependency {
}
@@ -0,0 +1,12 @@
package unexported
class Klass
interface Interface
typealias TypeAliasToPublic = String
typealias TypeAliasToUnexported = Klass
fun function() {}
val valProperty = ""
var varProperty = ""
@@ -0,0 +1,3 @@
module main {
requires dependency;
}
@@ -0,0 +1,17 @@
import unexported.*
fun usage(): String {
val k: <error>Klass</error> = <error>Klass</error>()
val i: <error>Interface</error>? = null
val ta1: <error>TypeAliasToPublic</error> = <error>TypeAliasToPublic</error>()
val ta2: <error>TypeAliasToUnexported</error> = <error>TypeAliasToUnexported</error>()
<error>function</error>()
<error>valProperty</error>
<error>varProperty</error>
<error>varProperty</error> = ""
return "$k$i$ta1$ta2"
}
@@ -0,0 +1,3 @@
package dependency;
public class Foo {}
@@ -0,0 +1,3 @@
module dependency {
exports dependency to first;
}
@@ -0,0 +1,6 @@
import dependency.Foo
fun firstUsage(): String {
val foo: Foo = Foo()
return foo.toString()
}
@@ -0,0 +1,3 @@
module first {
requires dependency;
}
@@ -0,0 +1,3 @@
module second {
requires dependency;
}
@@ -0,0 +1,6 @@
import dependency.<error>Foo</error>
fun secondUsage(): String {
val foo: <error>Foo</error> = <error>Foo</error>()
return foo.<error>toString</error>()
}
@@ -0,0 +1,6 @@
import dependency.<error>Foo</error>
fun unnamedUsage(): String {
val foo: <error>Foo</error> = <error>Foo</error>()
return foo.<error>toString</error>()
}
@@ -0,0 +1,4 @@
package dependency;
public class J {
}
@@ -0,0 +1,3 @@
module main {
requires dependency;
}
@@ -0,0 +1,6 @@
import dependency.J
fun usage(): String {
val j: J = J()
return j.toString()
}
@@ -0,0 +1,7 @@
package dependency;
import dependency.impl.JImpl;
public class J {
public static JImpl getInstance() { return new JImpl(); }
}
@@ -0,0 +1,9 @@
package dependency
import dependency.impl.KImpl
open class K {
companion object {
fun getInstance(): KImpl = KImpl()
}
}
@@ -0,0 +1,6 @@
package dependency.impl;
import dependency.J;
public class JImpl extends J {
}
@@ -0,0 +1,5 @@
package dependency.impl
import dependency.K
class KImpl : K()
@@ -0,0 +1,3 @@
module library {
exports dependency;
}
@@ -0,0 +1,3 @@
module main {
requires library;
}
@@ -0,0 +1,16 @@
import dependency.*
import dependency.J
import dependency.K
import dependency.impl.*
import dependency.impl.<error>JImpl</error>
import dependency.impl.<error>KImpl</error>
fun usage(): String {
val j: J = J.getInstance()
val k: K = K.getInstance()
val jImpl: <error>JImpl</error> = J.getInstance()
val kImpl: <error>KImpl</error> = K.getInstance()
return "$j$k$jImpl$kImpl"
}
@@ -0,0 +1,7 @@
package dependency;
import dependency.impl.JImpl;
public class J {
public static JImpl getInstance() { return new JImpl(); }
}
@@ -0,0 +1,9 @@
package dependency
import dependency.impl.KImpl
open class K {
companion object {
fun getInstance(): KImpl = KImpl()
}
}
@@ -0,0 +1,6 @@
package dependency.impl;
import dependency.J;
public class JImpl extends J {
}
@@ -0,0 +1,5 @@
package dependency.impl
import dependency.K
class KImpl : K()
@@ -0,0 +1,3 @@
module dependency {
exports dependency;
}
@@ -0,0 +1,3 @@
module main {
requires dependency;
}
@@ -0,0 +1,16 @@
import dependency.*
import dependency.J
import dependency.K
import dependency.impl.*
import dependency.impl.<error>JImpl</error>
import dependency.impl.<error>KImpl</error>
fun usage(): String {
val j: J = J.getInstance()
val k: K = K.getInstance()
val jImpl: <error>JImpl</error> = J.getInstance()
val kImpl: <error>KImpl</error> = K.getInstance()
return "$j$k$jImpl$kImpl"
}
@@ -0,0 +1,7 @@
package dependency;
import dependency.impl.JImpl;
public class J {
public static JImpl getInstance() { return new JImpl(); }
}
@@ -0,0 +1,9 @@
package dependency
import dependency.impl.KImpl
open class K {
companion object {
fun getInstance(): KImpl = KImpl()
}
}
@@ -0,0 +1,6 @@
package dependency.impl;
import dependency.J;
public class JImpl extends J {
}
@@ -0,0 +1,5 @@
package dependency.impl
import dependency.K
class KImpl : K()
@@ -0,0 +1,3 @@
module dependency {
exports dependency;
}
@@ -0,0 +1,16 @@
import dependency.*
import dependency.J
import dependency.K
import dependency.impl.*
import dependency.impl.<error>JImpl</error>
import dependency.impl.<error>KImpl</error>
fun usage(): String {
val j: J = J.getInstance()
val k: K = K.getInstance()
val jImpl: <error>JImpl</error> = J.getInstance()
val kImpl: <error>KImpl</error> = K.getInstance()
return "$j$k$jImpl$kImpl"
}
@@ -20,10 +20,11 @@ import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.stubs.AbstractMultiHighlightingTest
import org.jetbrains.kotlin.test.TestJdkKind
abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTest() {
protected fun checkHighlightingInAllFiles(
protected open fun checkHighlightingInAllFiles(
shouldCheckFile: () -> Boolean = { !file.text.contains("// !CHECK_HIGHLIGHTING") }
) {
checkFiles(shouldCheckFile) {
@@ -35,9 +36,9 @@ abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTe
vararg platforms: TargetPlatformKind<*>,
withStdlibCommon: Boolean = false,
configureModule: (Module, TargetPlatformKind<*>) -> Unit = { _, _ -> },
useFullJdk: Boolean = false
jdk: TestJdkKind = TestJdkKind.MOCK_JDK
) {
val commonModule = module("common", useFullJdk = useFullJdk)
val commonModule = module("common", jdk)
commonModule.createFacet(TargetPlatformKind.Common)
if (withStdlibCommon) {
commonModule.addLibrary(ForTestCompileRuntime.stdlibCommonForTests())
@@ -49,7 +50,7 @@ abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTe
is TargetPlatformKind.JavaScript -> "js"
else -> error("Unsupported platform: $platform")
}
val platformModule = module(path, useFullJdk = useFullJdk)
val platformModule = module(path, jdk)
platformModule.createFacet(platform)
platformModule.enableMultiPlatform()
platformModule.addDependency(commonModule)
@@ -17,12 +17,13 @@
package org.jetbrains.kotlin.idea.caches.resolve
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.TestJdkKind
class Java8MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleHighlighting/"
fun testDifferentJdk() {
val module1 = module("jdk8", useFullJdk = true)
val module1 = module("jdk8", TestJdkKind.FULL_JDK)
val module2 = module("mockJdk")
module1.addDependency(module2)
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK_9
class Java9MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
override fun getTestDataPath(): String = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleHighlighting/java9/"
private inline fun doTest(test: () -> Unit) {
// Skip this test if no Java 9 is found
if (KotlinTestUtils.getJdk9HomeIfPossible() != null) {
test()
}
}
private fun module(name: String): Module = super.module(name, FULL_JDK_9, false)
fun testSimpleModuleExportsPackage() = doTest {
module("main").addDependency(module("dependency"))
checkHighlightingInAllFiles()
}
fun testSimpleLibraryExportsPackage() = doTest {
val jdk9Home = KotlinTestUtils.getJdk9HomeIfPossible() ?: return
val library = MockLibraryUtil.compileJvmLibraryToJar(
testDataPath + "${getTestName(true)}/library", "library",
extraOptions = listOf("-jdk-home", jdk9Home.path),
useJava9 = true
)
module("main").addLibrary(library, "library")
checkHighlightingInAllFiles()
}
fun testNamedDependsOnUnnamed() = doTest {
module("main").addDependency(module("dependency"))
checkHighlightingInAllFiles()
}
fun testUnnamedDependsOnNamed() = doTest {
module("main").addDependency(module("dependency"))
checkHighlightingInAllFiles()
}
fun testDeclarationKinds() = doTest {
module("main").addDependency(module("dependency"))
checkHighlightingInAllFiles()
}
fun testExportsTo() = doTest {
val d = module("dependency")
module("first").addDependency(d)
module("second").addDependency(d)
module("unnamed").addDependency(d)
checkHighlightingInAllFiles()
}
}
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.facet.KotlinFacetConfiguration
import org.jetbrains.kotlin.idea.facet.KotlinFacetType
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.test.TestJdkKind.FULL_JDK
open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleHighlighting/"
@@ -110,10 +111,10 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
}
fun testLanguageVersionsViaFacets() {
val m1 = module("m1", useFullJdk = true).setupKotlinFacet {
val m1 = module("m1", FULL_JDK).setupKotlinFacet {
settings.languageLevel = LanguageVersion.KOTLIN_1_1
}
val m2 = module("m2", useFullJdk = true).setupKotlinFacet {
val m2 = module("m2", FULL_JDK).setupKotlinFacet {
settings.languageLevel = LanguageVersion.KOTLIN_1_0
}
@@ -174,7 +175,7 @@ open class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
fun testUseCorrectBuiltInsForCommonModule() {
doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_8], TargetPlatformKind.JavaScript,
withStdlibCommon = true, useFullJdk = true, configureModule = { module, platform ->
withStdlibCommon = true, jdk = FULL_JDK, configureModule = { module, platform ->
if (platform == TargetPlatformKind.JavaScript) {
module.addLibrary(ForTestCompileRuntime.stdlibJsForTests())
module.addLibrary(ForTestCompileRuntime.stdlibCommonForTests())
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.KotlinJdkAndLibraryProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TestJdkKind
import org.junit.Assert
import java.io.File
@@ -53,15 +54,14 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory())
}
protected fun module(name: String, hasTestRoot: Boolean = false, useFullJdk: Boolean = false): Module {
protected fun module(name: String, jdk: TestJdkKind = TestJdkKind.MOCK_JDK, hasTestRoot: Boolean = false): Module {
val srcDir = testDataPath + "${getTestName(true)}/$name"
val moduleWithSrcRootSet = createModuleFromTestData(srcDir, name, StdModuleTypes.JAVA, true)!!
if (hasTestRoot) {
setTestRoot(moduleWithSrcRootSet, name)
}
val jdkToUse = if (useFullJdk) PluginTestCaseBase.fullJdk() else PluginTestCaseBase.mockJdk()
ConfigLibraryUtil.configureSdk(moduleWithSrcRootSet, jdkToUse)
ConfigLibraryUtil.configureSdk(moduleWithSrcRootSet, PluginTestCaseBase.jdk(jdk))
return moduleWithSrcRootSet
}
@@ -86,9 +86,9 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
exported: Boolean = false
) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported)
protected fun Module.addLibrary(jar: File) {
protected fun Module.addLibrary(jar: File, name: String = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME) {
ConfigLibraryUtil.addLibrary(NewLibraryEditor().apply {
name = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME
this.name = name
addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES)
}, this)
}
@@ -939,7 +939,7 @@ class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() {
}
fun testJre9() {
val path = KotlinTestUtils.getJre9HomeIfPossible()?.absolutePath ?: return
val path = KotlinTestUtils.getJdk9HomeIfPossible()?.absolutePath ?: return
val jdk = myModel.global.addSdk(JDK_NAME, path, "9", JpsJavaSdkType.INSTANCE)
jdk.addRoot(StandardFileSystems.JRT_PROTOCOL_PREFIX + path + URLUtil.JAR_SEPARATOR + "java.base", JpsOrderRootType.COMPILED)