Use DefaultAnalyzerFacade for common code in diagnostic tests

Because of incomplete infrastructure, introduce a few dirty hacks to make
diagnostic tests analyze the common code via DefaultAnalyzerFacade, and also
add common module sources to analysis of platform-specific modules.

Also do not render 'platform' declarations from platform-specific modules to
the .txt file, since they are very likely to be exact copies of the same
declaration in the common module (see RecursiveDescriptorComparator)
This commit is contained in:
Alexander Udalov
2016-11-07 14:26:05 +03:00
parent a4169416ad
commit 926b81fb35
4 changed files with 108 additions and 18 deletions
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.context.LazyResolveToken
import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
@@ -54,21 +55,30 @@ object DefaultAnalyzerFacade : AnalyzerFacade<PlatformAnalysisParameters>() {
LanguageVersion.LATEST, ApiVersion.LATEST, setOf(LanguageFeature.MultiPlatformProjects)
)
private class SourceModuleInfo(override val name: Name, private val dependOnOldBuiltIns: Boolean) : ModuleInfo {
private class SourceModuleInfo(
override val name: Name,
override val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>,
private val dependOnOldBuiltIns: Boolean
) : ModuleInfo {
override fun dependencies() = listOf(this)
override fun dependencyOnBuiltIns(): ModuleInfo.DependencyOnBuiltIns =
if (dependOnOldBuiltIns) ModuleInfo.DependenciesOnBuiltIns.LAST else ModuleInfo.DependenciesOnBuiltIns.NONE
}
fun analyzeFiles(files: Collection<KtFile>, moduleName: Name, dependOnBuiltIns: Boolean): AnalysisResult {
val moduleInfo = SourceModuleInfo(moduleName, dependOnBuiltIns)
fun analyzeFiles(
files: Collection<KtFile>, moduleName: Name, dependOnBuiltIns: Boolean,
capabilities: Map<ModuleDescriptor.Capability<*>, Any?> = mapOf(MultiTargetPlatform.CAPABILITY to MultiTargetPlatform.Common),
packagePartProviderFactory: (ModuleInfo, ModuleContent) -> PackagePartProvider
): AnalysisResult {
val moduleInfo = SourceModuleInfo(moduleName, capabilities, dependOnBuiltIns)
val project = files.firstOrNull()?.project ?: throw AssertionError("No files to analyze")
val resolver = setupResolverForProject(
"sources for metadata serializer",
ProjectContext(project), listOf(moduleInfo),
{ ModuleContent(files, GlobalSearchScope.EMPTY_SCOPE) },
object : PlatformAnalysisParameters {}
{ ModuleContent(files, GlobalSearchScope.allScope(project)) },
object : PlatformAnalysisParameters {},
packagePartProviderFactory = packagePartProviderFactory
)
val moduleDescriptor = resolver.descriptorForModule(moduleInfo)
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.renderer.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.MemberComparator;
import org.jetbrains.kotlin.resolve.MultiTargetPlatform;
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
@@ -204,6 +205,10 @@ public class RecursiveDescriptorComparator {
}
}
// 'platform' declarations do not belong to the platform-specific module, even though they participate in the analysis
if (descriptor instanceof MemberDescriptor && ((MemberDescriptor) descriptor).isPlatform() &&
module.getCapability(MultiTargetPlatform.CAPABILITY) != MultiTargetPlatform.Common.INSTANCE) return false;
return module.equals(DescriptorUtils.getContainingModule(descriptor));
}
@@ -23,12 +23,18 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
import kotlin.TuplesKt;
import kotlin.collections.CollectionsKt;
import kotlin.collections.MapsKt;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.analyzer.ModuleContent;
import org.jetbrains.kotlin.analyzer.ModuleInfo;
import org.jetbrains.kotlin.analyzer.common.DefaultAnalyzerFacade;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.config.CommonConfigurationKeys;
@@ -94,6 +100,8 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
}
};
private static final ModuleDescriptor.Capability<List<KtFile>> MODULE_FILES = new ModuleDescriptor.Capability<List<KtFile>>("");
@Override
protected void analyzeAndCheck(File testDataFile, List<TestFile> testFiles) {
Map<TestModule, List<TestFile>> groupedByModule = CollectionsKt.groupByTo(
@@ -141,21 +149,36 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
List<KtFile> jetFiles = getJetFiles(testFilesInModule, true);
ModuleDescriptorImpl module = modules.get(testModule);
BindingTrace moduleTrace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
moduleBindings.put(testModule, moduleTrace.getBindingContext());
ModuleDescriptorImpl oldModule = modules.get(testModule);
LanguageVersionSettings languageVersionSettings = loadLanguageVersionSettings(testFilesInModule);
ModuleContext moduleContext = ContextKt.withModule(ContextKt.withProject(context, getProject()), module);
ModuleContext moduleContext = ContextKt.withModule(ContextKt.withProject(context, getProject()), oldModule);
boolean separateModules = groupedByModule.size() == 1 && groupedByModule.keySet().iterator().next() == null;
AnalysisResult result = analyzeModuleContents(moduleContext, jetFiles, moduleTrace, languageVersionSettings, separateModules);
if (separateModules) {
modules.put(testModule, (ModuleDescriptorImpl) result.getModuleDescriptor());
AnalysisResult result = analyzeModuleContents(
moduleContext, jetFiles, new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
languageVersionSettings, separateModules
);
ModuleDescriptorImpl newModule = (ModuleDescriptorImpl) result.getModuleDescriptor();
if (oldModule != newModule) {
// For common modules, we use DefaultAnalyzerFacade who creates ModuleDescriptor instances by itself
// (its API does not support working with a module created beforehand).
// So, we should replace the old (effectively discarded) module with the new one everywhere in dependencies.
// TODO: dirty hack, refactor this test so that it doesn't create ModuleDescriptor instances
modules.put(testModule, newModule);
for (ModuleDescriptorImpl module : modules.values()) {
@SuppressWarnings("deprecation")
ListIterator<ModuleDescriptorImpl> it = module.getTestOnly_AllDependentModules().listIterator();
while (it.hasNext()) {
if (it.next() == oldModule) {
it.set(newModule);
}
}
}
}
checkAllResolvedCallsAreCompleted(jetFiles, moduleTrace.getBindingContext());
moduleBindings.put(testModule, result.getBindingContext());
checkAllResolvedCallsAreCompleted(jetFiles, result.getBindingContext());
}
// We want to always create a test data file (txt) if it was missing,
@@ -183,8 +206,12 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
StringBuilder actualText = new StringBuilder();
for (TestFile testFile : testFiles) {
ok &= testFile.getActualText(moduleBindings.get(testFile.getModule()), actualText,
shouldSkipJvmSignatureDiagnostics(groupedByModule));
TestModule module = testFile.getModule();
boolean isCommonModule = MultiTargetPlatformKt.getMultiTargetPlatform(modules.get(module)) == MultiTargetPlatform.Common.INSTANCE;
ok &= testFile.getActualText(
moduleBindings.get(module), actualText,
shouldSkipJvmSignatureDiagnostics(groupedByModule) || isCommonModule
);
}
Throwable exceptionFromDynamicCallDescriptorsValidation = null;
@@ -329,6 +356,31 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
);
}
ModuleDescriptorImpl moduleDescriptor = (ModuleDescriptorImpl) moduleContext.getModule();
MultiTargetPlatform platform = MultiTargetPlatformKt.getMultiTargetPlatform(moduleDescriptor);
if (platform == MultiTargetPlatform.Common.INSTANCE) {
//noinspection unchecked
return DefaultAnalyzerFacade.INSTANCE.analyzeFiles(
files, moduleDescriptor.getName(), true,
MapsKt.mapOf(
TuplesKt.to(MultiTargetPlatform.CAPABILITY, MultiTargetPlatform.Common.INSTANCE),
TuplesKt.to(MODULE_FILES, files)
),
new Function2<ModuleInfo, ModuleContent, PackagePartProvider>() {
@Override
public PackagePartProvider invoke(ModuleInfo info, ModuleContent content) {
// TODO
return PackagePartProvider.Empty.INSTANCE;
}
}
);
}
else if (platform != null) {
// TODO: analyze with the correct platform, not always JVM
files = CollectionsKt.plus(files, getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor));
}
GlobalSearchScope moduleContentScope = GlobalSearchScope.allScope(moduleContext.getProject());
SingleModuleClassResolver moduleClassResolver = new SingleModuleClassResolver();
ComponentProvider container = InjectionKt.createContainerForTopDownAnalyzerForJvm(
@@ -341,10 +393,9 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
languageVersionSettings,
moduleClassResolver
);
InjectionKt.initJvmBuiltInsForTopDownAnalysis(container, moduleContext.getModule(), languageVersionSettings);
InjectionKt.initJvmBuiltInsForTopDownAnalysis(container, moduleDescriptor, languageVersionSettings);
moduleClassResolver.setResolver(DslKt.getService(container, JavaDescriptorResolver.class));
ModuleDescriptorImpl moduleDescriptor = (ModuleDescriptorImpl) moduleContext.getModule();
moduleDescriptor.initialize(new CompositePackageFragmentProvider(Arrays.asList(
DslKt.getService(container, KotlinCodeAnalyzer.class).getPackageFragmentProvider(),
DslKt.getService(container, JavaDescriptorResolver.class).getPackageFragmentProvider()
@@ -357,6 +408,27 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest {
return AnalysisResult.success(moduleTrace.getBindingContext(), moduleDescriptor);
}
@NotNull
private static List<KtFile> getCommonCodeFilesForPlatformSpecificModule(@NotNull ModuleDescriptorImpl moduleDescriptor) {
// We assume that a platform-specific module _implements_ all declarations from common modules which are immediate dependencies.
// So we collect all sources from such modules to analyze in the platform-specific module as well
@SuppressWarnings("deprecation")
List<ModuleDescriptorImpl> dependencies = moduleDescriptor.getTestOnly_AllDependentModules();
// TODO: diagnostics on common code reported during the platform module analysis should be distinguished somehow
// E.g. "<!JVM:PLATFORM_DEFINITION_WITHOUT_DECLARATION!>...<!>
List<KtFile> result = new ArrayList<KtFile>(0);
for (ModuleDescriptorImpl dependency : dependencies) {
if (dependency.getCapability(MultiTargetPlatform.CAPABILITY) == MultiTargetPlatform.Common.INSTANCE) {
List<KtFile> files = dependency.getCapability(MODULE_FILES);
assert files != null : "MODULE_FILES should have been set for the common module: " + dependency;
result.addAll(files);
}
}
return result;
}
private void validateAndCompareDescriptorWithFile(
File expectedFile,
List<TestFile> testFiles,
@@ -46,6 +46,9 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
fqName: FqName -> LazyPackageViewDescriptorImpl(this, fqName, storageManager)
}
@Deprecated("This method is not going to be supported. Please do not use it")
val testOnly_AllDependentModules: List<ModuleDescriptorImpl> get() = this.dependencies!!.allDependencies
override val allDependentModules: List<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this }