diff --git a/compiler/frontend/src/org/jetbrains/jet/plugin/MainFunctionDetector.java b/compiler/frontend/src/org/jetbrains/jet/plugin/MainFunctionDetector.java index a3a1783809d..a97084b686d 100644 --- a/compiler/frontend/src/org/jetbrains/jet/plugin/MainFunctionDetector.java +++ b/compiler/frontend/src/org/jetbrains/jet/plugin/MainFunctionDetector.java @@ -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 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 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 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 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 diff --git a/idea/src/org/jetbrains/jet/plugin/run/JetRunConfiguration.java b/idea/src/org/jetbrains/jet/plugin/run/JetRunConfiguration.java index 69fb02f49b1..b89d32ecfbd 100644 --- a/idea/src/org/jetbrains/jet/plugin/run/JetRunConfiguration.java +++ b/idea/src/org/jetbrains/jet/plugin/run/JetRunConfiguration.java @@ -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 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() { + @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 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; } diff --git a/idea/src/org/jetbrains/jet/plugin/run/JetRunConfigurationProducer.java b/idea/src/org/jetbrains/jet/plugin/run/JetRunConfigurationProducer.java index 761401eed9c..ff41ee45006 100644 --- a/idea/src/org/jetbrains/jet/plugin/run/JetRunConfigurationProducer.java +++ b/idea/src/org/jetbrains/jet/plugin/run/JetRunConfigurationProducer.java @@ -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 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) { diff --git a/idea/testData/run/ClassesAndObjects/module/src/test.kt b/idea/testData/run/ClassesAndObjects/module/src/test.kt new file mode 100644 index 00000000000..222c4a87846 --- /dev/null +++ b/idea/testData/run/ClassesAndObjects/module/src/test.kt @@ -0,0 +1,78 @@ +package q + +import kotlin.platform.platformStatic + +// RUN: q.Foo +object Foo { + // RUN: q.Foo + platformStatic fun main(s: Array) { + println("Foo") + } + + // RUN: q.Foo.InnerFoo + class InnerFoo { + class object { + // RUN: q.Foo.InnerFoo + platformStatic fun main(s: Array) { + println("InnerFoo") + } + } + } + + // RUN: q.Foo + class InnerFoo2 { + // RUN: q.Foo + platformStatic fun main(s: Array) { + println("InnerFoo") + } + } +} + +// RUN: q.QPackage +object Foo2 { + // RUN: q.QPackage + fun main(s: Array) { + println("Foo2") + } +} + +// RUN: q.Bar +class Bar { + class object { + // RUN: q.Bar + platformStatic fun main(s: Array) { + println("Bar") + } + } +} + +// RUN: q.QPackage +class Bar2 { + class object { + // RUN: q.QPackage + fun main(s: Array) { + println("Bar2") + } + } +} + +// RUN: q.QPackage +class Baz { + // RUN: q.QPackage + platformStatic fun main(s: Array) { + println("Baz") + } +} + +// RUN: q.QPackage +class Baz2 { + // RUN: q.QPackage + fun main(s: Array) { + println("Baz2") + } +} + +// RUN: q.QPackage +fun main(s: Array) { + println("Top-level") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/run/RunConfigurationTest.kt b/idea/tests/org/jetbrains/jet/plugin/run/RunConfigurationTest.kt index 4a599512934..7b706605572 100644 --- a/idea/tests/org/jetbrains/jet/plugin/run/RunConfigurationTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/run/RunConfigurationTest.kt @@ -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() + val actualClasses = ArrayList() + + val testFile = PsiManager.getInstance(getTestProject()).findFile(srcDir.findFileByRelativePath("test.kt")) + testFile.accept( + object: JetTreeVisitorVoid() { + override fun visitComment(comment: PsiComment) { + val declaration = comment.getStrictParentOfType()!! + 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