From 1b5e3f5d516501d51793b5cd15ea795b5d15526d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 18 Nov 2021 01:11:48 +0100 Subject: [PATCH] Deserialize repeated annotations inside implicit container #KT-49651 Fixed --- ...peatableAnnotationWithExplicitContainer.kt | 22 ++++++++++++ ...eatableAnnotationWithExplicitContainer.txt | 20 +++++++++++ ...peatableAnnotationWithImplicitContainer.kt | 17 +++++++++ ...eatableAnnotationWithImplicitContainer.txt | 14 ++++++++ .../jvm/compiler/AbstractLoadJavaTest.java | 2 +- .../generators/tests/GenerateJava8Tests.kt | 1 + .../jvm/compiler/LoadJava8TestGenerated.java | 36 +++++++++++++++++++ ...tBinaryClassAnnotationAndConstantLoader.kt | 5 ++- ...aryClassAnnotationAndConstantLoaderImpl.kt | 16 +++++++-- 9 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.kt create mode 100644 compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.txt create mode 100644 compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.kt create mode 100644 compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.txt diff --git a/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.kt b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.kt new file mode 100644 index 00000000000..5cfd4fee1f4 --- /dev/null +++ b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.kt @@ -0,0 +1,22 @@ +// FULL_JDK + +package test + +@java.lang.annotation.Repeatable(Anno.Container::class) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS) +annotation class Anno(val code: Int) { + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS) + annotation class Container(val value: Array) +} + +@Anno(1) +@Anno(2) +class Z + +@Anno(3) +@Anno(4) +fun f() {} + +@Anno(5) +@Anno(6) +typealias S = String diff --git a/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.txt b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.txt new file mode 100644 index 00000000000..4f29b59fa03 --- /dev/null +++ b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.txt @@ -0,0 +1,20 @@ +package test + +@test.Anno(code = 3) @test.Anno(code = 4) public fun f(): kotlin.Unit + +@java.lang.annotation.Repeatable(value = test.Anno.Container::class) @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS}) public final annotation class Anno : kotlin.Annotation { + /*primary*/ public constructor Anno(/*0*/ code: kotlin.Int) + public final val code: kotlin.Int + public final fun (): kotlin.Int + + @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS}) public final annotation class Container : kotlin.Annotation { + /*primary*/ public constructor Container(/*0*/ value: kotlin.Array) + public final val value: kotlin.Array + public final fun (): kotlin.Array + } +} + +@test.Anno(code = 1) @test.Anno(code = 2) public final class Z { + /*primary*/ public constructor Z() +} +@test.Anno(code = 5) @test.Anno(code = 6) public typealias S = kotlin.String diff --git a/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.kt b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.kt new file mode 100644 index 00000000000..a383a471455 --- /dev/null +++ b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.kt @@ -0,0 +1,17 @@ +package test + +@Repeatable +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS) +annotation class Anno(val code: Int) + +@Anno(1) +@Anno(2) +class Z + +@Anno(3) +@Anno(4) +fun f() {} + +@Anno(5) +@Anno(6) +typealias S = String diff --git a/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.txt b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.txt new file mode 100644 index 00000000000..80e6a5949cb --- /dev/null +++ b/compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.txt @@ -0,0 +1,14 @@ +package test + +@test.Anno(code = 3) @test.Anno(code = 4) public fun f(): kotlin.Unit + +@kotlin.annotation.Repeatable @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS}) public final annotation class Anno : kotlin.Annotation { + /*primary*/ public constructor Anno(/*0*/ code: kotlin.Int) + public final val code: kotlin.Int + public final fun (): kotlin.Int +} + +@test.Anno(code = 1) @test.Anno(code = 2) public final class Z { + /*primary*/ public constructor Z() +} +@test.Anno(code = 5) @test.Anno(code = 6) public typealias S = kotlin.String diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractLoadJavaTest.java b/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractLoadJavaTest.java index 0b76d9d691a..bb84483560d 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractLoadJavaTest.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/jvm/compiler/AbstractLoadJavaTest.java @@ -132,7 +132,7 @@ public abstract class AbstractLoadJavaTest extends TestCaseWithTmpdir { File ktFile = new File(ktFileName); File txtFile = getTxtFileFromKtFile(ktFileName); - CompilerConfiguration configuration = newConfiguration(configurationKind, TestJdkKind.MOCK_JDK, getClasspath(), Collections.emptyList()); + CompilerConfiguration configuration = newConfiguration(configurationKind, getJdkKind(), getClasspath(), Collections.emptyList()); updateConfiguration(configuration); if (useTypeTableInSerializer) { configuration.put(JVMConfigurationKeys.USE_TYPE_TABLE, true); diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/generators/tests/GenerateJava8Tests.kt b/compiler/tests-java8/tests/org/jetbrains/kotlin/generators/tests/GenerateJava8Tests.kt index 22afb83d491..b392292ba95 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/generators/tests/GenerateJava8Tests.kt +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/generators/tests/GenerateJava8Tests.kt @@ -30,6 +30,7 @@ fun main(args: Array) { testGroup("compiler/tests-java8/tests", "compiler/testData") { testClass { model("loadJava8/compiledJava", extension = "java", testMethod = "doTestCompiledJava") + model("loadJava8/compiledKotlinWithStdlib", testMethod = "doTestCompiledKotlinWithStdlib") model("loadJava8/sourceJava", extension = "java", testMethod = "doTestSourceJava") } diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/jvm/compiler/LoadJava8TestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/jvm/compiler/LoadJava8TestGenerated.java index 856807bd506..bf289d4bc4a 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/jvm/compiler/LoadJava8TestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/jvm/compiler/LoadJava8TestGenerated.java @@ -123,6 +123,42 @@ public class LoadJava8TestGenerated extends AbstractLoadJava8Test { } } + @TestMetadata("compiler/testData/loadJava8/compiledKotlinWithStdlib") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CompiledKotlinWithStdlib extends AbstractLoadJava8Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTestCompiledKotlinWithStdlib, this, testDataFilePath); + } + + public void testAllFilesPresentInCompiledKotlinWithStdlib() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/loadJava8/compiledKotlinWithStdlib"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Annotations extends AbstractLoadJava8Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTestCompiledKotlinWithStdlib, this, testDataFilePath); + } + + public void testAllFilesPresentInAnnotations() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("RepeatableAnnotationWithExplicitContainer.kt") + public void testRepeatableAnnotationWithExplicitContainer() throws Exception { + runTest("compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithExplicitContainer.kt"); + } + + @TestMetadata("RepeatableAnnotationWithImplicitContainer.kt") + public void testRepeatableAnnotationWithImplicitContainer() throws Exception { + runTest("compiler/testData/loadJava8/compiledKotlinWithStdlib/annotations/RepeatableAnnotationWithImplicitContainer.kt"); + } + } + } + @TestMetadata("compiler/testData/loadJava8/sourceJava") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/AbstractBinaryClassAnnotationAndConstantLoader.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/AbstractBinaryClassAnnotationAndConstantLoader.kt index df1d894dd11..7dfd5fd9f22 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/AbstractBinaryClassAnnotationAndConstantLoader.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/AbstractBinaryClassAnnotationAndConstantLoader.kt @@ -401,7 +401,10 @@ abstract class AbstractBinaryClassAnnotationAndConstantLoader( val containerKClassValue = arguments[Name.identifier("value")] as? KClassValue ?: return false val normalClass = containerKClassValue.value as? KClassValue.Value.NormalClass ?: return false - val classId = normalClass.classId + return isImplicitRepeatableContainer(normalClass.classId) + } + + protected fun isImplicitRepeatableContainer(classId: ClassId): Boolean { if (classId.outerClassId == null || classId.shortClassName.asString() != JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME ) return false diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/BinaryClassAnnotationAndConstantLoaderImpl.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/BinaryClassAnnotationAndConstantLoaderImpl.kt index 271d0d4b44e..c8e4c41da32 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/BinaryClassAnnotationAndConstantLoaderImpl.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/BinaryClassAnnotationAndConstantLoaderImpl.kt @@ -114,6 +114,12 @@ class BinaryClassAnnotationAndConstantLoaderImpl( val parameter = DescriptorResolverUtils.getAnnotationParameterByName(name, annotationClass) if (parameter != null) { arguments[name] = ConstantValueFactory.createArrayValue(elements.compact(), parameter.type) + } else if (isImplicitRepeatableContainer(annotationClassId) && name.asString() == "value") { + // In case this is an implicit repeatable annotation container, its class descriptor can't be resolved by the + // frontend, so we'd like to flatten its value and add repeated annotations to the list. + // E.g. if we see `@Foo.Container(@Foo(1), @Foo(2))` in the bytecode on some declaration where `Foo` is some + // Kotlin-repeatable annotation, we want to read annotations on that declaration as a list `[@Foo(1), @Foo(2)]`. + elements.filterIsInstance().mapTo(result, AnnotationValue::value) } } } @@ -134,9 +140,13 @@ class BinaryClassAnnotationAndConstantLoaderImpl( // Do not load the @java.lang.annotation.Repeatable annotation instance generated automatically by the compiler for // Kotlin-repeatable annotation classes. Otherwise the reference to the implicit nested "Container" class cannot be // resolved, since that class is only generated in the backend, and is not visible to the frontend. - if (!isRepeatableWithImplicitContainer(annotationClassId, arguments)) { - result.add(AnnotationDescriptorImpl(annotationClass.defaultType, arguments, source)) - } + if (isRepeatableWithImplicitContainer(annotationClassId, arguments)) return + + // Do not load the implicit repeatable annotation container entry. The contents of its "value" argument have been flattened + // and added to the result already, see `visitArray`. + if (isImplicitRepeatableContainer(annotationClassId)) return + + result.add(AnnotationDescriptorImpl(annotationClass.defaultType, arguments, source)) } private fun createConstant(name: Name?, value: Any?): ConstantValue<*> {