diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/finder/JavaElementFinder.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/finder/JavaElementFinder.kt index 8614d43b760..9d61d3a88d6 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/finder/JavaElementFinder.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/finder/JavaElementFinder.kt @@ -26,16 +26,17 @@ import com.intellij.psi.util.PsiUtilCore import com.intellij.util.SmartList import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport +import org.jetbrains.kotlin.asJava.hasRepeatableAnnotationContainer import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.isValidJavaFqName import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtEnumEntry import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import java.util.* class JavaElementFinder( private val project: Project, @@ -64,9 +65,10 @@ class JavaElementFinder( } // Finds explicitly declared classes and objects, not package classes - // Also DefaultImpls classes of interfaces + // Also DefaultImpls classes of interfaces, Container classes of repeatable annotations private fun findClassesAndObjects(qualifiedName: FqName, scope: GlobalSearchScope, answer: MutableList) { findInterfaceDefaultImpls(qualifiedName, scope, answer) + findRepeatableAnnotationContainer(qualifiedName, scope, answer) val classOrObjectDeclarations = kotlinAsJavaSupport.findClassOrObjectDeclarations(qualifiedName, scope) @@ -80,17 +82,30 @@ class JavaElementFinder( } } - private fun findInterfaceDefaultImpls(qualifiedName: FqName, scope: GlobalSearchScope, answer: MutableList) { - if (qualifiedName.isRoot) return + private fun findInterfaceDefaultImpls(qualifiedName: FqName, scope: GlobalSearchScope, answer: MutableList) = + findSyntheticInnerClass(qualifiedName, JvmAbi.DEFAULT_IMPLS_CLASS_NAME, scope, answer) { + it is KtClass && it.isInterface() + } - if (qualifiedName.shortName().asString() != JvmAbi.DEFAULT_IMPLS_CLASS_NAME) return + private fun findRepeatableAnnotationContainer(qualifiedName: FqName, scope: GlobalSearchScope, answer: MutableList) = + findSyntheticInnerClass(qualifiedName, JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME, scope, answer) { + it.hasRepeatableAnnotationContainer + } + + private fun findSyntheticInnerClass( + qualifiedName: FqName, + syntheticName: String, + scope: GlobalSearchScope, + answer: MutableList, + predicate: (KtClassOrObject) -> Boolean, + ) { + if (qualifiedName.isRoot || qualifiedName.shortName().asString() != syntheticName) return for (classOrObject in kotlinAsJavaSupport.findClassOrObjectDeclarations(qualifiedName.parent(), scope)) { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() - //NOTE: can't filter out more interfaces right away because decompiled declarations do not have member bodies - if (classOrObject is KtClass && classOrObject.isInterface()) { + if (predicate(classOrObject)) { val interfaceClass = kotlinAsJavaSupport.getLightClass(classOrObject) ?: continue - val implsClass = interfaceClass.findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false) ?: continue + val implsClass = interfaceClass.findInnerClassByName(syntheticName, false) ?: continue answer.add(implsClass) } } diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index c163a002127..9aa230d1e63 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -17,10 +17,11 @@ package org.jetbrains.kotlin.asJava import com.intellij.psi.* -import com.intellij.psi.impl.light.LightField import com.intellij.psi.search.GlobalSearchScope -import org.jetbrains.kotlin.asJava.classes.* -import org.jetbrains.kotlin.asJava.elements.PsiElementWithOrigin +import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade +import org.jetbrains.kotlin.asJava.classes.runReadAction import org.jetbrains.kotlin.asJava.elements.* import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper @@ -157,6 +158,22 @@ val PsiElement.namedUnwrappedElement: PsiNamedElement? val KtClassOrObject.hasInterfaceDefaultImpls: Boolean get() = this is KtClass && isInterface() && hasNonAbstractMembers(this) +val KtClassOrObject.hasRepeatableAnnotationContainer: Boolean + get() = this is KtClass && + isAnnotation() && + run { + var hasRepeatableAnnotation = false + for (annotation in annotationEntries) when (annotation.shortName?.asString()) { + "JvmRepeatable" -> return false + "Repeatable" -> { + if (annotation.valueArgumentList != null) return false + hasRepeatableAnnotation = true + } + } + + return hasRepeatableAnnotation + } + private fun hasNonAbstractMembers(ktInterface: KtClass): Boolean { return ktInterface.declarations.any(::isNonAbstractMember) } @@ -169,6 +186,9 @@ private fun isNonAbstractMember(member: KtDeclaration?): Boolean { private val DEFAULT_IMPLS_CLASS_NAME = Name.identifier(JvmAbi.DEFAULT_IMPLS_CLASS_NAME) fun FqName.defaultImplsChild() = child(DEFAULT_IMPLS_CLASS_NAME) +private val REPEATABLE_ANNOTATION_CONTAINER_NAME = Name.identifier(JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME) +fun FqName.repeatableAnnotationContainerChild() = child(REPEATABLE_ANNOTATION_CONTAINER_NAME) + @Suppress("unused") fun KtElement.toLightAnnotation(): PsiAnnotation? { val ktDeclaration = getStrictParentOfType()?.parent as? KtDeclaration ?: return null diff --git a/compiler/testData/asJava/findClasses/RepeatableAnnotation.kt b/compiler/testData/asJava/findClasses/RepeatableAnnotation.kt new file mode 100644 index 00000000000..b1fedcbe2a6 --- /dev/null +++ b/compiler/testData/asJava/findClasses/RepeatableAnnotation.kt @@ -0,0 +1,11 @@ +@Repeatable +annotation class RepeatableAnnotation(val value: Int) + +@Repeatable +@JvmRepeatable(RepeatableAnnotation2Container::class) +annotation class RepeatableAnnotation2(val value: Int) +annotation class RepeatableAnnotation2Container(val value: Array) + +@JvmRepeatable(RepeatableAnnotation3Container::class) +annotation class RepeatableAnnotation3(val value: Int) +annotation class RepeatableAnnotation3Container(val value: Array) \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/asJava/JavaElementFinderTest.java b/compiler/tests/org/jetbrains/kotlin/asJava/JavaElementFinderTest.java index 70f8d56e024..8c4793c40a5 100644 --- a/compiler/tests/org/jetbrains/kotlin/asJava/JavaElementFinderTest.java +++ b/compiler/tests/org/jetbrains/kotlin/asJava/JavaElementFinderTest.java @@ -1,17 +1,6 @@ /* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.asJava; @@ -19,6 +8,10 @@ package org.jetbrains.kotlin.asJava; import com.intellij.psi.PsiClass; import com.intellij.psi.search.GlobalSearchScope; import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt; +import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; +import org.jetbrains.kotlin.config.CompilerConfiguration; import java.io.File; import java.util.Collections; @@ -39,6 +32,11 @@ public class JavaElementFinderTest extends KotlinAsJavaTestBase { super.tearDown(); } + @Override + protected void extraConfiguration(@NotNull CompilerConfiguration configuration) { + JvmContentRootsKt.addJvmClasspathRoot(configuration, ForTestCompileRuntime.runtimeJarForTestsWithJdk8()); + } + public void testFromEnumEntry() { assertClass("Direction"); assertNoClass("Direction.NORTH"); @@ -52,6 +50,12 @@ public class JavaElementFinderTest extends KotlinAsJavaTestBase { assertNoClass(""); } + public void testRepeatableAnnotation() { + assertClass("RepeatableAnnotation.Container"); + assertNoClass("RepeatableAnnotation2.Container"); + assertNoClass("RepeatableAnnotation2.Container"); + } + private void assertClass(String qualifiedName) { PsiClass psiClass = finder.findClass(qualifiedName, GlobalSearchScope.allScope(getProject())); TestCase.assertNotNull(String.format("Class with fqn='%s' wasn't found.", qualifiedName), psiClass);