Support main entry-point without arguments in frontend

#KT-26574 In Progress
This commit is contained in:
Denis Zharkov
2018-09-06 14:04:28 +03:00
parent 3cf1c56794
commit 2c920b732c
13 changed files with 146 additions and 24 deletions
+2 -1
View File
@@ -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)
@@ -0,0 +1,5 @@
// FILE: a.kt
fun main() {}
// FILE: b.kt
fun main() {}
@@ -0,0 +1,4 @@
package
public fun main(): kotlin.Unit
public fun main(): kotlin.Unit
@@ -0,0 +1,7 @@
// FILE: a.kt
<!CONFLICTING_OVERLOADS!>fun main()<!> {}
suspend fun main(args: Array<String>) {}
// FILE: b.kt
<!CONFLICTING_OVERLOADS!>fun main()<!> {}
@@ -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");
@@ -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)
)
}
} }
} }
} }