Runner: Support classes and objects with [platformStatic] main()

#KT-6072 Fixed
This commit is contained in:
Alexey Sedunov
2014-12-12 19:11:27 +03:00
parent 4ecd3726b3
commit 3760eed8ff
5 changed files with 255 additions and 54 deletions
@@ -19,13 +19,13 @@ package org.jetbrains.jet.plugin;
import com.intellij.util.NotNullFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetNamedFunction;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.annotations.AnnotationsPackage;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeProjection;
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
@@ -61,25 +61,29 @@ public class MainFunctionDetector {
}
public boolean isMain(@NotNull JetNamedFunction function) {
if ("main".equals(function.getName())) {
FunctionDescriptor functionDescriptor = getFunctionDescriptor.fun(function);
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
if (parameters.size() == 1) {
ValueParameterDescriptor parameter = parameters.get(0);
JetType parameterType = parameter.getType();
KotlinBuiltIns kotlinBuiltIns = KotlinBuiltIns.getInstance();
if (KotlinBuiltIns.isArray(parameterType)) {
List<TypeProjection> typeArguments = parameterType.getArguments();
if (typeArguments.size() == 1) {
JetType typeArgument = typeArguments.get(0).getType();
if (JetTypeChecker.DEFAULT.equalTypes(typeArgument, kotlinBuiltIns.getStringType())) {
return true;
}
}
}
}
}
return false;
if (!"main".equals(function.getName())) return false;
FunctionDescriptor functionDescriptor = getFunctionDescriptor.fun(function);
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
if (parameters.size() != 1) return false;
ValueParameterDescriptor parameter = parameters.get(0);
JetType parameterType = parameter.getType();
KotlinBuiltIns kotlinBuiltIns = KotlinBuiltIns.getInstance();
if (!KotlinBuiltIns.isArray(parameterType)) return false;
List<TypeProjection> typeArguments = parameterType.getArguments();
if (typeArguments.size() != 1) return false;
JetType typeArgument = typeArguments.get(0).getType();
if (!JetTypeChecker.DEFAULT.equalTypes(typeArgument, kotlinBuiltIns.getStringType())) return false;
if (DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) return true;
DeclarationDescriptor containingDeclaration = functionDescriptor.getContainingDeclaration();
return containingDeclaration instanceof ClassDescriptor
&& ((ClassDescriptor) containingDeclaration).getKind().isSingleton()
&& AnnotationsPackage.hasPlatformStaticAnnotation(functionDescriptor);
}
@Nullable
@@ -35,9 +35,16 @@ import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.KotlinLightClassForExplicitDeclaration;
import org.jetbrains.jet.asJava.KotlinLightClassForPackage;
import org.jetbrains.jet.asJava.KotlinLightMethod;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetNamedFunction;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.name.FqName;
@@ -237,15 +244,14 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
PsiClass psiClass = JavaExecutionUtil.findMainClass(module, runClass);
if (psiClass == null) throw CantRunException.classNotFound(runClass, module);
FqName packageFqName = new FqName(runClass).parent();
JetNamedFunction mainFun = findMainFun(module, packageFqName);
if (mainFun == null) throw new CantRunException(String.format("Top-level function 'main' not found in package '%s'", packageFqName));
JetNamedFunction mainFun = findMainFun(module, psiClass);
if (mainFun == null) throw new CantRunException(noFunctionFoundMessage(psiClass));
Module classModule = ModuleUtilCore.findModuleForPsiElement(mainFun);
if (classModule == null) classModule = module;
VirtualFile virtualFileForMainFun = mainFun.getContainingFile().getVirtualFile();
if (virtualFileForMainFun == null) throw new CantRunException(String.format("Top-level function 'main' not found in package '%s'", packageFqName));
if (virtualFileForMainFun == null) throw new CantRunException(noFunctionFoundMessage(psiClass));
ModuleFileIndex fileIndex = ModuleRootManager.getInstance(classModule).getFileIndex();
if (fileIndex.isInSourceContent(virtualFileForMainFun)) {
@@ -265,17 +271,48 @@ public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurati
return JavaParameters.JDK_AND_CLASSES;
}
@NotNull
private String noFunctionFoundMessage(@NotNull PsiClass psiClass) {
//noinspection ConstantConditions
FqName classFqName = new FqName(psiClass.getQualifiedName());
if (psiClass instanceof KotlinLightClassForExplicitDeclaration) {
return String.format("Function 'main' not found in class '%s'", classFqName);
}
return String.format("Top-level function 'main' not found in package '%s'", classFqName.parent());
}
@NotNull
private 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)
);
}
return KotlinPackage.filterNotNull(
KotlinPackage.map(
psiClass.findMethodsByName("main", false),
new Function1<PsiMethod, JetNamedFunction>() {
@Override
public JetNamedFunction invoke(PsiMethod method) {
if (!(method instanceof KotlinLightMethod)) return null;
JetDeclaration declaration = ((KotlinLightMethod) method).getOrigin();
return declaration instanceof JetNamedFunction ? (JetNamedFunction) declaration : null;
}
}
)
);
}
@Nullable
private JetNamedFunction findMainFun(@NotNull Module module, @NotNull FqName packageFqName) throws CantRunException {
String mainFunFqName = packageFqName.child(Name.identifier("main")).asString();
Collection<JetNamedFunction> mainFunctions = JetTopLevelFunctionFqnNameIndex.getInstance().get(
mainFunFqName, module.getProject(), module.getModuleRuntimeScope(true));
for (JetNamedFunction function : mainFunctions) {
private JetNamedFunction findMainFun(@NotNull Module module, @NotNull PsiClass psiClass) throws CantRunException {
for (JetNamedFunction function : getMainFunCandidates(module, psiClass)) {
BindingContext bindingContext = ResolvePackage.analyze(function);
MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(bindingContext);
if (mainFunctionDetector.isMain(function)) {
return function;
}
if (mainFunctionDetector.isMain(function)) return function;
}
return null;
}
@@ -26,12 +26,13 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.NotNullFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetNamedFunction;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.plugin.MainFunctionDetector;
@@ -58,12 +59,13 @@ public class JetRunConfigurationProducer extends RuntimeConfigurationProducer im
@Override
protected RunnerAndConfigurationSettings createConfigurationByElement(@NotNull Location location, ConfigurationContext configurationContext) {
JetFile file = getStartClassFile(location);
if (file == null) return null;
JetDeclarationContainer container = getEntryPointContainer(location);
if (container == null) return null;
mySourceElement = file;
mySourceElement = (PsiElement) container;
FqName startClassFQName = PackageClassUtils.getPackageClassFqName(file.getPackageFqName());
FqName startClassFQName = getStartClassFqName(container);
if (startClassFQName == null) return null;
Module module = location.getModule();
assert module != null;
@@ -72,7 +74,21 @@ public class JetRunConfigurationProducer extends RuntimeConfigurationProducer im
}
@Nullable
private static JetFile getStartClassFile(@NotNull Location location) {
private static FqName getStartClassFqName(@Nullable JetDeclarationContainer container) {
if (container == null) return null;
if (container instanceof JetFile) return PackageClassUtils.getPackageClassFqName(((JetFile) container).getPackageFqName());
if (container instanceof JetClassOrObject) {
JetClassOrObject classOrObject = (JetClassOrObject) container;
if (classOrObject instanceof JetObjectDeclaration && ((JetObjectDeclaration) classOrObject).isClassObject()) {
classOrObject = PsiTreeUtil.getParentOfType(classOrObject, JetClass.class);
}
return classOrObject != null ? classOrObject.getFqName() : null;
}
throw new IllegalArgumentException("Invalid entry-point container: " + ((PsiElement) container).getText());
}
@Nullable
private static JetDeclarationContainer getEntryPointContainer(@NotNull Location location) {
if (DumbService.getInstance(location.getProject()).isDumb()) return null;
Module module = location.getModule();
@@ -80,7 +96,9 @@ public class JetRunConfigurationProducer extends RuntimeConfigurationProducer im
if (ProjectStructureUtil.isJsKotlinModule(module)) return null;
PsiFile psiFile = location.getPsiElement().getContainingFile();
PsiElement locationElement = location.getPsiElement();
PsiFile psiFile = locationElement.getContainingFile();
if (!(psiFile instanceof JetFile && ProjectRootsUtil.isInProjectOrLibSource(psiFile))) return null;
JetFile jetFile = (JetFile) psiFile;
@@ -94,7 +112,19 @@ public class JetRunConfigurationProducer extends RuntimeConfigurationProducer im
}
});
return mainFunctionDetector.hasMain(jetFile.getDeclarations()) ? jetFile : null;
for (JetDeclarationContainer currentElement = PsiTreeUtil.getNonStrictParentOfType(locationElement, JetClassOrObject.class,
JetFile.class);
currentElement != null;
currentElement = PsiTreeUtil.getParentOfType((PsiElement) currentElement, JetClassOrObject.class, JetFile.class)) {
JetDeclarationContainer entryPointContainer = currentElement;
if (entryPointContainer instanceof JetClass) {
JetClassObject classObject = ((JetClass) currentElement).getClassObject();
entryPointContainer = classObject != null ? classObject.getObjectDeclaration() : null;
}
if (entryPointContainer != null && mainFunctionDetector.hasMain(entryPointContainer.getDeclarations())) return entryPointContainer;
}
return null;
}
@NotNull
@@ -117,10 +147,8 @@ public class JetRunConfigurationProducer extends RuntimeConfigurationProducer im
@NotNull List<RunnerAndConfigurationSettings> existingConfigurations,
ConfigurationContext context
) {
JetFile file = getStartClassFile(location);
if (file == null) return null;
FqName startClassFQName = PackageClassUtils.getPackageClassFqName(file.getPackageFqName());
FqName startClassFQName = getStartClassFqName(getEntryPointContainer(location));
if (startClassFQName == null) return null;
for (RunnerAndConfigurationSettings existingConfiguration : existingConfigurations) {
if (existingConfiguration.getType() instanceof JetRunConfigurationType) {
@@ -0,0 +1,78 @@
package q
import kotlin.platform.platformStatic
// RUN: q.Foo
object Foo {
// RUN: q.Foo
platformStatic fun main(s: Array<String>) {
println("Foo")
}
// RUN: q.Foo.InnerFoo
class InnerFoo {
class object {
// RUN: q.Foo.InnerFoo
platformStatic fun main(s: Array<String>) {
println("InnerFoo")
}
}
}
// RUN: q.Foo
class InnerFoo2 {
// RUN: q.Foo
platformStatic fun main(s: Array<String>) {
println("InnerFoo")
}
}
}
// RUN: q.QPackage
object Foo2 {
// RUN: q.QPackage
fun main(s: Array<String>) {
println("Foo2")
}
}
// RUN: q.Bar
class Bar {
class object {
// RUN: q.Bar
platformStatic fun main(s: Array<String>) {
println("Bar")
}
}
}
// RUN: q.QPackage
class Bar2 {
class object {
// RUN: q.QPackage
fun main(s: Array<String>) {
println("Bar2")
}
}
}
// RUN: q.QPackage
class Baz {
// RUN: q.QPackage
platformStatic fun main(s: Array<String>) {
println("Baz")
}
}
// RUN: q.QPackage
class Baz2 {
// RUN: q.QPackage
fun main(s: Array<String>) {
println("Baz2")
}
}
// RUN: q.QPackage
fun main(s: Array<String>) {
println("Top-level")
}
@@ -40,6 +40,22 @@ import com.intellij.openapi.roots.ModuleRootModificationUtil
import org.jetbrains.jet.plugin.search.allScope
import org.jetbrains.jet.plugin.util.application.runWriteAction
import org.jetbrains.jet.plugin.stubindex.JetTopLevelFunctionFqnNameIndex
import org.jetbrains.jet.lang.psi.JetNamedFunction
import org.jetbrains.jet.lang.psi.JetTreeVisitorVoid
import org.jetbrains.jet.lang.psi.JetNamedDeclaration
import java.util.ArrayList
import com.intellij.psi.PsiComment
import org.jetbrains.jet.lang.psi.psiUtil.siblings
import org.jetbrains.jet.lang.psi.JetClass
import org.jetbrains.jet.lang.psi.JetObjectDeclaration
import org.jetbrains.jet.lang.psi.JetClassOrObject
import com.intellij.psi.PsiManager
import org.jetbrains.jet.testing.ConfigLibraryUtil
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.psiUtil.getParentOfType
import org.jetbrains.jet.lang.psi.psiUtil.getStrictParentOfType
private val RUN_PREFIX = "// RUN: "
class RunConfigurationTest: CodeInsightTestCase() {
fun getTestProject() = myProject!!
@@ -51,12 +67,12 @@ class RunConfigurationTest: CodeInsightTestCase() {
val runConfiguration = createConfigurationFromMain("some.main")
val javaParameters = getJavaRunParameters(runConfiguration)
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.src))
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.test))
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.srcOutputDir))
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(createResult.testOutputDir))
}
fun testDependencyModuleClasspath() {
val dependencyModuleSrcDir = configureModule(moduleDirPath("module"), getTestProject().getBaseDir()!!).src
val dependencyModuleSrcDir = configureModule(moduleDirPath("module"), getTestProject().getBaseDir()!!).srcOutputDir
val moduleWithDependencyDir = runWriteAction { getTestProject().getBaseDir()!!.createChildDirectory(this, "moduleWithDependency") }
@@ -64,7 +80,7 @@ class RunConfigurationTest: CodeInsightTestCase() {
ModuleRootModificationUtil.setModuleSdk(moduleWithDependency, getTestProjectJdk())
val moduleWithDependencySrcDir = configureModule(
moduleDirPath("moduleWithDependency"), moduleWithDependencyDir, configModule = moduleWithDependency).src
moduleDirPath("moduleWithDependency"), moduleWithDependencyDir, configModule = moduleWithDependency).srcOutputDir
ModuleRootModificationUtil.addDependency(moduleWithDependency, getModule())
@@ -77,6 +93,39 @@ class RunConfigurationTest: CodeInsightTestCase() {
Assert.assertTrue(javaParameters.getClassPath().getRootDirs().contains(moduleWithDependencySrcDir))
}
fun testClassesAndObjects() {
val baseDir = getTestProject().getBaseDir()!!
val createModuleResult = configureModule(moduleDirPath("module"), baseDir)
val srcDir = createModuleResult.srcDir
ConfigLibraryUtil.configureKotlinRuntime(createModuleResult.module, PluginTestCaseBase.fullJdk())
val expectedClasses = ArrayList<String>()
val actualClasses = ArrayList<String>()
val testFile = PsiManager.getInstance(getTestProject()).findFile(srcDir.findFileByRelativePath("test.kt"))
testFile.accept(
object: JetTreeVisitorVoid() {
override fun visitComment(comment: PsiComment) {
val declaration = comment.getStrictParentOfType<JetNamedDeclaration>()!!
val text = comment.getText() ?: return
if (!text.startsWith(RUN_PREFIX)) return
expectedClasses.add(text.substring(RUN_PREFIX.length()).trim())
val dataContext = MapDataContext()
dataContext.put(Location.DATA_KEY, PsiLocation(getTestProject(), declaration))
val context = ConfigurationContext.getFromContext(dataContext)
val actualClass = (context?.getConfiguration()?.getConfiguration() as? JetRunConfiguration)?.getRunClass()
if (actualClass != null) {
actualClasses.add(actualClass)
}
}
}
)
Assert.assertEquals(expectedClasses, actualClasses);
}
private fun createConfigurationFromMain(mainFqn: String): JetRunConfiguration {
val mainFunction = JetTopLevelFunctionFqnNameIndex.getInstance().get(mainFqn, getTestProject(), getTestProject().allScope()).first()
@@ -88,7 +137,7 @@ class RunConfigurationTest: CodeInsightTestCase() {
private fun configureModule(moduleDir: String, outputParentDir: VirtualFile, configModule: Module = getModule()): CreateModuleResult {
val srcPath = moduleDir + "/src"
PsiTestUtil.createTestProjectStructure(getProject(), configModule, srcPath, PlatformTestCase.myFilesToDelete, true)
val srcDir = PsiTestUtil.createTestProjectStructure(getProject(), configModule, srcPath, PlatformTestCase.myFilesToDelete, true)
val testPath = moduleDir + "/test"
if (File(testPath).exists()) {
@@ -109,7 +158,7 @@ class RunConfigurationTest: CodeInsightTestCase() {
PsiDocumentManager.getInstance(getTestProject()).commitAllDocuments()
return CreateModuleResult(configModule, srcOutDir, testOutDir)
return CreateModuleResult(configModule, srcDir, srcOutDir, testOutDir)
}
private fun moduleDirPath(moduleName: String) = "${getTestDataPath()}${getTestName(false)}/$moduleName"
@@ -127,7 +176,12 @@ class RunConfigurationTest: CodeInsightTestCase() {
override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + "/run/"
override fun getTestProjectJdk() = PluginTestCaseBase.jdkFromIdeaHome()
private class CreateModuleResult(val module: Module, val src: VirtualFile, val test: VirtualFile)
private class CreateModuleResult(
val module: Module,
val srcDir: VirtualFile,
val srcOutputDir: VirtualFile,
val testOutputDir: VirtualFile
)
private object MockExecutor : DefaultRunExecutor() {
override fun getId() = DefaultRunExecutor.EXECUTOR_ID