Implement modules in IDE

IDE:
Rewrite AnalyzerFacade and implementations for JS and JVM to support creating separate analyzers for each module
Introduce ModuleInfo which is an intermediate entity between configuration (tests or idea modules) and ModuleDescriptor
Implement IdeaModuleInfos which represent IDEA modules, sdks and libraries
Add (somewhat thin) test checking their behaviour
Implement getModuleInfo() - utility to obtain IdeaModuleInfo for PsiElement
Drop Project.getLazyResolveSession() - not possible to obtain resolve session for the whole project any more
Adjust JavaResolveExtension accordingly
KotlinSignature Intention/Marker - make sure that analyzed element is cls element (he's not in resolve scope otherwise)

LightClasses:
Create separate package light classes for each module
Java code can only reference light class from the first module among it's dependencies
Duplicate jvm signature is only reported on package declarations inside one module

Injectors:
Receive GlobalSearchScope as paramer for VirtualFileFinder and JavaClassFinder
which allows to narrow analyzer scope

JDR:
Introduce ModuleClassResolver resolves java classes in correct java descriptor resolver (corresponding ModuleDescriptor)
Add test checking that java classes belong to correct module

Debugger:
Provide context to analyze files created by debugger in

Converter:
Postprocessor now needs a context to analyze resulting code in

JetPsiFactory:
Add verification that files created by psi factory are not analyzed without context (that is almost never a good idea)

Other:
Use new API in various tests, utilities, run configuration producers and builtin serializers
Various "TODO: (module refactoring)" which mark the unfinished parts
This commit is contained in:
Pavel V. Talanov
2014-06-10 16:50:35 +04:00
parent 07935c837a
commit db5303c019
82 changed files with 1813 additions and 527 deletions
@@ -809,6 +809,7 @@ public class JetTestUtils {
return generatorClassFqName.substring(generatorClassFqName.lastIndexOf(".") + 1);
}
@NotNull
public static JetFile loadJetFile(@NotNull Project project, @NotNull File ioFile) throws IOException {
String text = FileUtil.loadFile(ioFile, true);
return JetPsiFactory(project).createPhysicalFile(ioFile.getName(), text);
@@ -27,6 +27,7 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
@@ -355,7 +356,8 @@ public abstract class BaseDiagnosticsTest extends JetLiteFixture {
Set<Diagnostic> jvmSignatureDiagnostics = new HashSet<Diagnostic>();
Collection<JetDeclaration> declarations = PsiTreeUtil.findChildrenOfType(jetFile, JetDeclaration.class);
for (JetDeclaration declaration : declarations) {
Diagnostics diagnostics = AsJavaPackage.getJvmSignatureDiagnostics(declaration, bindingContext.getDiagnostics());
Diagnostics diagnostics = AsJavaPackage.getJvmSignatureDiagnostics(declaration, bindingContext.getDiagnostics(),
GlobalSearchScope.allScope(getProject()));
if (diagnostics == null) continue;
jvmSignatureDiagnostics.addAll(diagnostics.forElement(declaration));
}
@@ -0,0 +1,168 @@
/*
* Copyright 2010-2014 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.jet.jvm.compiler
import org.jetbrains.jet.lang.resolve.java.JvmAnalyzerFacade
import org.jetbrains.jet.context.GlobalContext
import org.jetbrains.jet.JetTestUtils
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.jet.lang.resolve.java.JvmPlatformParameters
import org.jetbrains.jet.lang.psi.JetFile
import java.io.File
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.jet.lang.resolve.name.Name
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.junit.Assert
import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment
import org.jetbrains.jet.config.CompilerConfiguration
import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.resolve.java.JvmResolverForModule
import org.jetbrains.jet.analyzer.ResolverForProject
import org.jetbrains.jet.analyzer.ModuleInfo
import java.util.HashMap
import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor
import org.jetbrains.jet.analyzer.ModuleContent
public class MultiModuleJavaAnalysisCustomTest : UsefulTestCase() {
private class TestModule(val _name: String, val kotlinFiles: List<JetFile>, val javaFilesScope: GlobalSearchScope,
val _dependencies: TestModule.() -> List<TestModule>) :
ModuleInfo {
override fun dependencies() = _dependencies()
override val name = Name.special("<$_name>")
}
fun testJavaEntitiesBelongToCorrectModule() {
val moduleDirs = File(PATH_TO_TEST_ROOT_DIR).listFiles { it.isDirectory() }!!
val environment = createEnvironment(moduleDirs)
val modules = setupModules(environment, moduleDirs)
val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
GlobalContext(), environment.getProject(), modules,
{ m -> ModuleContent(m.kotlinFiles, m.javaFilesScope) },
JvmPlatformParameters {
javaClass ->
val moduleName = javaClass.getName().asString().toLowerCase().first().toString()
modules.first { it._name == moduleName }
}
)
performChecks(resolverForProject, modules)
}
private fun createEnvironment(moduleDirs: Array<File>): JetCoreEnvironment {
val configuration = CompilerConfiguration()
configuration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, moduleDirs.toList())
return JetCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration)
}
private fun setupModules(environment: JetCoreEnvironment, moduleDirs: Array<File>): List<TestModule> {
val project = environment.getProject()
val modules = HashMap<String, TestModule>()
for (dir in moduleDirs) {
val name = dir.getName()
val kotlinFiles = JetTestUtils.loadToJetFiles(environment, dir.listFiles { it.extension == "kt" }?.toList().orEmpty())
val javaFilesScope = object : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
override fun contains(file: VirtualFile): Boolean {
if (file !in myBaseScope!!) return false
if (file.isDirectory()) return true
return file.getParent()!!.getParent()!!.getName() == name
}
}
modules[name] = TestModule(name, kotlinFiles, javaFilesScope) {
when (this._name) {
"a" -> listOf(this)
"b" -> listOf(this, modules["a"]!!)
"c" -> listOf(this, modules["b"]!!, modules["a"]!!)
else -> throw IllegalStateException("$_name")
}
}
}
return modules.values().toList()
}
private fun performChecks(resolverForProject: ResolverForProject<TestModule, JvmResolverForModule>, modules: List<TestModule>) {
modules.forEach {
module ->
val moduleDescriptor = resolverForProject.descriptorForModule(module)
checkClassInPackage(moduleDescriptor, "test", "Kotlin${module._name.toUpperCase()}")
checkClassInPackage(moduleDescriptor, "custom", "${module._name.toUpperCase()}Class")
}
}
private fun checkClassInPackage(moduleDescriptor: ModuleDescriptor, packageName: String, className: String) {
val kotlinPackage = moduleDescriptor.getPackage(FqName(packageName))!!
val kotlinClassName = Name.identifier(className)
val kotlinClass = kotlinPackage.getMemberScope().getClassifier(kotlinClassName) as ClassDescriptor
checkClass(kotlinClass)
}
private fun checkClass(classDescriptor: ClassDescriptor) {
classDescriptor.getDefaultType().getMemberScope().getAllDescriptors().filterIsInstance(javaClass<CallableDescriptor>()).forEach {
checkCallable(it, classDescriptor)
}
}
private fun checkCallable(callable: CallableDescriptor, classDescriptor: ClassDescriptor) {
val returnType = callable.getReturnType()!!
if (!KotlinBuiltIns.getInstance().isUnit(returnType)) {
checkDescriptor(returnType.getConstructor().getDeclarationDescriptor()!!, callable)
}
callable.getValueParameters().map {
it.getType().getConstructor().getDeclarationDescriptor()!!
}.forEach { checkDescriptor(it, callable) }
callable.getAnnotations().map {
it.getType().getConstructor().getDeclarationDescriptor()!!
}.forEach { checkDescriptor(it, callable) }
checkSupertypes(classDescriptor)
}
private fun checkSupertypes(classDescriptor: ClassDescriptor) {
classDescriptor.getDefaultType().getConstructor().getSupertypes().filter {
!KotlinBuiltIns.getInstance().isAnyOrNullableAny(it)
}.map {
it.getConstructor().getDeclarationDescriptor()!!
}.forEach {
checkDescriptor(it, classDescriptor)
}
}
private fun checkDescriptor(referencedDescriptor: ClassifierDescriptor, context: DeclarationDescriptor) {
val descriptorName = referencedDescriptor.getName().asString()
val expectedModuleName = "<${descriptorName.toLowerCase().first().toString()}>"
val moduleName = DescriptorUtils.getContainingModule(referencedDescriptor).getName().asString()
Assert.assertEquals(
"Java class $descriptorName in $context should be in module $expectedModuleName, but instead was in $moduleName",
expectedModuleName, moduleName
)
}
class object {
val PATH_TO_TEST_ROOT_DIR = "compiler/testData/multiModule/java/custom"
}
}
@@ -20,7 +20,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.cli.jvm.compiler.CliLightClassGenerationSupport;
@@ -33,14 +32,14 @@ import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.TopDownAnalysisParameters;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lang.resolve.name.SpecialNames;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import java.util.List;
import java.util.Set;
import static org.jetbrains.jet.lang.resolve.lazy.LazyPackage.createResolveSessionForFiles;
public class LazyResolveTestUtil {
private LazyResolveTestUtil() {
}
@@ -61,16 +60,18 @@ public class LazyResolveTestUtil {
return injector.getModuleDescriptor();
}
public static KotlinCodeAnalyzer resolveLazilyWithSession(List<JetFile> files, JetCoreEnvironment environment, boolean addBuiltIns) {
@NotNull
public static KotlinCodeAnalyzer resolveLazilyWithSession(
@NotNull List<JetFile> files,
@NotNull JetCoreEnvironment environment,
boolean addBuiltIns
) {
JetTestUtils.newTrace(environment);
Project project = environment.getProject();
CliLightClassGenerationSupport support = CliLightClassGenerationSupport.getInstanceForCli(project);
BindingTrace sharedTrace = support.getTrace();
ResolveSession lazyResolveSession = AnalyzerFacadeForJVM.createSetup(project, files, GlobalSearchScope.EMPTY_SCOPE,
sharedTrace, addBuiltIns).getLazyResolveSession();
support.setModule((ModuleDescriptorImpl)lazyResolveSession.getModuleDescriptor());
ResolveSession lazyResolveSession = createResolveSessionForFiles(project, files, addBuiltIns);
support.setModule((ModuleDescriptorImpl) lazyResolveSession.getModuleDescriptor());
return lazyResolveSession;
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2014 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.jet.lang.resolve.lazy
import com.intellij.openapi.project.Project
import org.jetbrains.jet.lang.psi.JetFile
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.jet.context.GlobalContext
import org.jetbrains.jet.lang.resolve.java.JvmAnalyzerFacade
import org.jetbrains.jet.analyzer.ModuleInfo
import org.jetbrains.jet.lang.resolve.name.Name
import org.jetbrains.jet.lang.resolve.java.JvmPlatformParameters
import org.jetbrains.jet.analyzer.ModuleContent
public fun createResolveSessionForFiles(
project: Project,
syntheticFiles: Collection<JetFile>,
addBuiltIns: Boolean
): ResolveSession {
val globalContext = GlobalContext()
val testModule = TestModule(addBuiltIns)
val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
globalContext, project, listOf(testModule),
{ ModuleContent(syntheticFiles, GlobalSearchScope.allScope(project)) },
JvmPlatformParameters { testModule }
)
return resolverForProject.resolverForModule(testModule).lazyResolveSession
}
private class TestModule(val dependsOnBuiltins: Boolean) : ModuleInfo {
override val name: Name = Name.special("<Test module for lazy resolve>")
override fun dependencies() = listOf(this)
override fun dependencyOnBuiltins() =
if (dependsOnBuiltins)
ModuleInfo.DependenciesOnBuiltins.LAST
else
ModuleInfo.DependenciesOnBuiltins.NONE
}