diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/DefaultAnnotationsProvider.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/DefaultAnnotationsProvider.kt new file mode 100644 index 00000000000..d7cf7406411 --- /dev/null +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/DefaultAnnotationsProvider.kt @@ -0,0 +1,308 @@ +/* + * Copyright 2010-2023 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.light.classes.symbol.annotations + +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiModifierList +import org.jetbrains.kotlin.analysis.api.annotations.* +import org.jetbrains.kotlin.analysis.api.symbols.KtClassLikeSymbol +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.light.classes.symbol.methods.SymbolLightMethod +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.StandardClassIds +import java.lang.annotation.ElementType + +internal object DefaultAnnotationsProvider : AdditionalAnnotationsProvider { + override fun addAllAnnotations( + currentRawAnnotations: MutableList, + foundQualifiers: MutableSet, + owner: PsiModifierList, + ) { + val parent = owner.parent + if (parent.isAnnotationClass()) { + addAllAnnotationsFromAnnotationClass(currentRawAnnotations, foundQualifiers, owner) + } else if (parent.isMethodWithOverride()) { + addSimpleAnnotationIfMissing(JvmAnnotationNames.OVERRIDE_ANNOTATION.asString(), currentRawAnnotations, foundQualifiers, owner) + } + } + + override fun findAdditionalAnnotation( + annotationsBox: LazyAnnotationsBox, + qualifiedName: String, + owner: PsiModifierList, + ): PsiAnnotation? { + val parent = owner.parent + return when { + parent.isAnnotationClass() -> findAdditionalAnnotationFromAnnotationClass(annotationsBox, qualifiedName, owner) + parent.isMethodWithOverride() -> { + val overrideQualifier = JvmAnnotationNames.OVERRIDE_ANNOTATION.asString() + if (qualifiedName == overrideQualifier) SymbolLightSimpleAnnotation(overrideQualifier, owner) else null + } + + else -> null + } + } +} + +private fun addAllAnnotationsFromAnnotationClass( + currentRawAnnotations: MutableList, + foundQualifiers: MutableSet, + owner: PsiModifierList, +) { + for (index in currentRawAnnotations.indices) { + val currentAnnotation = currentRawAnnotations[index] as? SymbolLightLazyAnnotation ?: continue + val newAnnotation = currentAnnotation.tryConvertToRetentionJavaAnnotation(owner) + ?: currentAnnotation.tryConvertToTargetJavaAnnotation(owner) + ?: currentAnnotation.tryConvertToDocumentedJavaAnnotation(owner) + ?: currentAnnotation.tryConvertToRepeatableJavaAnnotation(owner) + ?: continue + + val qualifiedName = newAnnotation.qualifiedName + requireNotNull(qualifiedName) { "The annotation must have 'qualifiedName'" } + + if (!foundQualifiers.add(qualifiedName)) continue + currentRawAnnotations += newAnnotation + } + + if (foundQualifiers.add(JvmAnnotationNames.RETENTION_ANNOTATION.asString())) { + currentRawAnnotations += createRetentionJavaAnnotation(owner) + } +} + +private fun findAdditionalAnnotationFromAnnotationClass( + annotationsBox: LazyAnnotationsBox, + qualifiedName: String, + owner: PsiModifierList, +): PsiAnnotation? = annotationsBox.tryConvertToRetentionJavaAnnotation(qualifiedName, owner) + ?: annotationsBox.tryConvertToTargetJavaAnnotation(qualifiedName, owner) + ?: annotationsBox.tryConvertToDocumentedJavaAnnotation(qualifiedName, owner) + ?: annotationsBox.tryConvertToRepeatableJavaAnnotation(qualifiedName, owner) + +private fun PsiElement.isAnnotationClass(): Boolean = this is PsiClass && isAnnotationType +private fun PsiElement.isMethodWithOverride(): Boolean = this is SymbolLightMethod<*> && (isDelegated || isOverride()) + +private fun addSimpleAnnotationIfMissing( + qualifier: String, + currentRawAnnotations: MutableList, + foundQualifiers: MutableSet, + owner: PsiModifierList, +) { + if (!foundQualifiers.add(qualifier)) return + currentRawAnnotations += SymbolLightSimpleAnnotation(qualifier, owner) +} + +private fun LazyAnnotationsBox.tryConvertToDocumentedJavaAnnotation( + qualifiedName: String, + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + qualifiedName = qualifiedName, + javaQualifier = JvmAnnotationNames.DOCUMENTED_ANNOTATION.asString(), + kotlinQualifier = StandardNames.FqNames.mustBeDocumented.asString(), + owner = owner, +) + +private fun SymbolLightLazyAnnotation.tryConvertToDocumentedJavaAnnotation( + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + javaQualifier = JvmAnnotationNames.DOCUMENTED_ANNOTATION.asString(), + owner = owner, +) + +private fun LazyAnnotationsBox.tryConvertToRetentionJavaAnnotation( + qualifiedName: String, + owner: PsiModifierList, +): PsiAnnotation? { + val javaQualifier = JvmAnnotationNames.RETENTION_ANNOTATION.asString() + return tryConvertToJavaAnnotation( + qualifiedName = qualifiedName, + javaQualifier = javaQualifier, + kotlinQualifier = StandardNames.FqNames.retention.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeJavaRetentionArguments, + ) ?: owner.takeIf { qualifiedName == javaQualifier }?.let(::createRetentionJavaAnnotation) +} + +private fun SymbolLightLazyAnnotation.tryConvertToRetentionJavaAnnotation( + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + javaQualifier = JvmAnnotationNames.RETENTION_ANNOTATION.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeJavaRetentionArguments, +) + +private fun SymbolLightJavaAnnotation.computeJavaRetentionArguments(): List { + val argumentWithKotlinRetention = originalLightAnnotation.annotationApplication + .value + .arguments + .firstOrNull { + it.name == StandardNames.DEFAULT_VALUE_PARAMETER + }?.expression as? KtEnumEntryAnnotationValue + + val kotlinRetentionName = argumentWithKotlinRetention?.callableId?.callableName?.asString() + return javaRetentionArguments(kotlinRetentionName) +} + +private fun createRetentionJavaAnnotation(owner: PsiModifierList): PsiAnnotation = SymbolLightSimpleAnnotation( + fqName = JvmAnnotationNames.RETENTION_ANNOTATION.asString(), + parent = owner, + arguments = javaRetentionArguments(kotlinRetentionName = null), +) + +private fun javaRetentionArguments(kotlinRetentionName: String?): List = listOf( + KtNamedAnnotationValue( + name = StandardNames.DEFAULT_VALUE_PARAMETER, + expression = KtEnumEntryAnnotationValue( + callableId = CallableId( + StandardClassIds.Annotations.Java.RetentionPolicy, + Name.identifier(kotlinRetentionName ?: AnnotationRetention.RUNTIME.name), + ), + sourcePsi = null, + ) + ) +) + +private fun LazyAnnotationsBox.tryConvertToRepeatableJavaAnnotation( + qualifiedName: String, + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + qualifiedName = qualifiedName, + javaQualifier = JvmAnnotationNames.REPEATABLE_ANNOTATION.asString(), + kotlinQualifier = StandardNames.FqNames.repeatable.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeRepeatableJavaAnnotationArguments +) + +private fun SymbolLightLazyAnnotation.tryConvertToRepeatableJavaAnnotation( + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + javaQualifier = JvmAnnotationNames.REPEATABLE_ANNOTATION.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeRepeatableJavaAnnotationArguments, +) + +private fun SymbolLightJavaAnnotation.computeRepeatableJavaAnnotationArguments(): List { + val annotationClassId = originalLightAnnotation.withAnnotatedSymbol { ktAnnotatedSymbol -> + (ktAnnotatedSymbol as? KtClassLikeSymbol)?.classIdIfNonLocal + } ?: return emptyList() + + return listOf( + KtNamedAnnotationValue( + name = StandardNames.DEFAULT_VALUE_PARAMETER, + expression = KtKClassAnnotationValue.KtNonLocalKClassAnnotationValue( + classId = annotationClassId.createNestedClassId(Name.identifier(JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME)), + sourcePsi = null, + ) + ) + ) +} + +private fun LazyAnnotationsBox.tryConvertToTargetJavaAnnotation( + qualifiedName: String, + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + qualifiedName = qualifiedName, + javaQualifier = JvmAnnotationNames.TARGET_ANNOTATION.asString(), + kotlinQualifier = StandardNames.FqNames.target.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeTargetJavaAnnotationArguments, +) + +private fun SymbolLightLazyAnnotation.tryConvertToTargetJavaAnnotation( + owner: PsiModifierList, +): PsiAnnotation? = tryConvertToJavaAnnotation( + javaQualifier = JvmAnnotationNames.TARGET_ANNOTATION.asString(), + owner = owner, + argumentsComputer = SymbolLightJavaAnnotation::computeTargetJavaAnnotationArguments, +) + +private fun SymbolLightJavaAnnotation.computeTargetJavaAnnotationArguments(): List { + val allowedKotlinTargets = originalLightAnnotation.annotationApplication + .value + .arguments + .firstOrNull() + ?.expression as? KtArrayAnnotationValue + ?: return emptyList() + + val javaTargetNames = allowedKotlinTargets.values.mapNotNullTo(linkedSetOf(), KtAnnotationValue::mapToJavaTarget) + return listOf( + KtNamedAnnotationValue( + name = StandardNames.DEFAULT_VALUE_PARAMETER, + expression = KtArrayAnnotationValue( + values = javaTargetNames.map { + KtEnumEntryAnnotationValue( + callableId = CallableId( + classId = StandardClassIds.Annotations.Java.ElementType, + callableName = Name.identifier(it), + ), + sourcePsi = null, + ) + }, + sourcePsi = null, + ) + ) + ) +} + +private fun KtAnnotationValue.mapToJavaTarget(): String? { + if (this !is KtEnumEntryAnnotationValue) return null + + val callableId = callableId ?: return null + if (callableId.classId != StandardClassIds.AnnotationTarget) return null + return when (callableId.callableName.asString()) { + AnnotationTarget.CLASS.name -> ElementType.TYPE + AnnotationTarget.ANNOTATION_CLASS.name -> ElementType.ANNOTATION_TYPE + AnnotationTarget.FIELD.name -> ElementType.FIELD + AnnotationTarget.LOCAL_VARIABLE.name -> ElementType.LOCAL_VARIABLE + AnnotationTarget.VALUE_PARAMETER.name -> ElementType.PARAMETER + AnnotationTarget.CONSTRUCTOR.name -> ElementType.CONSTRUCTOR + AnnotationTarget.FUNCTION.name, AnnotationTarget.PROPERTY_GETTER.name, AnnotationTarget.PROPERTY_SETTER.name -> ElementType.METHOD + AnnotationTarget.TYPE_PARAMETER.name -> ElementType.TYPE_PARAMETER + AnnotationTarget.TYPE.name -> ElementType.TYPE_USE + else -> null + }?.name +} + +private fun LazyAnnotationsBox.tryConvertToJavaAnnotation( + qualifiedName: String, + javaQualifier: String, + kotlinQualifier: String, + owner: PsiModifierList, + argumentsComputer: SymbolLightJavaAnnotation.() -> List = { emptyList() }, +): PsiAnnotation? { + if (qualifiedName != javaQualifier) return null + if (hasAnnotation(javaQualifier)) return null + + val originalLightAnnotation = findAnnotation( + kotlinQualifier, + withAdditionalAnnotations = false, + ) as? SymbolLightLazyAnnotation ?: return null + + return SymbolLightJavaAnnotation( + originalLightAnnotation = originalLightAnnotation, + javaQualifier = javaQualifier, + argumentsComputer = argumentsComputer, + owner = owner, + ) +} + +private fun SymbolLightLazyAnnotation.tryConvertToJavaAnnotation( + javaQualifier: String, + owner: PsiModifierList, + argumentsComputer: SymbolLightJavaAnnotation.() -> List = { emptyList() }, +): PsiAnnotation? { + if (qualifiedName != javaQualifier) return null + return SymbolLightJavaAnnotation( + originalLightAnnotation = this, + javaQualifier = javaQualifier, + argumentsComputer = argumentsComputer, + owner = owner, + ) +} diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/LazyAnnotationsBox.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/LazyAnnotationsBox.kt index 292a2a0fb25..c60e8a3a078 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/LazyAnnotationsBox.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/LazyAnnotationsBox.kt @@ -25,7 +25,7 @@ internal class LazyAnnotationsBox( private val annotatedSymbolPointer: KtSymbolPointer, private val ktModule: KtModule, private val owner: PsiModifierList, - private val additionalAnnotationsProvider: AdditionalAnnotationsProvider, + private val additionalAnnotationsProvider: AdditionalAnnotationsProvider = DefaultAnnotationsProvider, ) : PsiAnnotationOwner { private inline fun withAnnotatedSymbol(crossinline action: context(KtAnalysisSession) (KtAnnotatedSymbol) -> T): T = annotatedSymbolPointer.withSymbol(ktModule, action) diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/SymbolLightJavaAnnotation.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/SymbolLightJavaAnnotation.kt new file mode 100644 index 00000000000..a84c5d425ab --- /dev/null +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/annotations/SymbolLightJavaAnnotation.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2023 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.light.classes.symbol.annotations + +import com.intellij.psi.PsiAnnotationParameterList +import com.intellij.psi.PsiModifierList +import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue +import org.jetbrains.kotlin.asJava.classes.lazyPub +import org.jetbrains.kotlin.psi.KtCallElement + +internal open class SymbolLightJavaAnnotation( + val originalLightAnnotation: SymbolLightLazyAnnotation, + private val javaQualifier: String, + owner: PsiModifierList, + private val argumentsComputer: SymbolLightJavaAnnotation.() -> List, +) : SymbolLightAbstractAnnotation(owner) { + override val kotlinOrigin: KtCallElement? get() = originalLightAnnotation.kotlinOrigin + + override fun equals(other: Any?): Boolean = other === this || + other is SymbolLightJavaAnnotation && + other.javaQualifier == javaQualifier && + other.originalLightAnnotation == originalLightAnnotation + + override fun hashCode(): Int = javaQualifier.hashCode() + + override fun getQualifiedName(): String = javaQualifier + + private val _parameterList: PsiAnnotationParameterList by lazyPub { + SymbolLightLazyAnnotationParameterList(this, lazyPub { argumentsComputer() }) + } + + override fun getParameterList(): PsiAnnotationParameterList = _parameterList +} diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index 25ffc50f3e7..a068ad5d746 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -1,17 +1,6 @@ /* - * Copyright 2010-2015 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-2023 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.load.java; @@ -54,6 +43,7 @@ public final class JvmAnnotationNames { public static final FqName DEPRECATED_ANNOTATION = new FqName(Deprecated.class.getName()); public static final FqName DOCUMENTED_ANNOTATION = new FqName(Documented.class.getName()); public static final FqName REPEATABLE_ANNOTATION = new FqName("java.lang.annotation.Repeatable"); + public static final FqName OVERRIDE_ANNOTATION = new FqName(Override.class.getName()); public static final FqName JETBRAINS_NOT_NULL_ANNOTATION = new FqName("org.jetbrains.annotations.NotNull"); public static final FqName JETBRAINS_NULLABLE_ANNOTATION = new FqName("org.jetbrains.annotations.Nullable");