From 2c920b732cbf5532c2a37d4d169ae9b7c5cb1111 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 6 Sep 2018 14:04:28 +0300 Subject: [PATCH] Support main entry-point without arguments in frontend #KT-26574 In Progress --- .idea/dictionaries/dzharkov.xml | 3 +- .../kotlin/idea/MainFunctionDetector.kt | 73 ++++++++++++++----- .../RedeclarationParameterlessMain.kt | 5 ++ .../RedeclarationParameterlessMain.txt | 4 + .../RedeclarationParameterlessMainInvalid.kt | 7 ++ .../RedeclarationParameterlessMainInvalid.txt | 5 ++ .../checkers/DiagnosticsTestGenerated.java | 10 +++ .../DiagnosticsUsingJavacTestGenerated.java | 10 +++ .../module/src/parameterlessInObject.kt | 15 ++++ .../module/src/parameterlessInvalid.kt | 9 +++ .../module/src/parameterlessValid.kt | 4 + .../module/src/parameterlessWithJvmName.kt | 5 ++ .../kotlin/idea/run/RunConfigurationTest.kt | 20 +++-- 13 files changed, 146 insertions(+), 24 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.kt create mode 100644 compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMain.txt create mode 100644 compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.kt create mode 100644 compiler/testData/diagnostics/tests/redeclarations/RedeclarationParameterlessMainInvalid.txt create mode 100644 idea/testData/run/MainInTest/module/src/parameterlessInObject.kt create mode 100644 idea/testData/run/MainInTest/module/src/parameterlessInvalid.kt create mode 100644 idea/testData/run/MainInTest/module/src/parameterlessValid.kt create mode 100644 idea/testData/run/MainInTest/module/src/parameterlessWithJvmName.kt 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) + ) + } } } }