Better main function recognition and tests

This commit is contained in:
Sergey Mashkov
2015-07-23 21:12:14 +03:00
parent 273e69c02c
commit 8457b53f8b
14 changed files with 217 additions and 58 deletions
@@ -30,8 +30,6 @@ import org.jetbrains.kotlin.codegen.binding.PsiCodegenPredictor;
import org.jetbrains.kotlin.codegen.context.CodegenContext;
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor;
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor;
@@ -54,8 +52,6 @@ import org.jetbrains.kotlin.resolve.annotations.AnnotationsPackage;
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.StringValue;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
@@ -71,7 +67,6 @@ import org.jetbrains.org.objectweb.asm.commons.Method;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isUnit;
import static org.jetbrains.kotlin.codegen.AsmUtil.*;
@@ -716,20 +711,6 @@ public class JetTypeMapper {
}
}
@Nullable
private static String getPlatformName(@NotNull Annotated descriptor) {
AnnotationDescriptor platformNameAnnotation = descriptor.getAnnotations().findAnnotation(new FqName("kotlin.platform.platformName"));
if (platformNameAnnotation == null) return null;
Map<ValueParameterDescriptor, ConstantValue<?>> arguments = platformNameAnnotation.getAllValueArguments();
if (arguments.isEmpty()) return null;
ConstantValue<?> name = arguments.values().iterator().next();
if (!(name instanceof StringValue)) return null;
return ((StringValue) name).getValue();
}
@NotNull
public JvmMethodSignature mapSignature(@NotNull FunctionDescriptor descriptor) {
return mapSignature(descriptor, OwnerKind.IMPLEMENTATION);
@@ -29,13 +29,11 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.annotations.AnnotationsPackage;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeProjection;
import org.jetbrains.kotlin.types.checker.JetTypeChecker;
import org.jetbrains.kotlin.types.Variance;
import java.util.Collection;
import java.util.List;
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getBuiltIns;
public class MainFunctionDetector {
private final NotNullFunction<JetNamedFunction, FunctionDescriptor> getFunctionDescriptor;
@@ -63,9 +61,15 @@ public class MainFunctionDetector {
}
public boolean isMain(@NotNull JetNamedFunction function) {
if (!"main".equals(function.getName())) return false;
if (!"main".equals(function.getName()) && function.getAnnotationEntries().isEmpty()) {
return false;
}
FunctionDescriptor functionDescriptor = getFunctionDescriptor.fun(function);
if (!getJVMFunctionName(functionDescriptor).equals("main")) {
return false;
}
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
if (parameters.size() != 1) return false;
@@ -77,7 +81,12 @@ public class MainFunctionDetector {
if (typeArguments.size() != 1) return false;
JetType typeArgument = typeArguments.get(0).getType();
if (!JetTypeChecker.DEFAULT.equalTypes(typeArgument, getBuiltIns(functionDescriptor).getStringType())) return false;
if (!KotlinBuiltIns.isString(typeArgument)) {
return false;
}
if (typeArguments.get(0).getProjectionKind() != Variance.INVARIANT) {
return false;
}
if (DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) return true;
@@ -110,4 +119,14 @@ public class MainFunctionDetector {
}
return null;
}
@NotNull
private static String getJVMFunctionName(FunctionDescriptor functionDescriptor) {
String platformName = DescriptorUtils.getPlatformName(functionDescriptor);
if (platformName != null) {
return platformName;
}
return functionDescriptor.getName().asString();
}
}
@@ -21,6 +21,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.FunctionExpressionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl;
@@ -28,6 +30,8 @@ import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.name.SpecialNames;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.StringValue;
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
import org.jetbrains.kotlin.resolve.scopes.FilteringScope;
import org.jetbrains.kotlin.resolve.scopes.JetScope;
@@ -547,6 +551,20 @@ public class DescriptorUtils {
return result;
}
@Nullable
public static String getPlatformName(@NotNull Annotated descriptor) {
AnnotationDescriptor platformNameAnnotation = descriptor.getAnnotations().findAnnotation(new FqName("kotlin.platform.platformName"));
if (platformNameAnnotation == null) return null;
Map<ValueParameterDescriptor, ConstantValue<?>> arguments = platformNameAnnotation.getAllValueArguments();
if (arguments.isEmpty()) return null;
ConstantValue<?> name = arguments.values().iterator().next();
if (!(name instanceof StringValue)) return null;
return ((StringValue) name).getValue();
}
private static void getSubPackagesFqNames(PackageViewDescriptor packageView, Set<FqName> result) {
FqName fqName = packageView.getFqName();
if (!fqName.isRoot()) {
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.configuration
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.JetIcons
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetVisitorVoid
import org.jetbrains.kotlin.resolve.BindingContext
import javax.swing.Icon
class MainFunctionGutterVisitor(val holder: AnnotationHolder, val bindingContext: BindingContext) : JetVisitorVoid() {
override fun visitNamedFunction(function: JetNamedFunction) {
if (MainFunctionDetector(bindingContext).isMain(function)) {
function.nameIdentifier?.let { nameIdentifier ->
holder.createInfoAnnotation(nameIdentifier, "Run configuration from here").gutterIconRenderer = MainGutterIconRenderer(nameIdentifier)
}
}
}
}
class MainGutterIconRenderer(val nameIdentifier: PsiElement) : GutterIconRenderer() {
override fun getIcon(): Icon = JetIcons.LAUNCH
override fun hashCode(): Int = nameIdentifier.hashCode()
override fun equals(other: Any?): Boolean = other is MainGutterIconRenderer && other.nameIdentifier == nameIdentifier
}
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.configuration.MainFunctionGutterVisitor
import org.jetbrains.kotlin.idea.kdoc.KDocHighlightingVisitor
import org.jetbrains.kotlin.idea.quickfix.QuickFixes
import org.jetbrains.kotlin.idea.references.mainReference
@@ -249,8 +250,9 @@ public open class JetPsiChecker : Annotator, HighlightRangeExtension {
PropertiesHighlightingVisitor(holder, bindingContext),
FunctionsHighlightingVisitor(holder, bindingContext),
VariablesHighlightingVisitor(holder, bindingContext),
TypeKindHighlightingVisitor(holder, bindingContext)//,
TypeKindHighlightingVisitor(holder, bindingContext),
//DeprecatedAnnotationVisitor(holder, bindingContext)
MainFunctionGutterVisitor(holder, bindingContext)
)
public fun createQuickfixes(diagnostic: Diagnostic): Collection<IntentionAction> {
@@ -49,14 +49,11 @@ import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.KotlinLightClassForExplicitDeclaration;
import org.jetbrains.kotlin.asJava.KotlinLightClassForPackage;
import org.jetbrains.kotlin.asJava.KotlinLightMethod;
import org.jetbrains.kotlin.idea.MainFunctionDetector;
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
import org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.JetDeclaration;
import org.jetbrains.kotlin.psi.JetDeclarationContainer;
import org.jetbrains.kotlin.psi.JetNamedFunction;
@@ -228,11 +225,8 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
if (psiClass == null) {
throw new RuntimeConfigurationWarning("Class '" + MAIN_CLASS_NAME + "' not found in module " + getConfigurationModule().getModuleName());
}
try {
findMainFun(module, psiClass);
}
catch (CantRunException e) {
throw new RuntimeConfigurationWarning(e.getMessage());
if (findMainFun(psiClass) == null) {
throw new RuntimeConfigurationWarning("The class " + MAIN_CLASS_NAME + " has no main method");
}
}
@@ -311,18 +305,10 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
}
@NotNull
private static Collection<JetNamedFunction> getMainFunCandidates(@NotNull Module module, @NotNull PsiClass psiClass) {
if (psiClass instanceof KotlinLightClassForPackage) {
String qualifiedName = psiClass.getQualifiedName();
if (qualifiedName == null) return Collections.emptyList();
FqName mainFunFqName = new FqName(qualifiedName).parent().child(Name.identifier("main"));
return JetTopLevelFunctionFqnNameIndex.getInstance().get(
mainFunFqName.asString(), module.getProject(), module.getModuleRuntimeScope(true)
);
}
private static Collection<JetNamedFunction> getMainFunCandidates(@NotNull PsiClass psiClass) {
return KotlinPackage.filterNotNull(
KotlinPackage.map(
psiClass.findMethodsByName("main", false),
psiClass.getAllMethods(),
new Function1<PsiMethod, JetNamedFunction>() {
@Override
public JetNamedFunction invoke(PsiMethod method) {
@@ -337,8 +323,8 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
}
@Nullable
private static JetNamedFunction findMainFun(@NotNull Module module, @NotNull PsiClass psiClass) throws CantRunException {
for (JetNamedFunction function : getMainFunCandidates(module, psiClass)) {
private static JetNamedFunction findMainFun(@NotNull PsiClass psiClass) {
for (JetNamedFunction function : getMainFunCandidates(psiClass)) {
BindingContext bindingContext = ResolvePackage.analyze(function, BodyResolveMode.FULL);
MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(bindingContext);
if (mainFunctionDetector.isMain(function)) return function;
@@ -377,7 +363,7 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
PsiClass psiClass = JavaExecutionUtil.findMainClass(module, runClass);
if (psiClass == null) throw CantRunException.classNotFound(runClass, module);
JetNamedFunction mainFun = myConfiguration.findMainFun(module, psiClass);
JetNamedFunction mainFun = findMainFun(psiClass);
if (mainFun == null) throw new CantRunException(noFunctionFoundMessage(psiClass));
Module classModule = ModuleUtilCore.findModuleForPsiElement(mainFun);
-5
View File
@@ -1,5 +0,0 @@
package some
fun foo() {
}
@@ -0,0 +1,39 @@
package companions
import kotlin.platform.platformName
import kotlin.platform.platformStatic
class A {
companion object {
platformStatic
fun main(args: Array<String>) {
// yes
}
}
}
class B {
companion object {
fun main(args: Array<String>) {
// no
}
}
}
class C {
companion object {
platformStatic
platformName("main0")
fun main(args: Array<String>) { // no
}
}
}
class D {
companion object {
platformStatic
platformName("main")
fun badName(args: Array<String>) { // yes
}
}
}
@@ -0,0 +1,8 @@
package pkg2
import kotlin.platform.platformName
platformName("aa")
fun main(args: Array<String>) {
// no
}
@@ -0,0 +1,8 @@
package pkg3
import kotlin.platform.platformName
platformName("main")
fun aaa(args: Array<String>) {
// yes
}
@@ -0,0 +1,5 @@
package pkg1
fun main(args: Array<String>) { // yes
}
@@ -0,0 +1,5 @@
package pkg4
fun main(args: Array<in String>) {
// no
}
@@ -0,0 +1,5 @@
package pkg5
fun main(args: Array<out String>) {
// no
}
@@ -22,10 +22,7 @@ import com.intellij.execution.Location
import com.intellij.execution.PsiLocation
import com.intellij.execution.RunManagerEx
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configurations.JavaCommandLine
import com.intellij.execution.configurations.JavaParameters
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.configurations.RunProfile
import com.intellij.execution.configurations.*
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
@@ -38,6 +35,8 @@ import com.intellij.refactoring.RefactoringFactory
import com.intellij.testFramework.MapDataContext
import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.kotlin.idea.MainFunctionDetector
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.stubindex.JetFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex
@@ -46,10 +45,10 @@ import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinJsRuntime
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil.configureKotlinRuntimeAndSdk
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.JetFunction
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetTreeVisitorVoid
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.junit.Assert
import java.io.File
import java.util.ArrayList
@@ -62,12 +61,58 @@ class RunConfigurationTest: CodeInsightTestCase() {
fun testMainInTest() {
val createResult = configureModule(moduleDirPath("module"), getTestProject().getBaseDir()!!)
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(createResult.module, PluginTestCaseBase.mockJdk())
val runConfiguration = createConfigurationFromMain("some.main")
val javaParameters = getJavaRunParameters(runConfiguration)
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.srcOutputDir))
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.testOutputDir))
fun functionVisitor(function: JetNamedFunction) {
val options = function.bodyExpression?.allChildren?.filterIsInstance<PsiComment>()?.map { it.text.trim().replace("//", "").trim() }?.filter { it.isNotBlank() }?.toList() ?: emptyList()
if (options.isNotEmpty()) {
val assertIsMain = "yes" in options
val assertIsNotMain = "no" in options
val bindingContext = function.analyze(BodyResolveMode.FULL)
val isMainFunction = MainFunctionDetector(bindingContext).isMain(function)
if (assertIsMain) {
Assert.assertTrue("The function ${function.fqName?.asString()} should be main", isMainFunction)
}
if (assertIsNotMain) {
Assert.assertFalse("The function ${function.fqName?.asString()} should NOT be main", isMainFunction)
}
if (isMainFunction) {
createConfigurationFromMain(function.fqName?.asString()!!).checkConfiguration()
Assert.assertNotNull("Kotlin configuration producer should produce configuration for ${function.fqName?.asString()}",
KotlinRunConfigurationProducer.getEntryPointContainer(function))
} else {
try {
createConfigurationFromMain(function.fqName?.asString()!!).checkConfiguration()
Assert.fail("configuration for function ${function.fqName?.asString()} at least shouldn't pass checkConfiguration()")
} catch (expected: Throwable) {
}
Assert.assertNull("Kotlin configuration producer shouldN'T produce configuration for ${function.fqName?.asString()}",
KotlinRunConfigurationProducer.getEntryPointContainer(function))
}
}
}
createResult.srcDir.children.filter { it.extension == "kt" }.forEach {
val psiFile = PsiManager.getInstance(createResult.module.project).findFile(it)
if (psiFile is JetFile) {
psiFile.acceptChildren(object : JetVisitorVoid() {
override fun visitNamedFunction(function: JetNamedFunction) {
functionVisitor(function)
}
})
}
}
}
fun testDependencyModuleClasspath() {