diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/util/IrTypeUtils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/util/IrTypeUtils.kt index e73e1f7fbd5..f0f3dae7a16 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/util/IrTypeUtils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/util/IrTypeUtils.kt @@ -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] } -} \ No newline at end of file +} + +fun IrType.isNonPrimitiveArray() = + (this.isArray() || this.isNullableArray()) && !this.isPrimitiveArray() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AnnotationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AnnotationLowering.kt index 5b7e69dbe99..a5ab83f5492 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AnnotationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AnnotationLowering.kt @@ -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 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 } -} \ No newline at end of file + + // Lower the types on annotation class fields (KClass -> Class, Array -> Array) + 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>) + * + * 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 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): IrType = + javaLangClass.createType(false, typeArguments) + + private fun javaClassArrayType(variance: Variance, typeArguments: List): 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() diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt index cd4dcee21ee..21f0d02786b 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt @@ -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) diff --git a/compiler/testData/codegen/box/annotations/defaultParameterValues.kt b/compiler/testData/codegen/box/annotations/defaultParameterValues.kt index d8e9168db34..1fc6240a68e 100644 --- a/compiler/testData/codegen/box/annotations/defaultParameterValues.kt +++ b/compiler/testData/codegen/box/annotations/defaultParameterValues.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsInArgumentPosition.kt b/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsInArgumentPosition.kt index d2c102c418f..7f01c7db9c5 100644 --- a/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsInArgumentPosition.kt +++ b/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsInArgumentPosition.kt @@ -1,6 +1,5 @@ // WITH_REFLECT -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM import java.util.Arrays diff --git a/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsWithVarargs.kt b/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsWithVarargs.kt index 8337132748f..9346e62dd04 100644 --- a/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsWithVarargs.kt +++ b/compiler/testData/codegen/box/collectionLiterals/collectionLiteralsWithVarargs.kt @@ -1,6 +1,5 @@ // WITH_REFLECT -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM import java.util.Arrays diff --git a/compiler/testData/codegen/box/collectionLiterals/defaultAnnotationParameterValues.kt b/compiler/testData/codegen/box/collectionLiterals/defaultAnnotationParameterValues.kt index 42a004c5d1b..f361eb970c7 100644 --- a/compiler/testData/codegen/box/collectionLiterals/defaultAnnotationParameterValues.kt +++ b/compiler/testData/codegen/box/collectionLiterals/defaultAnnotationParameterValues.kt @@ -1,6 +1,5 @@ // WITH_REFLECT -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM import java.util.Arrays diff --git a/compiler/testData/codegen/box/reflection/createAnnotation/arrayOfKClasses.kt b/compiler/testData/codegen/box/reflection/createAnnotation/arrayOfKClasses.kt index 49b02c17d93..61e7bd86924 100644 --- a/compiler/testData/codegen/box/reflection/createAnnotation/arrayOfKClasses.kt +++ b/compiler/testData/codegen/box/reflection/createAnnotation/arrayOfKClasses.kt @@ -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 diff --git a/compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt b/compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt index f4ac9bc17e8..51b0ca24a0b 100644 --- a/compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt +++ b/compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt b/compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt index 68c3f7bb9a3..3e2e883f374 100644 --- a/compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt +++ b/compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/array.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/array.kt index cbf30b7dadd..eb5fa7c620a 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/array.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/array.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/arrayInJava.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/arrayInJava.kt index 28ae50dedff..c0b8945fc5a 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/arrayInJava.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/arrayInJava.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/basic.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/basic.kt index decc9e44200..1bff2410205 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/basic.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/basic.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/basicInJava.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/basicInJava.kt index 716db28488e..42ea45c6080 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/basicInJava.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/basicInJava.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/forceWrapping.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/forceWrapping.kt index 98e79883814..a0b723c1759 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/forceWrapping.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/forceWrapping.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS, NATIVE // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/vararg.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/vararg.kt index d14f394a485..e347aac0798 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/vararg.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/vararg.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_REFLECT diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/varargInJava.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/varargInJava.kt index 4bf33b50a27..2e5a2302223 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/varargInJava.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/varargInJava.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/reflection/kClassInAnnotation/wrappingForCallableReferences.kt b/compiler/testData/codegen/box/reflection/kClassInAnnotation/wrappingForCallableReferences.kt index 9d696100eb4..8d7e42e429b 100644 --- a/compiler/testData/codegen/box/reflection/kClassInAnnotation/wrappingForCallableReferences.kt +++ b/compiler/testData/codegen/box/reflection/kClassInAnnotation/wrappingForCallableReferences.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS, NATIVE // WITH_REFLECT diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt index e15ee423bf4..64675ba2613 100644 --- a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR import kotlin.reflect.KClass @Retention(AnnotationRetention.RUNTIME) diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt index 595f1629c16..d1568b07934 100644 --- a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR import kotlin.reflect.KClass @Retention(AnnotationRetention.RUNTIME)