From 926b81fb3550fb18725fcda5f1183883b9d85c91 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 7 Nov 2016 14:26:05 +0300 Subject: [PATCH] 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) --- .../analyzer/common/DefaultAnalyzerFacade.kt | 20 +++- .../util/RecursiveDescriptorComparator.java | 5 + .../checkers/AbstractDiagnosticsTest.java | 98 ++++++++++++++++--- .../descriptors/impl/ModuleDescriptorImpl.kt | 3 + 4 files changed, 108 insertions(+), 18 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt index 9beb9245fa5..4abe7d376c8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/common/DefaultAnalyzerFacade.kt @@ -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() { 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, 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, moduleName: Name, dependOnBuiltIns: Boolean): AnalysisResult { - val moduleInfo = SourceModuleInfo(moduleName, dependOnBuiltIns) + fun analyzeFiles( + files: Collection, moduleName: Name, dependOnBuiltIns: Boolean, + capabilities: Map, 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) diff --git a/compiler/tests-common/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java b/compiler/tests-common/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java index 396ca3bf336..d63554538bc 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java +++ b/compiler/tests-common/org/jetbrains/kotlin/test/util/RecursiveDescriptorComparator.java @@ -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)); } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java index 13acd88b9c4..e8053b6b3b4 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java @@ -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> MODULE_FILES = new ModuleDescriptor.Capability>(""); + @Override protected void analyzeAndCheck(File testDataFile, List testFiles) { Map> groupedByModule = CollectionsKt.groupByTo( @@ -141,21 +149,36 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest { List 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 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() { + @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 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 dependencies = moduleDescriptor.getTestOnly_AllDependentModules(); + + // TODO: diagnostics on common code reported during the platform module analysis should be distinguished somehow + // E.g. "... + List result = new ArrayList(0); + for (ModuleDescriptorImpl dependency : dependencies) { + if (dependency.getCapability(MultiTargetPlatform.CAPABILITY) == MultiTargetPlatform.Common.INSTANCE) { + List 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 testFiles, diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt index b57d4b49c15..c8f3772ee1d 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt @@ -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 get() = this.dependencies!!.allDependencies + override val allDependentModules: List get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this }