Support main entry-point without arguments in frontend
#KT-26574 In Progress
This commit is contained in:
Generated
+1
@@ -7,6 +7,7 @@
|
|||||||
<w>experimentality</w>
|
<w>experimentality</w>
|
||||||
<w>insn</w>
|
<w>insn</w>
|
||||||
<w>liveness</w>
|
<w>liveness</w>
|
||||||
|
<w>parameterless</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</component>
|
||||||
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.name.FqName
|
|||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation
|
import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
@@ -48,16 +49,19 @@ class MainFunctionDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun isMain(function: KtNamedFunction, checkJvmStaticAnnotation: Boolean = true): Boolean {
|
fun isMain(
|
||||||
|
function: KtNamedFunction,
|
||||||
|
checkJvmStaticAnnotation: Boolean = true,
|
||||||
|
allowParameterless: Boolean = true
|
||||||
|
): Boolean {
|
||||||
if (function.isLocal) {
|
if (function.isLocal) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var parametersCount = function.valueParameters.size
|
var parametersCount = function.valueParameters.size
|
||||||
if (function.receiverTypeReference != null) parametersCount++
|
if (function.receiverTypeReference != null) parametersCount++
|
||||||
|
|
||||||
if (parametersCount != 1) {
|
if (!isParameterNumberSuitsForMain(parametersCount, function.isTopLevel, allowParameterless)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,13 +80,14 @@ class MainFunctionDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val functionDescriptor = getFunctionDescriptor(function) ?: return false
|
val functionDescriptor = getFunctionDescriptor(function) ?: return false
|
||||||
return isMain(functionDescriptor, checkJvmStaticAnnotation)
|
return isMain(functionDescriptor, checkJvmStaticAnnotation, allowParameterless = allowParameterless)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isMain(
|
fun isMain(
|
||||||
descriptor: DeclarationDescriptor,
|
descriptor: DeclarationDescriptor,
|
||||||
checkJvmStaticAnnotation: Boolean = true,
|
checkJvmStaticAnnotation: Boolean = true,
|
||||||
checkReturnType: Boolean = true
|
checkReturnType: Boolean = true,
|
||||||
|
allowParameterless: Boolean = true
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (descriptor !is FunctionDescriptor) return false
|
if (descriptor !is FunctionDescriptor) return false
|
||||||
|
|
||||||
@@ -93,20 +98,40 @@ class MainFunctionDetector {
|
|||||||
val parameters = descriptor.valueParameters.mapTo(mutableListOf()) { it.type }
|
val parameters = descriptor.valueParameters.mapTo(mutableListOf()) { it.type }
|
||||||
descriptor.extensionReceiverParameter?.type?.let { parameters += it }
|
descriptor.extensionReceiverParameter?.type?.let { parameters += it }
|
||||||
|
|
||||||
if (parameters.size != 1 || !descriptor.typeParameters.isEmpty()) return false
|
if (!isParameterNumberSuitsForMain(
|
||||||
|
parameters.size,
|
||||||
val parameterType = parameters[0]
|
DescriptorUtils.isTopLevelDeclaration(descriptor),
|
||||||
if (!KotlinBuiltIns.isArray(parameterType)) return false
|
allowParameterless
|
||||||
|
)
|
||||||
val typeArguments = parameterType.arguments
|
) {
|
||||||
if (typeArguments.size != 1) return false
|
|
||||||
|
|
||||||
val typeArgument = typeArguments[0].type
|
|
||||||
if (!KotlinBuiltIns.isString(typeArgument)) {
|
|
||||||
return false
|
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
|
if (checkReturnType && !isMainReturnType(descriptor)) return false
|
||||||
@@ -119,6 +144,10 @@ class MainFunctionDetector {
|
|||||||
&& (descriptor.hasJvmStaticAnnotation() || !checkJvmStaticAnnotation)
|
&& (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))
|
fun getMainFunction(module: ModuleDescriptor): FunctionDescriptor? = getMainFunction(module, module.getPackage(FqName.ROOT))
|
||||||
|
|
||||||
@@ -141,6 +170,16 @@ class MainFunctionDetector {
|
|||||||
declarations.filterIsInstance<KtNamedFunction>().find { isMain(it) }
|
declarations.filterIsInstance<KtNamedFunction>().find { isMain(it) }
|
||||||
|
|
||||||
companion object {
|
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 {
|
private fun isMainReturnType(descriptor: FunctionDescriptor): Boolean {
|
||||||
val returnType = descriptor.returnType
|
val returnType = descriptor.returnType
|
||||||
return returnType != null && KotlinBuiltIns.isUnit(returnType)
|
return returnType != null && KotlinBuiltIns.isUnit(returnType)
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// FILE: a.kt
|
||||||
|
fun main() {}
|
||||||
|
|
||||||
|
// FILE: b.kt
|
||||||
|
fun main() {}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun main(): kotlin.Unit
|
||||||
|
public fun main(): kotlin.Unit
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// FILE: a.kt
|
||||||
|
<!CONFLICTING_OVERLOADS!>fun main()<!> {}
|
||||||
|
|
||||||
|
suspend fun main(args: Array<String>) {}
|
||||||
|
|
||||||
|
// FILE: b.kt
|
||||||
|
<!CONFLICTING_OVERLOADS!>fun main()<!> {}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun main(): kotlin.Unit
|
||||||
|
public fun main(): kotlin.Unit
|
||||||
|
public suspend fun main(/*0*/ args: kotlin.Array<kotlin.String>): kotlin.Unit
|
||||||
@@ -15926,6 +15926,16 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationMainInMultiFile.kt");
|
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")
|
@TestMetadata("RedeclarationSuspendMainInMultiFile.kt")
|
||||||
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
|
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
|
||||||
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
|
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
|
||||||
|
|||||||
Generated
+10
@@ -15926,6 +15926,16 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
|||||||
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationMainInMultiFile.kt");
|
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")
|
@TestMetadata("RedeclarationSuspendMainInMultiFile.kt")
|
||||||
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
|
public void testRedeclarationSuspendMainInMultiFile() throws Exception {
|
||||||
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
|
runTest("compiler/testData/diagnostics/tests/redeclarations/RedeclarationSuspendMainInMultiFile.kt");
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package parameterless
|
||||||
|
|
||||||
|
class A {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun main() { // no
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object B {
|
||||||
|
@JvmStatic
|
||||||
|
fun main() { // no
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// entryPointExists
|
||||||
|
package parameterlessInvalid
|
||||||
|
|
||||||
|
fun main() { // no
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("main")
|
||||||
|
fun notMain(args: Array<String>) { // yes
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package parameterless
|
||||||
|
|
||||||
|
fun main() { // yes
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package parameterlessWithJvmName
|
||||||
|
|
||||||
|
@JvmName("main")
|
||||||
|
fun notMain() { // no
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ import com.intellij.refactoring.RefactoringFactory
|
|||||||
import com.intellij.testFramework.MapDataContext
|
import com.intellij.testFramework.MapDataContext
|
||||||
import com.intellij.testFramework.PsiTestUtil
|
import com.intellij.testFramework.PsiTestUtil
|
||||||
import org.jetbrains.kotlin.idea.MainFunctionDetector
|
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.search.allScope
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
|
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.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -69,8 +68,8 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
|
|||||||
val assertIsMain = "yes" in options
|
val assertIsMain = "yes" in options
|
||||||
val assertIsNotMain = "no" in options
|
val assertIsNotMain = "no" in options
|
||||||
|
|
||||||
val bindingContext = function.analyze(BodyResolveMode.FULL)
|
val isMainFunction =
|
||||||
val isMainFunction = MainFunctionDetector(bindingContext).isMain(function)
|
MainFunctionDetector { it.resolveToDescriptorIfAny() }.isMain(function)
|
||||||
|
|
||||||
if (assertIsMain) {
|
if (assertIsMain) {
|
||||||
Assert.assertTrue("The function ${function.fqName?.asString()} should be main", isMainFunction)
|
Assert.assertTrue("The function ${function.fqName?.asString()} should be main", isMainFunction)
|
||||||
@@ -91,8 +90,17 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
|
|||||||
} catch (expected: Throwable) {
|
} catch (expected: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertNull("Kotlin configuration producer shouldN'T produce configuration for ${function.fqName?.asString()}",
|
if (function.containingFile.text.startsWith("// entryPointExists")) {
|
||||||
KotlinRunConfigurationProducer.getEntryPointContainer(function))
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user