Implement support for KClass fields in annotation classes.
This commit is contained in:
committed by
max-kammerer
parent
4ff2825de0
commit
29b7da7c49
@@ -24,6 +24,7 @@ val kotlinCoroutinesPackageFqn = kotlinPackageFqn.child(Name.identifier("corouti
|
|||||||
|
|
||||||
|
|
||||||
fun IrType.isFunction() = this.isNameInPackage("Function", kotlinPackageFqn)
|
fun IrType.isFunction() = this.isNameInPackage("Function", kotlinPackageFqn)
|
||||||
|
fun IrType.isKClass() = this.isNameInPackage("KClass", kotlinReflectionPackageFqn)
|
||||||
fun IrType.isKFunction() = this.isNameInPackage("KFunction", kotlinReflectionPackageFqn)
|
fun IrType.isKFunction() = this.isNameInPackage("KFunction", kotlinReflectionPackageFqn)
|
||||||
fun IrType.isSuspendFunction() = this.isNameInPackage("SuspendFunction", kotlinCoroutinesPackageFqn)
|
fun IrType.isSuspendFunction() = this.isNameInPackage("SuspendFunction", kotlinCoroutinesPackageFqn)
|
||||||
|
|
||||||
@@ -84,4 +85,7 @@ fun IrType.isPrimitiveArray() = isTypeFromKotlinPackage { it in FQ_NAMES.primiti
|
|||||||
|
|
||||||
fun IrType.getPrimitiveArrayElementType() = (this as? IrSimpleType)?.let {
|
fun IrType.getPrimitiveArrayElementType() = (this as? IrSimpleType)?.let {
|
||||||
(it.classifier.owner as? IrClass)?.fqNameWhenAvailable?.toUnsafe()?.let { fqn -> FQ_NAMES.arrayClassFqNameToPrimitiveType[fqn] }
|
(it.classifier.owner as? IrClass)?.fqNameWhenAvailable?.toUnsafe()?.let { fqn -> FQ_NAMES.arrayClassFqNameToPrimitiveType[fqn] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun IrType.isNonPrimitiveArray() =
|
||||||
|
(this.isArray() || this.isNullableArray()) && !this.isPrimitiveArray()
|
||||||
|
|||||||
+164
-8
@@ -5,26 +5,182 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.jvm.lower
|
package org.jetbrains.kotlin.backend.jvm.lower
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.intrinsics.KClassJavaProperty
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.buildField
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irCall
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irGet
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetterCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
|
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||||
|
import org.jetbrains.kotlin.ir.util.functions
|
||||||
import org.jetbrains.kotlin.ir.util.isAnnotationClass
|
import org.jetbrains.kotlin.ir.util.isAnnotationClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.isKClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.isNonPrimitiveArray
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
|
||||||
internal val annotationPhase = makeIrFilePhase(
|
internal val annotationPhase = makeIrFilePhase(
|
||||||
::AnnotationLowering,
|
::AnnotationLowering,
|
||||||
name = "Annotation",
|
name = "Annotation",
|
||||||
description = "Remove constructors from annotation classes"
|
description = "Remove constructors and modify field types in annotation classes"
|
||||||
)
|
)
|
||||||
|
|
||||||
private class AnnotationLowering() : ClassLoweringPass {
|
/**
|
||||||
constructor(@Suppress("UNUSED_PARAMETER") context: BackendContext) : this()
|
* Remove the constructors from annotation classes and change the types of KClass
|
||||||
|
* and Array<KClass> fields to use java.lang.Class instead. This phase also rewrites
|
||||||
|
* the uses of annotation class fields appropriately.
|
||||||
|
*/
|
||||||
|
private class AnnotationLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() {
|
||||||
|
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
irFile.transformChildrenVoid(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitClass(irClass: IrClass): IrStatement {
|
||||||
|
if (!irClass.isAnnotationClass) return super.visitClass(irClass)
|
||||||
|
|
||||||
override fun lower(irClass: IrClass) {
|
|
||||||
if (!irClass.isAnnotationClass) return
|
|
||||||
irClass.declarations.removeIf {
|
irClass.declarations.removeIf {
|
||||||
it is IrConstructor
|
it is IrConstructor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (declaration in irClass.declarations)
|
||||||
|
if (declaration is IrSimpleFunction)
|
||||||
|
lowerAnnotationField(declaration)
|
||||||
|
|
||||||
|
return irClass
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Lower the types on annotation class fields (KClass -> Class, Array<KClass> -> Array<Class>)
|
||||||
|
private fun lowerAnnotationField(declaration: IrSimpleFunction) {
|
||||||
|
val property = declaration.correspondingPropertySymbol?.owner ?: return
|
||||||
|
val field = property.backingField ?: return
|
||||||
|
|
||||||
|
val newType = when {
|
||||||
|
field.type.isKClass() ->
|
||||||
|
javaClassType((field.type as IrSimpleType).arguments)
|
||||||
|
field.type.isKClassArray() -> {
|
||||||
|
val projection = field.type.singleTypeProjectionOrNull as IrTypeProjection
|
||||||
|
|
||||||
|
javaClassArrayType(
|
||||||
|
projection.variance,
|
||||||
|
(projection.type as IrSimpleType).arguments
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val newField = buildField {
|
||||||
|
updateFrom(field)
|
||||||
|
name = field.name
|
||||||
|
type = newType
|
||||||
|
}
|
||||||
|
|
||||||
|
newField.correspondingPropertySymbol = property.symbol
|
||||||
|
newField.initializer = field.initializer
|
||||||
|
property.backingField = newField
|
||||||
|
declaration.returnType = newType
|
||||||
|
declaration.body = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap property accesses to annotation class fields if needed.
|
||||||
|
* For example, assume that we have
|
||||||
|
*
|
||||||
|
* annotation class Ann(val c: KClass<*>, val ca: Array<KClass<*>>)
|
||||||
|
*
|
||||||
|
* and a variable `a: Ann`. Then we wrap a call of the form `a.c` in a call
|
||||||
|
* to `getOrCreateKotlinClass` while `a.c.java` is reduced to `a.c`. Similarly,
|
||||||
|
* a call of the form `a.ca` is wrapped with a call to `getOrCreateKotlinClasses`.
|
||||||
|
*/
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
// Skip the KClass wrapper when it is only used to project out the Class instance
|
||||||
|
var wrapIntoKClass = true
|
||||||
|
var subject: IrExpression = expression
|
||||||
|
if (expression.isGetJava()) {
|
||||||
|
subject = expression.extensionReceiver!!
|
||||||
|
wrapIntoKClass = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a property access on a KClass or Array<KClass> field of an
|
||||||
|
// annotation class instance.
|
||||||
|
val receiver = subject as? IrGetterCallImpl
|
||||||
|
?: return super.visitCall(expression)
|
||||||
|
|
||||||
|
val wrapIntoArray = receiver.type.isKClassArray()
|
||||||
|
if (!wrapIntoArray && !receiver.type.isKClass())
|
||||||
|
return super.visitCall(expression)
|
||||||
|
|
||||||
|
val function = (receiver.symbol.owner as? IrSimpleFunction)
|
||||||
|
?.takeIf { (it.parent as? IrClass)?.isAnnotationClass ?: false }
|
||||||
|
?: return super.visitCall(expression)
|
||||||
|
|
||||||
|
val field = function.correspondingProperty?.backingField
|
||||||
|
?: return super.visitCall(expression)
|
||||||
|
|
||||||
|
// Wrap the property access with a call to getOrCreateKClass(es) and fix the type
|
||||||
|
val irBuilder = context.createIrBuilder(function.symbol, expression.startOffset, expression.endOffset)
|
||||||
|
val getField = irBuilder.irGet(field.type, receiver.dispatchReceiver!!, receiver.symbol)
|
||||||
|
if (!wrapIntoKClass)
|
||||||
|
return getField
|
||||||
|
val functionSymbol = if (wrapIntoArray) getOrCreateKClassesSymbol else getOrCreateKClassSymbol
|
||||||
|
return irBuilder.irCall(functionSymbol, functionSymbol.owner.returnType).apply {
|
||||||
|
putValueArgument(0, getField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val javaLangClass = context.getIrClass(FqName("java.lang.Class"))
|
||||||
|
private val reflectionClass = context.getIrClass(FqName("kotlin.jvm.internal.Reflection"))
|
||||||
|
|
||||||
|
private val getOrCreateKClassSymbol by lazy {
|
||||||
|
reflectionClass.getFunctionByName("getOrCreateKotlinClass", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val getOrCreateKClassesSymbol by lazy {
|
||||||
|
reflectionClass.getFunctionByName("getOrCreateKotlinClasses", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun javaClassType(typeArguments: List<IrTypeArgument>): IrType =
|
||||||
|
javaLangClass.createType(false, typeArguments)
|
||||||
|
|
||||||
|
private fun javaClassArrayType(variance: Variance, typeArguments: List<IrTypeArgument>): IrType {
|
||||||
|
val argument = makeTypeProjection(javaClassType(typeArguments), variance)
|
||||||
|
return context.irBuiltIns.arrayClass.createType(false, listOf(argument))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val intrinsics = IrIntrinsicMethods(context.irBuiltIns)
|
||||||
|
|
||||||
|
private fun IrCall.isGetJava(): Boolean {
|
||||||
|
val intrinsic = intrinsics.getIntrinsic(descriptor.original)
|
||||||
|
return intrinsic is KClassJavaProperty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrClassSymbol.getFunctionByName(name: String, numParams: Int): IrSimpleFunctionSymbol =
|
||||||
|
functions
|
||||||
|
.filter { it.owner.name.asString() == name }
|
||||||
|
.single { it.owner.valueParameters.size == numParams }
|
||||||
|
|
||||||
|
private fun IrType.isKClassArray() =
|
||||||
|
isNonPrimitiveArray() && singleTypeProjectionOrNull?.isKClass() ?: false
|
||||||
|
|
||||||
|
private val IrType.singleTypeProjectionOrNull: IrType?
|
||||||
|
get() = (singleTypeArgumentOrNull as? IrTypeProjection)?.type
|
||||||
|
|
||||||
|
private val IrType.singleTypeArgumentOrNull: IrTypeArgument?
|
||||||
|
get() = (this as? IrSimpleType)?.arguments?.singleOrNull()
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ fun IrType.isNullableAny(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAME
|
|||||||
fun IrType.isString(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.string)
|
fun IrType.isString(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.string)
|
||||||
fun IrType.isNullableString(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES.string)
|
fun IrType.isNullableString(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES.string)
|
||||||
fun IrType.isArray(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.array)
|
fun IrType.isArray(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.array)
|
||||||
|
fun IrType.isNullableArray(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES.array)
|
||||||
fun IrType.isCollection(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.collection.toUnsafe())
|
fun IrType.isCollection(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.collection.toUnsafe())
|
||||||
fun IrType.isNothing(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.nothing)
|
fun IrType.isNothing(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.nothing)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|||||||
-1
@@ -1,6 +1,5 @@
|
|||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
|
|||||||
-1
@@ -1,6 +1,5 @@
|
|||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
|
|||||||
-1
@@ -1,6 +1,5 @@
|
|||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|||||||
Vendored
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
|||||||
Reference in New Issue
Block a user