JVM IR: Support parameterless main methods

This commit:

- introduces tests explicating what is and isn't considered a
  proper main method on the JVM backends.
- implements support for parameterless main methods on the JVM IR
  backend
- See KT-34338 for more tests.
This commit is contained in:
Kristoffer Andersen
2019-10-14 11:58:02 +02:00
committed by Alexander Udalov
parent 4b77db8979
commit 28b6913a25
18 changed files with 346 additions and 5 deletions
@@ -43,4 +43,5 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true)
object GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD : IrDeclarationOriginImpl("GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD", isSynthetic = true)
}
@@ -172,8 +172,15 @@ private val syntheticAccessorPhase = makeIrFilePhase(
prerequisite = setOf(objectClassPhase, staticDefaultFunctionPhase, interfacePhase)
)
private val mainMethodGenerationPhase = makeIrFilePhase(
::MainMethodGenerationLowering,
name = "MainMethodGeneration",
description = "Identify parameterless main methods and generate bridge main-methods"
)
@Suppress("Reformat")
private val jvmFilePhases =
mainMethodGenerationPhase then
typeAliasAnnotationMethodsPhase then
stripTypeAliasDeclarationsPhase then
provisionalFunctionExpressionPhase then
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.lower.allOverridden
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -122,11 +123,6 @@ class MethodSignatureMapper(private val context: JvmBackendContext) {
getJvmModuleNameForDeserializedDescriptor(function.descriptor)
else null) ?: context.state.moduleName
private fun IrDeclaration.getJvmNameFromAnnotation(): String? {
val const = getAnnotation(DescriptorUtils.JVM_NAME)?.getValueArgument(0) as? IrConst<*>
return const?.value as? String
}
private fun IrFunction.isPublishedApi(): Boolean =
propertyIfAccessor.annotations.hasAnnotation(KotlinBuiltIns.FQ_NAMES.publishedApi)
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
@@ -25,6 +26,7 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_DEFAULT_FQ_NAME
/**
@@ -82,6 +84,12 @@ val IrType.erasedUpperBound: IrClass
else -> throw IllegalStateException()
}
fun IrDeclaration.getJvmNameFromAnnotation(): String? {
val const = getAnnotation(DescriptorUtils.JVM_NAME)?.getValueArgument(0) as? IrConst<*>
return const?.value as? String
}
val IrFunction.propertyIfAccessor: IrDeclaration
get() = (this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: this
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.ir.allParameters
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.Variance
internal class MainMethodGenerationLowering(val context: JvmBackendContext) : ClassLoweringPass {
override fun lower(irClass: IrClass) {
if (!context.configuration.languageVersionSettings.supportsFeature(LanguageFeature.ExtendedMainConvention)) return
if (irClass.origin != IrDeclarationOrigin.FILE_CLASS) return
val parameterlessMain = irClass.functions.find { it.isParameterlessMainMethod() } ?: return
if (irClass.functions.any { it.isMainMethod() }) return
generateMainMethod(irClass, parameterlessMain)
}
private fun generateMainMethod(irClass: IrClass, parameterlessMain: IrSimpleFunction) {
irClass.addFunction {
name = Name.identifier("main")
visibility = Visibilities.PUBLIC
returnType = context.irBuiltIns.unitType
modality = Modality.FINAL
origin = JvmLoweredDeclarationOrigin.GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD
}.apply {
addValueParameter {
name = Name.identifier("args")
type = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType)
}
body = context.createIrBuilder(this.symbol).irBlockBody {
+irReturn(this.irCall(parameterlessMain))
}
}
}
private fun IrSimpleFunction.isParameterlessMainMethod(): Boolean =
typeParameters.isEmpty() && valueParameters.isEmpty() && returnType.isUnit() && name.asString() == "main"
private fun IrSimpleFunction.isMainMethod(): Boolean {
if (getJvmNameFromAnnotation() ?: name.asString() != "main") return false
if (!returnType.isUnit()) return false
val parameter = allParameters.singleOrNull() ?: return false
if (!parameter.type.isArray() && !parameter.type.isNullableArray()) return false
val argType = (parameter.type as IrSimpleType).arguments.first()
return when (argType) {
is IrTypeProjection -> {
(argType.variance != Variance.IN_VARIANCE) && argType.type.isStringClassType()
}
else -> false
}
}
}
@@ -0,0 +1,8 @@
fun main() {
}
@JvmName("main")
fun foo(args: Array<String>) {
}
@@ -0,0 +1,7 @@
fun main() {
}
fun main(args: Array<String>) {
}
@@ -0,0 +1,7 @@
fun main() {
}
fun Array<String>.main() {
}
@@ -0,0 +1,7 @@
fun main() {
}
fun main(args: Array<String>?) {
}
@@ -0,0 +1,11 @@
fun main() {
}
fun main(args: Array<in String>) {
}
// method: ArrayWithContravariantStringIsNotMainMethodKt::main
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
@@ -0,0 +1,24 @@
fun main() {
}
fun main(args: Array<Int>) {
}
fun main(args: Array<in String>) {
}
fun main(args: Array<String>): Unit? {
return Unit
}
fun Any.main(args: Array<String>) {
}
// method: ImpostorMainsKt::main
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
@@ -0,0 +1,7 @@
fun main(args: Array<String>) {
}
// method: MainKt::main
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
@@ -0,0 +1,21 @@
// WITH_RUNTIME
fun main() {
}
@JvmName("notMain")
fun main(args: Array<String>) {
}
// method: NotJvmMainNameKt::main
// jvm signature: ()V
// generic signature: null
// method: NotJvmMainNameKt::main
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
// method: NotJvmMainNameKt::notMain
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
@@ -0,0 +1,11 @@
fun main() {
}
// method: SimpleKt::main
// jvm signature: ()V
// generic signature: null
// method: SimpleKt::main
// jvm signature: ([Ljava/lang/String;)V
// generic signature: null
@@ -3438,6 +3438,39 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/parameterlessMain")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ParameterlessMain extends AbstractBytecodeTextTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInParameterlessMain() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/parameterlessMain"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("dontGenerateOnJvmNameMain.kt")
public void testDontGenerateOnJvmNameMain() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnJvmNameMain.kt");
}
@TestMetadata("dontGenerateOnMain.kt")
public void testDontGenerateOnMain() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnMain.kt");
}
@TestMetadata("dontGenerateOnMainExtension.kt")
public void testDontGenerateOnMainExtension() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnMainExtension.kt");
}
@TestMetadata("dontGenerateOnNullableArray.kt")
public void testDontGenerateOnNullableArray() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnNullableArray.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/properties")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -3408,6 +3408,39 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/parameterlessMain")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ParameterlessMain extends AbstractIrBytecodeTextTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInParameterlessMain() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/parameterlessMain"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("dontGenerateOnJvmNameMain.kt")
public void testDontGenerateOnJvmNameMain() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnJvmNameMain.kt");
}
@TestMetadata("dontGenerateOnMain.kt")
public void testDontGenerateOnMain() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnMain.kt");
}
@TestMetadata("dontGenerateOnMainExtension.kt")
public void testDontGenerateOnMainExtension() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnMainExtension.kt");
}
@TestMetadata("dontGenerateOnNullableArray.kt")
public void testDontGenerateOnNullableArray() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/parameterlessMain/dontGenerateOnNullableArray.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/properties")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -613,4 +613,42 @@ public class IrWriteSignatureTestGenerated extends AbstractIrWriteSignatureTest
runTest("compiler/testData/writeSignature/nothing/nullableNothing.kt");
}
}
@TestMetadata("compiler/testData/writeSignature/parameterlessMain")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ParameterlessMain extends AbstractIrWriteSignatureTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInParameterlessMain() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeSignature/parameterlessMain"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("arrayWithContravariantStringIsNotMainMethod.kt")
public void testArrayWithContravariantStringIsNotMainMethod() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/arrayWithContravariantStringIsNotMainMethod.kt");
}
@TestMetadata("impostorMains.kt")
public void testImpostorMains() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/impostorMains.kt");
}
@TestMetadata("main.kt")
public void testMain() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/main.kt");
}
@TestMetadata("notJvmMainName.kt")
public void testNotJvmMainName() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/notJvmMainName.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/simple.kt");
}
}
}
@@ -613,4 +613,42 @@ public class WriteSignatureTestGenerated extends AbstractWriteSignatureTest {
runTest("compiler/testData/writeSignature/nothing/nullableNothing.kt");
}
}
@TestMetadata("compiler/testData/writeSignature/parameterlessMain")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ParameterlessMain extends AbstractWriteSignatureTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInParameterlessMain() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeSignature/parameterlessMain"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("arrayWithContravariantStringIsNotMainMethod.kt")
public void testArrayWithContravariantStringIsNotMainMethod() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/arrayWithContravariantStringIsNotMainMethod.kt");
}
@TestMetadata("impostorMains.kt")
public void testImpostorMains() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/impostorMains.kt");
}
@TestMetadata("main.kt")
public void testMain() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/main.kt");
}
@TestMetadata("notJvmMainName.kt")
public void testNotJvmMainName() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/notJvmMainName.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/testData/writeSignature/parameterlessMain/simple.kt");
}
}
}