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
@@ -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);