From 98c3e030a1ee82f1ed744a6ebcfee7014c33fb3c Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 24 May 2016 17:36:54 +0300 Subject: [PATCH] Support enabling/disabling language features in diagnostic tests --- .../kotlin/config/LanguageFeatureSettings.kt | 1 + compiler/testData/diagnostics/ReadMe.md | 32 ++++++--- .../checkers/AbstractDiagnosticsTest.java | 50 +++++++++++--- .../AbstractDiagnosticsTestWithJsStdLib.java | 16 +++-- ...csTestWithJsStdLibAndBackendCompilation.kt | 16 +++-- .../kotlin/checkers/BaseDiagnosticsTest.java | 69 +++++++++++++++++++ 6 files changed, 154 insertions(+), 30 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/config/LanguageFeatureSettings.kt b/compiler/frontend/src/org/jetbrains/kotlin/config/LanguageFeatureSettings.kt index d9638f44319..67ade88b0ca 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/config/LanguageFeatureSettings.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/config/LanguageFeatureSettings.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.config +// Note: names of these parameters are also used in diagnostic tests class LanguageFeatureSettings( val typeAliases: Boolean = true, val localDelegatedProperties: Boolean = true, diff --git a/compiler/testData/diagnostics/ReadMe.md b/compiler/testData/diagnostics/ReadMe.md index 54681b52bec..57280d8c454 100644 --- a/compiler/testData/diagnostics/ReadMe.md +++ b/compiler/testData/diagnostics/ReadMe.md @@ -12,12 +12,11 @@ Must be where -* `'+'` means 'include'; -* `'-'` means 'exclude'; -* `'!'` means 'exclude everything but this'. +* `+` means 'include'; +* `-` means 'exclude'; +* `!` means 'exclude everything but this'. - Directives are applied in the order of appearance, - i.e. `!FOO +BAR` means include only `FOO` and `BAR`. + Directives are applied in the order of appearance, i.e. `!FOO +BAR` means include only `FOO` and `BAR`. #### Examples: @@ -48,11 +47,24 @@ With that, an exact type of an expression can be checked in the following way: ### 3. FILE -The directive let you compose a test consisting of several files in one actual file. +The directive lets you compose a test consisting of several files in one actual file. #### Usage: -// FILE: A.java -/* Java code */ -// FILE: B.kt -/* kotlin code */ \ No newline at end of file + // FILE: A.java + /* Java code */ + + // FILE: B.kt + /* Kotlin code */ + +### 4. LANGUAGE + +This directive lets you enable or disable certain language features. Language features are named as +parameters of the primary constructor of the class LanguageFeatureSettings. Each feature can be +enabled with `+` or disabled with `-`. + +#### Usage: + + // !LANGUAGE: -topLevelSealedInheritance + + // !LANGUAGE: +typeAliases -localDelegatedProperties diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java index 46ea2cc3f06..5ae5ca8d474 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTest.java @@ -26,8 +26,12 @@ import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider; +import org.jetbrains.kotlin.config.CommonConfigurationKeys; +import org.jetbrains.kotlin.config.CompilerConfiguration; +import org.jetbrains.kotlin.config.LanguageFeatureSettings; import org.jetbrains.kotlin.context.ContextKt; import org.jetbrains.kotlin.context.GlobalContext; import org.jetbrains.kotlin.context.ModuleContext; @@ -61,6 +65,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.util.DescriptorValidator; import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; +import org.junit.Assert; import java.io.File; import java.util.*; @@ -130,9 +135,9 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest { moduleBindings.put(testModule, moduleTrace.getBindingContext()); - + LanguageFeatureSettings languageFeatureSettings = loadCustomLanguageFeatureSettings(testFilesInModule); ModuleContext moduleContext = ContextKt.withModule(ContextKt.withProject(context, getProject()), module); - analyzeModuleContents(moduleContext, jetFiles, moduleTrace); + analyzeModuleContents(moduleContext, jetFiles, moduleTrace, languageFeatureSettings); checkAllResolvedCallsAreCompleted(jetFiles, moduleTrace.getBindingContext()); } @@ -191,6 +196,24 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest { } } + @Nullable + private LanguageFeatureSettings loadCustomLanguageFeatureSettings(List module) { + LanguageFeatureSettings result = null; + for (TestFile file : module) { + if (file.customLanguageFeatureSettings != null) { + if (result != null) { + Assert.fail( + "More than one file in the module has " + BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " directive specified. " + + "This is not supported. Please move all directives into one file" + ); + } + result = file.customLanguageFeatureSettings; + } + } + + return result; + } + private void checkDynamicCallDescriptors(File expectedFile, List testFiles) { RecursiveDescriptorComparator serializer = new RecursiveDescriptorComparator(RECURSIVE_ALL); @@ -236,19 +259,26 @@ public abstract class AbstractDiagnosticsTest extends BaseDiagnosticsTest { return new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".lazy.log"); } - protected void analyzeModuleContents( + @NotNull + protected AnalysisResult analyzeModuleContents( @NotNull ModuleContext moduleContext, @NotNull List files, - @NotNull BindingTrace moduleTrace + @NotNull BindingTrace moduleTrace, + @Nullable LanguageFeatureSettings languageFeatureSettings ) { + CompilerConfiguration configuration; + if (languageFeatureSettings != null) { + configuration = getEnvironment().getConfiguration().copy(); + configuration.put(CommonConfigurationKeys.LANGUAGE_FEATURE_SETTINGS, languageFeatureSettings); + } + else { + configuration = getEnvironment().getConfiguration(); + } + // New JavaDescriptorResolver is created for each module, which is good because it emulates different Java libraries for each module, // albeit with same class names - TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( - moduleContext, - files, - moduleTrace, - getEnvironment().getConfiguration(), - new JvmPackagePartProvider(getEnvironment()) + return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + moduleContext, files, moduleTrace, configuration, new JvmPackagePartProvider(getEnvironment()) ); } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLib.java b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLib.java index ea03fc895da..3997262fecf 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLib.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLib.java @@ -17,12 +17,15 @@ package org.jetbrains.kotlin.checkers; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.config.CompilerConfiguration; +import org.jetbrains.kotlin.config.LanguageFeatureSettings; import org.jetbrains.kotlin.context.ModuleContext; import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS; +import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult; import org.jetbrains.kotlin.js.config.JSConfigurationKeys; import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.config.LibrarySourcesConfig; @@ -63,12 +66,17 @@ public abstract class AbstractDiagnosticsTestWithJsStdLib extends AbstractDiagno } @Override - protected void analyzeModuleContents( + @NotNull + protected JsAnalysisResult analyzeModuleContents( @NotNull ModuleContext moduleContext, - @NotNull List jetFiles, - @NotNull BindingTrace moduleTrace + @NotNull List ktFiles, + @NotNull BindingTrace moduleTrace, + @Nullable LanguageFeatureSettings languageFeatureSettings ) { - TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(jetFiles, moduleTrace, moduleContext, config); + // TODO: support LANGUAGE directive in JS diagnostic tests + assert languageFeatureSettings == null + : BaseDiagnosticsTest.LANGUAGE_DIRECTIVE + " directive is not supported in JS diagnostic tests"; + return TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(ktFiles, moduleTrace, moduleContext, config); } @Override diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation.kt b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation.kt index f728213de91..f0f8234936d 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation.kt +++ b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation.kt @@ -16,9 +16,10 @@ package org.jetbrains.kotlin.checkers +import org.jetbrains.kotlin.config.LanguageFeatureSettings import org.jetbrains.kotlin.context.ModuleContext import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError -import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS +import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult import org.jetbrains.kotlin.js.facade.K2JSTranslator import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.psi.KtFile @@ -27,15 +28,18 @@ import org.jetbrains.kotlin.resolve.BindingTrace abstract class AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation : AbstractDiagnosticsTestWithJsStdLib() { override fun analyzeModuleContents( moduleContext: ModuleContext, - jetFiles: MutableList, - moduleTrace: BindingTrace - ) { - val analysisResult = TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(jetFiles, moduleTrace, moduleContext, config) + ktFiles: MutableList, + moduleTrace: BindingTrace, + languageFeatureSettings: LanguageFeatureSettings? + ): JsAnalysisResult { + val analysisResult = super.analyzeModuleContents(moduleContext, ktFiles, moduleTrace, languageFeatureSettings) val diagnostics = analysisResult.bindingTrace.bindingContext.diagnostics if (!hasError(diagnostics)) { val translator = K2JSTranslator(config) - translator.translate(jetFiles, MainCallParameters.noCall(), analysisResult) + translator.translate(ktFiles, MainCallParameters.noCall(), analysisResult) } + + return analysisResult } } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java b/compiler/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java index 1aef8035191..10f0f9e7c6c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java @@ -28,11 +28,18 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; import kotlin.collections.CollectionsKt; +import kotlin.jvm.JvmClassMappingKt; import kotlin.jvm.functions.Function1; +import kotlin.reflect.KClass; +import kotlin.reflect.KClasses; +import kotlin.reflect.KFunction; +import kotlin.reflect.KParameter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.DuplicateJvmSignatureUtilKt; +import org.jetbrains.kotlin.config.LanguageFeatureSettings; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.load.java.InternalFlexibleTypeTransformer; @@ -41,6 +48,7 @@ import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.utils.StringsKt; import org.junit.Assert; import java.io.File; @@ -63,6 +71,9 @@ public abstract class BaseDiagnosticsTest CheckerTestUtil.DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET ); + public static final String LANGUAGE_DIRECTIVE = "LANGUAGE"; + private static final Pattern LANGUAGE_PATTERN = Pattern.compile("([\\+\\-])(\\w+)\\s*"); + public static final String CHECK_TYPE_DIRECTIVE = "CHECK_TYPE"; public static final String CHECK_TYPE_PACKAGE = "tests._checkType"; private static final String CHECK_TYPE_DECLARATIONS = "\npackage " + CHECK_TYPE_PACKAGE + @@ -84,6 +95,26 @@ public abstract class BaseDiagnosticsTest public static final String MARK_DYNAMIC_CALLS_DIRECTIVE = "MARK_DYNAMIC_CALLS"; + private static final KFunction LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR; + private static final Map LANGUAGE_FEATURE_SETTINGS_PARAMETERS; + + static { + KClass kotlinClass = JvmClassMappingKt.getKotlinClass(LanguageFeatureSettings.class); + LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR = KClasses.getPrimaryConstructor(kotlinClass); + assert LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR != null + : "LanguageFeatureSettings should have a primary constructor: " + kotlinClass.getConstructors(); + + LANGUAGE_FEATURE_SETTINGS_PARAMETERS = CollectionsKt.associateBy( + LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR.getParameters(), + new Function1() { + @Override + public String invoke(KParameter parameter) { + return parameter.getName(); + } + } + ); + } + @Override protected TestModule createTestModule(@NotNull String name) { return new TestModule(name); @@ -146,6 +177,42 @@ public abstract class BaseDiagnosticsTest return jetFiles; } + @Nullable + private static LanguageFeatureSettings parseLanguageFeatureSettingsDirective(Map directiveMap) { + String directives = directiveMap.get(LANGUAGE_DIRECTIVE); + if (directives == null) return null; + + Matcher matcher = LANGUAGE_PATTERN.matcher(directives); + if (!matcher.find()) { + Assert.fail( + "Wrong syntax in the '// !LANGUAGE: ...' directive:\n" + + "found: '" + directives + "'\n" + + "Must be '([+-]languageFeatureName)+'\n" + + "where '+' means 'enable' and '-' means 'disable'\n" + + "and language feature names are names of parameters of the class LanguageFeatureSettings" + ); + } + + Map values = new HashMap(); + do { + boolean enable = matcher.group(1).equals("+"); + String name = matcher.group(2); + KParameter parameter = LANGUAGE_FEATURE_SETTINGS_PARAMETERS.get(name); + if (parameter == null) { + Assert.fail( + "Language feature not found, please check spelling: " + name + "\n" + + "Known features:\n " + StringsKt.join(LANGUAGE_FEATURE_SETTINGS_PARAMETERS.keySet(), "\n ") + ); + } + if (values.put(parameter, enable) != null) { + Assert.fail("Duplicate entry for the language feature: " + name); + } + } + while (matcher.find()); + + return LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR.callBy(values); + } + public static Condition parseDiagnosticFilterDirective(Map directiveMap) { String directives = directiveMap.get(DIAGNOSTICS_DIRECTIVE); if (directives == null) { @@ -243,6 +310,7 @@ public abstract class BaseDiagnosticsTest private final String clearText; private final KtFile jetFile; private final Condition whatDiagnosticsToConsider; + public final LanguageFeatureSettings customLanguageFeatureSettings; private final boolean declareCheckType; private final boolean declareFlexibleType; public final boolean checkLazyLog; @@ -257,6 +325,7 @@ public abstract class BaseDiagnosticsTest ) { this.module = module; this.whatDiagnosticsToConsider = parseDiagnosticFilterDirective(directives); + this.customLanguageFeatureSettings = parseLanguageFeatureSettingsDirective(directives); this.checkLazyLog = directives.containsKey(CHECK_LAZY_LOG_DIRECTIVE) || CHECK_LAZY_LOG_DEFAULT; this.declareCheckType = directives.containsKey(CHECK_TYPE_DIRECTIVE); this.declareFlexibleType = directives.containsKey(EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE);