diff --git a/.idea/dictionaries/dzharkov.xml b/.idea/dictionaries/dzharkov.xml
index 6a38863e835..0438a6e7a43 100644
--- a/.idea/dictionaries/dzharkov.xml
+++ b/.idea/dictionaries/dzharkov.xml
@@ -7,6 +7,7 @@
experimentality
insn
liveness
+ parameterless
-
\ No newline at end of file
+
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt
index a1367d9ed8b..4aa3ea0d0ac 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -48,16 +49,19 @@ class MainFunctionDetector {
}
@JvmOverloads
- fun isMain(function: KtNamedFunction, checkJvmStaticAnnotation: Boolean = true): Boolean {
+ fun isMain(
+ function: KtNamedFunction,
+ checkJvmStaticAnnotation: Boolean = true,
+ allowParameterless: Boolean = true
+ ): Boolean {
if (function.isLocal) {
return false
}
-
var parametersCount = function.valueParameters.size
if (function.receiverTypeReference != null) parametersCount++
- if (parametersCount != 1) {
+ if (!isParameterNumberSuitsForMain(parametersCount, function.isTopLevel, allowParameterless)) {
return false
}
@@ -76,13 +80,14 @@ class MainFunctionDetector {
}
val functionDescriptor = getFunctionDescriptor(function) ?: return false
- return isMain(functionDescriptor, checkJvmStaticAnnotation)
+ return isMain(functionDescriptor, checkJvmStaticAnnotation, allowParameterless = allowParameterless)
}
fun isMain(
descriptor: DeclarationDescriptor,
checkJvmStaticAnnotation: Boolean = true,
- checkReturnType: Boolean = true
+ checkReturnType: Boolean = true,
+ allowParameterless: Boolean = true
): Boolean {
if (descriptor !is FunctionDescriptor) return false
@@ -93,20 +98,40 @@ class MainFunctionDetector {
val parameters = descriptor.valueParameters.mapTo(mutableListOf()) { it.type }
descriptor.extensionReceiverParameter?.type?.let { parameters += it }
- if (parameters.size != 1 || !descriptor.typeParameters.isEmpty()) return false
-
- val parameterType = parameters[0]
- if (!KotlinBuiltIns.isArray(parameterType)) return false
-
- val typeArguments = parameterType.arguments
- if (typeArguments.size != 1) return false
-
- val typeArgument = typeArguments[0].type
- if (!KotlinBuiltIns.isString(typeArgument)) {
+ if (!isParameterNumberSuitsForMain(
+ parameters.size,
+ DescriptorUtils.isTopLevelDeclaration(descriptor),
+ allowParameterless
+ )
+ ) {
return false
}
- if (typeArguments[0].projectionKind === Variance.IN_VARIANCE) {
- return false
+
+ if (descriptor.typeParameters.isNotEmpty()) return false
+
+ if (parameters.size == 1) {
+ val parameterType = parameters[0]
+ if (!KotlinBuiltIns.isArray(parameterType)) return false
+
+ val typeArguments = parameterType.arguments
+ if (typeArguments.size != 1) return false
+
+ val typeArgument = typeArguments[0].type
+ if (!KotlinBuiltIns.isString(typeArgument)) {
+ return false
+ }
+ if (typeArguments[0].projectionKind === Variance.IN_VARIANCE) {
+ return false
+ }
+ } else {
+ assert(parameters.size == 0) { "Parameter list is expected to be empty" }
+ assert(DescriptorUtils.isTopLevelDeclaration(descriptor)) { "main without parameters works only for top-level" }
+ val containingFile = DescriptorToSourceUtils.getContainingFile(descriptor)
+ // We do not support parameterless entry points having JvmName("name") but different real names
+ if (descriptor.name.asString() != "main") return false
+ if (containingFile?.declarations?.any { declaration -> isMainWithParameter(declaration, checkJvmStaticAnnotation) } == true) {
+ return false
+ }
}
if (checkReturnType && !isMainReturnType(descriptor)) return false
@@ -119,6 +144,10 @@ class MainFunctionDetector {
&& (descriptor.hasJvmStaticAnnotation() || !checkJvmStaticAnnotation)
}
+ private fun isMainWithParameter(
+ declaration: KtDeclaration,
+ checkJvmStaticAnnotation: Boolean
+ ) = declaration is KtNamedFunction && isMain(declaration, checkJvmStaticAnnotation, allowParameterless = false)
fun getMainFunction(module: ModuleDescriptor): FunctionDescriptor? = getMainFunction(module, module.getPackage(FqName.ROOT))
@@ -141,6 +170,16 @@ class MainFunctionDetector {
declarations.filterIsInstance().find { isMain(it) }
companion object {
+ private fun isParameterNumberSuitsForMain(
+ parametersCount: Int,
+ isTopLevel: Boolean,
+ allowParameterless: Boolean
+ ) = when (parametersCount) {
+ 1 -> true
+ 0 -> isTopLevel && allowParameterless
+ else -> false
+ }
+
private fun isMainReturnType(descriptor: FunctionDescriptor): Boolean {
val returnType = descriptor.returnType
return returnType != null && KotlinBuiltIns.isUnit(returnType)
diff --git a/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt
new file mode 100644
index 00000000000..41583bce9c6
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt
@@ -0,0 +1,5 @@
+// FILE: a.kt
+fun main() {}
+
+// FILE: b.kt
+fun main() {}
diff --git a/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.txt b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.txt
new file mode 100644
index 00000000000..0bff3544466
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.txt
@@ -0,0 +1,4 @@
+package
+
+public fun main(): kotlin.Unit
+public fun main(): kotlin.Unit
diff --git a/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt
new file mode 100644
index 00000000000..e94e65a7955
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt
@@ -0,0 +1,7 @@
+// FILE: a.kt
+fun main() {}
+
+suspend fun main(args: Array) {}
+
+// FILE: b.kt
+fun main() {}
diff --git a/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.txt b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.txt
new file mode 100644
index 00000000000..82a40d3a5a2
--- /dev/null
+++ b/compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.txt
@@ -0,0 +1,5 @@
+package
+
+public fun main(): kotlin.Unit
+public fun main(): kotlin.Unit
+public suspend fun main(/*0*/ args: kotlin.Array): kotlin.Unit
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
index 6d1f9c8dda0..30c48561874 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java
@@ -15926,6 +15926,16 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationMainInMultiFile.kt");
}
+ @TestMetadata("RedeclarationParameterlessMain.kt")
+ public void testRedeclarationParameterlessMain() throws Exception {
+ runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt");
+ }
+
+ @TestMetadata("RedeclarationParameterlessMainInvalid.kt")
+ public void testRedeclarationParameterlessMainInvalid() throws Exception {
+ runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt");
+ }
+
@TestMetadata("RedeclarationSuspendMainInMultiFile.kt")
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
index d2061b46fe7..779bf53c521 100644
--- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
+++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java
@@ -15926,6 +15926,16 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationMainInMultiFile.kt");
}
+ @TestMetadata("RedeclarationParameterlessMain.kt")
+ public void testRedeclarationParameterlessMain() throws Exception {
+ runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt");
+ }
+
+ @TestMetadata("RedeclarationParameterlessMainInvalid.kt")
+ public void testRedeclarationParameterlessMainInvalid() throws Exception {
+ runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt");
+ }
+
@TestMetadata("RedeclarationSuspendMainInMultiFile.kt")
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
diff --git a/idea/testData/run/MainInTest/module/src/parameterlessInObject.kt b/idea/testData/run/MainInTest/module/src/parameterlessInObject.kt
new file mode 100644
index 00000000000..2d9cbaebf13
--- /dev/null
+++ b/idea/testData/run/MainInTest/module/src/parameterlessInObject.kt
@@ -0,0 +1,15 @@
+package parameterless
+
+class A {
+ companion object {
+ @JvmStatic
+ fun main() { // no
+ }
+ }
+}
+
+object B {
+ @JvmStatic
+ fun main() { // no
+ }
+}
diff --git a/idea/testData/run/MainInTest/module/src/parameterlessInvalid.kt b/idea/testData/run/MainInTest/module/src/parameterlessInvalid.kt
new file mode 100644
index 00000000000..f8deae59ce7
--- /dev/null
+++ b/idea/testData/run/MainInTest/module/src/parameterlessInvalid.kt
@@ -0,0 +1,9 @@
+// entryPointExists
+package parameterlessInvalid
+
+fun main() { // no
+}
+
+@JvmName("main")
+fun notMain(args: Array) { // yes
+}
diff --git a/idea/testData/run/MainInTest/module/src/parameterlessValid.kt b/idea/testData/run/MainInTest/module/src/parameterlessValid.kt
new file mode 100644
index 00000000000..680992d497e
--- /dev/null
+++ b/idea/testData/run/MainInTest/module/src/parameterlessValid.kt
@@ -0,0 +1,4 @@
+package parameterless
+
+fun main() { // yes
+}
diff --git a/idea/testData/run/MainInTest/module/src/parameterlessWithJvmName.kt b/idea/testData/run/MainInTest/module/src/parameterlessWithJvmName.kt
new file mode 100644
index 00000000000..147938991b6
--- /dev/null
+++ b/idea/testData/run/MainInTest/module/src/parameterlessWithJvmName.kt
@@ -0,0 +1,5 @@
+package parameterlessWithJvmName
+
+@JvmName("main")
+fun notMain() { // no
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt
index c12fd5ab47e..6797149bc55 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt
+++ b/idea/tests/org/jetbrains/kotlin/idea/run/RunConfigurationTest.kt
@@ -31,7 +31,7 @@ import com.intellij.refactoring.RefactoringFactory
import com.intellij.testFramework.MapDataContext
import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.kotlin.idea.MainFunctionDetector
-import org.jetbrains.kotlin.idea.caches.resolve.analyze
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
@@ -42,7 +42,6 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction
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.*
@@ -69,8 +68,8 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
val assertIsMain = "yes" in options
val assertIsNotMain = "no" in options
- val bindingContext = function.analyze(BodyResolveMode.FULL)
- val isMainFunction = MainFunctionDetector(bindingContext).isMain(function)
+ val isMainFunction =
+ MainFunctionDetector { it.resolveToDescriptorIfAny() }.isMain(function)
if (assertIsMain) {
Assert.assertTrue("The function ${function.fqName?.asString()} should be main", isMainFunction)
@@ -91,8 +90,17 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
} catch (expected: Throwable) {
}
- Assert.assertNull("Kotlin configuration producer shouldN'T produce configuration for ${function.fqName?.asString()}",
- KotlinRunConfigurationProducer.getEntryPointContainer(function))
+ if (function.containingFile.text.startsWith("// entryPointExists")) {
+ Assert.assertNotNull(
+ "Kotlin configuration producer should produce configuration for ${function.fqName?.asString()}",
+ KotlinRunConfigurationProducer.getEntryPointContainer(function)
+ )
+ } else {
+ Assert.assertNull(
+ "Kotlin configuration producer shouldn't produce configuration for ${function.fqName?.asString()}",
+ KotlinRunConfigurationProducer.getEntryPointContainer(function)
+ )
+ }
}
}
}