diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java index 9c947619152..8e1028321c3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java @@ -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> 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); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.java b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.java index 0a09f9a2b64..52e2b74ff2a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.java @@ -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 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 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(); + } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java index 3f9fb979e0f..aed392cd670 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java @@ -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> 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 result) { FqName fqName = packageView.getFqName(); if (!fqName.isRoot()) { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/configuration/MainFunctionGutterVisitor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/configuration/MainFunctionGutterVisitor.kt new file mode 100644 index 00000000000..a1585ccb032 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/configuration/MainFunctionGutterVisitor.kt @@ -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 +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt index e6d441994be..9e981b0d3ff 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/JetPsiChecker.kt @@ -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 { diff --git a/idea/src/org/jetbrains/kotlin/idea/run/JetRunConfiguration.java b/idea/src/org/jetbrains/kotlin/idea/run/JetRunConfiguration.java index bcec8cd6725..18581e7c1cd 100644 --- a/idea/src/org/jetbrains/kotlin/idea/run/JetRunConfiguration.java +++ b/idea/src/org/jetbrains/kotlin/idea/run/JetRunConfiguration.java @@ -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 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 getMainFunCandidates(@NotNull PsiClass psiClass) { return KotlinPackage.filterNotNull( KotlinPackage.map( - psiClass.findMethodsByName("main", false), + psiClass.getAllMethods(), new Function1() { @Override public JetNamedFunction invoke(PsiMethod method) { @@ -337,8 +323,8 @@ public class JetRunConfiguration extends ModuleBasedConfiguration) { + // yes + } + } +} + +class B { + companion object { + fun main(args: Array) { + // no + } + } +} + +class C { + companion object { + platformStatic + platformName("main0") + fun main(args: Array) { // no + } + } +} + +class D { + companion object { + platformStatic + platformName("main") + fun badName(args: Array) { // yes + } + } +} diff --git a/idea/testData/run/MainInTest/module/src/mainInPackageNamedInvalid.kt b/idea/testData/run/MainInTest/module/src/mainInPackageNamedInvalid.kt new file mode 100644 index 00000000000..2560c3b5aa9 --- /dev/null +++ b/idea/testData/run/MainInTest/module/src/mainInPackageNamedInvalid.kt @@ -0,0 +1,8 @@ +package pkg2 + +import kotlin.platform.platformName + +platformName("aa") +fun main(args: Array) { + // no +} diff --git a/idea/testData/run/MainInTest/module/src/mainInPackageNamedValid.kt b/idea/testData/run/MainInTest/module/src/mainInPackageNamedValid.kt new file mode 100644 index 00000000000..937f41dec23 --- /dev/null +++ b/idea/testData/run/MainInTest/module/src/mainInPackageNamedValid.kt @@ -0,0 +1,8 @@ +package pkg3 + +import kotlin.platform.platformName + +platformName("main") +fun aaa(args: Array) { + // yes +} diff --git a/idea/testData/run/MainInTest/module/src/mainInPackageSimple.kt b/idea/testData/run/MainInTest/module/src/mainInPackageSimple.kt new file mode 100644 index 00000000000..b7b1c41641f --- /dev/null +++ b/idea/testData/run/MainInTest/module/src/mainInPackageSimple.kt @@ -0,0 +1,5 @@ +package pkg1 + +fun main(args: Array) { // yes +} + diff --git a/idea/testData/run/MainInTest/module/src/mainInPackageVariance.kt b/idea/testData/run/MainInTest/module/src/mainInPackageVariance.kt new file mode 100644 index 00000000000..b51c70970a2 --- /dev/null +++ b/idea/testData/run/MainInTest/module/src/mainInPackageVariance.kt @@ -0,0 +1,5 @@ +package pkg4 + +fun main(args: Array) { + // no +} diff --git a/idea/testData/run/MainInTest/module/src/mainInPackageVarianceOut.kt b/idea/testData/run/MainInTest/module/src/mainInPackageVarianceOut.kt new file mode 100644 index 00000000000..07b47be2695 --- /dev/null +++ b/idea/testData/run/MainInTest/module/src/mainInPackageVarianceOut.kt @@ -0,0 +1,5 @@ +package pkg5 + +fun main(args: Array) { + // no +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt index 506285369f9..10beea50de1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt @@ -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()?.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() {