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
}
}
}