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.isKClass() = this.isNameInPackage("KClass", kotlinReflectionPackageFqn)
|
||||
fun IrType.isKFunction() = this.isNameInPackage("KFunction", kotlinReflectionPackageFqn)
|
||||
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 {
|
||||
(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
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
||||
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
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.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.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(
|
||||
::AnnotationLowering,
|
||||
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 {
|
||||
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.isNullableString(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES.string)
|
||||
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.isNothing(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.nothing)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_RUNTIME
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
// WITH_REFLECT
|
||||
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
import java.util.Arrays
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
// WITH_REFLECT
|
||||
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
import java.util.Arrays
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
// WITH_REFLECT
|
||||
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
import java.util.Arrays
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_RUNTIME
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_RUNTIME
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_REFLECT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// WITH_RUNTIME
|
||||
|
||||
Vendored
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
// WITH_REFLECT
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
||||
Reference in New Issue
Block a user