Support instantiation of Java annotations with defaults

For annotations defined in Java, IrProperties do not contain initializers in backing fields,
as annotation properties are represented as Java methods.
Therefore, it is not possible to use initializer values as default values for constructor parameters.

However, K2 stores default values in annotation's constructor parameters,
so it is possible to fix this issue if they're properly transfered to the IR
and inspected in JvmAnnotationImplementationTransformer

#KT-47702 Fixed
#KT-47702 tag fixed-in-k2
This commit is contained in:
Leonid Startsev
2023-07-26 18:44:14 +02:00
committed by Space Team
parent cc2bc5e8b1
commit 7f795f212f
16 changed files with 239 additions and 41 deletions
@@ -795,3 +795,13 @@ fun FirCallableDeclaration.contextReceiversForFunctionOrContainingProperty(): Li
fun IrActualizedResult?.extractFirDeclarations(): Set<FirDeclaration>? {
return this?.actualizedExpectDeclarations?.mapNotNullTo(mutableSetOf()) { ((it as IrMetadataSourceOwner).metadata as FirMetadataSource).fir }
}
// This method is intended to be used for default values of annotation parameters (compile-time strings, numbers, enum values, KClasses)
// where they are needed and may produce incorrect results for values that may be encountered outside annotations.
fun FirExpression.asCompileTimeIrInitializer(components: Fir2IrComponents): IrExpressionBody? {
return when (val elem = this.accept(Fir2IrVisitor(components, Fir2IrConversionScope()), null)) {
is IrExpressionBody -> elem
is IrExpression -> components.irFactory.createExpressionBody(elem)
else -> null
}
}
@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin.GeneratedByPlugi
import org.jetbrains.kotlin.ir.declarations.impl.*
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyClass
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.IrSyntheticBodyKind
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
import org.jetbrains.kotlin.ir.symbols.*
@@ -1346,6 +1347,11 @@ class Fir2IrDeclarationStorage(
useStubForDefaultValueStub: Boolean = true,
typeOrigin: ConversionTypeOrigin = ConversionTypeOrigin.DEFAULT,
skipDefaultParameter: Boolean = false,
// Use this parameter if you want to insert the actual default value instead of the stub (overrides useStubForDefaultValueStub parameter).
// This parameter is intended to be used for default values of annotation parameters where they are needed and
// may produce incorrect results for values that may be encountered outside annotations.
// Does not do anything if valueParameter.defaultValue is already FirExpressionStub.
forcedDefaultValueConversion: Boolean = false,
): IrValueParameter = convertCatching(valueParameter) {
val origin = valueParameter.computeIrOrigin()
val type = valueParameter.returnTypeRef.toIrType(typeOrigin)
@@ -1366,16 +1372,20 @@ class Fir2IrDeclarationStorage(
isNoinline = valueParameter.isNoinline,
isHidden = false,
).apply {
if (!skipDefaultParameter && valueParameter.defaultValue.let {
it != null && (useStubForDefaultValueStub || it !is FirExpressionStub)
val defaultValue = valueParameter.defaultValue
if (!skipDefaultParameter && defaultValue != null) {
this.defaultValue = when {
forcedDefaultValueConversion && defaultValue !is FirExpressionStub ->
defaultValue.asCompileTimeIrInitializer(components)
useStubForDefaultValueStub || defaultValue !is FirExpressionStub ->
factory.createExpressionBody(
IrErrorExpressionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type,
"Stub expression for default value of ${valueParameter.name}"
)
)
else -> null
}
) {
this.defaultValue = factory.createExpressionBody(
IrErrorExpressionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type,
"Stub expression for default value of ${valueParameter.name}"
)
)
}
annotationGenerator.generate(this, valueParameter)
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.ir.declarations.lazy.lazyVar
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
@@ -110,7 +111,8 @@ class Fir2IrLazyConstructor(
fir.valueParameters.mapIndexedTo(this) { index, valueParameter ->
declarationStorage.createIrParameter(
valueParameter, index + contextReceiverParametersCount,
useStubForDefaultValueStub = (parent as? IrClass)?.name != Name.identifier("Enum")
useStubForDefaultValueStub = (parent as? IrClass)?.name != Name.identifier("Enum"),
forcedDefaultValueConversion = (parent as? IrClass)?.isAnnotationClass == true,
).apply {
this.parent = this@Fir2IrLazyConstructor
}
@@ -94,13 +94,7 @@ class Fir2IrLazyProperty(
private fun toIrInitializer(initializer: FirExpression?): IrExpressionBody? {
// Annotations need full initializer information to instantiate them correctly
return when {
containingClass?.classKind?.isAnnotationClass == true -> {
when (val elem = initializer?.accept(Fir2IrVisitor(components, Fir2IrConversionScope()), null)) {
is IrExpressionBody -> elem
is IrExpression -> factory.createExpressionBody(elem)
else -> null
}
}
containingClass?.classKind?.isAnnotationClass == true -> initializer?.asCompileTimeIrInitializer(components)
// Setting initializers to every other class causes some cryptic errors in lowerings
initializer is FirConstExpression<*> -> {
val constType = with(typeConverter) { initializer.typeRef.toIrType() }
@@ -582,6 +582,18 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotation.kt");
}
@Test
@TestMetadata("javaAnnotationDefault.kt")
public void testJavaAnnotationDefault() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotationDefault.kt");
}
@Test
@TestMetadata("javaExistingAnnotation.kt")
public void testJavaExistingAnnotation() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaExistingAnnotation.kt");
}
@Test
@TestMetadata("kotlinExistingAnnotation.kt")
public void testKotlinExistingAnnotation() throws Exception {
@@ -582,6 +582,18 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotation.kt");
}
@Test
@TestMetadata("javaAnnotationDefault.kt")
public void testJavaAnnotationDefault() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotationDefault.kt");
}
@Test
@TestMetadata("javaExistingAnnotation.kt")
public void testJavaExistingAnnotation() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaExistingAnnotation.kt");
}
@Test
@TestMetadata("kotlinExistingAnnotation.kt")
public void testKotlinExistingAnnotation() throws Exception {
@@ -17,8 +17,8 @@ import org.jetbrains.kotlin.fir.expressions.builder.buildArrayLiteral
import org.jetbrains.kotlin.fir.expressions.builder.buildConstExpression
import org.jetbrains.kotlin.fir.expressions.builder.buildErrorExpression
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.expectedConeType
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.createArrayType
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.java.RXJAVA3_ANNOTATIONS
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
@@ -54,8 +54,19 @@ val JavaClass.classKind: ClassKind
fun JavaClass.hasMetadataAnnotation(): Boolean =
annotations.any { it.isResolvedTo(JvmAnnotationNames.METADATA_FQ_NAME) }
internal fun Any?.createConstantOrError(session: FirSession): FirExpression {
return createConstantIfAny(session) ?: buildErrorExpression {
internal fun Any?.createConstantOrError(session: FirSession, expectedTypeRef: FirTypeRef? = null): FirExpression {
val coneType = expectedTypeRef?.coneTypeOrNull
val value = if (this is Int && coneType != null) {
// special case for Java literals in annotation default values:
// literal value is always integer, but an expected parameter type can be any other number type
when {
coneType.isByte -> this.toByte()
coneType.isShort -> this.toShort()
coneType.isLong -> this.toLong()
else -> this
}
} else this
return value.createConstantIfAny(session) ?: buildErrorExpression {
diagnostic = ConeSimpleDiagnostic("Unknown value in JavaLiteralAnnotationArgument: $this", DiagnosticKind.Java)
}
}
@@ -84,7 +84,10 @@ internal fun JavaAnnotationArgument.toFirExpression(
session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, expectedTypeRef: FirTypeRef?
): FirExpression {
return when (this) {
is JavaLiteralAnnotationArgument -> value.createConstantOrError(session)
is JavaLiteralAnnotationArgument -> value.createConstantOrError(
session,
expectedTypeRef?.resolveIfJavaType(session, javaTypeParameterStack)
)
is JavaArrayAnnotationArgument -> buildArrayLiteral {
val argumentTypeRef = expectedTypeRef?.let {
typeRef = if (it is FirJavaTypeRef) buildResolvedTypeRef {
@@ -275,7 +278,9 @@ private fun JavaAnnotation.toFirAnnotationCall(session: FirSession): FirAnnotati
classId == StandardClassIds.Annotations.Java.Deprecated -> {
mapOf(
StandardClassIds.Annotations.ParameterNames.deprecatedMessage to "Deprecated in Java".createConstantOrError(session)
StandardClassIds.Annotations.ParameterNames.deprecatedMessage to "Deprecated in Java".createConstantOrError(
session,
)
)
}
@@ -116,6 +116,7 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
generatedConstructor: IrConstructor
) {
implementor.implementAnnotationPropertiesAndConstructor(
annotationClass,
annotationClass.getAnnotationProperties(),
implClass,
generatedConstructor,
@@ -231,6 +232,7 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
}
fun implementAnnotationPropertiesAndConstructor(
annotationClass: IrClass,
annotationProperties: List<IrProperty>,
implClass: IrClass,
generatedConstructor: IrConstructor,
@@ -248,6 +250,11 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
generatedConstructor.body = ctorBody
// For annotations defined in Java, IrProperties do not contain initializers in backing fields, as annotation properties are represented as Java methods
// (that are later converted to synthetic properties w/o fields).
// However, K2 stores default values in annotation's constructor parameters.
val fallbackPrimaryCtorParamsMap = annotationClass.primaryConstructor?.valueParameters?.associateBy { it.name }.orEmpty()
annotationProperties.forEach { property ->
val propType = property.getter!!.returnType
val storedFieldType = propType.kClassToJClassIfNeeded()
@@ -266,12 +273,15 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
val defaultExpression = property.backingField?.initializer?.expression
val newDefaultValue: IrExpressionBody? =
if (defaultExpression is IrGetValue && defaultExpression.symbol.owner is IrValueParameter) {
// INITIALIZE_PROPERTY_FROM_PARAMETER
(defaultExpression.symbol.owner as IrValueParameter).defaultValue
} else if (defaultExpression != null) {
property.backingField!!.initializer
} else null
// INITIALIZE_PROPERTY_FROM_PARAMETER
when {
defaultExpression is IrGetValue && defaultExpression.symbol.owner is IrValueParameter ->
(defaultExpression.symbol.owner as IrValueParameter).defaultValue
defaultExpression != null -> property.backingField!!.initializer
propName in fallbackPrimaryCtorParamsMap ->
fallbackPrimaryCtorParamsMap[propName]?.defaultValue?.takeIf { it.expression !is IrErrorExpression }
else -> null
}
parameter.defaultValue = newDefaultValue?.deepCopyWithVariables()
?.also { if (defaultValueTransformer != null) it.transformChildrenVoid(defaultValueTransformer) }
@@ -20,12 +20,6 @@ public @interface C {
String v2();
}
// FILE: D.java
public @interface D {
String value() default "hello";
}
// FILE: b.kt
fun box(): String {
@@ -34,10 +28,5 @@ fun box(): String {
assert(b.value == "OK")
val c = C(v2 = "v2", v1 = intArrayOf(1))
assert(c.v2 == "v2")
// TODO(KT-47702): Looks like we have to force users either to pass default java parameters explicitly
// or hack LazyJavaClassDescriptor/JavaPropertyDescriptor to load annotation param default value,
// because it is not stored currently anywhere.
// val d = D()
val d = D("OK").value
return d
return "OK"
}
@@ -0,0 +1,78 @@
// TARGET_BACKEND: JVM_IR
// IGNORE_BACKEND_K1: JVM_IR
// WITH_STDLIB
// !LANGUAGE: +InstantiationOfAnnotationClasses
// FILE: A.java
public @interface A {
String value() default "OK";
}
// FILE: ChildAnnotation.java
@interface ChildAnnotation {
String value();
}
// FILE: D.java
public @interface D {
//Primitive types
boolean booleanValue() default false;
byte byteValue() default 1;
short shortValue() default 2;
int intValue() default 3;
long longValue() default 4;
float floatValue() default 5.0f;
double doubleValue() default 6.0;
char charValue() default 'a';
// String
String stringValue() default "default";
// Class type
Class<?> classValue() default Object.class;
// Enum type
MyEnum enumValue() default MyEnum.FIRST;
// Annotation
ChildAnnotation annotationValue() default @ChildAnnotation(value = "child") ;
// Arrays of the above
boolean[] booleanArrayValue() default { false, true };
byte[] byteArrayValue() default { 1, 2 };
short[] shortArrayValue() default { 3, 4 };
int[] intArrayValue() default { 5, 6 };
long[] longArrayValue() default { 7, 8 };
float[] floatArrayValue() default { 9.0f, 10.0f };
double[] doubleArrayValue() default { 11.0, 12.0 };
char[] charArrayValue() default { 'x', 'y' };
String[] stringArrayValue() default { "Hello", "World" };
Class<?>[] classArrayValue() default { Object.class, String.class };
MyEnum[] enumArrayValue() default { MyEnum.FIRST, MyEnum.SECOND };
ChildAnnotation[] annotationArrayValue() default { @ChildAnnotation(value = "child1"), @ChildAnnotation(value = "child2") };
// other annotation with default value
A annotationWithDefault() default @A;
}
// Supporting enum
enum MyEnum { FIRST, SECOND }
// FILE: b.kt
fun box(): String {
val d = D()
val str = d.toString()
val golden = """@D(booleanValue=false, byteValue=1, shortValue=2, intValue=3, longValue=4, floatValue=5.0, doubleValue=6.0, charValue=a, stringValue=default, """+
"""classValue=class java.lang.Object, enumValue=FIRST, annotationValue=@ChildAnnotation(value=child), booleanArrayValue=[false, true], byteArrayValue=[1, 2], """ +
"""shortArrayValue=[3, 4], intArrayValue=[5, 6], longArrayValue=[7, 8], floatArrayValue=[9.0, 10.0], doubleArrayValue=[11.0, 12.0], charArrayValue=[x, y], stringArrayValue=[Hello, World], """ +
"""classArrayValue=[class java.lang.Object, class java.lang.String], enumArrayValue=[FIRST, SECOND], annotationArrayValue=[@ChildAnnotation(value=child1), @ChildAnnotation(value=child2)], """ +
"""annotationWithDefault=@A(value=OK))"""
if (str != golden) return str
if (d.longValue != 4L) return d.longValue.toString()
if (d.annotationValue.value != "child") return d.annotationValue.value
if (d.doubleArrayValue[0] != 11.0) return d.doubleArrayValue[0].toString()
if (d.classArrayValue[1] != String::class) return d.classArrayValue.contentToString()
return d.annotationWithDefault.value
}
@@ -0,0 +1,31 @@
// TARGET_BACKEND: JVM_IR
// IGNORE_BACKEND_K1: JVM_IR
// IGNORE_DEXING
// WITH_STDLIB
// !LANGUAGE: +InstantiationOfAnnotationClasses
// MODULE: lib
// FILE: LibAnnotation.java
package a;
public @interface LibAnnotation {
long longValue() default 4;
String stringValue() default "OK";
Class<?> classValue() default String.class;
float[] floatArrayValue() default { 9.0f, 10.0f };
}
// MODULE: app(lib)
// FILE: app.kt
package test
import a.*
fun box(): String {
val l = LibAnnotation()
if (l.toString() != "@a.LibAnnotation(longValue=4, stringValue=OK, classValue=class java.lang.String, floatArrayValue=[9.0, 10.0])")
return l.toString()
return "OK"
}
@@ -582,6 +582,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotation.kt");
}
@Test
@TestMetadata("javaAnnotationDefault.kt")
public void testJavaAnnotationDefault() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotationDefault.kt");
}
@Test
@TestMetadata("javaExistingAnnotation.kt")
public void testJavaExistingAnnotation() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaExistingAnnotation.kt");
}
@Test
@TestMetadata("kotlinExistingAnnotation.kt")
public void testKotlinExistingAnnotation() throws Exception {
@@ -582,6 +582,18 @@ public class IrBlackBoxCodegenWithIrInlinerTestGenerated extends AbstractIrBlack
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotation.kt");
}
@Test
@TestMetadata("javaAnnotationDefault.kt")
public void testJavaAnnotationDefault() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotationDefault.kt");
}
@Test
@TestMetadata("javaExistingAnnotation.kt")
public void testJavaExistingAnnotation() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaExistingAnnotation.kt");
}
@Test
@TestMetadata("kotlinExistingAnnotation.kt")
public void testKotlinExistingAnnotation() throws Exception {
@@ -430,6 +430,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/annotations/instances/innerAnnotationInstantiation.kt");
}
@TestMetadata("javaAnnotationDefault.kt")
public void ignoreJavaAnnotationDefault() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaAnnotationDefault.kt");
}
@TestMetadata("javaExistingAnnotation.kt")
public void ignoreJavaExistingAnnotation() throws Exception {
runTest("compiler/testData/codegen/box/annotations/instances/javaExistingAnnotation.kt");
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
@@ -91,7 +91,7 @@ class SerialInfoImplJvmIrGenerator(
visibility = DescriptorVisibilities.PUBLIC
}
implementor.implementAnnotationPropertiesAndConstructor(properties, subclass, ctor, null)
implementor.implementAnnotationPropertiesAndConstructor(annotationClass, properties, subclass, ctor, null)
}
private fun createPackage(packageName: String): IrPackageFragment =