Instantiation of annotations for JVM IR with the corresponding feature flag
Seperate checker for platforms that do not support this language feature yet Synthetic implementations of annotations are generated on-demand with proper equals, hashCode, and annotationType methods #KT-47699 Fixed
This commit is contained in:
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.common.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.deepCopyWithVariables
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.types.isKClass
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
val ANNOTATION_IMPLEMENTATION = object : IrDeclarationOriginImpl("ANNOTATION_IMPLEMENTATION", isSynthetic = true) {}
|
||||
|
||||
class AnnotationImplementationLowering(
|
||||
val transformer: (IrFile) -> AnnotationImplementationTransformer
|
||||
) : FileLoweringPass {
|
||||
override fun lower(irFile: IrFile) {
|
||||
val tf = transformer(irFile)
|
||||
irFile.transformChildrenVoid(tf)
|
||||
tf.implementations.values.forEach {
|
||||
val parentClass = it.parent as IrDeclarationContainer
|
||||
parentClass.declarations += it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class AnnotationImplementationTransformer(val context: BackendContext, val irFile: IrFile) : IrElementTransformerVoidWithContext() {
|
||||
internal val implementations: MutableMap<IrClass, IrClass> = mutableMapOf()
|
||||
|
||||
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
|
||||
val constructedClass = expression.type.classOrNull?.owner ?: return expression
|
||||
if (!constructedClass.isAnnotationClass) return expression
|
||||
if (constructedClass.typeParameters.isNotEmpty()) return expression // Not supported yet
|
||||
|
||||
val implClass = implementations.getOrPut(constructedClass) { createAnnotationImplementation(constructedClass) }
|
||||
val ctor = implClass.constructors.single()
|
||||
val newCall = IrConstructorCallImpl.fromSymbolOwner(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
implClass.defaultType,
|
||||
ctor.symbol,
|
||||
)
|
||||
newCall.copyTypeAndValueArgumentsFrom(expression)
|
||||
newCall.transformChildrenVoid() // for annotations in annotations
|
||||
return newCall
|
||||
}
|
||||
|
||||
private fun createAnnotationImplementation(annotationClass: IrClass): IrClass {
|
||||
val localDeclarationParent = currentClass?.scope?.getLocalDeclarationParent() as? IrClass
|
||||
val parentFqName = annotationClass.fqNameWhenAvailable!!.asString().replace('.', '_')
|
||||
val wrapperName = Name.identifier("annotationImpl\$$parentFqName$0")
|
||||
val subclass = context.irFactory.buildClass {
|
||||
name = wrapperName
|
||||
origin = ANNOTATION_IMPLEMENTATION
|
||||
// It can be seen from inline functions and multiple classes within one file
|
||||
// JavaDescriptorVisibilities.PACKAGE_VISIBILITY also can be used here, like in SAM, but that's not a big difference
|
||||
// since declaration is synthetic anyway
|
||||
visibility = DescriptorVisibilities.INTERNAL
|
||||
}.apply {
|
||||
parent = localDeclarationParent ?: irFile
|
||||
createImplicitParameterDeclarationWithWrappedDescriptor()
|
||||
superTypes = listOf(annotationClass.defaultType)
|
||||
}
|
||||
|
||||
val ctor = subclass.addConstructor {
|
||||
visibility = DescriptorVisibilities.PUBLIC
|
||||
}
|
||||
val (originalProps, implementationProps) = implementAnnotationProperties(subclass, annotationClass, ctor)
|
||||
implementEqualsAndHashCode(annotationClass, subclass, originalProps, implementationProps)
|
||||
implementPlatformSpecificParts(annotationClass, subclass)
|
||||
return subclass
|
||||
}
|
||||
|
||||
fun implementAnnotationProperties(implClass: IrClass, annotationClass: IrClass, generatedConstructor: IrConstructor): Pair<List<IrProperty>, List<IrProperty>> {
|
||||
val ctorBody = context.irFactory.createBlockBody(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
|
||||
IrDelegatingConstructorCallImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.unitType, context.irBuiltIns.anyClass.constructors.single(),
|
||||
typeArgumentsCount = 0, valueArgumentsCount = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
generatedConstructor.body = ctorBody
|
||||
|
||||
val properties = annotationClass.getAnnotationProperties()
|
||||
|
||||
return properties to properties.map { property ->
|
||||
|
||||
val propType = property.getter!!.returnType
|
||||
val propName = property.name
|
||||
val field = context.irFactory.buildField {
|
||||
name = propName
|
||||
type = propType
|
||||
origin = ANNOTATION_IMPLEMENTATION
|
||||
isFinal = true
|
||||
visibility = DescriptorVisibilities.PRIVATE
|
||||
}.also { it.parent = implClass }
|
||||
|
||||
val parameter = generatedConstructor.addValueParameter(propName.asString(), propType)
|
||||
// VALUE_FROM_PARAMETER
|
||||
val originalParameter = ((property.backingField?.initializer?.expression as? IrGetValue)?.symbol?.owner as? IrValueParameter)
|
||||
if (originalParameter?.defaultValue != null) {
|
||||
parameter.defaultValue = originalParameter.defaultValue!!.deepCopyWithVariables().also { it.transformChildrenVoid() }
|
||||
}
|
||||
|
||||
ctorBody.statements += IrSetFieldImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, field.symbol,
|
||||
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, implClass.thisReceiver!!.symbol),
|
||||
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, parameter.symbol),
|
||||
context.irBuiltIns.unitType,
|
||||
)
|
||||
|
||||
val prop = implClass.addProperty {
|
||||
name = propName
|
||||
isVar = false
|
||||
origin = ANNOTATION_IMPLEMENTATION
|
||||
}.apply {
|
||||
backingField = field
|
||||
parent = implClass
|
||||
}
|
||||
|
||||
prop.addGetter {
|
||||
name = propName // Annotation value getter should be named 'x', not 'getX'
|
||||
returnType = propType.kClassToJClassIfNeeded() // On JVM, annotation store j.l.Class even if declared with KClass
|
||||
origin = ANNOTATION_IMPLEMENTATION
|
||||
visibility = DescriptorVisibilities.PUBLIC
|
||||
modality = Modality.FINAL
|
||||
}.apply {
|
||||
dispatchReceiverParameter = implClass.thisReceiver!!.copyTo(this)
|
||||
body = context.createIrBuilder(symbol).irBlockBody {
|
||||
var value: IrExpression = irGetField(irGet(dispatchReceiverParameter!!), field)
|
||||
if (propType.isKClass()) value = this.kClassExprToJClassIfNeeded(value)
|
||||
+irReturn(value)
|
||||
}
|
||||
}
|
||||
|
||||
prop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun IrClass.getAnnotationProperties(): List<IrProperty> {
|
||||
// For some weird reason, annotations defined in other IrFiles, do not have IrProperties in declarations.
|
||||
// (although annotations imported from Java do have)
|
||||
val props = declarations.filterIsInstance<IrProperty>()
|
||||
if (props.isNotEmpty()) return props
|
||||
return declarations.filterIsInstance<IrSimpleFunction>().filter { it.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR }
|
||||
.mapNotNull { it.correspondingPropertySymbol?.owner }
|
||||
}
|
||||
|
||||
open fun IrType.kClassToJClassIfNeeded(): IrType = this
|
||||
|
||||
open fun IrBuilderWithScope.kClassExprToJClassIfNeeded(irExpression: IrExpression): IrExpression = irExpression
|
||||
|
||||
open fun generatedEquals(irBuilder: IrBlockBodyBuilder, type: IrType, arg1: IrExpression, arg2: IrExpression): IrExpression =
|
||||
irBuilder.irEquals(arg1, arg2)
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
fun implementEqualsAndHashCode(annotationClass: IrClass, implClass: IrClass, originalProps: List<IrProperty>, childProps: List<IrProperty>) {
|
||||
val creator = MethodsFromAnyGeneratorForLowerings(context, implClass, ANNOTATION_IMPLEMENTATION)
|
||||
val generator =
|
||||
creator.LoweringDataClassMemberGenerator(
|
||||
nameForToString = "@" + annotationClass.fqNameWhenAvailable!!.asString(),
|
||||
typeForEquals = annotationClass.defaultType
|
||||
) { type, a, b ->
|
||||
generatedEquals(this, type, a, b)
|
||||
}
|
||||
|
||||
// Manual implementation of equals is required for two reasons:
|
||||
// 1. `other` should be casted to interface instead of implementation
|
||||
// 2. Properties should be retrieved using getters without accessing backing fields
|
||||
// (DataClassMembersGenerator typically tries to access fields)
|
||||
val eqFun = creator.createEqualsMethodDeclaration()
|
||||
generator.generateEqualsUsingGetters(eqFun, annotationClass.defaultType, originalProps)
|
||||
|
||||
val hcFun = creator.createHashCodeMethodDeclaration()
|
||||
generator.generateHashCodeMethod(hcFun, childProps)
|
||||
|
||||
val toStringFun = creator.createToStringMethodDeclaration()
|
||||
generator.generateToStringMethod(toStringFun, childProps)
|
||||
}
|
||||
|
||||
open fun implementPlatformSpecificParts(annotationClass: IrClass, implClass: IrClass) {}
|
||||
}
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.common.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BackendContext
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.types.isArray
|
||||
import org.jetbrains.kotlin.ir.util.DataClassMembersGenerator
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
|
||||
|
||||
class MethodsFromAnyGeneratorForLowerings(val context: BackendContext, val irClass: IrClass, val origin: IrDeclarationOrigin) {
|
||||
fun createToStringMethodDeclaration(): IrSimpleFunction = irClass.addFunction("toString", context.irBuiltIns.stringType).apply {
|
||||
overriddenSymbols = irClass.collectOverridenSymbols { it.isToString() }
|
||||
}
|
||||
|
||||
fun createHashCodeMethodDeclaration(): IrSimpleFunction = irClass.addFunction("hashCode", context.irBuiltIns.intType).apply {
|
||||
overriddenSymbols = irClass.collectOverridenSymbols { it.isHashCode() }
|
||||
}
|
||||
|
||||
fun createEqualsMethodDeclaration(): IrSimpleFunction = irClass.addFunction("equals", context.irBuiltIns.booleanType).apply {
|
||||
overriddenSymbols = irClass.collectOverridenSymbols { it.isEquals(context) }
|
||||
addValueParameter("other", context.irBuiltIns.anyNType)
|
||||
}
|
||||
|
||||
inner class LoweringDataClassMemberGenerator(
|
||||
val nameForToString: String,
|
||||
val typeForEquals: IrType,
|
||||
val selectEquals: IrBlockBodyBuilder.(IrType, IrExpression, IrExpression) -> IrExpression,
|
||||
) :
|
||||
DataClassMembersGenerator(
|
||||
IrLoweringContext(context),
|
||||
context.ir.symbols.externalSymbolTable,
|
||||
irClass,
|
||||
origin
|
||||
) {
|
||||
|
||||
override fun declareSimpleFunction(startOffset: Int, endOffset: Int, functionDescriptor: FunctionDescriptor): IrFunction {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun generateSyntheticFunctionParameterDeclarations(irFunction: IrFunction) {
|
||||
// no-op — irFunction from lowering should already have necessary parameters
|
||||
}
|
||||
|
||||
override fun getProperty(parameter: ValueParameterDescriptor?, irValueParameter: IrValueParameter?): IrProperty? {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override fun transform(typeParameterDescriptor: TypeParameterDescriptor): IrType {
|
||||
error("Descriptor API shouldn't be used in lowerings")
|
||||
}
|
||||
|
||||
override val intPlusSymbol: IrSimpleFunctionSymbol =
|
||||
context.irBuiltIns.intClass.functions.single { it.owner.name.asString() == "plus" && it.owner.valueParameters[0].type == context.irBuiltIns.intType }
|
||||
|
||||
override val intTimesSymbol: IrSimpleFunctionSymbol =
|
||||
context.irBuiltIns.intClass.functions.single { it.owner.name.asString() == "times" && it.owner.valueParameters[0].type == context.irBuiltIns.intType }
|
||||
|
||||
override fun getHashCodeFunctionInfo(type: IrType): HashCodeFunctionInfo {
|
||||
val symbol = if (type.isArray() || type.isPrimitiveArray()) {
|
||||
context.irBuiltIns.dataClassArrayMemberHashCodeSymbol
|
||||
} else {
|
||||
context.irBuiltIns.anyClass.functions.single { it.owner.name.asString() == "hashCode" }
|
||||
}
|
||||
return object : HashCodeFunctionInfo {
|
||||
override val symbol: IrSimpleFunctionSymbol = symbol
|
||||
|
||||
override fun commitSubstituted(irMemberAccessExpression: IrMemberAccessExpression<*>) {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun IrClass.classNameForToString(): String = nameForToString
|
||||
|
||||
fun generateEqualsUsingGetters(equalsFun: IrSimpleFunction, typeForEquals: IrType, properties: List<IrProperty>) = equalsFun.apply {
|
||||
body = this@MethodsFromAnyGeneratorForLowerings.context.createIrBuilder(symbol).irBlockBody {
|
||||
val irType = typeForEquals
|
||||
fun irOther() = irGet(valueParameters[0])
|
||||
fun irThis() = irGet(dispatchReceiverParameter!!)
|
||||
fun IrProperty.get(receiver: IrExpression) = irCall(getter!!).apply {
|
||||
dispatchReceiver = receiver
|
||||
}
|
||||
|
||||
+irIfThenReturnFalse(irNotIs(irOther(), irType))
|
||||
val otherWithCast = irTemporary(irAs(irOther(), irType), "other_with_cast")
|
||||
for (property in properties) {
|
||||
val arg1 = property.get(irThis())
|
||||
val arg2 = property.get(irGet(irType, otherWithCast.symbol))
|
||||
+irIfThenReturnFalse(irNot(selectEquals(property.getter?.returnType ?: property.backingField!!.type, arg1, arg2)))
|
||||
}
|
||||
+irReturnTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun IrFunction.isToString(): Boolean =
|
||||
name.asString() == "toString" && extensionReceiverParameter == null && valueParameters.isEmpty()
|
||||
|
||||
fun IrFunction.isHashCode() =
|
||||
name.asString() == "hashCode" && extensionReceiverParameter == null && valueParameters.isEmpty()
|
||||
|
||||
fun IrFunction.isEquals(context: BackendContext) =
|
||||
name.asString() == "equals" &&
|
||||
extensionReceiverParameter == null &&
|
||||
valueParameters.singleOrNull()?.type == context.irBuiltIns.anyNType
|
||||
|
||||
|
||||
fun IrClass.collectOverridenSymbols(predicate: (IrFunction) -> Boolean): List<IrSimpleFunctionSymbol> =
|
||||
superTypes.mapNotNull { it.getClass()?.functions?.singleOrNull(predicate)?.symbol }
|
||||
|
||||
}
|
||||
}
|
||||
+11
-24
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.common.ScopeWithIr
|
||||
import org.jetbrains.kotlin.backend.common.ir.addFakeOverrides
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isHashCode
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
|
||||
@@ -273,8 +274,9 @@ class SamEqualsHashCodeMethodsGenerator(
|
||||
|
||||
fun generate() {
|
||||
generateGetFunctionDelegate()
|
||||
generateEquals()
|
||||
generateHashCode()
|
||||
val anyGenerator = MethodsFromAnyGeneratorForLowerings(context, klass, IrDeclarationOrigin.SYNTHETIC_GENERATED_SAM_IMPLEMENTATION)
|
||||
generateEquals(anyGenerator)
|
||||
generateHashCode(anyGenerator)
|
||||
}
|
||||
|
||||
private fun generateGetFunctionDelegate() {
|
||||
@@ -286,17 +288,9 @@ class SamEqualsHashCodeMethodsGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateEquals() {
|
||||
klass.addFunction("equals", builtIns.booleanType).apply {
|
||||
overriddenSymbols = klass.superTypes.mapNotNull {
|
||||
it.getClass()?.functions?.singleOrNull {
|
||||
it.name.asString() == "equals" &&
|
||||
it.extensionReceiverParameter == null &&
|
||||
it.valueParameters.singleOrNull()?.type == builtIns.anyNType
|
||||
}?.symbol
|
||||
}
|
||||
|
||||
val other = addValueParameter("other", builtIns.anyNType)
|
||||
private fun generateEquals(anyGenerator: MethodsFromAnyGeneratorForLowerings) {
|
||||
anyGenerator.createEqualsMethodDeclaration().apply {
|
||||
val other = valueParameters[0]
|
||||
body = context.createIrBuilder(symbol).run {
|
||||
irExprBody(
|
||||
irIfThenElse(
|
||||
@@ -322,16 +316,9 @@ class SamEqualsHashCodeMethodsGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateHashCode() {
|
||||
klass.addFunction("hashCode", builtIns.intType).apply {
|
||||
|
||||
fun isHashCode(function: IrSimpleFunction) =
|
||||
function.name.asString() == "hashCode" && function.extensionReceiverParameter == null && function.valueParameters.isEmpty()
|
||||
|
||||
overriddenSymbols = klass.superTypes.mapNotNull {
|
||||
it.getClass()?.functions?.singleOrNull(::isHashCode)?.symbol
|
||||
}
|
||||
val hashCode = context.irBuiltIns.functionClass.owner.functions.single(::isHashCode).symbol
|
||||
private fun generateHashCode(anyGenerator: MethodsFromAnyGeneratorForLowerings) {
|
||||
anyGenerator.createHashCodeMethodDeclaration().apply {
|
||||
val hashCode = context.irBuiltIns.functionClass.owner.functions.single{ it.isHashCode() }.symbol
|
||||
body = context.createIrBuilder(symbol).run {
|
||||
irExprBody(
|
||||
irCall(hashCode).also {
|
||||
|
||||
Reference in New Issue
Block a user