Fix loading default Java annotation values in actual typealias from binary classes

#KT-22704 Fixed
This commit is contained in:
Alexander Udalov
2018-12-24 17:48:01 +01:00
parent 3fee84b966
commit 8ab9226805
15 changed files with 243 additions and 84 deletions
@@ -16,14 +16,13 @@
package org.jetbrains.kotlin.load.java.structure.impl;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnnotationMethod;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.load.java.structure.JavaMethod;
import org.jetbrains.kotlin.load.java.structure.JavaType;
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter;
import org.jetbrains.kotlin.load.java.structure.JavaValueParameter;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.load.java.structure.*;
import org.jetbrains.kotlin.name.Name;
import java.util.List;
@@ -57,9 +56,17 @@ public class JavaMethodImpl extends JavaMemberImpl<PsiMethod> implements JavaMet
}
@Override
public boolean getHasAnnotationParameterDefaultValue() {
@Nullable
public JavaAnnotationArgument getAnnotationParameterDefaultValue() {
PsiMethod psiMethod = getPsi();
return psiMethod instanceof PsiAnnotationMethod && ((PsiAnnotationMethod) psiMethod).getDefaultValue() != null;
if (psiMethod instanceof PsiAnnotationMethod) {
PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod) psiMethod).getDefaultValue();
if (defaultValue != null) {
return JavaAnnotationArgumentImpl.Factory.create(defaultValue, null);
}
}
return null;
}
@Override
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.*
import java.lang.reflect.Array
@@ -37,11 +36,10 @@ internal class AnnotationsAndParameterCollectorMethodVisitor(
private var visibleAnnotableParameterCount = parametersCountInMethodDesc
private var invisibleAnnotableParameterCount = parametersCountInMethodDesc
override fun visitAnnotationDefault(): AnnotationVisitor? {
member.safeAs<BinaryJavaMethod>()?.hasAnnotationParameterDefaultValue = true
// We don't store default value in Java model
return null
}
override fun visitAnnotationDefault(): AnnotationVisitor? =
BinaryJavaAnnotationVisitor(context, signatureParser) {
member.safeAs<BinaryJavaMethod>()?.annotationParameterDefaultValue = it
}
override fun visitParameter(name: String?, access: Int) {
if (name != null) {
@@ -165,19 +163,26 @@ class BinaryJavaAnnotation private constructor(
}
class BinaryJavaAnnotationVisitor(
private val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
private val arguments: MutableCollection<JavaAnnotationArgument>
private val context: ClassifierResolutionContext,
private val signatureParser: BinaryClassSignatureParser,
private val sink: (JavaAnnotationArgument) -> Unit
) : AnnotationVisitor(ASM_API_VERSION_FOR_CLASS_READING) {
constructor(
context: ClassifierResolutionContext,
signatureParser: BinaryClassSignatureParser,
arguments: MutableCollection<JavaAnnotationArgument>
) : this(context, signatureParser, { arguments.add(it) })
private fun addArgument(argument: JavaAnnotationArgument?) {
arguments.addIfNotNull(argument)
if (argument != null) {
sink(argument)
}
}
override fun visitAnnotation(name: String?, desc: String): AnnotationVisitor {
val (annotation, visitor) =
BinaryJavaAnnotation.createAnnotationAndVisitor(desc, context, signatureParser)
val (annotation, visitor) = BinaryJavaAnnotation.createAnnotationAndVisitor(desc, context, signatureParser)
arguments.add(PlainJavaAnnotationAsAnnotationArgument(name, annotation))
sink(PlainJavaAnnotationAsAnnotationArgument(name, annotation))
return visitor
}
@@ -169,7 +169,15 @@ class BinaryJavaMethod(
) : BinaryJavaMethodBase(
flags, containingClass, valueParameters, typeParameters, name
), JavaMethod {
override var hasAnnotationParameterDefaultValue: Boolean = false
override var annotationParameterDefaultValue: JavaAnnotationArgument? = null
internal set(value) {
if (field != null) {
throw AssertionError(
"Annotation method cannot have two default values: $this (old=$field, new=$value)"
)
}
field = value
}
}
class BinaryJavaConstructor(
@@ -5,13 +5,12 @@
package org.jetbrains.kotlin.resolve.jvm.multiplatform
import com.intellij.psi.PsiAnnotationMethod
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.load.java.components.JavaPropertyInitializerEvaluatorImpl
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.load.java.structure.impl.JavaAnnotationArgumentImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
@@ -23,11 +22,10 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.builtIns
class JavaActualAnnotationArgumentExtractor : ExpectedActualDeclarationChecker.ActualAnnotationArgumentExtractor {
override fun extractActualValue(argument: PsiElement, expectedType: KotlinType): ConstantValue<*>? =
(argument as? PsiAnnotationMethod)
?.defaultValue
?.let { JavaAnnotationArgumentImpl.create(it, null) }
?.convert(expectedType)
override fun extractDefaultValue(parameter: ValueParameterDescriptor, expectedType: KotlinType): ConstantValue<*>? {
val element = (parameter.source as? JavaSourceElement)?.javaElement
return (element as? JavaMethod)?.annotationParameterDefaultValue?.convert(expectedType)
}
// This code is similar to LazyJavaAnnotationDescriptor.resolveAnnotationArgument, but cannot be reused until
// KClassValue/AnnotationValue are untied from descriptors/types, because here we do not have an instance of LazyJavaResolverContext.
@@ -43,7 +43,7 @@ import java.io.File
class ExpectedActualDeclarationChecker(val argumentExtractors: List<ActualAnnotationArgumentExtractor> = emptyList()) : DeclarationChecker {
interface ActualAnnotationArgumentExtractor {
fun extractActualValue(argument: PsiElement, expectedType: KotlinType): ConstantValue<*>?
fun extractDefaultValue(parameter: ValueParameterDescriptor, expectedType: KotlinType): ConstantValue<*>?
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
@@ -233,14 +233,15 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: List<ActualAnnota
if (expectedParameterDescriptor.declaresDefaultValue() && actualParameterDescriptor.declaresDefaultValue()) {
val expectedParameter =
DescriptorToSourceUtils.descriptorToDeclaration(expectedParameterDescriptor) as? KtParameter ?: continue
val actualParameter = DescriptorToSourceUtils.descriptorToDeclaration(actualParameterDescriptor)
val expectedValue = trace.bindingContext.get(BindingContext.COMPILE_TIME_VALUE, expectedParameter.defaultValue)
?.toConstantValue(expectedParameterDescriptor.type)
val actualValue = getActualAnnotationParameterValue(actualParameter, trace.bindingContext, expectedParameterDescriptor.type)
val actualValue =
getActualAnnotationParameterValue(actualParameterDescriptor, trace.bindingContext, expectedParameterDescriptor.type)
if (expectedValue != actualValue) {
val target = (actualParameter as? KtParameter)?.defaultValue ?: (reportOn as? KtTypeAlias)?.nameIdentifier ?: reportOn
val ktParameter = DescriptorToSourceUtils.descriptorToDeclaration(actualParameterDescriptor)
val target = (ktParameter as? KtParameter)?.defaultValue ?: (reportOn as? KtTypeAlias)?.nameIdentifier ?: reportOn
trace.report(Errors.ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE.on(target, actualParameterDescriptor))
}
}
@@ -248,16 +249,15 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: List<ActualAnnota
}
private fun getActualAnnotationParameterValue(
actualParameter: PsiElement?, bindingContext: BindingContext, expectedType: KotlinType
actualParameter: ValueParameterDescriptor, bindingContext: BindingContext, expectedType: KotlinType
): ConstantValue<*>? {
if (actualParameter is KtParameter) {
return bindingContext.get(BindingContext.COMPILE_TIME_VALUE, actualParameter.defaultValue)?.toConstantValue(expectedType)
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(actualParameter)
if (declaration is KtParameter) {
return bindingContext.get(BindingContext.COMPILE_TIME_VALUE, declaration.defaultValue)?.toConstantValue(expectedType)
}
if (actualParameter != null) {
for (extractor in argumentExtractors) {
extractor.extractActualValue(actualParameter, expectedType)?.let { return it }
}
for (extractor in argumentExtractors) {
extractor.extractDefaultValue(actualParameter, expectedType)?.let { return it }
}
return null
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.javac.wrappers.symbols
import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.name.Name
import javax.lang.model.element.ExecutableElement
class SymbolBasedMethod(
@@ -35,7 +36,9 @@ class SymbolBasedMethod(
override val returnType: JavaType
get() = SymbolBasedType.create(element.returnType, javac)
override val hasAnnotationParameterDefaultValue: Boolean
get() = element.defaultValue != null
// TODO: allow nullable names in Symbol-based annotation arguments and pass null instead of a synthetic name
override val annotationParameterDefaultValue: JavaAnnotationArgument?
get() = element.defaultValue?.let { defaultValue ->
SymbolBasedAnnotationArgument.create(defaultValue, Name.identifier("value"), javac)
}
}
@@ -100,46 +100,50 @@ class TreeBasedAnnotationAsAnnotationArgument(private val annotation: JCTree.JCA
}
private fun createAnnotationArguments(annotation: TreeBasedAnnotation,
javac: JavacWrapper,
onElement: JavaElement): Collection<JavaAnnotationArgument> =
private fun createAnnotationArguments(
annotation: TreeBasedAnnotation, javac: JavacWrapper, onElement: JavaElement
): Collection<JavaAnnotationArgument> =
annotation.annotation.arguments.mapNotNull {
val name = if (it is JCTree.JCAssign) Name.identifier(it.lhs.toString()) else Name.identifier("value")
createAnnotationArgument(it, name, annotation.compilationUnit, javac, annotation, onElement)
createAnnotationArgument(it, name, annotation.compilationUnit, javac, annotation.resolve(), onElement)
}
private fun createAnnotationArgument(argument: JCTree.JCExpression,
name: Name,
compilationUnit: CompilationUnitTree,
javac: JavacWrapper,
annotation: TreeBasedAnnotation,
onElement: JavaElement): JavaAnnotationArgument? =
when (argument) {
is JCTree.JCLiteral -> TreeBasedLiteralAnnotationArgument(name, argument.value, javac)
is JCTree.JCFieldAccess -> {
if (argument.name.contentEquals("class")) {
TreeBasedJavaClassObjectAnnotationArgument(argument.selected, name, compilationUnit, javac, onElement)
}
else {
TreeBasedReferenceAnnotationArgument(name, compilationUnit, argument, javac, onElement)
}
internal fun createAnnotationArgument(
argument: JCTree.JCExpression,
name: Name,
compilationUnit: CompilationUnitTree,
javac: JavacWrapper,
containingClass: JavaClass?,
onElement: JavaElement
): JavaAnnotationArgument? =
when (argument) {
is JCTree.JCLiteral -> TreeBasedLiteralAnnotationArgument(name, argument.value, javac)
is JCTree.JCFieldAccess -> {
if (argument.name.contentEquals("class")) {
TreeBasedJavaClassObjectAnnotationArgument(argument.selected, name, compilationUnit, javac, onElement)
} else {
TreeBasedReferenceAnnotationArgument(name, compilationUnit, argument, javac, onElement)
}
is JCTree.JCAssign -> createAnnotationArgument(argument.rhs, name, compilationUnit, javac, annotation, onElement)
is JCTree.JCNewArray -> TreeBasedArrayAnnotationArgument(argument.elems.mapNotNull { createAnnotationArgument(it, name, compilationUnit, javac, annotation, onElement) }, name, javac)
is JCTree.JCAnnotation -> TreeBasedAnnotationAsAnnotationArgument(argument, name, compilationUnit, javac, onElement)
is JCTree.JCParens -> createAnnotationArgument(argument.expr, name, compilationUnit, javac, annotation, onElement)
is JCTree.JCBinary -> resolveArgumentValue(argument, annotation, name, compilationUnit, javac)
is JCTree.JCUnary -> resolveArgumentValue(argument, annotation, name, compilationUnit, javac)
else -> throw UnsupportedOperationException("Unknown annotation argument $argument")
}
is JCTree.JCAssign -> createAnnotationArgument(argument.rhs, name, compilationUnit, javac, containingClass, onElement)
is JCTree.JCNewArray -> TreeBasedArrayAnnotationArgument(argument.elems.mapNotNull {
createAnnotationArgument(it, name, compilationUnit, javac, containingClass, onElement)
}, name, javac)
is JCTree.JCAnnotation -> TreeBasedAnnotationAsAnnotationArgument(argument, name, compilationUnit, javac, onElement)
is JCTree.JCParens -> createAnnotationArgument(argument.expr, name, compilationUnit, javac, containingClass, onElement)
is JCTree.JCBinary -> resolveArgumentValue(argument, containingClass, name, compilationUnit, javac)
is JCTree.JCUnary -> resolveArgumentValue(argument, containingClass, name, compilationUnit, javac)
else -> throw UnsupportedOperationException("Unknown annotation argument $argument")
}
private fun resolveArgumentValue(argument: JCTree.JCExpression,
annotation: TreeBasedAnnotation,
name: Name,
compilationUnit: CompilationUnitTree,
javac: JavacWrapper): JavaAnnotationArgument? {
val containingAnnotation = annotation.resolve() ?: return null
val evaluator = ConstantEvaluator(containingAnnotation, javac, compilationUnit)
private fun resolveArgumentValue(
argument: JCTree.JCExpression,
containingClass: JavaClass?,
name: Name,
compilationUnit: CompilationUnitTree,
javac: JavacWrapper
): JavaAnnotationArgument? {
if (containingClass == null) return null
val evaluator = ConstantEvaluator(containingClass, javac, compilationUnit)
return evaluator.getValue(argument)?.let { TreeBasedLiteralAnnotationArgument(name, it, javac) }
}
}
@@ -55,6 +55,9 @@ class TreeBasedMethod(
override val returnType: JavaType
get() = TreeBasedType.create(tree.returnType, compilationUnit, javac, annotations, this)
override val hasAnnotationParameterDefaultValue: Boolean
get() = tree.defaultValue != null
}
// TODO: allow nullable names in Tree-based annotation arguments and pass null instead of a synthetic name
override val annotationParameterDefaultValue: JavaAnnotationArgument?
get() = tree.defaultValue?.let { defaultValue ->
createAnnotationArgument(defaultValue, Name.identifier("value"), compilationUnit, javac, containingClass, this)
}
}
@@ -0,0 +1,92 @@
// !LANGUAGE: +MultiPlatformProjects
// WITH_REFLECT
// IGNORE_BACKEND: JVM_IR
// FILE: main.kt
// See compiler/testData/diagnostics/tests/multiplatform/defaultArguments/annotationsViaActualTypeAlias2.kt
// This test checks the same behavior but against the Java implementation compiled to the .class file (as opposed to a .java source file).
// Enum annotation argument is commented below, because to be able to resolve E in Jnno.java we have to have a multi-module test where
// one of the modules also contains Java files, and that is too complicated for our test infrastructure at the moment.
import kotlin.reflect.KClass
expect annotation class Anno(
val b: Byte = 1.toByte(),
val c: Char = 'x',
val d: Double = 3.14,
val f: Float = -2.72f,
val i: Int = 42424242,
val i2: Int = 53535353,
val j: Long = 239239239239239L,
val j2: Long = 239239L,
val s: Short = 42.toShort(),
val z: Boolean = true,
val ba: ByteArray = [(-1).toByte()],
val ca: CharArray = ['y'],
val da: DoubleArray = [-3.14159],
val fa: FloatArray = [2.7218f],
val ia: IntArray = [424242],
val ja: LongArray = [239239239239L, 239239L],
val sa: ShortArray = [(-43).toShort()],
val za: BooleanArray = [false, true],
val str: String = "fizz",
val k: KClass<*> = Number::class,
// val e: E = E.E1,
// TODO: val a: A = A("1"),
val stra: Array<String> = ["bu", "zz"],
val ka: Array<KClass<*>> = [Double::class, String::class, LongArray::class, Array<Array<Array<Int>>>::class, Unit::class]
// val ea: Array<E> = [E.E2, E.E3],
// TODO: val aa: Array<A> = [A("2"), A("3")]
)
// enum class E { E1, E2, E3 }
annotation class A(val value: String)
@Anno
fun test() {}
actual typealias Anno = Jnno
fun box(): String {
// We don't need to check the contents, just check that there are no anomalities in the bytecode by loading annotations
::test.annotations.toString()
return "OK"
}
// FILE: Jnno.java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Jnno {
byte b() default 1;
char c() default 'x';
double d() default 3.14;
float f() default -2.72f;
int i() default 42424242;
int i2() default 21212121 + 32323232;
long j() default 239239239239239L;
long j2() default 239239;
short s() default 42;
boolean z() default true;
byte[] ba() default {-1};
char[] ca() default {'y'};
double[] da() default {-3.14159};
float[] fa() default {2.7218f};
int[] ia() default {424242};
long[] ja() default {239239239239L, 239239};
short[] sa() default {-43};
boolean[] za() default {false, true};
String str() default "fi" + "zz";
Class<?> k() default Number.class;
// E e() default E.E1;
// TODO: A a() default @A("1");
String[] stra() default {"bu", "zz"};
Class<?>[] ka() default {double.class, String.class, long[].class, Integer[][][].class, void.class};
// E[] ea() default {E.E2, E.E3};
// TODO: A[] aa() default {@A("2"), @A("3")};
}
@@ -2,6 +2,8 @@
// MODULE: m1-common
// FILE: common.kt
// See also compiler/testData/codegen/boxAgainstJava/multiplatform/annotationsViaActualTypeAliasFromBinary.kt
import kotlin.reflect.KClass
expect annotation class Anno(
@@ -407,6 +407,24 @@ public class BlackBoxAgainstJavaCodegenTestGenerated extends AbstractBlackBoxAga
}
}
@TestMetadata("compiler/testData/codegen/boxAgainstJava/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractBlackBoxAgainstJavaCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxAgainstJava/multiplatform"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("annotationsViaActualTypeAliasFromBinary.kt")
public void testAnnotationsViaActualTypeAliasFromBinary() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/multiplatform/annotationsViaActualTypeAliasFromBinary.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxAgainstJava/notNullAssertions")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -407,6 +407,24 @@ public class IrBlackBoxAgainstJavaCodegenTestGenerated extends AbstractIrBlackBo
}
}
@TestMetadata("compiler/testData/codegen/boxAgainstJava/multiplatform")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Multiplatform extends AbstractIrBlackBoxAgainstJavaCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInMultiplatform() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxAgainstJava/multiplatform"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("annotationsViaActualTypeAliasFromBinary.kt")
public void testAnnotationsViaActualTypeAliasFromBinary() throws Exception {
runTest("compiler/testData/codegen/boxAgainstJava/multiplatform/annotationsViaActualTypeAliasFromBinary.kt");
}
}
@TestMetadata("compiler/testData/codegen/boxAgainstJava/notNullAssertions")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -675,7 +675,7 @@ class LazyJavaClassMemberScope(
method.name,
// Parameters of annotation constructors in Java are never nullable
TypeUtils.makeNotNullable(returnType),
method.hasAnnotationParameterDefaultValue,
method.annotationParameterDefaultValue != null,
/* isCrossinline = */ false,
/* isNoinline = */ false,
// Nulls are not allowed in annotation arguments in Java
@@ -105,7 +105,7 @@ interface JavaMethod : JavaMember, JavaTypeParameterListOwner {
val valueParameters: List<JavaValueParameter>
val returnType: JavaType
val hasAnnotationParameterDefaultValue: Boolean
val annotationParameterDefaultValue: JavaAnnotationArgument?
}
interface JavaField : JavaMember {
@@ -16,6 +16,7 @@
package kotlin.reflect.jvm.internal.structure
import org.jetbrains.kotlin.load.java.structure.JavaAnnotationArgument
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.load.java.structure.JavaValueParameter
import java.lang.reflect.Method
@@ -27,8 +28,8 @@ class ReflectJavaMethod(override val member: Method) : ReflectJavaMember(), Java
override val returnType: ReflectJavaType
get() = ReflectJavaType.create(member.genericReturnType)
override val hasAnnotationParameterDefaultValue: Boolean
get() = member.defaultValue != null
override val annotationParameterDefaultValue: JavaAnnotationArgument?
get() = member.defaultValue?.let { ReflectJavaAnnotationArgument.create(it, null) }
override val typeParameters: List<ReflectJavaTypeParameter>
get() = member.typeParameters.map(::ReflectJavaTypeParameter)