Support enabling/disabling language features in diagnostic tests

This commit is contained in:
Alexander Udalov
2016-05-24 17:36:54 +03:00
parent e1ba4480d7
commit 98c3e030a1
6 changed files with 154 additions and 30 deletions
@@ -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,
+22 -10
View File
@@ -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 */
// 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
@@ -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<? extends TestFile> 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<TestFile> 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<KtFile> 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())
);
}
@@ -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<KtFile> jetFiles,
@NotNull BindingTrace moduleTrace
@NotNull List<KtFile> 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
@@ -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<KtFile>,
moduleTrace: BindingTrace
) {
val analysisResult = TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(jetFiles, moduleTrace, moduleContext, config)
ktFiles: MutableList<KtFile>,
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
}
}
@@ -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<LanguageFeatureSettings> LANGUAGE_FEATURE_SETTINGS_CONSTRUCTOR;
private static final Map<String, KParameter> LANGUAGE_FEATURE_SETTINGS_PARAMETERS;
static {
KClass<LanguageFeatureSettings> 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<KParameter, String>() {
@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<String, String> 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<KParameter, Boolean> values = new HashMap<KParameter, Boolean>();
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<Diagnostic> parseDiagnosticFilterDirective(Map<String, String> 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<Diagnostic> 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);